From bb79f2e834fe02d4758f956439558098a094efc2 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sun, 22 Feb 2015 21:17:50 -0500 Subject: [PATCH 001/274] adds pkg utils --- lib/pkg/dependencies.js | 10 ++++++++++ lib/pkg/devDependencies.js | 10 ++++++++++ lib/pkg/index.js | 1 + lib/pkg/repo.js | 6 ++++++ plugins/travis.js | 30 ++++++++++++++++++++++++++++++ 5 files changed, 57 insertions(+) create mode 100644 lib/pkg/dependencies.js create mode 100644 lib/pkg/devDependencies.js create mode 100644 lib/pkg/index.js create mode 100644 lib/pkg/repo.js create mode 100644 plugins/travis.js diff --git a/lib/pkg/dependencies.js b/lib/pkg/dependencies.js new file mode 100644 index 0000000..9f25886 --- /dev/null +++ b/lib/pkg/dependencies.js @@ -0,0 +1,10 @@ + + + +exports.removeVerb = function(pkg) { + if (pkg && pkg.dependencies && pkg.dependencies['verb-tag-jscomments']) { + delete pkg['verb-tag-jscomments']; + delete pkg.verb; + } + return pkg; +}; diff --git a/lib/pkg/devDependencies.js b/lib/pkg/devDependencies.js new file mode 100644 index 0000000..0fb05a1 --- /dev/null +++ b/lib/pkg/devDependencies.js @@ -0,0 +1,10 @@ + + + +exports.removeVerb = function(pkg) { + if (pkg && pkg.devDependencies && pkg.devDependencies['verb-tag-jscomments']) { + delete pkg.devDependencies['verb-tag-jscomments']; + delete pkg.devDependencies.verb; + } + return pkg; +}; diff --git a/lib/pkg/index.js b/lib/pkg/index.js new file mode 100644 index 0000000..23b2930 --- /dev/null +++ b/lib/pkg/index.js @@ -0,0 +1 @@ +module.exports = require('export-files')(__dirname); diff --git a/lib/pkg/repo.js b/lib/pkg/repo.js new file mode 100644 index 0000000..aa9beea --- /dev/null +++ b/lib/pkg/repo.js @@ -0,0 +1,6 @@ + + + +exports.toString = function(url) { + return url.replace(/\w+:\/\/github.com\/(.*?)(?:\.git)?$/, '$1'); +}; diff --git a/plugins/travis.js b/plugins/travis.js new file mode 100644 index 0000000..8ef26d7 --- /dev/null +++ b/plugins/travis.js @@ -0,0 +1,30 @@ +'use strict'; + +var through = require('through2'); +var yaml = require('js-yaml'); +var utils = require('../lib/utils'); +var logger = require('../lib/logging'); + +module.exports = function (verb) { + return function travis() { + return through.obj(function (file, enc, cb) { + if (file.isNull() || !file.isBuffer()) { + this.push(file); + return cb(); + } + + if (utils.contains(file, '.travis.yml')) { + var str = file.contents.toString(); + var log = logger(str); + var obj = yaml.load(str); + // console.log(verb.cache.data); + + // log.success(str, 'updated patterns in', file.relative); + } + + // file.contents = new Buffer(str); + this.push(file); + cb(); + }); + }; +}; From 08a88d0c7fe1733a97414d1c54d86588f382f38a Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sun, 22 Feb 2015 21:18:03 -0500 Subject: [PATCH 002/274] update dotfiles --- .gitattributes | 4 +++- .jshintrc | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index e9dd6bc..4a3f1d3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,7 +1,9 @@ # Enforce Unix newlines -*.* text eol=lf +* text eol=lf # binaries +*.ai binary +*.psd binary *.jpg binary *.gif binary *.png binary diff --git a/.jshintrc b/.jshintrc index 6e5a84a..01134d6 100644 --- a/.jshintrc +++ b/.jshintrc @@ -1,6 +1,7 @@ { "asi": false, "boss": true, + "camelcase": true, "curly": true, "eqeqeq": true, "eqnull": true, From d982d4ea0873146183e84fd6c52f8b80c3c11e06 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sun, 22 Feb 2015 21:18:26 -0500 Subject: [PATCH 003/274] adds binaries --- plugins/dotfiles.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/plugins/dotfiles.js b/plugins/dotfiles.js index baee580..866707c 100644 --- a/plugins/dotfiles.js +++ b/plugins/dotfiles.js @@ -11,24 +11,27 @@ module.exports = function dotfilesPlugin() { return cb(); } - var str = file.contents.toString(); - var log = logger(str); - if (utils.contains(file, '.gitattributes')) { + var str = file.contents.toString(); + var log = logger(str); + str = [ '# Enforce Unix newlines', - '*.* text eol=lf', + '* text eol=lf', '', '# binaries', + '*.ai binary', + '*.psd binary', '*.jpg binary', '*.gif binary', '*.png binary', '*.jpeg binary' ].join('\n'); + log.success(str, 'updated patterns in', file.relative); + file.contents = new Buffer(str); } - file.contents = new Buffer(str); this.push(file); cb(); }); From b799a2f9d5690fd1ee97901938e1af412fe69e5c Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sun, 22 Feb 2015 21:18:46 -0500 Subject: [PATCH 004/274] adds basic logic to update gitignore --- plugins/gitignore.js | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/plugins/gitignore.js b/plugins/gitignore.js index caa2101..a1b2e5c 100644 --- a/plugins/gitignore.js +++ b/plugins/gitignore.js @@ -1,27 +1,46 @@ 'use strict'; +var difference = require('arr-diff'); var through = require('through2'); var utils = require('../lib/utils'); var logger = require('../lib/logging'); -module.exports = function dotfilesPlugin() { +module.exports = function gitignore() { return through.obj(function (file, enc, cb) { if (file.isNull() || !file.isBuffer()) { this.push(file); return cb(); } - var str = file.contents.toString(); - console.log(str) - var log = logger(str); + if (utils.contains(file, '.gitignore')) { + // just get these started + var patterns = ['*.DS_Store']; - // if (utils.contains(file, '.gitattributes')) { - // str = '*.* text'; - // log.success(str, 'updated patterns in', file.relative); - // } + var str = file.contents.toString(); + var log = logger(str); + + var lines = str.split('\n').map(trim); + var diff = difference(patterns, lines); + + if (lines[0].charAt(0) === '#') { + var comment = lines.slice(0, 1); + var rest = lines.slice(1); + lines = comment.concat(diff).concat(rest); + } else { + lines = diff.concat(lines); + } + + str = lines.join('\n'); + log.success(str, 'updated patterns in', file.relative); + file.contents = new Buffer(str); + } - file.contents = new Buffer(str); this.push(file); cb(); }); }; + + +function trim(str) { + return str.trim(); +} From 4a73cd119dfa9993d85af4ca3a6e7405e05b03cd Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sun, 22 Feb 2015 21:18:57 -0500 Subject: [PATCH 005/274] camelCase prop --- plugins/jshint.js | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/jshint.js b/plugins/jshint.js index c04c69b..7d5eec1 100644 --- a/plugins/jshint.js +++ b/plugins/jshint.js @@ -20,6 +20,7 @@ module.exports = function jshintPlugin() { obj = sortObj(merge(obj, { "asi": false, "boss": true, + "camelcase": true, "curly": true, "eqeqeq": true, "eqnull": true, From e74f06a79116186a3f0c09749010fcc4add84f10 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sun, 22 Feb 2015 21:19:07 -0500 Subject: [PATCH 006/274] fix date --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 6d53705..fa30c4c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013-2015, Jon Schlinkert. +Copyright (c) 2014-2015, Jon Schlinkert. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 6b122c7770c5f857dd17549674f608fc7a962458 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sun, 22 Feb 2015 21:19:17 -0500 Subject: [PATCH 007/274] format --- package.json | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index f2cb051..5534097 100644 --- a/package.json +++ b/package.json @@ -1,29 +1,20 @@ { "name": "update", "description": "Update the year in all files in a project using glob patterns.", - "version": "0.3.2-beta", + "version": "0.3.3-beta", "homepage": "https://github.com/jonschlinkert/update", "author": { "name": "Jon Schlinkert", "url": "https://github.com/jonschlinkert" }, - "repository": { - "type": "git", - "url": "git://github.com/jonschlinkert/update.git" - }, + "repository": "jonschlinkert/update", "bugs": { "url": "https://github.com/jonschlinkert/update/issues" }, - "licenses": [ - { - "type": "MIT", - "url": "https://github.com/jonschlinkert/update/blob/master/LICENSE" - } - ], + "license": "MIT", "files": [ - "index.js", - "cli.js", - "lib/" + "lib", + "cli.js" ], "main": "index.js", "preferGlobal": true, @@ -38,22 +29,25 @@ }, "dependencies": { "arr-diff": "^1.0.1", + "array-intersection": "^0.1.1", + "array-unique": "^0.1.1", "chalk": "^0.5.1", "clone-deep": "^0.1.1", + "del": "^1.1.1", "delete": "^0.1.5", + "export-files": "^1.0.0", "gray-matter": "^1.2.4", + "js-yaml": "^3.2.7", + "kind-of": "^1.1.0", "log-symbols": "^1.0.1", "merge-deep": "^0.1.5", + "micromatch": "^1.4.0", "parse-copyright": "^0.4.0", "sort-object": "^1.0.0", "through2": "^0.6.3", "update-banner": "^0.1.0", "update-license": "^0.3.0", - "update-package": "^0.1.1", - "verb": "^0.4.5" - }, - "devDependencies": { - "del": "^1.1.1" + "update-package": "^0.1.1" }, "keywords": [ "javascript", From 248d6519c1c26052d322c7a9485b02c35b89867c Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sun, 22 Feb 2015 21:19:33 -0500 Subject: [PATCH 008/274] add more props --- plugins/pkg.js | 51 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/plugins/pkg.js b/plugins/pkg.js index 98fa8a2..2915e62 100644 --- a/plugins/pkg.js +++ b/plugins/pkg.js @@ -3,10 +3,14 @@ var path = require('path'); var diff = require('arr-diff'); var clone = require('clone-deep'); +var typeOf = require('kind-of'); +var unique = require('array-unique'); var through = require('through2'); var sortObj = require('sort-object'); var update = require('update-package'); var logger = require('../lib/logging'); +var utils = require('../lib/utils'); +var pkgUtil = require('../lib/pkg'); module.exports = function pkgPlugin(verb) { return function() { @@ -17,23 +21,37 @@ module.exports = function pkgPlugin(verb) { } try { - verb.cache.data = verb.cache.data || {}; - var licenses = verb.cache.data.licenses; - var license = verb.cache.data.license; - - if (Array.isArray(licenses)) { - licenses = [fixLicense(licenses[0])]; - } else if (typeof license === 'object') { - licenses = [fixLicense(license)]; - } - var log = logger({nocompare: true}); var str = file.contents.toString(); var obj = JSON.parse(str); // run updates on package.json fields - var pkg = update(clone(obj)); + var pkg = update(obj); + + // populate the `files` property + pkg.files = utils.toPkgFiles(file.base, pkg.files); + // // populate the `browser` property + // var browser = utils.toPkgFiles(file.base, ['browser.js']); + // if (browser.length || pkg.browser && pkg.browser.length) { + // pkg.browser = unique(browser, pkg.browser); + // } + + var repo = pkg && pkg.repository && typeof pkg.repository === 'object' + ? pkg.repository.url + : pkg.repository; + + // make repo a string. + if (repo) { + pkg.repository = pkgUtil.repo.toString(repo); + } + pkg = pkgUtil.devDependencies.removeVerb(pkg); + + pkg.license = 'MIT'; + delete pkg.licenses; + + // order of preference. keywords last to keep most keys + // on the same screen when reviewing properties var defaults = [ 'name', 'description', @@ -46,10 +64,12 @@ module.exports = function pkgPlugin(verb) { 'license', 'licenses', 'files', + 'browser', 'main', 'private', 'preferGlobal', 'bin', + 'engineStrict', 'engines', 'scripts', 'dependencies', @@ -59,8 +79,6 @@ module.exports = function pkgPlugin(verb) { var keys = diff(Object.keys(pkg), defaults); var res = sortObj(pkg, defaults.concat(keys)); - res.licenses = licenses; - delete res.license; if (res.scripts && res.scripts.test && /mocha -r/i.test(res.scripts.test)) { res.scripts.test = 'mocha'; @@ -79,8 +97,13 @@ module.exports = function pkgPlugin(verb) { }; function fixLicense(license) { - if (license && license.url && license.url.indexOf('LICENSE-MIT') !== -1) { + if (typeof license === 'string') { + return license; + } + + if (typeOf(license) === 'object' && license.url && license.url.indexOf('LICENSE-MIT') !== -1) { license.url = license.url.split('LICENSE-MIT').join('LICENSE'); } + return license; } From 2472c3346d43263470f5e10ea740b8d31455d4bb Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 23 Feb 2015 00:43:40 -0500 Subject: [PATCH 009/274] adds travis plugin --- lib/delete.js | 4 ++-- lib/pkg/devDependencies.js | 17 ++++++++++++++++- plugins/index.js | 3 ++- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/delete.js b/lib/delete.js index b1a21ef..a8a9e60 100644 --- a/lib/delete.js +++ b/lib/delete.js @@ -9,10 +9,10 @@ var bold = chalk.bold; module.exports = function _delete(fp, opts) { if (!fs.existsSync(fp)) { if (opts && opts.silent !== true) { - console.log(symbol.error, bold(src), 'does not exist.'); + console.log(symbol.error, bold(fp), 'does not exist.'); } } else { del.sync(fp, opts && opts.force); console.log(symbol.success, 'deleted', bold(fp)); } -}; \ No newline at end of file +}; diff --git a/lib/pkg/devDependencies.js b/lib/pkg/devDependencies.js index 0fb05a1..693a7b2 100644 --- a/lib/pkg/devDependencies.js +++ b/lib/pkg/devDependencies.js @@ -1,5 +1,8 @@ +'use strict'; - +/** + * Remove the old verb + */ exports.removeVerb = function(pkg) { if (pkg && pkg.devDependencies && pkg.devDependencies['verb-tag-jscomments']) { @@ -8,3 +11,15 @@ exports.removeVerb = function(pkg) { } return pkg; }; + +/** + * Remove `should` when it's not being used + * in tests. + */ + +exports.removeShould = function(pkg) { + if (pkg && pkg.devDependencies) { + delete pkg.devDependencies.should; + } + return pkg; +}; diff --git a/plugins/index.js b/plugins/index.js index 942fd0f..5de9ce9 100644 --- a/plugins/index.js +++ b/plugins/index.js @@ -16,7 +16,8 @@ module.exports = function (verb) { jshint : require('./jshint.js'), license : require('./license.js'), pkg : require('./pkg.js')(verb), - tests : require('./tests.js'), + tests : require('./tests.js')(verb), + travis : require('./travis.js')(verb), verbmd : require('./verbmd.js')(verb) }; }; From 16d88a52698acaa2c8ce88361e097d6cba0ed1fa Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 23 Feb 2015 00:44:31 -0500 Subject: [PATCH 010/274] lint, adds `hasShould` property to context --- lib/logging.js | 2 +- lib/mkdir.js | 4 ++-- lib/move.js | 3 ++- package.json | 6 +++--- plugins/pkg.js | 11 ++++++++--- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/logging.js b/lib/logging.js index 03786ad..5ae72c0 100644 --- a/lib/logging.js +++ b/lib/logging.js @@ -23,4 +23,4 @@ module.exports = function (str, options) { }; return log; -} \ No newline at end of file +}; diff --git a/lib/mkdir.js b/lib/mkdir.js index a47ff94..d7adacb 100644 --- a/lib/mkdir.js +++ b/lib/mkdir.js @@ -20,7 +20,7 @@ var path = require('path'); * @api private */ -function mkdir(dir, mode) { +module.exports = function mkdir(dir, mode) { mode = mode || parseInt('0777', 8) & (~process.umask()); if (!fs.existsSync(dir)) { var parent = path.dirname(dir); @@ -32,4 +32,4 @@ function mkdir(dir, mode) { fs.mkdirSync(dir, mode); } } -} \ No newline at end of file +}; diff --git a/lib/move.js b/lib/move.js index d254f87..5e245a4 100644 --- a/lib/move.js +++ b/lib/move.js @@ -1,5 +1,6 @@ 'use strict'; +var fs = require('fs'); var path = require('path'); var rename = require('./rename'); var mkdir = require('./mkdir'); @@ -10,4 +11,4 @@ module.exports = function move(src, dest, force) { mkdir(dir); } rename(src, dest, force); -}; \ No newline at end of file +}; diff --git a/package.json b/package.json index 5534097..bc1020e 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,8 @@ }, "license": "MIT", "files": [ - "lib", - "cli.js" + "cli.js", + "lib/" ], "main": "index.js", "preferGlobal": true, @@ -55,4 +55,4 @@ "object", "sort" ] -} +} \ No newline at end of file diff --git a/plugins/pkg.js b/plugins/pkg.js index 2915e62..e1f1572 100644 --- a/plugins/pkg.js +++ b/plugins/pkg.js @@ -29,9 +29,10 @@ module.exports = function pkgPlugin(verb) { var pkg = update(obj); // populate the `files` property - pkg.files = utils.toPkgFiles(file.base, pkg.files); - // // populate the `browser` property - // var browser = utils.toPkgFiles(file.base, ['browser.js']); + pkg.files = pkgUtil.files.toFiles(file.base, pkg.files); + + // populate the `browser` property + // var browser = pkgUtil.files.toFiles(file.base, ['browser.js']); // if (browser.length || pkg.browser && pkg.browser.length) { // pkg.browser = unique(browser, pkg.browser); // } @@ -47,6 +48,10 @@ module.exports = function pkgPlugin(verb) { pkg = pkgUtil.devDependencies.removeVerb(pkg); + if (!verb.get('data.hasShould')) { + pkg = pkgUtil.devDependencies.removeShould(pkg); + } + pkg.license = 'MIT'; delete pkg.licenses; From c7cc95a8c61c3d5f3257208143c498632335c3ad Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 23 Feb 2015 12:07:02 -0500 Subject: [PATCH 011/274] adds `files` helper to `pkg` --- lib/pkg/files.js | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 lib/pkg/files.js diff --git a/lib/pkg/files.js b/lib/pkg/files.js new file mode 100644 index 0000000..3a27e9d --- /dev/null +++ b/lib/pkg/files.js @@ -0,0 +1,37 @@ +'use strict'; + +var fs = require('fs'); +var path = require('path'); +var unique = require('array-unique'); +var intersect = require('array-intersection'); + +exports.toFiles = function(fp, names) { + var lookFor = names || ['index.js', 'cli.js', 'lib/', 'bin/', 'completion/', 'templates/']; + var files = fs.readdirSync(path.resolve(fp)); + names = formatFiles(names); + files = formatFiles(files); + return unique(intersect(lookFor, files).concat(names)); +}; + +function formatFiles(files) { + var res = [], i = 0; + var len; + if (files && (len = files.length)) { + while (len--) { + var file = files[i++]; + var stat = fs.statSync(file); + res.push(formatPath(file, stat)); + } + } + return res; +} + +function formatPath(fp, stat) { + if (stat.isFile()) { + return fp; + } + if (fp && fp[fp.length - 1] !== '/') { + return fp + '/'; + } + return fp; +} From 9573647b0d9a1f97d0f158297f95ffacc2b6a8c8 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 23 Feb 2015 12:15:43 -0500 Subject: [PATCH 012/274] adds `authors`, tests stuff --- lib/verbmd.js | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/verbmd.js b/lib/verbmd.js index 0211566..74f2d14 100644 --- a/lib/verbmd.js +++ b/lib/verbmd.js @@ -1,5 +1,15 @@ 'use strict'; +var authors = [ + '{%= include("authors", {', + ' authors: [', + ' {name: \'Jon Schlinkert\', username: \'jonschlinkert\'},', + ' {name: \'Brian Woodward\', username: \'doowb\'}', + ' ]', + '}) %}' +].join('\n'); + + function fixInstall(str) { var re = /## Install[\s\n]+{%= include/g; str = str.replace(re, '{%= include'); @@ -10,6 +20,9 @@ function fixInstall(str) { function fixHelpers(str) { str = str.split('{%= jscomments').join('{%= apidocs'); str = str.split('{%= comments').join('{%= apidocs'); + str = str.split('{%= contrib("contributing") %}').join('{%= include("contributing") %}'); + str = str.split('{%= contrib("author") %}').join('{%= include("author") %}'); + str = str.split('{%= contrib("authors") %}').join(authors); return str; } @@ -18,9 +31,14 @@ function fixCopyright(str, year) { return str.replace(re, '{%= copyright({year: ' + (year || '2015') + '}) %}'); } -module.exports = function (str, year) { - // str = fixCopyright(str, year); +function runningTests(str) { + var re = /## Run tests\n\n```bash\nnpm test\n```/; + return str.replace(re, '## Running tests\n{%= include("tests") %}'); +} + +module.exports = function (str) { + str = runningTests(str); str = fixInstall(str); str = fixHelpers(str); return str; -}; \ No newline at end of file +}; From 86cc1987a03021a2c05a6e7d2f881087709d1f01 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 23 Feb 2015 14:34:29 -0500 Subject: [PATCH 013/274] move `lib/pkg` to `plugins/helpers` --- lib/pkg/dependencies.js | 10 ------ lib/pkg/repo.js | 6 ---- plugins/helpers/dependencies.js | 5 +++ .../helpers}/devDependencies.js | 0 {lib/pkg => plugins/helpers}/files.js | 13 ++------ {lib/pkg => plugins/helpers}/index.js | 0 plugins/helpers/keys.js | 33 +++++++++++++++++++ plugins/helpers/repo.js | 12 +++++++ plugins/helpers/scripts.js | 18 ++++++++++ 9 files changed, 70 insertions(+), 27 deletions(-) delete mode 100644 lib/pkg/dependencies.js delete mode 100644 lib/pkg/repo.js create mode 100644 plugins/helpers/dependencies.js rename {lib/pkg => plugins/helpers}/devDependencies.js (100%) rename {lib/pkg => plugins/helpers}/files.js (77%) rename {lib/pkg => plugins/helpers}/index.js (100%) create mode 100644 plugins/helpers/keys.js create mode 100644 plugins/helpers/repo.js create mode 100644 plugins/helpers/scripts.js diff --git a/lib/pkg/dependencies.js b/lib/pkg/dependencies.js deleted file mode 100644 index 9f25886..0000000 --- a/lib/pkg/dependencies.js +++ /dev/null @@ -1,10 +0,0 @@ - - - -exports.removeVerb = function(pkg) { - if (pkg && pkg.dependencies && pkg.dependencies['verb-tag-jscomments']) { - delete pkg['verb-tag-jscomments']; - delete pkg.verb; - } - return pkg; -}; diff --git a/lib/pkg/repo.js b/lib/pkg/repo.js deleted file mode 100644 index aa9beea..0000000 --- a/lib/pkg/repo.js +++ /dev/null @@ -1,6 +0,0 @@ - - - -exports.toString = function(url) { - return url.replace(/\w+:\/\/github.com\/(.*?)(?:\.git)?$/, '$1'); -}; diff --git a/plugins/helpers/dependencies.js b/plugins/helpers/dependencies.js new file mode 100644 index 0000000..961d595 --- /dev/null +++ b/plugins/helpers/dependencies.js @@ -0,0 +1,5 @@ +'use strict'; + +/** + * PLACEHOLDER + */ diff --git a/lib/pkg/devDependencies.js b/plugins/helpers/devDependencies.js similarity index 100% rename from lib/pkg/devDependencies.js rename to plugins/helpers/devDependencies.js diff --git a/lib/pkg/files.js b/plugins/helpers/files.js similarity index 77% rename from lib/pkg/files.js rename to plugins/helpers/files.js index 3a27e9d..f072ba6 100644 --- a/lib/pkg/files.js +++ b/plugins/helpers/files.js @@ -4,6 +4,7 @@ var fs = require('fs'); var path = require('path'); var unique = require('array-unique'); var intersect = require('array-intersection'); +var utils = require('../../lib/utils'); exports.toFiles = function(fp, names) { var lookFor = names || ['index.js', 'cli.js', 'lib/', 'bin/', 'completion/', 'templates/']; @@ -20,18 +21,8 @@ function formatFiles(files) { while (len--) { var file = files[i++]; var stat = fs.statSync(file); - res.push(formatPath(file, stat)); + res.push(utils.trailingSlash(file, stat)); } } return res; } - -function formatPath(fp, stat) { - if (stat.isFile()) { - return fp; - } - if (fp && fp[fp.length - 1] !== '/') { - return fp + '/'; - } - return fp; -} diff --git a/lib/pkg/index.js b/plugins/helpers/index.js similarity index 100% rename from lib/pkg/index.js rename to plugins/helpers/index.js diff --git a/plugins/helpers/keys.js b/plugins/helpers/keys.js new file mode 100644 index 0000000..4db53ca --- /dev/null +++ b/plugins/helpers/keys.js @@ -0,0 +1,33 @@ +/** + * This is not comprehensive, it's just a start + * and can be customized by the user. + * + * Order of preference. `keywords` goes last in + * order to keep most keys on the same screen when + * reviewing properties + */ + +module.exports = [ + 'name', + 'description', + 'version', + 'homepage', + 'author', + 'maintainers', + 'repository', + 'bugs', + 'license', + 'licenses', + 'files', + 'browser', + 'main', + 'private', + 'preferGlobal', + 'bin', + 'engineStrict', + 'engines', + 'scripts', + 'dependencies', + 'devDependencies', + 'keywords' +]; diff --git a/plugins/helpers/repo.js b/plugins/helpers/repo.js new file mode 100644 index 0000000..706d401 --- /dev/null +++ b/plugins/helpers/repo.js @@ -0,0 +1,12 @@ +'use strict'; + +var utils = require('../../lib/utils'); + +/** + * Get a `username/name` github url string. + * + * @param {String} `url` + * @return {String} + */ + +exports.toString = utils.repo; diff --git a/plugins/helpers/scripts.js b/plugins/helpers/scripts.js new file mode 100644 index 0000000..ba548d9 --- /dev/null +++ b/plugins/helpers/scripts.js @@ -0,0 +1,18 @@ +'use strict'; + +var sortObj = require('sort-object'); +var diff = require('arr-diff'); +var keys = require('./keys'); + +/** + * Remove the old verb + */ + +exports.fixMocha = function(pkg) { + var keys = diff(Object.keys(pkg), keys); + var res = sortObj(pkg, keys.concat(keys)); + if (res.scripts && res.scripts.test && /mocha -r/i.test(res.scripts.test)) { + res.scripts.test = 'mocha'; + } + return res; +}; From 2762a82e0cc5e0a025a6056ec20a9f4bd07ce4b3 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 23 Feb 2015 14:34:57 -0500 Subject: [PATCH 014/274] utils for plugins, tests --- lib/{to-stream.js => _plugin-base.js} | 8 ++++++-- lib/tests.js | 6 ++++++ 2 files changed, 12 insertions(+), 2 deletions(-) rename lib/{to-stream.js => _plugin-base.js} (63%) create mode 100644 lib/tests.js diff --git a/lib/to-stream.js b/lib/_plugin-base.js similarity index 63% rename from lib/to-stream.js rename to lib/_plugin-base.js index 19b3375..146b4a8 100644 --- a/lib/to-stream.js +++ b/lib/_plugin-base.js @@ -1,4 +1,8 @@ -function toStream(options, cb) { +'use strict'; + +var through = require('through2'); + +module.exports = function(options, cb) { return through.obj(function (file, enc, cb) { var str = file.contents.toString(); @@ -6,4 +10,4 @@ function toStream(options, cb) { this.push(file); cb(); }); -} \ No newline at end of file +}; diff --git a/lib/tests.js b/lib/tests.js new file mode 100644 index 0000000..caadad0 --- /dev/null +++ b/lib/tests.js @@ -0,0 +1,6 @@ +'use strict'; + +exports.fixShould = function fixShould(str) { + var segs = str.split('var should = require(\'should\');'); + return segs.join('require(\'should\');'); +}; From f10981fed8363f19ac1671593d2e5f2bcbc15e67 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 23 Feb 2015 14:35:16 -0500 Subject: [PATCH 015/274] adds utils --- lib/utils.js | 80 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 76 insertions(+), 4 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index d272617..d96e41b 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,9 +1,81 @@ 'use strict'; +var fs = require('fs'); +var path = require('path'); + +/** + * Pass a file or an array of filepaths that "might" exist, + * and an object is returned with `ENOENT` and `EXISTS` + * arrays. + * + * @param {Array|String} + */ + +exports.exists = function exists(files) { + files = Array.isArray(files) ? files : [files]; + var len = files.length; + var res = {ENOENT: new Array(len), EXISTS: files.slice()}; + + while (len--) { + var fp = path.resolve(files[len]); + if (!fs.existsSync(fp)) { + res.EXISTS.splice(len, 1); + res.ENOENT[len] = fp; + } + } + return res; +}; + +/** + * Stringify the `contents` property of a vinyl file. + * + * @param {Object} `file` + * @return {String} + */ + exports.toString = function toString(file) { - return file && file.contents && file.contents.toString(); + return file.contents.toString(); +}; + +/** + * Return true if `file.path` contains the given + * string. We know if it passes through the stream + * that the file exists. + * + * @param {Object} `file` + * @param {String} `str` + * @return {Boolean} + */ + +exports.contains = function contains(file, str) { + return file.path.indexOf(str) !== -1; }; -exports.contains = function contains(file, name) { - return file.path.indexOf(name) !== -1; -}; \ No newline at end of file +/** + * Get a `username/name` github url string. + * + * @param {String} `url` + * @return {String} + */ + +exports.repo = function repo(str) { + return str.replace(/\w+:\/\/github.com\/(.*?)(?:\.git)?$/, '$1'); +}; + +/** + * Add a trailing slash to a file path. + * + * @param {String} `fp` + * @param {Object} `stat` + * @return {String} + */ + +exports.trailingSlash = function trailingSlash(fp, stat) { + if (stat.isFile()) { + return fp; + } + if (fp && fp[fp.length - 1] !== '/') { + return fp + '/'; + } + return fp; +}; From 0efb45c255724c0e16d1b08a4a97ed5df6a90901 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 23 Feb 2015 14:35:29 -0500 Subject: [PATCH 016/274] update `jshint` defaults --- plugins/jshint.js | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/jshint.js b/plugins/jshint.js index 7d5eec1..c04c69b 100644 --- a/plugins/jshint.js +++ b/plugins/jshint.js @@ -20,7 +20,6 @@ module.exports = function jshintPlugin() { obj = sortObj(merge(obj, { "asi": false, "boss": true, - "camelcase": true, "curly": true, "eqeqeq": true, "eqnull": true, From 21906d5312a32b7a167f5afb4072dd8c2f565df8 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 23 Feb 2015 14:37:23 -0500 Subject: [PATCH 017/274] clean up --- plugins/jshint.js | 1 + plugins/pkg.js | 57 ++++++++++++----------------------------------- plugins/tests.js | 44 ++++++++++++++++++------------------ plugins/travis.js | 9 ++++---- 4 files changed, 41 insertions(+), 70 deletions(-) diff --git a/plugins/jshint.js b/plugins/jshint.js index c04c69b..6bb6c78 100644 --- a/plugins/jshint.js +++ b/plugins/jshint.js @@ -17,6 +17,7 @@ module.exports = function jshintPlugin() { delete obj.globals; + // TODO: move to user preferences obj = sortObj(merge(obj, { "asi": false, "boss": true, diff --git a/plugins/pkg.js b/plugins/pkg.js index e1f1572..3d13bee 100644 --- a/plugins/pkg.js +++ b/plugins/pkg.js @@ -1,18 +1,21 @@ 'use strict'; var path = require('path'); -var diff = require('arr-diff'); var clone = require('clone-deep'); var typeOf = require('kind-of'); var unique = require('array-unique'); var through = require('through2'); -var sortObj = require('sort-object'); var update = require('update-package'); var logger = require('../lib/logging'); var utils = require('../lib/utils'); -var pkgUtil = require('../lib/pkg'); +var helpers = require('./helpers/'); -module.exports = function pkgPlugin(verb) { +/** + * virtually everything in this file is a temporary + * hack until I get update-package straightened out. + */ + +module.exports = function(verb) { return function() { return through.obj(function (file, enc, cb) { if (file.isNull() || !file.isBuffer() || path.basename(file.path) !== 'package.json') { @@ -29,10 +32,10 @@ module.exports = function pkgPlugin(verb) { var pkg = update(obj); // populate the `files` property - pkg.files = pkgUtil.files.toFiles(file.base, pkg.files); + pkg.files = helpers.files.toFiles(file.base, pkg.files); // populate the `browser` property - // var browser = pkgUtil.files.toFiles(file.base, ['browser.js']); + // var browser = helpers.files.toFiles(file.base, ['browser.js']); // if (browser.length || pkg.browser && pkg.browser.length) { // pkg.browser = unique(browser, pkg.browser); // } @@ -43,53 +46,21 @@ module.exports = function pkgPlugin(verb) { // make repo a string. if (repo) { - pkg.repository = pkgUtil.repo.toString(repo); + pkg.repository = helpers.repo.toString(repo); } - pkg = pkgUtil.devDependencies.removeVerb(pkg); + pkg = helpers.devDependencies.removeVerb(pkg); if (!verb.get('data.hasShould')) { - pkg = pkgUtil.devDependencies.removeShould(pkg); + pkg = helpers.devDependencies.removeShould(pkg); } pkg.license = 'MIT'; delete pkg.licenses; - // order of preference. keywords last to keep most keys - // on the same screen when reviewing properties - var defaults = [ - 'name', - 'description', - 'version', - 'homepage', - 'author', - 'maintainers', - 'repository', - 'bugs', - 'license', - 'licenses', - 'files', - 'browser', - 'main', - 'private', - 'preferGlobal', - 'bin', - 'engineStrict', - 'engines', - 'scripts', - 'dependencies', - 'devDependencies', - 'keywords' - ]; - - var keys = diff(Object.keys(pkg), defaults); - var res = sortObj(pkg, defaults.concat(keys)); - - if (res.scripts && res.scripts.test && /mocha -r/i.test(res.scripts.test)) { - res.scripts.test = 'mocha'; - } + pkg = helpers.scripts.fixMocha(pkg); - file.contents = new Buffer(JSON.stringify(res, null, 2)); + file.contents = new Buffer(JSON.stringify(pkg, null, 2)); log.success(null, 'updated properties in', file.relative); } catch (err) { console.log('plugin:pkg', err); diff --git a/plugins/tests.js b/plugins/tests.js index d8ccc67..de9de06 100644 --- a/plugins/tests.js +++ b/plugins/tests.js @@ -2,30 +2,30 @@ var through = require('through2'); var logger = require('../lib/logging'); +var tests = require('../lib/tests'); -module.exports = function testsPlugin() { - return through.obj(function (file, enc, cb) { - if (file.isNull() || !file.isBuffer()) { - this.push(file); - return cb(); - } +module.exports = function testsPlugin(verb) { + return function() { + return through.obj(function (file, enc, cb) { + if (file.isNull() || !file.isBuffer()) { + this.push(file); + return cb(); + } - var str = file.contents.toString(); - var log = logger(str); + var str = file.contents.toString(); + var log = logger(str); - if (str.indexOf('var should') !== -1) { - str = fixShould(str, file.relative); - log.success(str, 'updated "should" statements in', file.relative); - } + var hasShould = /['"]should/.test(str); + verb.set('data.hasShould', hasShould); - file.contents = new Buffer(str); - this.push(file); - cb(); - }); -}; + if (hasShould) { + str = tests.fixShould(str, file.relative); + log.success(str, 'updated "should" statements in', file.relative); + } -function fixShould(str) { - var segs = str.split('var should = require(\'should\');'); - str = segs.join('require(\'should\');'); - return str; -} + file.contents = new Buffer(str); + this.push(file); + cb(); + }); + } +}; diff --git a/plugins/travis.js b/plugins/travis.js index 8ef26d7..2f8cfcf 100644 --- a/plugins/travis.js +++ b/plugins/travis.js @@ -5,8 +5,8 @@ var yaml = require('js-yaml'); var utils = require('../lib/utils'); var logger = require('../lib/logging'); -module.exports = function (verb) { - return function travis() { +module.exports = function travis(verb) { + return function() { return through.obj(function (file, enc, cb) { if (file.isNull() || !file.isBuffer()) { this.push(file); @@ -17,12 +17,11 @@ module.exports = function (verb) { var str = file.contents.toString(); var log = logger(str); var obj = yaml.load(str); - // console.log(verb.cache.data); - // log.success(str, 'updated patterns in', file.relative); + file.contents = new Buffer(str); + log.success(str, 'updated patterns in', file.relative); } - // file.contents = new Buffer(str); this.push(file); cb(); }); From f590b4587fad2f25148f69045f366f9a6b47b05e Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 23 Feb 2015 14:37:32 -0500 Subject: [PATCH 018/274] run update --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 559e5f0..58ec3bb 100644 --- a/README.md +++ b/README.md @@ -40,8 +40,8 @@ Pull requests and stars are always welcome. For bugs and feature requests, [plea ## License Copyright (c) 2014-2015 Jon Schlinkert -Released under the MIT license +Released under the license *** -_This file was generated by [verb](https://github.com/assemble/verb) on February 19, 2015._ \ No newline at end of file +_This file was generated by [verb](https://github.com/assemble/verb) on February 20, 2015._ \ No newline at end of file From c67664af070b50fd87825df233b91e133e4b530b Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 23 Feb 2015 14:41:28 -0500 Subject: [PATCH 019/274] adds `known` method, experimental --- lib/verbmd.js | 7 ++++-- verbfile.js | 62 ++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 59 insertions(+), 10 deletions(-) diff --git a/lib/verbmd.js b/lib/verbmd.js index 74f2d14..2b09701 100644 --- a/lib/verbmd.js +++ b/lib/verbmd.js @@ -27,8 +27,11 @@ function fixHelpers(str) { } function fixCopyright(str, year) { - var re = new RegExp('{%= copyright\\(\\) %}', 'g'); - return str.replace(re, '{%= copyright({year: ' + (year || '2015') + '}) %}'); + if (!year) { + return str; + } + var segs = str.split('{%= copyright() %}'); + return segs.join('{%= copyright({year: ' + (year || '2015') + '}) %}'); } function runningTests(str) { diff --git a/verbfile.js b/verbfile.js index 540c853..ed5b883 100644 --- a/verbfile.js +++ b/verbfile.js @@ -3,10 +3,34 @@ var del = require('del'); var path = require('path'); var verb = require('verb'); +var parse = require('parse-copyright'); var log = require('./lib/logging')({nocompare: true}); var plugins = require('./plugins')(verb); -var parse = require('parse-copyright'); +var utils = require('./lib/utils'); +verb.known(function(env) { + var orgs = ['jonschlinkert', 'doowb', 'assemble', 'verb', 'helpers', 'regexps']; + var repo = env.repository; + if (typeof repo === 'object') { + repo = repo.url; + } + var len = orgs.length; + while(len--) { + var ele = orgs[len]; + if (repo.indexOf(ele) !== -1) { + return true; + } + } + return false; +}); + +verb.data({ + travis: { + sudo: false, + language: 'node_js', + node_js: ['iojs', '0.12', '0.10'] + } +}); verb.onLoad(/./, function (file, next) { file.render = false; @@ -58,8 +82,26 @@ verb.task('jshint', function () { })); }); +verb.task('travis', function () { + verb.src('.travis.yml', {render: false}) + .pipe(plugins.travis()) + .pipe(verb.dest(function (file) { + file.path = '.travis.yml'; + return path.dirname(file.path); + })); +}); + verb.task('license', function () { - verb.src('LICENSE', {render: false}) + verb.src('LICENSE{,-MIT}', {render: false}) + .pipe(plugins.license()) + .pipe(verb.dest(function (file) { + file.path = 'LICENSE'; + return path.dirname(file.path); + })); +}); + +verb.task('travis', function () { + verb.src('LICENSE{,-MIT}', {render: false}) .pipe(plugins.license()) .pipe(verb.dest(function (file) { file.path = 'LICENSE'; @@ -70,14 +112,17 @@ verb.task('license', function () { verb.task('dotfiles', function () { verb.src('.git*', {render: false, dot: true}) .pipe(plugins.dotfiles()) - // .pipe(plugins.gitignore()) + .pipe(plugins.gitignore()) .pipe(verb.dest(function (file) { return path.dirname(file.path); })) .on('end', function (cb) { var files = ['.npmignore', 'test/mocha.opts', '.verbrc.md', 'LICENSE-MIT']; - log.info('deleted', files.join(', ')); - del(files, cb); + var exists = utils.exists(files).EXISTS; + if (exists.length) { + del(exists, cb); + log.info('deleted', exists.join(', ')); + } }); }); @@ -96,10 +141,11 @@ verb.task('default', [ 'banners', 'verbfile', 'dotfiles', + 'travis', 'jshint', 'license', - 'pkg', - 'readme' + 'pkg' ]); -verb.run(); \ No newline at end of file +// verb.diff() +verb.run(); From 6a7b083cdd2a7ff9177e7cff7a78648399109b91 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 24 Feb 2015 00:26:16 -0500 Subject: [PATCH 020/274] udpate verbmd plugin --- plugins/verbmd.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/verbmd.js b/plugins/verbmd.js index dd3a201..74ca37c 100644 --- a/plugins/verbmd.js +++ b/plugins/verbmd.js @@ -5,7 +5,7 @@ var through = require('through2'); var verbmd = require('../lib/verbmd'); var logger = require('../lib/logging'); -module.exports = function verbmdPlugin(verb) { +module.exports = function(verb) { return function () { return through.obj(function (file, enc, cb) { if (file.isNull() || !file.isBuffer()) { @@ -13,7 +13,7 @@ module.exports = function verbmdPlugin(verb) { return cb(); } - if (file.path.indexOf('verbfile')) { + if (utils.contains(file.path, '.verb')) { var str = file.contents.toString(); var log = logger(str); @@ -22,11 +22,10 @@ module.exports = function verbmdPlugin(verb) { if (keys.length && obj.data.hasOwnProperty('tags')) { str = obj.content.replace(/^\s+/, ''); - log.success(str, 'stripped front-matter in', file.relative); + log.success(str, 'stripped deprecated front-matter tags in', file.relative); } str = verbmd(str); - log.success(str, 'updated helpers in', file.relative); file.contents = new Buffer(str); } From 5957009fa4715d49a471bb99ddfe6d7c592f1706 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 24 Feb 2015 00:26:44 -0500 Subject: [PATCH 021/274] compare travis string --- plugins/travis.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/travis.js b/plugins/travis.js index 2f8cfcf..b6e440b 100644 --- a/plugins/travis.js +++ b/plugins/travis.js @@ -13,13 +13,13 @@ module.exports = function travis(verb) { return cb(); } - if (utils.contains(file, '.travis.yml')) { + if (utils.contains(file.path, '.travis')) { var str = file.contents.toString(); - var log = logger(str); + var log = logger({nocompare: true}); var obj = yaml.load(str); file.contents = new Buffer(str); - log.success(str, 'updated patterns in', file.relative); + log.success(true, 'loaded data from', file.relative); } this.push(file); From 4015c09c2c027050d1e1cee3b0f07e19d27e8fe8 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 24 Feb 2015 00:27:14 -0500 Subject: [PATCH 022/274] only read tests --- plugins/tests.js | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/plugins/tests.js b/plugins/tests.js index de9de06..dc59c5e 100644 --- a/plugins/tests.js +++ b/plugins/tests.js @@ -4,7 +4,7 @@ var through = require('through2'); var logger = require('../lib/logging'); var tests = require('../lib/tests'); -module.exports = function testsPlugin(verb) { +module.exports = function(verb) { return function() { return through.obj(function (file, enc, cb) { if (file.isNull() || !file.isBuffer()) { @@ -12,18 +12,26 @@ module.exports = function testsPlugin(verb) { return cb(); } - var str = file.contents.toString(); - var log = logger(str); + if (utils.contains(file.path, 'test')) { + var str = file.contents.toString(); + var log = logger(str); - var hasShould = /['"]should/.test(str); - verb.set('data.hasShould', hasShould); + var hasShould = /['"]should/.test(str); + verb.set('data.hasShould', hasShould); - if (hasShould) { - str = tests.fixShould(str, file.relative); - log.success(str, 'updated "should" statements in', file.relative); + // `should` exists in test files + if (hasShould) { + str = tests.fixShould(str, file.relative); + log.success(str, 'updated "should" statements in', file.relative); + } + + var deps = verb.env.devDependencies; + if (hasShould && !deps.hasOwnProperty('should')) { + console.log(verb.env); + } + file.contents = new Buffer(str); } - file.contents = new Buffer(str); this.push(file); cb(); }); From a44ccbc4268b126c2fe0d0f27510e7d9b0323b76 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Wed, 25 Feb 2015 07:55:12 -0500 Subject: [PATCH 023/274] split out more helpers, adds editorconfig plugin and template --- plugins/editorconfig.js | 27 +++++++++++++++++++++++++++ plugins/helpers/browser.js | 15 +++++++++++++++ plugins/helpers/devDependencies.js | 13 +++++++++++++ plugins/helpers/license.js | 20 ++++++++++++++++++++ plugins/helpers/licenses.js | 15 +++++++++++++++ plugins/helpers/repo.js | 2 +- plugins/helpers/scripts.js | 12 +++--------- plugins/helpers/url.js | 5 +++++ templates/editorconfig.js | 19 +++++++++++++++++++ 9 files changed, 118 insertions(+), 10 deletions(-) create mode 100644 plugins/editorconfig.js create mode 100644 plugins/helpers/browser.js create mode 100644 plugins/helpers/license.js create mode 100644 plugins/helpers/licenses.js create mode 100644 plugins/helpers/url.js create mode 100644 templates/editorconfig.js diff --git a/plugins/editorconfig.js b/plugins/editorconfig.js new file mode 100644 index 0000000..28106e7 --- /dev/null +++ b/plugins/editorconfig.js @@ -0,0 +1,27 @@ +'use strict'; + +var gutil = require('gulp-util'); +var through = require('through2'); +var utils = require('../lib/utils'); +var logger = require('../lib/logging'); + +module.exports = function editorconfig(verb) { + return function() { + var writeFile = false; + + return through.obj(function (file, enc, cb) { + this.push(file); + cb(); + }, function (cb) { + var stats = verb.get('stats'); + if (stats.files.indexOf('.editorconfig') === -1) { + var tmpl = require('../templates/editorconfig'); + var file = new gutil.File({path: '.editorconfig'}); + file.contents = new Buffer(tmpl); + this.push(file); + } + cb(); + }); + }; +}; + diff --git a/plugins/helpers/browser.js b/plugins/helpers/browser.js new file mode 100644 index 0000000..916af4c --- /dev/null +++ b/plugins/helpers/browser.js @@ -0,0 +1,15 @@ +'use strict'; + + +var unique = require('array-unique'); +var files = require('./files'); + +module.exports = function(pkg, file) { + // populate the `browser` property + var browser = files.toFiles(file.base, ['browser.js']); + if (browser.length || pkg.browser && pkg.browser.length) { + pkg.browser = unique(browser, pkg.browser); + } + return pkg; +}; + diff --git a/plugins/helpers/devDependencies.js b/plugins/helpers/devDependencies.js index 693a7b2..5e63420 100644 --- a/plugins/helpers/devDependencies.js +++ b/plugins/helpers/devDependencies.js @@ -9,9 +9,22 @@ exports.removeVerb = function(pkg) { delete pkg.devDependencies['verb-tag-jscomments']; delete pkg.devDependencies.verb; } + + if (pkg && pkg.devDependencies && pkg.devDependencies.verb) { + var version = stripNonNumber(pkg.devDependencies.verb).split('.'); + if (version[1] < 3) { + delete pkg.devDependencies.verb; + } else if (version[1] >= 3) { + pkg.devDependencies.verb = '^0.5.0'; + } + } return pkg; }; +function stripNonNumber(str) { + return str.replace(/^[\D\s]+/, ''); +} + /** * Remove `should` when it's not being used * in tests. diff --git a/plugins/helpers/license.js b/plugins/helpers/license.js new file mode 100644 index 0000000..3baecdc --- /dev/null +++ b/plugins/helpers/license.js @@ -0,0 +1,20 @@ +'use strict'; + +var typeOf = require('kind-of'); +var utils = require('../../lib/utils'); + +exports.normalize = function normalize(pkg) { + var license = pkg.license; + + if (!license) { return pkg; } + var type = typeOf(license); + + if (type === 'string') { + license = {type: license}; + } + + if (type === 'object' && license.url && utils.contains(license.url, 'LICENSE-MIT')) { + license.url = license.url.split('LICENSE-MIT').join('LICENSE'); + } + return pkg; +}; diff --git a/plugins/helpers/licenses.js b/plugins/helpers/licenses.js new file mode 100644 index 0000000..13ceed3 --- /dev/null +++ b/plugins/helpers/licenses.js @@ -0,0 +1,15 @@ +'use strict'; + +var typeOf = require('kind-of'); +var utils = require('../../lib/utils'); + +exports.normalize = function normalize(pkg) { + var licenses = pkg.licenses; + + if (Array.isArray(licenses) && licenses.length === 1) { + pkg.license = pkg.licenses[0]; + delete pkg.licenses; + } + + return pkg; +}; diff --git a/plugins/helpers/repo.js b/plugins/helpers/repo.js index 706d401..0227b7f 100644 --- a/plugins/helpers/repo.js +++ b/plugins/helpers/repo.js @@ -9,4 +9,4 @@ var utils = require('../../lib/utils'); * @return {String} */ -exports.toString = utils.repo; +exports.repo = utils.repo; diff --git a/plugins/helpers/scripts.js b/plugins/helpers/scripts.js index ba548d9..7d016f3 100644 --- a/plugins/helpers/scripts.js +++ b/plugins/helpers/scripts.js @@ -1,18 +1,12 @@ 'use strict'; -var sortObj = require('sort-object'); -var diff = require('arr-diff'); -var keys = require('./keys'); - /** * Remove the old verb */ exports.fixMocha = function(pkg) { - var keys = diff(Object.keys(pkg), keys); - var res = sortObj(pkg, keys.concat(keys)); - if (res.scripts && res.scripts.test && /mocha -r/i.test(res.scripts.test)) { - res.scripts.test = 'mocha'; + if (pkg.scripts && pkg.scripts.test && /mocha -r/i.test(pkg.scripts.test)) { + pkg.scripts.test = 'mocha'; } - return res; + return pkg; }; diff --git a/plugins/helpers/url.js b/plugins/helpers/url.js new file mode 100644 index 0000000..6cbb4bb --- /dev/null +++ b/plugins/helpers/url.js @@ -0,0 +1,5 @@ +'use strict'; + +var origin = require('remote-origin-url'); + +// console.log(origin) diff --git a/templates/editorconfig.js b/templates/editorconfig.js new file mode 100644 index 0000000..1b6b157 --- /dev/null +++ b/templates/editorconfig.js @@ -0,0 +1,19 @@ +module.exports = [ + '# http://editorconfig.org', + 'root = true', + '', + '[*]', + 'indent_style = space', + 'indent_size = 2', + 'end_of_line = lf', + 'charset = utf-8', + 'trim_trailing_whitespace = true', + 'insert_final_newline = true', + '', + '[*.md]', + 'trim_trailing_whitespace = false', + '', + '[test/fixtures/*]', + 'insert_final_newline = false', + 'trim_trailing_whitespace = false' +].join('\n'); From f02d793404eea8901a9476f4b389344af7ec3e8b Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Wed, 25 Feb 2015 07:56:18 -0500 Subject: [PATCH 024/274] temporary error handling --- plugins/banners.js | 33 ++++++++++------ plugins/dotfiles.js | 71 ++++++++++++++++++++--------------- plugins/gitignore.js | 67 ++++++++++++++++++--------------- plugins/index.js | 20 +++++----- plugins/jshint.js | 80 +++++++++++++++++++++++---------------- plugins/license.js | 42 ++++++++++++--------- plugins/pkg.js | 89 ++++++++++++++++++++++---------------------- plugins/tests.js | 57 ++++++++++++++++++++-------- plugins/travis.js | 22 +++++++++-- plugins/verbmd.js | 12 +++++- 10 files changed, 297 insertions(+), 196 deletions(-) diff --git a/plugins/banners.js b/plugins/banners.js index c83725f..eec5a61 100644 --- a/plugins/banners.js +++ b/plugins/banners.js @@ -1,13 +1,16 @@ 'use strict'; +var gutil = require('gulp-util'); var through = require('through2'); var parse = require('parse-copyright'); var banner = require('update-banner'); +var hasBanner = require('has-banner'); var merge = require('merge-deep'); var logger = require('../lib/logging'); +var utils = require('../lib/utils'); -module.exports = function (verb) { - return function bannersPlugin(options) { +module.exports = function(verb) { + return function(options) { var opts = merge({}, options); return through.obj(function (file, enc, cb) { @@ -16,19 +19,27 @@ module.exports = function (verb) { return cb(); } - var str = file.contents.toString(); - var log = logger(str); + try { + if (utils.contains(file.path, '.js')) { + var str = file.contents.toString(); + var log = logger(str); - if (/^\/\*[!*]/.test(str.trim()) || opts.banner) { - var copyright = parse(str); - if (copyright && copyright.length) { - file.data.copyright = copyright[0]; + if (hasBanner(str) || opts.banner) { + var copyright = parse(str); + if (copyright && copyright.length) { + file.data.copyright = copyright[0]; + } + str = banner(str, file.data); + log.success(str, 'updated banners in', file.relative); + } + + file.contents = new Buffer(str); } - str = banner(str, file.data); - log.success(str, 'updated banners in', file.relative); + } catch (err) { + this.emit('error', new gutil.PluginError('update:banners', err)); + return cb(); } - file.contents = new Buffer(str); this.push(file); cb(); }); diff --git a/plugins/dotfiles.js b/plugins/dotfiles.js index 866707c..bd3374c 100644 --- a/plugins/dotfiles.js +++ b/plugins/dotfiles.js @@ -1,39 +1,50 @@ 'use strict'; +var gutil = require('gulp-util'); var through = require('through2'); var utils = require('../lib/utils'); var logger = require('../lib/logging'); -module.exports = function dotfilesPlugin() { - return through.obj(function (file, enc, cb) { - if (file.isNull() || !file.isBuffer()) { +module.exports = function(verb) { + return function() { + return through.obj(function (file, enc, cb) { + if (file.isNull() || !file.isBuffer()) { + this.push(file); + return cb(); + } + + try { + + if (utils.contains(file.path, '.gitattributes')) { + var str = file.contents.toString(); + var log = logger(str); + + str = [ + '# Enforce Unix newlines', + '* text eol=lf', + '', + '# binaries', + '*.ai binary', + '*.psd binary', + '*.jpg binary', + '*.gif binary', + '*.png binary', + '*.jpeg binary' + ].join('\n'); + + log.success(str, 'updated patterns in', file.relative); + file.contents = new Buffer(str); + } + + } catch (err) { + this.emit('error', new gutil.PluginError('update:dotfiles', err)); + return cb(); + } + + this.push(file); - return cb(); - } - - if (utils.contains(file, '.gitattributes')) { - var str = file.contents.toString(); - var log = logger(str); - - str = [ - '# Enforce Unix newlines', - '* text eol=lf', - '', - '# binaries', - '*.ai binary', - '*.psd binary', - '*.jpg binary', - '*.gif binary', - '*.png binary', - '*.jpeg binary' - ].join('\n'); - - log.success(str, 'updated patterns in', file.relative); - file.contents = new Buffer(str); - } - - this.push(file); - cb(); - }); + cb(); + }); + }; }; diff --git a/plugins/gitignore.js b/plugins/gitignore.js index a1b2e5c..b6f4448 100644 --- a/plugins/gitignore.js +++ b/plugins/gitignore.js @@ -1,43 +1,50 @@ 'use strict'; +var gutil = require('gulp-util'); var difference = require('arr-diff'); var through = require('through2'); var utils = require('../lib/utils'); var logger = require('../lib/logging'); -module.exports = function gitignore() { - return through.obj(function (file, enc, cb) { - if (file.isNull() || !file.isBuffer()) { - this.push(file); - return cb(); - } - - if (utils.contains(file, '.gitignore')) { - // just get these started - var patterns = ['*.DS_Store']; - - var str = file.contents.toString(); - var log = logger(str); - - var lines = str.split('\n').map(trim); - var diff = difference(patterns, lines); - - if (lines[0].charAt(0) === '#') { - var comment = lines.slice(0, 1); - var rest = lines.slice(1); - lines = comment.concat(diff).concat(rest); - } else { - lines = diff.concat(lines); +module.exports = function gitignore(verb) { + return function() { + return through.obj(function (file, enc, cb) { + if (file.isNull() || !file.isBuffer()) { + this.push(file); + return cb(); } - str = lines.join('\n'); - log.success(str, 'updated patterns in', file.relative); - file.contents = new Buffer(str); - } + try { + if (utils.contains(file.path, '.gitignore')) { + // just get these started + var patterns = ['*.DS_Store']; + var str = file.contents.toString(); + var log = logger(str); + + var lines = str.split('\n').map(trim); + var diff = difference(patterns, lines); + + if (lines[0].charAt(0) === '#') { + var comment = lines.slice(0, 1); + var rest = lines.slice(1); + lines = comment.concat(diff).concat(rest); + } else { + lines = diff.concat(lines); + } + + str = lines.join('\n'); + log.success(str, 'updated patterns in', file.relative); + file.contents = new Buffer(str); + } + } catch (err) { + this.emit('error', new gutil.PluginError('update:gitignore', err)); + return cb(); + } - this.push(file); - cb(); - }); + this.push(file); + cb(); + }); + }; }; diff --git a/plugins/index.js b/plugins/index.js index 5de9ce9..94c1097 100644 --- a/plugins/index.js +++ b/plugins/index.js @@ -9,15 +9,15 @@ module.exports = function (verb) { return { - banners : require('./banners.js')(verb), - dotfiles : require('./dotfiles.js'), - index : require('./index.js'), - gitignore : require('./gitignore.js'), - jshint : require('./jshint.js'), - license : require('./license.js'), - pkg : require('./pkg.js')(verb), - tests : require('./tests.js')(verb), - travis : require('./travis.js')(verb), - verbmd : require('./verbmd.js')(verb) + banners : require('./banners.js')(verb), + dotfiles : require('./dotfiles.js')(verb), + editorconfig : require('./editorconfig.js')(verb), + gitignore : require('./gitignore.js')(verb), + jshint : require('./jshint.js')(verb), + license : require('./license.js')(verb), + pkg : require('./pkg.js')(verb), + tests : require('./tests.js')(verb), + travis : require('./travis.js')(verb), + verbmd : require('./verbmd.js')(verb) }; }; diff --git a/plugins/jshint.js b/plugins/jshint.js index 6bb6c78..d6b0cd0 100644 --- a/plugins/jshint.js +++ b/plugins/jshint.js @@ -1,45 +1,59 @@ 'use strict'; +var gutil = require('gulp-util'); var through = require('through2'); var merge = require('merge-deep'); var sortObj = require('sort-object'); var logger = require('../lib/logging'); +var utils = require('../lib/utils'); -module.exports = function jshintPlugin() { - return through.obj(function (file, enc, cb) { - if (file.isNull() || !file.isBuffer()) { - this.push(file); - return cb(); - } +module.exports = function jshint(verb) { + return function() { + return through.obj(function (file, enc, cb) { + if (file.isNull() || !file.isBuffer()) { + this.push(file); + return cb(); + } - var obj = JSON.parse(file.contents.toString()); - var log = logger({nocompare: true}); + try { + if (utils.contains(file.path, 'jshint')) { + var str = file.contents.toString(); + var obj = JSON.parse(str); + var log = logger(str); - delete obj.globals; + delete obj.globals; - // TODO: move to user preferences - obj = sortObj(merge(obj, { - "asi": false, - "boss": true, - "curly": true, - "eqeqeq": true, - "eqnull": true, - "esnext": true, - "immed": true, - "latedef": false, - "laxcomma": false, - "mocha": true, - "newcap": true, - "noarg": true, - "node": true, - "sub": true, - "undef": true, - "unused": true - })); + // TODO: move to user preferences + obj = sortObj(merge(obj, { + "asi": false, + "boss": true, + "curly": true, + "eqeqeq": true, + "eqnull": true, + "esnext": true, + "immed": true, + "latedef": false, + "laxcomma": false, + "mocha": true, + "newcap": true, + "noarg": true, + "node": true, + "sub": true, + "undef": true, + "unused": true + })); + var res = JSON.stringify(obj, null, 2); + log.success(res, 'updated properties in', file.relative); + file.contents = new Buffer(res); + } + } catch (err) { + console.log('jshint:', err); + this.emit('error', new gutil.PluginError('update:jshint', err)); + return cb(); + } - file.contents = new Buffer(JSON.stringify(obj, null, 2)); - log.success(true, 'updated properties in', file.relative); - this.push(file); - cb(); - }); + this.push(file); + cb(); + }); + }; }; diff --git a/plugins/license.js b/plugins/license.js index a96a836..95a5eeb 100644 --- a/plugins/license.js +++ b/plugins/license.js @@ -1,27 +1,35 @@ 'use strict'; +var gutil = require('gulp-util'); var through = require('through2'); var update = require('update-license'); var utils = require('../lib/utils'); var logger = require('../lib/logging'); -module.exports = function license() { - return through.obj(function (file, enc, cb) { - if (file.isNull() || !file.isBuffer()) { - this.push(file); - return cb(); - } - - var str = file.contents.toString(); - var log = logger(str); +module.exports = function license(verb) { + return function() { + return through.obj(function (file, enc, cb) { + if (file.isNull() || !file.isBuffer()) { + this.push(file); + return cb(); + } - if (utils.contains(file, 'LICENSE')) { - str = update(str); - log.success(str, 'updated patterns in', file.relative); - } + try { + if (utils.contains(file.path, 'LICENSE')) { + var str = file.contents.toString(); + var log = logger(str); + str = update(str); + log.success(str, 'updated patterns in', file.relative); + file.contents = new Buffer(str); + } + } catch (err) { + console.log(err); + this.emit('error', new gutil.PluginError('update:license', err)); + return cb(); + } - file.contents = new Buffer(str); - this.push(file); - cb(); - }); + this.push(file); + cb(); + }); + }; }; diff --git a/plugins/pkg.js b/plugins/pkg.js index 3d13bee..71b1e1e 100644 --- a/plugins/pkg.js +++ b/plugins/pkg.js @@ -1,14 +1,23 @@ 'use strict'; +/** + * Module dependencies + */ + var path = require('path'); -var clone = require('clone-deep'); -var typeOf = require('kind-of'); -var unique = require('array-unique'); +var diff = require('arr-diff'); +var gutil = require('gulp-util'); var through = require('through2'); var update = require('update-package'); +var sortObj = require('sort-object'); + +/** + * Local dependencies + */ + +var helpers = require('./helpers/'); var logger = require('../lib/logging'); var utils = require('../lib/utils'); -var helpers = require('./helpers/'); /** * virtually everything in this file is a temporary @@ -24,62 +33,52 @@ module.exports = function(verb) { } try { - var log = logger({nocompare: true}); - var str = file.contents.toString(); - var obj = JSON.parse(str); + if (utils.contains(file.path, 'package.json')) { + var str = file.contents.toString(); + // pass the initial string to the logger. + var log = logger(str); - // run updates on package.json fields - var pkg = update(obj); + // parse the string + var obj = JSON.parse(str); - // populate the `files` property - pkg.files = helpers.files.toFiles(file.base, pkg.files); + // run updates on package.json fields + var pkg = update(obj); - // populate the `browser` property - // var browser = helpers.files.toFiles(file.base, ['browser.js']); - // if (browser.length || pkg.browser && pkg.browser.length) { - // pkg.browser = unique(browser, pkg.browser); - // } + // populate the `files` property + pkg.files = helpers.files.toFiles(file.base, pkg.files); - var repo = pkg && pkg.repository && typeof pkg.repository === 'object' - ? pkg.repository.url - : pkg.repository; + // remove old verb from deps + pkg = helpers.devDependencies.removeVerb(pkg); - // make repo a string. - if (repo) { - pkg.repository = helpers.repo.toString(repo); - } + // fix the scripts property + pkg = helpers.scripts.fixMocha(pkg); - pkg = helpers.devDependencies.removeVerb(pkg); + // if should doesn't exist, remove it + if (!verb.get('data.hasShould')) { + pkg = helpers.devDependencies.removeShould(pkg); + } - if (!verb.get('data.hasShould')) { - pkg = helpers.devDependencies.removeShould(pkg); - } + // fix the `license` and `licenses` properties + pkg = helpers.licenses.normalize(pkg); + pkg = helpers.license.normalize(pkg); - pkg.license = 'MIT'; - delete pkg.licenses; + var keys = helpers.keys.concat(diff(Object.keys(pkg), helpers.keys)); + var sorted = sortObj(pkg, keys); + var res = JSON.stringify(sorted, null, 2); - pkg = helpers.scripts.fixMocha(pkg); + log.success(res, 'updated properties in', file.relative); + file.contents = new Buffer(res); + } - file.contents = new Buffer(JSON.stringify(pkg, null, 2)); - log.success(null, 'updated properties in', file.relative); } catch (err) { - console.log('plugin:pkg', err); + console.log(err); + this.emit('error', new gutil.PluginError('update:pkg', err)); + return cb(); } + this.push(file); cb(); }); }; }; - -function fixLicense(license) { - if (typeof license === 'string') { - return license; - } - - if (typeOf(license) === 'object' && license.url && license.url.indexOf('LICENSE-MIT') !== -1) { - license.url = license.url.split('LICENSE-MIT').join('LICENSE'); - } - - return license; -} diff --git a/plugins/tests.js b/plugins/tests.js index dc59c5e..0d826ec 100644 --- a/plugins/tests.js +++ b/plugins/tests.js @@ -1,8 +1,11 @@ 'use strict'; +var gutil = require('gulp-util'); var through = require('through2'); +var regex = require('requires-regex'); var logger = require('../lib/logging'); var tests = require('../lib/tests'); +var utils = require('../lib/utils'); module.exports = function(verb) { return function() { @@ -12,26 +15,50 @@ module.exports = function(verb) { return cb(); } - if (utils.contains(file.path, 'test')) { - var str = file.contents.toString(); - var log = logger(str); + try { - var hasShould = /['"]should/.test(str); - verb.set('data.hasShould', hasShould); + if (utils.contains(file.path, 'test')) { + var stats = verb.get('stats'); - // `should` exists in test files - if (hasShould) { - str = tests.fixShould(str, file.relative); - log.success(str, 'updated "should" statements in', file.relative); - } + // console.log(verb.match('*.js')) + + var str = file.contents.toString(); + var log = logger(str); + + // test.js is at the root, let's make sure + // the path to `index.js` is correct + if (file.path.indexOf('test/') === -1) { + str.replace(regex(), function ($1, $2, $3, fp) { + if (fp && fp === '../' || fp === '..') { + str = str.replace(fp, './'); + } + }); + } + + var hasShould = /['"]should/.test(str); + verb.set('data.hasShould', hasShould); - var deps = verb.env.devDependencies; - if (hasShould && !deps.hasOwnProperty('should')) { - console.log(verb.env); + var deps = verb.env.devDependencies; + + // `should` exists in test files + if (hasShould) { + str = tests.fixShould(str, file.relative); + if (!deps.hasOwnProperty('should')) { + // TODO: add should to deps if exists in tets + // console.log(verb.env); + } + log.success(str, 'updated "should" statements in', file.relative); + } else if (deps.hasOwnProperty('should')) { + verb.set('strip.pkg.devDependencies', 'should'); + } + + file.contents = new Buffer(str); } - file.contents = new Buffer(str); + } catch (err) { + console.log(err); + this.emit('error', new gutil.PluginError('update:tests', err)); + return cb(); } - this.push(file); cb(); }); diff --git a/plugins/travis.js b/plugins/travis.js index b6e440b..ba01553 100644 --- a/plugins/travis.js +++ b/plugins/travis.js @@ -1,7 +1,9 @@ 'use strict'; +var gutil = require('gulp-util'); var through = require('through2'); var yaml = require('js-yaml'); +var mixin = require('mixin-deep'); var utils = require('../lib/utils'); var logger = require('../lib/logging'); @@ -13,13 +15,27 @@ module.exports = function travis(verb) { return cb(); } - if (utils.contains(file.path, '.travis')) { + try { var str = file.contents.toString(); var log = logger({nocompare: true}); - var obj = yaml.load(str); + // var obj = yaml.load(str); + + file.contents = new Buffer([ + 'sudo: false', + 'language: node_js', + 'node_js:', + ' - "0.10"', + ' - "0.12"', + ' - "iojs"', + 'git:', + ' depth: 10', + ].join('\n')); - file.contents = new Buffer(str); log.success(true, 'loaded data from', file.relative); + } catch (err) { + console.log(err); + this.emit('error', new gutil.PluginError('update:travis', err)); + return cb(); } this.push(file); diff --git a/plugins/verbmd.js b/plugins/verbmd.js index 74ca37c..5cb84d9 100644 --- a/plugins/verbmd.js +++ b/plugins/verbmd.js @@ -1,9 +1,11 @@ 'use strict'; +var gutil = require('gulp-util'); var matter = require('gray-matter'); var through = require('through2'); var verbmd = require('../lib/verbmd'); var logger = require('../lib/logging'); +var utils = require('../lib/utils'); module.exports = function(verb) { return function () { @@ -13,7 +15,8 @@ module.exports = function(verb) { return cb(); } - if (utils.contains(file.path, '.verb')) { + try { + var stats = verb.get('stats'); var str = file.contents.toString(); var log = logger(str); @@ -25,9 +28,14 @@ module.exports = function(verb) { log.success(str, 'stripped deprecated front-matter tags in', file.relative); } - str = verbmd(str); + str = verbmd(str, stats || {}); log.success(str, 'updated helpers in', file.relative); file.contents = new Buffer(str); + + } catch (err) { + console.log(err); + this.emit('error', new gutil.PluginError('update:verbmd', err)); + return cb(); } this.push(file); From bb52e6db42286c64a919ad963ae82cc1f00f3fdf Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Wed, 25 Feb 2015 07:56:36 -0500 Subject: [PATCH 025/274] adds `match` util --- lib/utils.js | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index d96e41b..76bd8af 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -2,6 +2,15 @@ var fs = require('fs'); var path = require('path'); +var mm = require('micromatch'); + + +exports.match = function match(files) { + return function(pattern, options) { + return mm(files, pattern, options); + }; +}; + /** * Pass a file or an array of filepaths that "might" exist, @@ -12,6 +21,10 @@ var path = require('path'); */ exports.exists = function exists(files) { + if (!files) { + throw new Error('utils.exists() expects an array or string.'); + } + files = Array.isArray(files) ? files : [files]; var len = files.length; var res = {ENOENT: new Array(len), EXISTS: files.slice()}; @@ -33,8 +46,11 @@ exports.exists = function exists(files) { * @return {String} */ -exports.toString = function toString(file) { - return file.contents.toString(); +exports.replace = function replace(str, a, b) { + if (typeof str !== 'string' || typeof a !== 'string' || typeof b !== 'string') { + throw new TypeError('utils.replace() expects a string.'); + } + return str.split(a).join(b); }; /** @@ -47,8 +63,15 @@ exports.toString = function toString(file) { * @return {Boolean} */ -exports.contains = function contains(file, str) { - return file.path.indexOf(str) !== -1; +exports.contains = function contains(str, ch) { + if (typeof str !== 'string' ) { + throw new TypeError('utils.contains() first arg should be a string, not:', str); + } + + if (typeof ch !== 'string') { + throw new TypeError('utils.contains() second arg should be a string, not:', ch); + } + return str.indexOf(ch) !== -1; }; /** @@ -59,6 +82,9 @@ exports.contains = function contains(file, str) { */ exports.repo = function repo(str) { + if (typeof str !== 'string') { + throw new TypeError('utils.repo() expects a string.'); + } return str.replace(/\w+:\/\/github.com\/(.*?)(?:\.git)?$/, '$1'); }; @@ -71,6 +97,9 @@ exports.repo = function repo(str) { */ exports.trailingSlash = function trailingSlash(fp, stat) { + if (typeof fp !== 'string') { + throw new TypeError('utils.trailingSlash() expects a string.'); + } if (stat.isFile()) { return fp; } From 890367d71d2e07e203316d561f33d578326fbe17 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Wed, 25 Feb 2015 07:57:08 -0500 Subject: [PATCH 026/274] travisBadge util for readme --- lib/verbmd.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/verbmd.js b/lib/verbmd.js index 2b09701..739cf9a 100644 --- a/lib/verbmd.js +++ b/lib/verbmd.js @@ -10,6 +10,13 @@ var authors = [ ].join('\n'); +function addTravisBadge(str, stats) { + if (stats && stats.hasTravis && !/badge\(.travis/.test(str)) { + str = str.split('{%= badge("fury") %}').join('{%= badge("fury") %} {%= badge("travis") %}'); + } + return str; +} + function fixInstall(str) { var re = /## Install[\s\n]+{%= include/g; str = str.replace(re, '{%= include'); @@ -39,7 +46,8 @@ function runningTests(str) { return str.replace(re, '## Running tests\n{%= include("tests") %}'); } -module.exports = function (str) { +module.exports = function (str, stats) { + str = addTravisBadge(str, stats); str = runningTests(str); str = fixInstall(str); str = fixHelpers(str); From 93baa329d235b2c3ec4712f1c50d498e82079e57 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Wed, 25 Feb 2015 07:57:42 -0500 Subject: [PATCH 027/274] add tasks --- verbfile.js | 103 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 69 insertions(+), 34 deletions(-) diff --git a/verbfile.js b/verbfile.js index ed5b883..9964bd9 100644 --- a/verbfile.js +++ b/verbfile.js @@ -1,5 +1,6 @@ 'use strict'; +var fs = require('fs'); var del = require('del'); var path = require('path'); var verb = require('verb'); @@ -7,32 +8,43 @@ var parse = require('parse-copyright'); var log = require('./lib/logging')({nocompare: true}); var plugins = require('./plugins')(verb); var utils = require('./lib/utils'); +var glob = require('glob'); + +function tryReaddir(fp) { + try { + return fs.readdirSync(fp); + } catch (err) {} + return []; +} + +// verb.known(function(env) { +// var orgs = ['jonschlinkert', 'doowb', 'assemble', 'verb', 'helpers', 'regexps']; +// var repo = env.repository; +// if (typeof repo === 'object') { +// repo = repo.url; +// } +// var len = orgs.length; +// while(len--) { +// var ele = orgs[len]; +// if (repo.indexOf(ele) !== -1) { +// return true; +// } +// } +// return false; +// }); -verb.known(function(env) { - var orgs = ['jonschlinkert', 'doowb', 'assemble', 'verb', 'helpers', 'regexps']; - var repo = env.repository; - if (typeof repo === 'object') { - repo = repo.url; - } - var len = orgs.length; - while(len--) { - var ele = orgs[len]; - if (repo.indexOf(ele) !== -1) { - return true; - } - } - return false; -}); +verb.onLoad(/./, function (file, next) { + var files = tryReaddir(process.cwd()); + var tests = []; -verb.data({ - travis: { - sudo: false, - language: 'node_js', - node_js: ['iojs', '0.12', '0.10'] + if (files.indexOf('test') !== -1) { + tests = tryReaddir(process.cwd() + '/test'); } -}); -verb.onLoad(/./, function (file, next) { + verb.set('stats.files', files.concat(tests || [])); + verb.set('stats.hasTravis', fs.existsSync('.travis.yml')); + verb.match = utils.match(files); + file.render = false; file.readme = false; next(); @@ -49,6 +61,21 @@ verb.copy('.verbrc.md', function (file) { return path.dirname(file.relative); }); +var files = glob.sync('test/**').filter(function (fp) { + return fs.statSync(fp).isDirectory(); +}); + +var singleTest = false; +if (files && files.length === 1) { + singleTest = true; + verb.set('singleTest', true); + verb.copy('test/test.js', function (file) { + file.path = 'test.js'; + log.success('moved', file.path); + return file.base; + }); +} + verb.copy('LICENSE-MIT', function (file) { file.path = 'LICENSE'; log.success('renamed', file.relative); @@ -57,7 +84,6 @@ verb.copy('LICENSE-MIT', function (file) { verb.task('banners', function () { verb.src(['*.js', 'test/*.js', 'lib/*.js'], {render: false}) - .pipe(plugins.tests()) .pipe(plugins.banners()) .pipe(verb.dest(function (file) { return path.dirname(file.path); @@ -91,16 +117,15 @@ verb.task('travis', function () { })); }); -verb.task('license', function () { - verb.src('LICENSE{,-MIT}', {render: false}) - .pipe(plugins.license()) +verb.task('tests', function () { + verb.src(['test.js', 'test/*.js'], {render: false}) + .pipe(plugins.tests()) .pipe(verb.dest(function (file) { - file.path = 'LICENSE'; return path.dirname(file.path); })); }); -verb.task('travis', function () { +verb.task('license', function () { verb.src('LICENSE{,-MIT}', {render: false}) .pipe(plugins.license()) .pipe(verb.dest(function (file) { @@ -111,14 +136,20 @@ verb.task('travis', function () { verb.task('dotfiles', function () { verb.src('.git*', {render: false, dot: true}) - .pipe(plugins.dotfiles()) + .pipe(plugins.editorconfig()) .pipe(plugins.gitignore()) .pipe(verb.dest(function (file) { return path.dirname(file.path); })) .on('end', function (cb) { var files = ['.npmignore', 'test/mocha.opts', '.verbrc.md', 'LICENSE-MIT']; - var exists = utils.exists(files).EXISTS; + var res = utils.exists(files); + + var exists = res.EXISTS; + if (verb.get('singleTest')) { + exists.push('test'); + } + if (exists.length) { del(exists, cb); log.info('deleted', exists.join(', ')); @@ -134,18 +165,22 @@ verb.task('pkg', function () { verb.task('readme', function () { verb.src('.verb.md') - .pipe(verb.dest('.')); + .pipe(verb.dest('.')) + .on('end', function (cb) { + log.success(true, 'updated.'); + }); }); verb.task('default', [ + 'tests', 'banners', 'verbfile', 'dotfiles', 'travis', 'jshint', 'license', - 'pkg' + 'pkg', + 'readme' ]); - -// verb.diff() +verb.diff() verb.run(); From 28badbc689dbc01c32946802660e861b2e14558e Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Wed, 25 Feb 2015 07:57:58 -0500 Subject: [PATCH 028/274] clean up --- README.md | 2 +- lib/_plugin-base.js | 2 +- lib/logging.js | 2 ++ package.json | 22 ++++++++++++++++------ test.js | 20 ++++++++++++++------ 5 files changed, 34 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 58ec3bb..2fc372d 100644 --- a/README.md +++ b/README.md @@ -44,4 +44,4 @@ Released under the license *** -_This file was generated by [verb](https://github.com/assemble/verb) on February 20, 2015._ \ No newline at end of file +_This file was generated by [verb](https://github.com/assemble/verb) on February 25, 2015._ \ No newline at end of file diff --git a/lib/_plugin-base.js b/lib/_plugin-base.js index 146b4a8..0cc7d02 100644 --- a/lib/_plugin-base.js +++ b/lib/_plugin-base.js @@ -2,7 +2,7 @@ var through = require('through2'); -module.exports = function(options, cb) { +module.exports = function(options) { return through.obj(function (file, enc, cb) { var str = file.contents.toString(); diff --git a/lib/logging.js b/lib/logging.js index 5ae72c0..3e220ed 100644 --- a/lib/logging.js +++ b/lib/logging.js @@ -1,3 +1,5 @@ +'use strict'; + var chalk = require('chalk'); var symbol = require('log-symbols'); var merge = require('merge-deep'); diff --git a/package.json b/package.json index bc1020e..b5b518a 100644 --- a/package.json +++ b/package.json @@ -31,23 +31,33 @@ "arr-diff": "^1.0.1", "array-intersection": "^0.1.1", "array-unique": "^0.1.1", - "chalk": "^0.5.1", - "clone-deep": "^0.1.1", + "chalk": "^1.0.0", "del": "^1.1.1", "delete": "^0.1.5", "export-files": "^1.0.0", - "gray-matter": "^1.2.4", + "glob": "^4.4.0", + "gray-matter": "^1.2.5", + "gulp-util": "^3.0.4", + "has-banner": "^0.1.0", "js-yaml": "^3.2.7", "kind-of": "^1.1.0", "log-symbols": "^1.0.1", "merge-deep": "^0.1.5", - "micromatch": "^1.4.0", + "micromatch": "^1.4.2", + "mixin-deep": "^1.0.1", "parse-copyright": "^0.4.0", + "remote-origin-url": "^0.2.1", + "requires-regex": "^0.2.0", "sort-object": "^1.0.0", + "template-utils": "^0.4.1", "through2": "^0.6.3", - "update-banner": "^0.1.0", + "update-banner": "^0.1.1", "update-license": "^0.3.0", - "update-package": "^0.1.1" + "update-package": "^0.1.1", + "verb": "^0.5.0" + }, + "devDependencies": { + "should": "^5.0.1" }, "keywords": [ "javascript", diff --git a/test.js b/test.js index b68bc8c..3b12785 100644 --- a/test.js +++ b/test.js @@ -1,17 +1,25 @@ /*! * update * - * Copyright (c) 2013-2015, Jon Schlinkert. + * Copyright (c) 2015, Jon Schlinkert. * Licensed under the MIT License. */ +'use strict'; + var assert = require('assert'); +require('should'); var update = require('./'); describe('update', function () { - it('should update the year', function () { - assert.equal(update('Copyright (c) 2013, Jon Schlinkert.'), 'Copyright (c) 2013-2015, Jon Schlinkert.'); - assert.equal(update('Copyright (c) 2014, Jon Schlinkert.'), 'Copyright (c) 2014-2015, Jon Schlinkert.'); - assert.equal(update('Copyright (c) 2015, Jon Schlinkert.'), 'Copyright (c) 2015, Jon Schlinkert.'); + it('should:', function () { + update('a').should.equal({a: 'b'}); + update('a').should.eql('a'); + }); + + it('should throw an error:', function () { + (function () { + update(); + }).should.throw('update expects valid arguments'); }); -}); \ No newline at end of file +}); From 0b2f5fa989f3491e95b4bd119b3e0f4f6d79924a Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Wed, 25 Feb 2015 08:05:23 -0500 Subject: [PATCH 029/274] adds travis template --- templates/travis.js | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 templates/travis.js diff --git a/templates/travis.js b/templates/travis.js new file mode 100644 index 0000000..65633ba --- /dev/null +++ b/templates/travis.js @@ -0,0 +1,10 @@ +module.exports = [ + 'sudo: false', + 'language: node_js', + 'node_js:', + ' - "0.10"', + ' - "0.12"', + ' - "iojs"', + 'git:', + ' depth: 10' +].join('\n'); From dbd985db9967deee0bddb9af8eaa21e851a6ab40 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Wed, 25 Feb 2015 08:13:15 -0500 Subject: [PATCH 030/274] update dotfile plugins/templates --- plugins/banners.js | 27 +++++++++++-------------- plugins/dotfiles.js | 40 +++++++------------------------------- plugins/editorconfig.js | 16 ++++++--------- plugins/travis.js | 36 ++++++++-------------------------- plugins/verbmd.js | 35 ++++++++++++++------------------- templates/gitattributes.js | 12 ++++++++++++ 6 files changed, 59 insertions(+), 107 deletions(-) create mode 100644 templates/gitattributes.js diff --git a/plugins/banners.js b/plugins/banners.js index eec5a61..7bf7f06 100644 --- a/plugins/banners.js +++ b/plugins/banners.js @@ -19,25 +19,20 @@ module.exports = function(verb) { return cb(); } - try { - if (utils.contains(file.path, '.js')) { - var str = file.contents.toString(); - var log = logger(str); + if (utils.contains(file.path, '.js')) { + var str = file.contents.toString(); + var log = logger(str); - if (hasBanner(str) || opts.banner) { - var copyright = parse(str); - if (copyright && copyright.length) { - file.data.copyright = copyright[0]; - } - str = banner(str, file.data); - log.success(str, 'updated banners in', file.relative); + if (hasBanner(str) || opts.banner) { + var copyright = parse(str); + if (copyright && copyright.length) { + file.data.copyright = copyright[0]; } - - file.contents = new Buffer(str); + str = banner(str, file.data); + log.success(str, 'updated banners in', file.relative); } - } catch (err) { - this.emit('error', new gutil.PluginError('update:banners', err)); - return cb(); + + file.contents = new Buffer(str); } this.push(file); diff --git a/plugins/dotfiles.js b/plugins/dotfiles.js index bd3374c..3183900 100644 --- a/plugins/dotfiles.js +++ b/plugins/dotfiles.js @@ -8,40 +8,14 @@ var logger = require('../lib/logging'); module.exports = function(verb) { return function() { return through.obj(function (file, enc, cb) { - if (file.isNull() || !file.isBuffer()) { - this.push(file); - return cb(); - } - - try { - - if (utils.contains(file.path, '.gitattributes')) { - var str = file.contents.toString(); - var log = logger(str); - - str = [ - '# Enforce Unix newlines', - '* text eol=lf', - '', - '# binaries', - '*.ai binary', - '*.psd binary', - '*.jpg binary', - '*.gif binary', - '*.png binary', - '*.jpeg binary' - ].join('\n'); - - log.success(str, 'updated patterns in', file.relative); - file.contents = new Buffer(str); - } - - } catch (err) { - this.emit('error', new gutil.PluginError('update:dotfiles', err)); - return cb(); - } - + this.push(file); + cb(); + }, function (cb) { + var file = new gutil.File({ + path: '.gitattributes' + }); + file.contents = new Buffer(require('../templates/gitattributes')); this.push(file); cb(); }); diff --git a/plugins/editorconfig.js b/plugins/editorconfig.js index 28106e7..f9121ad 100644 --- a/plugins/editorconfig.js +++ b/plugins/editorconfig.js @@ -5,21 +5,17 @@ var through = require('through2'); var utils = require('../lib/utils'); var logger = require('../lib/logging'); -module.exports = function editorconfig(verb) { +module.exports = function(verb) { return function() { - var writeFile = false; - return through.obj(function (file, enc, cb) { this.push(file); cb(); }, function (cb) { - var stats = verb.get('stats'); - if (stats.files.indexOf('.editorconfig') === -1) { - var tmpl = require('../templates/editorconfig'); - var file = new gutil.File({path: '.editorconfig'}); - file.contents = new Buffer(tmpl); - this.push(file); - } + var file = new gutil.File({ + contents: new Buffer(require('../templates/editorconfig')), + path: '.editorconfig' + }); + this.push(file); cb(); }); }; diff --git a/plugins/travis.js b/plugins/travis.js index ba01553..01f351c 100644 --- a/plugins/travis.js +++ b/plugins/travis.js @@ -10,34 +10,14 @@ var logger = require('../lib/logging'); module.exports = function travis(verb) { return function() { return through.obj(function (file, enc, cb) { - if (file.isNull() || !file.isBuffer()) { - this.push(file); - return cb(); - } - - try { - var str = file.contents.toString(); - var log = logger({nocompare: true}); - // var obj = yaml.load(str); - - file.contents = new Buffer([ - 'sudo: false', - 'language: node_js', - 'node_js:', - ' - "0.10"', - ' - "0.12"', - ' - "iojs"', - 'git:', - ' depth: 10', - ].join('\n')); - - log.success(true, 'loaded data from', file.relative); - } catch (err) { - console.log(err); - this.emit('error', new gutil.PluginError('update:travis', err)); - return cb(); - } - + this.push(file); + cb(); + }, function (cb) { + var log = logger({nocompare: true}); + var tmpl = require('../templates/travis'); + var file = new gutil.File({path: '.travis.yml'}); + file.contents = new Buffer(tmpl); + log.success(true, 'writing', file.relative); this.push(file); cb(); }); diff --git a/plugins/verbmd.js b/plugins/verbmd.js index 5cb84d9..1d18732 100644 --- a/plugins/verbmd.js +++ b/plugins/verbmd.js @@ -1,11 +1,9 @@ 'use strict'; -var gutil = require('gulp-util'); var matter = require('gray-matter'); var through = require('through2'); var verbmd = require('../lib/verbmd'); var logger = require('../lib/logging'); -var utils = require('../lib/utils'); module.exports = function(verb) { return function () { @@ -15,29 +13,26 @@ module.exports = function(verb) { return cb(); } - try { - var stats = verb.get('stats'); - var str = file.contents.toString(); - var log = logger(str); + var stats = verb.get('stats'); + var str = file.contents.toString(); - var obj = matter(str); - var keys = Object.keys(obj.data); + var orig = str; + var log = logger(orig); - if (keys.length && obj.data.hasOwnProperty('tags')) { - str = obj.content.replace(/^\s+/, ''); - log.success(str, 'stripped deprecated front-matter tags in', file.relative); - } + var obj = matter(str); + var keys = Object.keys(obj.data); - str = verbmd(str, stats || {}); - log.success(str, 'updated helpers in', file.relative); - file.contents = new Buffer(str); - - } catch (err) { - console.log(err); - this.emit('error', new gutil.PluginError('update:verbmd', err)); - return cb(); + if (keys.length && obj.data.hasOwnProperty('tags')) { + str = obj.content.replace(/^\s+/, ''); + log.success(str, 'stripped deprecated front-matter tags in', file.relative); } + str = verbmd(str, stats || {}); + log.success(str, 'updated front-matter in', file.relative); + + if (str !== orig) { + file.contents = new Buffer(str); + } this.push(file); cb(); }); diff --git a/templates/gitattributes.js b/templates/gitattributes.js new file mode 100644 index 0000000..c130085 --- /dev/null +++ b/templates/gitattributes.js @@ -0,0 +1,12 @@ +module.exports = [ + '# Enforce Unix newlines', + '* text eol=lf', + '', + '# binaries', + '*.ai binary', + '*.psd binary', + '*.jpg binary', + '*.gif binary', + '*.png binary', + '*.jpeg binary' +].join('\n'); From 12a7d17a5d291e77903e4646f8d9e6c38cd4861c Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Wed, 25 Feb 2015 08:16:01 -0500 Subject: [PATCH 031/274] clean up --- lib/utils.js | 15 +++++++++++++++ verbfile.js | 27 ++------------------------- 2 files changed, 17 insertions(+), 25 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index 76bd8af..8795004 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -4,6 +4,10 @@ var fs = require('fs'); var path = require('path'); var mm = require('micromatch'); +/** + * Creates a matching function to use against + * the list of given files. + */ exports.match = function match(files) { return function(pattern, options) { @@ -11,6 +15,17 @@ exports.match = function match(files) { }; }; +/** + * Try to read a directory of files. Silently catches + * any errors and returns an empty array. + */ + +exports.tryReaddir = function tryReaddir(fp) { + try { + return fs.readdirSync(fp); + } catch (err) {} + return []; +}; /** * Pass a file or an array of filepaths that "might" exist, diff --git a/verbfile.js b/verbfile.js index 9964bd9..27ef092 100644 --- a/verbfile.js +++ b/verbfile.js @@ -10,35 +10,12 @@ var plugins = require('./plugins')(verb); var utils = require('./lib/utils'); var glob = require('glob'); -function tryReaddir(fp) { - try { - return fs.readdirSync(fp); - } catch (err) {} - return []; -} - -// verb.known(function(env) { -// var orgs = ['jonschlinkert', 'doowb', 'assemble', 'verb', 'helpers', 'regexps']; -// var repo = env.repository; -// if (typeof repo === 'object') { -// repo = repo.url; -// } -// var len = orgs.length; -// while(len--) { -// var ele = orgs[len]; -// if (repo.indexOf(ele) !== -1) { -// return true; -// } -// } -// return false; -// }); - verb.onLoad(/./, function (file, next) { - var files = tryReaddir(process.cwd()); + var files = utils.tryReaddir(process.cwd()); var tests = []; if (files.indexOf('test') !== -1) { - tests = tryReaddir(process.cwd() + '/test'); + tests = utils.tryReaddir(process.cwd() + '/test'); } verb.set('stats.files', files.concat(tests || [])); From e28dda29ff3f52941de367111524b5ea4ceaafc0 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Wed, 25 Feb 2015 16:24:21 -0500 Subject: [PATCH 032/274] adds travis --- .travis.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..efe94c0 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,8 @@ +sudo: false +language: node_js +node_js: + - "0.10" + - "0.12" + - "iojs" +git: + depth: 10 \ No newline at end of file From 8fa0abefd6c2b1e3b3a40cab5bd927264f484c7d Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Wed, 25 Feb 2015 16:44:38 -0500 Subject: [PATCH 033/274] use verb.match --- verbfile.js | 51 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/verbfile.js b/verbfile.js index 27ef092..67aceed 100644 --- a/verbfile.js +++ b/verbfile.js @@ -5,22 +5,33 @@ var del = require('del'); var path = require('path'); var verb = require('verb'); var parse = require('parse-copyright'); -var log = require('./lib/logging')({nocompare: true}); +var logger = require('./lib/logging'); +var log = logger({nocompare: true}); var plugins = require('./plugins')(verb); +var verbmd = require('./lib/verbmd'); var utils = require('./lib/utils'); var glob = require('glob'); + verb.onLoad(/./, function (file, next) { var files = utils.tryReaddir(process.cwd()); var tests = []; - if (files.indexOf('test') !== -1) { + verb.match = utils.match(files); + if (verb.match('test').length) { tests = utils.tryReaddir(process.cwd() + '/test'); } verb.set('stats.files', files.concat(tests || [])); verb.set('stats.hasTravis', fs.existsSync('.travis.yml')); - verb.match = utils.match(files); + + var verbfile = verb.match('.verb*'); + if (verbfile.length) { + var fp = verbfile[0]; + var str = utils.antimatter(fp); + str = verbmd(str, verb.get('stats')); + utils.writeFile(fp, str); + } file.render = false; file.readme = false; @@ -38,6 +49,7 @@ verb.copy('.verbrc.md', function (file) { return path.dirname(file.relative); }); +// all of this junk needs to go... var files = glob.sync('test/**').filter(function (fp) { return fs.statSync(fp).isDirectory(); }); @@ -67,15 +79,6 @@ verb.task('banners', function () { })); }); -verb.task('verbfile', function () { - verb.src(['.verb{,rc}.md'], {render: false}) - .pipe(plugins.verbmd()) - .pipe(verb.dest(function (file) { - file.path = '.verb.md'; - return path.dirname(file.path); - })); -}); - verb.task('jshint', function () { verb.src('.jshintrc', {render: false}) .pipe(plugins.jshint()) @@ -137,20 +140,35 @@ verb.task('dotfiles', function () { verb.task('pkg', function () { verb.src('package.json', {render: false}) .pipe(plugins.pkg()) - .pipe(verb.dest('.')); + .pipe(verb.dest('.')) + .on('end', function () { + log.success(true, 'package.json'); + }); +}); + +verb.task('verbfile', function () { + verb.src(['.verb{,rc}.md'], {render: false}) + .pipe(plugins.verbmd()) + .pipe(verb.dest(function (file) { + file.path = '.verb.md'; + return path.dirname(file.path); + })) + .on('end', function () { + log.success(true, '.verb.md'); + }); }); verb.task('readme', function () { verb.src('.verb.md') .pipe(verb.dest('.')) - .on('end', function (cb) { + .on('end', function () { log.success(true, 'updated.'); }); }); verb.task('default', [ - 'tests', 'banners', + 'tests', 'verbfile', 'dotfiles', 'travis', @@ -159,5 +177,6 @@ verb.task('default', [ 'pkg', 'readme' ]); -verb.diff() + +verb.diff(); verb.run(); From 8210c30e1b0b990d34c1deee9f7a53993742eb96 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Wed, 25 Feb 2015 16:45:09 -0500 Subject: [PATCH 034/274] ensure that deps exists --- plugins/tests.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/tests.js b/plugins/tests.js index 0d826ec..951504d 100644 --- a/plugins/tests.js +++ b/plugins/tests.js @@ -43,12 +43,13 @@ module.exports = function(verb) { // `should` exists in test files if (hasShould) { str = tests.fixShould(str, file.relative); - if (!deps.hasOwnProperty('should')) { + if (deps && !deps.hasOwnProperty('should')) { // TODO: add should to deps if exists in tets // console.log(verb.env); } + log.success(str, 'updated "should" statements in', file.relative); - } else if (deps.hasOwnProperty('should')) { + } else if (deps && deps.hasOwnProperty('should')) { verb.set('strip.pkg.devDependencies', 'should'); } From 91b5704b6c48a5fe098add3551458b57e4c4617f Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Wed, 25 Feb 2015 17:02:19 -0500 Subject: [PATCH 035/274] start adding error handling --- verbfile.js | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/verbfile.js b/verbfile.js index 67aceed..0a768b9 100644 --- a/verbfile.js +++ b/verbfile.js @@ -4,6 +4,7 @@ var fs = require('fs'); var del = require('del'); var path = require('path'); var verb = require('verb'); +var gutil = require('gulp-util'); var parse = require('parse-copyright'); var logger = require('./lib/logging'); var log = logger({nocompare: true}); @@ -74,6 +75,7 @@ verb.copy('LICENSE-MIT', function (file) { verb.task('banners', function () { verb.src(['*.js', 'test/*.js', 'lib/*.js'], {render: false}) .pipe(plugins.banners()) + .on('error', gutil.log) .pipe(verb.dest(function (file) { return path.dirname(file.path); })); @@ -82,6 +84,7 @@ verb.task('banners', function () { verb.task('jshint', function () { verb.src('.jshintrc', {render: false}) .pipe(plugins.jshint()) + .on('error', gutil.log) .pipe(verb.dest(function (file) { file.path = '.jshintrc'; return path.dirname(file.path); @@ -91,6 +94,7 @@ verb.task('jshint', function () { verb.task('travis', function () { verb.src('.travis.yml', {render: false}) .pipe(plugins.travis()) + .on('error', gutil.log) .pipe(verb.dest(function (file) { file.path = '.travis.yml'; return path.dirname(file.path); @@ -100,6 +104,7 @@ verb.task('travis', function () { verb.task('tests', function () { verb.src(['test.js', 'test/*.js'], {render: false}) .pipe(plugins.tests()) + .on('error', gutil.log) .pipe(verb.dest(function (file) { return path.dirname(file.path); })); @@ -108,6 +113,7 @@ verb.task('tests', function () { verb.task('license', function () { verb.src('LICENSE{,-MIT}', {render: false}) .pipe(plugins.license()) + .on('error', gutil.log) .pipe(verb.dest(function (file) { file.path = 'LICENSE'; return path.dirname(file.path); @@ -118,6 +124,7 @@ verb.task('dotfiles', function () { verb.src('.git*', {render: false, dot: true}) .pipe(plugins.editorconfig()) .pipe(plugins.gitignore()) + .on('error', gutil.log) .pipe(verb.dest(function (file) { return path.dirname(file.path); })) @@ -134,42 +141,32 @@ verb.task('dotfiles', function () { del(exists, cb); log.info('deleted', exists.join(', ')); } - }); + }) }); verb.task('pkg', function () { verb.src('package.json', {render: false}) .pipe(plugins.pkg()) + .on('error', gutil.log) .pipe(verb.dest('.')) .on('end', function () { log.success(true, 'package.json'); }); }); -verb.task('verbfile', function () { - verb.src(['.verb{,rc}.md'], {render: false}) - .pipe(plugins.verbmd()) - .pipe(verb.dest(function (file) { - file.path = '.verb.md'; - return path.dirname(file.path); - })) - .on('end', function () { - log.success(true, '.verb.md'); - }); -}); - verb.task('readme', function () { verb.src('.verb.md') .pipe(verb.dest('.')) + .on('error', gutil.log) .on('end', function () { log.success(true, 'updated.'); - }); + }) + .on('error', gutil.log); }); verb.task('default', [ 'banners', 'tests', - 'verbfile', 'dotfiles', 'travis', 'jshint', From c0f4031efd037864478c3defb3856543d9b0f503 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 27 Feb 2015 14:08:47 -0500 Subject: [PATCH 036/274] clean up --- plugins/banners.js | 23 +++++++++-------------- plugins/dotfiles.js | 7 +------ plugins/editorconfig.js | 3 +-- plugins/helpers/files.js | 2 +- plugins/index.js | 12 +++--------- 5 files changed, 15 insertions(+), 32 deletions(-) diff --git a/plugins/banners.js b/plugins/banners.js index 7bf7f06..2d05250 100644 --- a/plugins/banners.js +++ b/plugins/banners.js @@ -1,13 +1,11 @@ 'use strict'; -var gutil = require('gulp-util'); var through = require('through2'); var parse = require('parse-copyright'); var banner = require('update-banner'); var hasBanner = require('has-banner'); var merge = require('merge-deep'); var logger = require('../lib/logging'); -var utils = require('../lib/utils'); module.exports = function(verb) { return function(options) { @@ -19,22 +17,19 @@ module.exports = function(verb) { return cb(); } - if (utils.contains(file.path, '.js')) { - var str = file.contents.toString(); - var log = logger(str); + var str = file.contents.toString(); + var log = logger(str); - if (hasBanner(str) || opts.banner) { - var copyright = parse(str); - if (copyright && copyright.length) { - file.data.copyright = copyright[0]; - } - str = banner(str, file.data); - log.success(str, 'updated banners in', file.relative); + if (hasBanner(str) || opts.banner) { + var copyright = parse(str); + if (copyright && copyright.length) { + file.data.copyright = copyright[0]; } - - file.contents = new Buffer(str); + str = banner(str, file.data); + log.success(str, 'updated banners in', file.relative); } + file.contents = new Buffer(str); this.push(file); cb(); }); diff --git a/plugins/dotfiles.js b/plugins/dotfiles.js index 3183900..2e382a5 100644 --- a/plugins/dotfiles.js +++ b/plugins/dotfiles.js @@ -2,8 +2,6 @@ var gutil = require('gulp-util'); var through = require('through2'); -var utils = require('../lib/utils'); -var logger = require('../lib/logging'); module.exports = function(verb) { return function() { @@ -11,10 +9,7 @@ module.exports = function(verb) { this.push(file); cb(); }, function (cb) { - var file = new gutil.File({ - path: '.gitattributes' - }); - + var file = new gutil.File({path: '.gitattributes'}); file.contents = new Buffer(require('../templates/gitattributes')); this.push(file); cb(); diff --git a/plugins/editorconfig.js b/plugins/editorconfig.js index f9121ad..142dbfb 100644 --- a/plugins/editorconfig.js +++ b/plugins/editorconfig.js @@ -2,8 +2,7 @@ var gutil = require('gulp-util'); var through = require('through2'); -var utils = require('../lib/utils'); -var logger = require('../lib/logging'); +// var ec = require('editorconfig'); module.exports = function(verb) { return function() { diff --git a/plugins/helpers/files.js b/plugins/helpers/files.js index f072ba6..7dc166d 100644 --- a/plugins/helpers/files.js +++ b/plugins/helpers/files.js @@ -7,7 +7,7 @@ var intersect = require('array-intersection'); var utils = require('../../lib/utils'); exports.toFiles = function(fp, names) { - var lookFor = names || ['index.js', 'cli.js', 'lib/', 'bin/', 'completion/', 'templates/']; + var lookFor = names || ['index.js', 'cli.js', 'lib/', 'bin/', 'completion/', 'templates/', 'app/']; var files = fs.readdirSync(path.resolve(fp)); names = formatFiles(names); files = formatFiles(files); diff --git a/plugins/index.js b/plugins/index.js index 94c1097..631b477 100644 --- a/plugins/index.js +++ b/plugins/index.js @@ -1,12 +1,7 @@ -/** - * update - * - * Copyright (c) 2014-2015, Jon Schlinkert. - * Licensed under the MIT license. - */ - 'use strict'; +// module.exports = require('export-files')(__dirname); + module.exports = function (verb) { return { banners : require('./banners.js')(verb), @@ -17,7 +12,6 @@ module.exports = function (verb) { license : require('./license.js')(verb), pkg : require('./pkg.js')(verb), tests : require('./tests.js')(verb), - travis : require('./travis.js')(verb), - verbmd : require('./verbmd.js')(verb) + travis : require('./travis.js')(verb) }; }; From b3447e568d64cadc623b7a1228390921918b6eb7 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 27 Feb 2015 14:08:56 -0500 Subject: [PATCH 037/274] add docs --- .verb.md | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/.verb.md b/.verb.md index 4ba2246..3dc1dcc 100644 --- a/.verb.md +++ b/.verb.md @@ -1,10 +1,42 @@ -# {%= name %} {%= badge("fury") %} +# {%= name %} {%= badge("fury") %} {%= badge("travis") %} > {%= description %} -Currently this only updates the year in a project, but the goal is to add other features for keeping a project up to date. +This is an experimental library that is not ready to use on your projects yet. But I'm using it on mine and I have to say, it's pretty amazing how much time it's saving me already. -Also, the regex used for updating the year in copyright statements is pretty opinionated at the moment, but I'm open to making this more flexible if someone wants to do a PR. +### What's this about? + +When I run `update` from the command line, it loads an object with my personal preferences, then it runs a series of [verb](https://github.com/assemble/verb) plugins that use those preferences along with "normalizers" on targeted files in the current project. + +For example: + +- it updates the copyright dates in banners, the README and LICENSE files +- it renames files to be the way I like them (like `LICENSE-MIT` => `LICENSE`) +- it updates banners to ensure they have the correct project URL and license information +- it updates `.jshintrc` with my lastest preferences +- it updates `.editorconfig` with my latest preferences +- it updates `package.json` properties with my latest preferences + +etc... this is maybe 20% of what it does currently. There are some bugs to work out, but I can tell this project is going to be worth spending time on. It's already paying off. + +### The goal + +The goal is to be able to easily update and normalize existing projects from the command line using a compbination of: + +- **global defaults** for project metadata, like your github username, license preference, and other properties that change very little if at all from project-to-project. +- **normalizers** that will normalize and update virtually anything in the project to: + + use your (latest) preference + + meet the (latest) standards for whatever piece of metadata is being updated. For example, it would lint your `.travis.md` files to ensure that `iojs` has been added to the `node_js` versions. + +### Next + +After I completely understand how this should work, I'll: + +1. remove all logic and preferences that are specific to my own projects. +2. Split everything out into plugins, helpers, utils etc. +3. Try to make everything compatible with gulp plugins, so we don't need to think about another format. + +For now, however, this project is not at all idiomatic. A lot of the logic is pretty opinionated, there is a lot of duplication, and some of the plugins are just sloppy. As a rule-of-thumb I like to get things working as a POC before I spend time cleaning up code. {%= include("install-global") %} @@ -36,4 +68,4 @@ Pull requests and stars are always welcome. For bugs and feature requests, [plea *** -{%= include("footer") %} \ No newline at end of file +{%= include("footer") %} From 2bd17a2cc98768da88b387bd83569c6c216a4714 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 27 Feb 2015 14:09:31 -0500 Subject: [PATCH 038/274] organize, lint --- .editorconfig | 2 +- lib/mkdir.js | 4 +++- lib/utils.js | 41 +++++++++++++++++++++++++++++++++++++ lib/verbmd.js | 1 + package.json | 5 +++-- plugins/helpers/licenses.js | 4 ---- plugins/helpers/scripts.js | 2 +- plugins/helpers/url.js | 5 ----- plugins/tests.js | 11 +++------- plugins/travis.js | 29 +++++++++++++++++--------- plugins/verbmd.js | 40 ------------------------------------ verbfile.js | 24 ++++++++-------------- 12 files changed, 80 insertions(+), 88 deletions(-) delete mode 100644 plugins/helpers/url.js delete mode 100644 plugins/verbmd.js diff --git a/.editorconfig b/.editorconfig index 192641a..2eed47c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -14,4 +14,4 @@ trim_trailing_whitespace = false [test/fixtures/*] insert_final_newline = false -trim_trailing_whitespace = false +trim_trailing_whitespace = false \ No newline at end of file diff --git a/lib/mkdir.js b/lib/mkdir.js index d7adacb..2c637b1 100644 --- a/lib/mkdir.js +++ b/lib/mkdir.js @@ -10,6 +10,8 @@ var fs = require('fs'); var path = require('path'); +module.exports = {}; + /** * Make the given directory and intermediates * if they don't already exist. @@ -20,7 +22,7 @@ var path = require('path'); * @api private */ -module.exports = function mkdir(dir, mode) { +module.exports.sync = function mkdir(dir, mode) { mode = mode || parseInt('0777', 8) & (~process.umask()); if (!fs.existsSync(dir)) { var parent = path.dirname(dir); diff --git a/lib/utils.js b/lib/utils.js index 8795004..4355567 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -3,6 +3,24 @@ var fs = require('fs'); var path = require('path'); var mm = require('micromatch'); +var matter = require('gray-matter'); +var logger = require('./logging'); + +/** + * Read a file + */ + +exports.readFile = function readFile(fp) { + return fs.readFileSync(fp, 'utf8'); +}; + +/** + * Write a file + */ + +exports.writeFile = function writeFile(fp, str) { + return fs.writeFileSync(fp, str); +}; /** * Creates a matching function to use against @@ -68,6 +86,29 @@ exports.replace = function replace(str, a, b) { return str.split(a).join(b); }; +/** + * Strip front matter from a string. + * + * @param {String} `fp` + * @return {String} + */ + +exports.antimatter = function antimatter(fp) { + if (typeof fp !== 'string' ) { + throw new TypeError('utils.antimatter() expects a string, got:', fp); + } + + var str = exports.readFile(fp); + var log = logger(str); + var obj = matter(str); + var keys = Object.keys(obj.data); + if (keys.length) { + str = obj.content.replace(/^\s+/, ''); + log.success(str, 'stripped deprecated front-matter tags in', fp); + } + return str; +}; + /** * Return true if `file.path` contains the given * string. We know if it passes through the stream diff --git a/lib/verbmd.js b/lib/verbmd.js index 739cf9a..6242eda 100644 --- a/lib/verbmd.js +++ b/lib/verbmd.js @@ -26,6 +26,7 @@ function fixInstall(str) { function fixHelpers(str) { str = str.split('{%= jscomments').join('{%= apidocs'); + str = str.split('{%= contrib("jon") %}').join('{%= include("author") %}'); str = str.split('{%= comments').join('{%= apidocs'); str = str.split('{%= contrib("contributing") %}').join('{%= include("contributing") %}'); str = str.split('{%= contrib("author") %}').join('{%= include("author") %}'); diff --git a/package.json b/package.json index b5b518a..8a3928b 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "glob": "^4.4.0", "gray-matter": "^1.2.5", "gulp-util": "^3.0.4", - "has-banner": "^0.1.0", + "has-banner": "^0.2.0", "js-yaml": "^3.2.7", "kind-of": "^1.1.0", "log-symbols": "^1.0.1", @@ -46,7 +46,8 @@ "micromatch": "^1.4.2", "mixin-deep": "^1.0.1", "parse-copyright": "^0.4.0", - "remote-origin-url": "^0.2.1", + "relative": "^2.0.0", + "remote-origin-url": "^0.3.0", "requires-regex": "^0.2.0", "sort-object": "^1.0.0", "template-utils": "^0.4.1", diff --git a/plugins/helpers/licenses.js b/plugins/helpers/licenses.js index 13ceed3..cfbc1bf 100644 --- a/plugins/helpers/licenses.js +++ b/plugins/helpers/licenses.js @@ -1,8 +1,5 @@ 'use strict'; -var typeOf = require('kind-of'); -var utils = require('../../lib/utils'); - exports.normalize = function normalize(pkg) { var licenses = pkg.licenses; @@ -10,6 +7,5 @@ exports.normalize = function normalize(pkg) { pkg.license = pkg.licenses[0]; delete pkg.licenses; } - return pkg; }; diff --git a/plugins/helpers/scripts.js b/plugins/helpers/scripts.js index 7d016f3..da06aa6 100644 --- a/plugins/helpers/scripts.js +++ b/plugins/helpers/scripts.js @@ -5,7 +5,7 @@ */ exports.fixMocha = function(pkg) { - if (pkg.scripts && pkg.scripts.test && /mocha -r/i.test(pkg.scripts.test)) { + if (pkg.scripts && pkg.scripts.test && /mocha --?r/i.test(pkg.scripts.test)) { pkg.scripts.test = 'mocha'; } return pkg; diff --git a/plugins/helpers/url.js b/plugins/helpers/url.js deleted file mode 100644 index 6cbb4bb..0000000 --- a/plugins/helpers/url.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict'; - -var origin = require('remote-origin-url'); - -// console.log(origin) diff --git a/plugins/tests.js b/plugins/tests.js index 951504d..113d357 100644 --- a/plugins/tests.js +++ b/plugins/tests.js @@ -16,12 +16,7 @@ module.exports = function(verb) { } try { - if (utils.contains(file.path, 'test')) { - var stats = verb.get('stats'); - - // console.log(verb.match('*.js')) - var str = file.contents.toString(); var log = logger(str); @@ -41,9 +36,9 @@ module.exports = function(verb) { var deps = verb.env.devDependencies; // `should` exists in test files - if (hasShould) { + if (hasShould && deps) { str = tests.fixShould(str, file.relative); - if (deps && !deps.hasOwnProperty('should')) { + if (!deps.hasOwnProperty('should')) { // TODO: add should to deps if exists in tets // console.log(verb.env); } @@ -63,5 +58,5 @@ module.exports = function(verb) { this.push(file); cb(); }); - } + }; }; diff --git a/plugins/travis.js b/plugins/travis.js index 01f351c..f1a854c 100644 --- a/plugins/travis.js +++ b/plugins/travis.js @@ -2,23 +2,32 @@ var gutil = require('gulp-util'); var through = require('through2'); -var yaml = require('js-yaml'); -var mixin = require('mixin-deep'); -var utils = require('../lib/utils'); var logger = require('../lib/logging'); +/** + * this plugin adds `.travis.yml` using a template + * if the file is missing but tests exist. + */ + module.exports = function travis(verb) { - return function() { + return function () { return through.obj(function (file, enc, cb) { this.push(file); cb(); }, function (cb) { - var log = logger({nocompare: true}); - var tmpl = require('../templates/travis'); - var file = new gutil.File({path: '.travis.yml'}); - file.contents = new Buffer(tmpl); - log.success(true, 'writing', file.relative); - this.push(file); + + if (!verb.exists('.travis.yml') && verb.exists('test*')) { + var log = logger({nocompare: true}); + var tmpl = require('../templates/travis'); + var file = new gutil.File({ + contents: new Buffer(tmpl), + path: '.travis.yml' + }); + + this.push(file); + log.success(true, 'writing', file.relative); + } + cb(); }); }; diff --git a/plugins/verbmd.js b/plugins/verbmd.js deleted file mode 100644 index 1d18732..0000000 --- a/plugins/verbmd.js +++ /dev/null @@ -1,40 +0,0 @@ -'use strict'; - -var matter = require('gray-matter'); -var through = require('through2'); -var verbmd = require('../lib/verbmd'); -var logger = require('../lib/logging'); - -module.exports = function(verb) { - return function () { - return through.obj(function (file, enc, cb) { - if (file.isNull() || !file.isBuffer()) { - this.push(file); - return cb(); - } - - var stats = verb.get('stats'); - var str = file.contents.toString(); - - var orig = str; - var log = logger(orig); - - var obj = matter(str); - var keys = Object.keys(obj.data); - - if (keys.length && obj.data.hasOwnProperty('tags')) { - str = obj.content.replace(/^\s+/, ''); - log.success(str, 'stripped deprecated front-matter tags in', file.relative); - } - - str = verbmd(str, stats || {}); - log.success(str, 'updated front-matter in', file.relative); - - if (str !== orig) { - file.contents = new Buffer(str); - } - this.push(file); - cb(); - }); - }; -}; diff --git a/verbfile.js b/verbfile.js index 0a768b9..0634276 100644 --- a/verbfile.js +++ b/verbfile.js @@ -13,27 +13,19 @@ var verbmd = require('./lib/verbmd'); var utils = require('./lib/utils'); var glob = require('glob'); +verb.transform('_init', function (verb) { + verb.set('stats.hasTravis', verb.exists('.travis.yml')); -verb.onLoad(/./, function (file, next) { - var files = utils.tryReaddir(process.cwd()); - var tests = []; - - verb.match = utils.match(files); - if (verb.match('test').length) { - tests = utils.tryReaddir(process.cwd() + '/test'); - } - - verb.set('stats.files', files.concat(tests || [])); - verb.set('stats.hasTravis', fs.existsSync('.travis.yml')); - - var verbfile = verb.match('.verb*'); + var verbfile = verb.files('.verb*'); if (verbfile.length) { var fp = verbfile[0]; var str = utils.antimatter(fp); str = verbmd(str, verb.get('stats')); utils.writeFile(fp, str); } +}); +verb.onLoad(/./, function (file, next) { file.render = false; file.readme = false; next(); @@ -124,6 +116,7 @@ verb.task('dotfiles', function () { verb.src('.git*', {render: false, dot: true}) .pipe(plugins.editorconfig()) .pipe(plugins.gitignore()) + .pipe(plugins.dotfiles()) .on('error', gutil.log) .pipe(verb.dest(function (file) { return path.dirname(file.path); @@ -160,8 +153,7 @@ verb.task('readme', function () { .on('error', gutil.log) .on('end', function () { log.success(true, 'updated.'); - }) - .on('error', gutil.log); + }); }); verb.task('default', [ @@ -175,5 +167,5 @@ verb.task('default', [ 'readme' ]); -verb.diff(); +// verb.diff(); verb.run(); From 14eb67032beb17321be05e2e13e24391a6354e9e Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 27 Feb 2015 14:20:25 -0500 Subject: [PATCH 039/274] build readme --- README.md | 42 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2fc372d..53b2a61 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,42 @@ -# update [![NPM version](https://badge.fury.io/js/update.svg)](http://badge.fury.io/js/update) +# update [![NPM version](https://badge.fury.io/js/update.svg)](http://badge.fury.io/js/update) [![Build Status](https://travis-ci.org/jonschlinkert/update.svg)](https://travis-ci.org/jonschlinkert/update) > Update the year in all files in a project using glob patterns. -Currently this only updates the year in a project, but the goal is to add other features for keeping a project up to date. +This is an experimental library that is not ready to use on your projects yet. But I'm using it on mine and I have to say, it's pretty amazing how much time it's saving me already. -Also, the regex used for updating the year in copyright statements is pretty opinionated at the moment, but I'm open to making this more flexible if someone wants to do a PR. +### What's this about? + +When I run `update` from the command line, it loads an object with my personal preferences, then it runs a series of [verb](https://github.com/assemble/verb) plugins that use those preferences along with "normalizers" on targeted files in the current project. + +For example: + +- it updates the copyright dates in banners, the README and LICENSE files +- it renames files to be the way I like them (like `LICENSE-MIT` => `LICENSE`) +- it updates banners to ensure they have the correct project URL and license information +- it updates `.jshintrc` with my lastest preferences +- it updates `.editorconfig` with my latest preferences +- it updates `package.json` properties with my latest preferences + +etc... this is maybe 20% of what it does currently. There are some bugs to work out, but I can tell this project is going to be worth spending time on. It's already paying off. + +### The goal + +The goal is to be able to easily update and normalize existing projects from the command line using a compbination of: + +- **global defaults** for project metadata, like your github username, license preference, and other properties that change very little if at all from project-to-project. +- **normalizers** that will normalize and update virtually anything in the project to: + + use your (latest) preference + + meet the (latest) standards for whatever piece of metadata is being updated. For example, it would lint your `.travis.md` files to ensure that `iojs` has been added to the `node_js` versions. + +### Next + +After I completely understand how this should work, I'll: + +1. remove all logic and preferences that are specific to my own projects. +2. Split everything out into plugins, helpers, utils etc. +3. Try to make everything compatible with gulp plugins, so we don't need to think about another format. + +For now, however, this project is not at all idiomatic. A lot of the logic is pretty opinionated, there is a lot of duplication, and some of the plugins are just sloppy. As a rule-of-thumb I like to get things working as a POC before I spend time cleaning up code. ## Install globally with [npm](npmjs.org): @@ -40,8 +72,8 @@ Pull requests and stars are always welcome. For bugs and feature requests, [plea ## License Copyright (c) 2014-2015 Jon Schlinkert -Released under the license +Released under the MIT license *** -_This file was generated by [verb](https://github.com/assemble/verb) on February 25, 2015._ \ No newline at end of file +_This file was generated by [verb-cli](https://github.com/assemble/verb-cli) on February 27, 2015._ From 3fa39b9228c29aa916a6cf9ce3976e322b5d72d5 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 27 Feb 2015 14:21:03 -0500 Subject: [PATCH 040/274] 0.3.3 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 8a3928b..0f29d46 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "update", "description": "Update the year in all files in a project using glob patterns.", - "version": "0.3.3-beta", + "version": "0.3.3", "homepage": "https://github.com/jonschlinkert/update", "author": { "name": "Jon Schlinkert", @@ -66,4 +66,4 @@ "object", "sort" ] -} \ No newline at end of file +} From ff78461f66535707a59aef3e36f5e05f51ecb864 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 27 Feb 2015 15:13:12 -0500 Subject: [PATCH 041/274] work on `files`/matching --- lib/delete.js | 2 +- package.json | 28 +++++++++++----------------- plugins/helpers/browser.js | 14 +++++++++----- plugins/helpers/files.js | 24 +++++++++++++++--------- plugins/pkg.js | 9 ++++++--- test.js | 25 ------------------------- verbfile.js | 7 +------ 7 files changed, 43 insertions(+), 66 deletions(-) delete mode 100644 test.js diff --git a/lib/delete.js b/lib/delete.js index a8a9e60..6c9c902 100644 --- a/lib/delete.js +++ b/lib/delete.js @@ -3,7 +3,7 @@ var fs = require('fs'); var chalk = require('chalk'); var symbol = require('log-symbols'); -var del = require('delete'); +var del = require('del'); var bold = chalk.bold; module.exports = function _delete(fp, opts) { diff --git a/package.json b/package.json index 0f29d46..ba887bb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "update", "description": "Update the year in all files in a project using glob patterns.", - "version": "0.3.3", + "version": "0.3.5", "homepage": "https://github.com/jonschlinkert/update", "author": { "name": "Jon Schlinkert", @@ -14,7 +14,9 @@ "license": "MIT", "files": [ "cli.js", - "lib/" + "lib/", + "plugins/", + "templates/" ], "main": "index.js", "preferGlobal": true, @@ -33,33 +35,25 @@ "array-unique": "^0.1.1", "chalk": "^1.0.0", "del": "^1.1.1", - "delete": "^0.1.5", - "export-files": "^1.0.0", - "glob": "^4.4.0", + "export-files": "^1.1.0", + "glob": "^4.4.1", "gray-matter": "^1.2.5", "gulp-util": "^3.0.4", "has-banner": "^0.2.0", - "js-yaml": "^3.2.7", "kind-of": "^1.1.0", - "log-symbols": "^1.0.1", - "merge-deep": "^0.1.5", + "log-symbols": "^1.0.2", + "merge-deep": "^1.0.1", "micromatch": "^1.4.2", - "mixin-deep": "^1.0.1", "parse-copyright": "^0.4.0", - "relative": "^2.0.0", - "remote-origin-url": "^0.3.0", "requires-regex": "^0.2.0", "sort-object": "^1.0.0", - "template-utils": "^0.4.1", "through2": "^0.6.3", "update-banner": "^0.1.1", "update-license": "^0.3.0", - "update-package": "^0.1.1", - "verb": "^0.5.0" - }, - "devDependencies": { - "should": "^5.0.1" + "update-package": "^0.2.0", + "verb": "^0.6.1" }, + "devDependencies": {}, "keywords": [ "javascript", "keys", diff --git a/plugins/helpers/browser.js b/plugins/helpers/browser.js index 916af4c..f1c90e6 100644 --- a/plugins/helpers/browser.js +++ b/plugins/helpers/browser.js @@ -4,12 +4,16 @@ var unique = require('array-unique'); var files = require('./files'); +/** + * TODO: list files to browserify + */ + module.exports = function(pkg, file) { // populate the `browser` property - var browser = files.toFiles(file.base, ['browser.js']); - if (browser.length || pkg.browser && pkg.browser.length) { - pkg.browser = unique(browser, pkg.browser); - } - return pkg; + // var browser = files.toFiles(file.base, ['browser.js']); + // if (browser.length || pkg.browser && pkg.browser.length) { + // pkg.browser = unique(browser, pkg.browser); + // } + return []; }; diff --git a/plugins/helpers/files.js b/plugins/helpers/files.js index 7dc166d..11b8b7d 100644 --- a/plugins/helpers/files.js +++ b/plugins/helpers/files.js @@ -1,20 +1,26 @@ 'use strict'; var fs = require('fs'); -var path = require('path'); var unique = require('array-unique'); -var intersect = require('array-intersection'); +var mm = require('micromatch'); var utils = require('../../lib/utils'); -exports.toFiles = function(fp, names) { - var lookFor = names || ['index.js', 'cli.js', 'lib/', 'bin/', 'completion/', 'templates/', 'app/']; - var files = fs.readdirSync(path.resolve(fp)); - names = formatFiles(names); - files = formatFiles(files); - return unique(intersect(lookFor, files).concat(names)); +/** + * Guess at which files should be included in package.json `files`. + * This isn't meant to be comprehensive, it's intended to tip you + * off that you need to fill in the field. + */ + +module.exports = function(patterns, options) { + var defaults = ['index.js', 'cli.js', 'lib/', 'bin/', 'completion/', 'templates/', 'app/']; + patterns = patterns ? formatPatterns(patterns) : []; + + return function (files) { + return unique(mm(files, defaults, options).concat(patterns)); + } }; -function formatFiles(files) { +function formatPatterns(files) { var res = [], i = 0; var len; if (files && (len = files.length)) { diff --git a/plugins/pkg.js b/plugins/pkg.js index 71b1e1e..bd26b3f 100644 --- a/plugins/pkg.js +++ b/plugins/pkg.js @@ -44,12 +44,15 @@ module.exports = function(verb) { // run updates on package.json fields var pkg = update(obj); - // populate the `files` property - pkg.files = helpers.files.toFiles(file.base, pkg.files); - // remove old verb from deps pkg = helpers.devDependencies.removeVerb(pkg); + // populate the `files` property. Not exposed on options + // currently, but can be if someone suggests a good option + var matched = require('./helpers/files')(pkg.files); + var files = verb.get('stats.files'); + pkg.files = matched(files); + // fix the scripts property pkg = helpers.scripts.fixMocha(pkg); diff --git a/test.js b/test.js deleted file mode 100644 index 3b12785..0000000 --- a/test.js +++ /dev/null @@ -1,25 +0,0 @@ -/*! - * update - * - * Copyright (c) 2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - -'use strict'; - -var assert = require('assert'); -require('should'); -var update = require('./'); - -describe('update', function () { - it('should:', function () { - update('a').should.equal({a: 'b'}); - update('a').should.eql('a'); - }); - - it('should throw an error:', function () { - (function () { - update(); - }).should.throw('update expects valid arguments'); - }); -}); diff --git a/verbfile.js b/verbfile.js index 0634276..0eb853f 100644 --- a/verbfile.js +++ b/verbfile.js @@ -42,13 +42,8 @@ verb.copy('.verbrc.md', function (file) { return path.dirname(file.relative); }); -// all of this junk needs to go... -var files = glob.sync('test/**').filter(function (fp) { - return fs.statSync(fp).isDirectory(); -}); - var singleTest = false; -if (files && files.length === 1) { +if (verb.files('test{,*.js,/*.js').length) { singleTest = true; verb.set('singleTest', true); verb.copy('test/test.js', function (file) { From 2877c9d812ac98e8a3ae62ac92afee9ddef84f5c Mon Sep 17 00:00:00 2001 From: Brian Woodward Date: Fri, 27 Feb 2015 18:15:38 -0500 Subject: [PATCH 042/274] adding verbfile.js to files --- package.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index ba887bb..c5cdc65 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,9 @@ "files": [ "cli.js", "lib/", - "plugins/", - "templates/" + "templates/", + "verbfile.js", + "plugins/" ], "main": "index.js", "preferGlobal": true, @@ -60,4 +61,4 @@ "object", "sort" ] -} +} \ No newline at end of file From 20af38473d5630c5b725a34f041df0aabf050935 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 27 Feb 2015 18:18:11 -0500 Subject: [PATCH 043/274] omit empty fields --- plugins/pkg.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/pkg.js b/plugins/pkg.js index bd26b3f..d4ba364 100644 --- a/plugins/pkg.js +++ b/plugins/pkg.js @@ -7,6 +7,7 @@ var path = require('path'); var diff = require('arr-diff'); var gutil = require('gulp-util'); +var omitEmpty = require('omit-empty'); var through = require('through2'); var update = require('update-package'); var sortObj = require('sort-object'); @@ -64,6 +65,7 @@ module.exports = function(verb) { // fix the `license` and `licenses` properties pkg = helpers.licenses.normalize(pkg); pkg = helpers.license.normalize(pkg); + pkg = omitEmpty(pkg); var keys = helpers.keys.concat(diff(Object.keys(pkg), helpers.keys)); var sorted = sortObj(pkg, keys); From 73f5a73b30c8bfd145b973dad498a2b94e181f42 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 28 Feb 2015 23:53:15 -0500 Subject: [PATCH 044/274] clean up/externalize test junk --- lib/tests/paths.js | 20 +++++++++++++++++ lib/tests/should.js | 54 +++++++++++++++++++++++++++++++++++++++++++++ plugins/tests.js | 48 +++++++++------------------------------- 3 files changed, 85 insertions(+), 37 deletions(-) create mode 100644 lib/tests/paths.js create mode 100644 lib/tests/should.js diff --git a/lib/tests/paths.js b/lib/tests/paths.js new file mode 100644 index 0000000..34aad32 --- /dev/null +++ b/lib/tests/paths.js @@ -0,0 +1,20 @@ +'use strict'; + +var regex = require('requires-regex'); + +module.exports = function paths(file, verb) { + var str = file.contents.toString(); + + // body... // test.js is at the root, let's make sure + // the path to `index.js` is correct + if (file.path.indexOf('test/') === -1) { + str.replace(regex(), function ($1, $2, $3, fp) { + if (fp && fp === '../' || fp === '..') { + str = str.replace(fp, './'); + } + }); + } + + file.contents = new Buffer(str); + return file; +}; diff --git a/lib/tests/should.js b/lib/tests/should.js new file mode 100644 index 0000000..e651ae7 --- /dev/null +++ b/lib/tests/should.js @@ -0,0 +1,54 @@ +'use strict'; + +var logger = require('../logging'); + +module.exports = function should(file, verb) { + var str = file.contents.toString(); + var log = logger(str); + + // does package.json have `devDependencies`? + var devDeps = verb.env && verb.env.devDependencies; + + // does `should` exists in test files? + var has = /var.*should/.test(str); + + // if so, is it used? + var used = /\.should\./.test(str); + + + if (has && devDeps) { + + // if `should` is *not* used + // - strip if from test files + // - delete it from package.json + if (!used) { + str = stripRequires(str); + delete verb.pkg.devDependencies.should; + + // if `should` *is* used + // - format it properly in test files + // - ensure it's in package.json + } else { + str = fixRequires(str); + // TODO: get the latest version from npm + verb.pkg.devDependencies.should = '^0.5.0'; + } + } + + log.results(str, 'updated "should" statements in', file.relative); + file.contents = new Buffer(str); + return file; +}; + + +var re = /var should[^\n]+/; + +function fixRequires(str) { + return str.replace(re, 'require(\'should\');'); +} + +function stripRequires(str) { + return str.replace(re, ''); +} + + diff --git a/plugins/tests.js b/plugins/tests.js index 113d357..ee64c5c 100644 --- a/plugins/tests.js +++ b/plugins/tests.js @@ -2,13 +2,13 @@ var gutil = require('gulp-util'); var through = require('through2'); -var regex = require('requires-regex'); var logger = require('../lib/logging'); -var tests = require('../lib/tests'); +var should = require('../lib/tests/should'); +var paths = require('../lib/tests/paths'); var utils = require('../lib/utils'); -module.exports = function(verb) { - return function() { +module.exports = function (verb) { + return function () { return through.obj(function (file, enc, cb) { if (file.isNull() || !file.isBuffer()) { this.push(file); @@ -16,42 +16,16 @@ module.exports = function(verb) { } try { - if (utils.contains(file.path, 'test')) { - var str = file.contents.toString(); - var log = logger(str); + if (verb.file('test')) { + // fix `should` related info + file = should(file, verb); - // test.js is at the root, let's make sure - // the path to `index.js` is correct - if (file.path.indexOf('test/') === -1) { - str.replace(regex(), function ($1, $2, $3, fp) { - if (fp && fp === '../' || fp === '..') { - str = str.replace(fp, './'); - } - }); - } - - var hasShould = /['"]should/.test(str); - verb.set('data.hasShould', hasShould); - - var deps = verb.env.devDependencies; - - // `should` exists in test files - if (hasShould && deps) { - str = tests.fixShould(str, file.relative); - if (!deps.hasOwnProperty('should')) { - // TODO: add should to deps if exists in tets - // console.log(verb.env); - } - - log.success(str, 'updated "should" statements in', file.relative); - } else if (deps && deps.hasOwnProperty('should')) { - verb.set('strip.pkg.devDependencies', 'should'); - } - - file.contents = new Buffer(str); + // fix paths + file = paths(file, verb); } + } catch (err) { - console.log(err); + console.error('update:tests', err); this.emit('error', new gutil.PluginError('update:tests', err)); return cb(); } From 24bb162e5d64319909c2f64bcd2c30b4be40f766 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sun, 1 Mar 2015 00:03:32 -0500 Subject: [PATCH 045/274] move module starters to `lib/_starters` --- lib/_starters/normalizer.js | 10 ++++++++++ lib/{_plugin-base.js => _starters/plugin.js} | 0 2 files changed, 10 insertions(+) create mode 100644 lib/_starters/normalizer.js rename lib/{_plugin-base.js => _starters/plugin.js} (100%) diff --git a/lib/_starters/normalizer.js b/lib/_starters/normalizer.js new file mode 100644 index 0000000..1e5dd97 --- /dev/null +++ b/lib/_starters/normalizer.js @@ -0,0 +1,10 @@ +'use strict'; + +module.exports = function paths(file, verb) { + var str = file.contents.toString(); + + // do stuff to `file` and `verb` + + file.contents = new Buffer(str); + return file; +}; diff --git a/lib/_plugin-base.js b/lib/_starters/plugin.js similarity index 100% rename from lib/_plugin-base.js rename to lib/_starters/plugin.js From 647323f6b4a73eb76a9c95239ce8daeb31fecce6 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sun, 1 Mar 2015 00:03:53 -0500 Subject: [PATCH 046/274] start organizing plugins --- lib/logging.js | 7 +++++ lib/tests.js | 6 ---- plugins/helpers/index.js | 61 +++++++++++++++++++++++++++++++++++++++- plugins/pkg.js | 46 ++---------------------------- plugins/tests.js | 5 +--- 5 files changed, 71 insertions(+), 54 deletions(-) delete mode 100644 lib/tests.js diff --git a/lib/logging.js b/lib/logging.js index 3e220ed..6bf5a80 100644 --- a/lib/logging.js +++ b/lib/logging.js @@ -19,10 +19,17 @@ module.exports = function (str, options) { } }; + log.results = log.success; + log.info = function (msg, fp) { var text = chalk.gray(msg.trim()) + (fp ? (' ' + fp) : ''); console.log(' ' + symbol.info + ' ' + text); }; + log.deleted = function (msg, fp) { + var text = chalk.gray(msg.trim()) + (fp ? (' ' + fp) : ''); + console.log(' ' + symbol.success + ' ' + text); + }; + return log; }; diff --git a/lib/tests.js b/lib/tests.js deleted file mode 100644 index caadad0..0000000 --- a/lib/tests.js +++ /dev/null @@ -1,6 +0,0 @@ -'use strict'; - -exports.fixShould = function fixShould(str) { - var segs = str.split('var should = require(\'should\');'); - return segs.join('require(\'should\');'); -}; diff --git a/plugins/helpers/index.js b/plugins/helpers/index.js index 23b2930..e9be7e8 100644 --- a/plugins/helpers/index.js +++ b/plugins/helpers/index.js @@ -1 +1,60 @@ -module.exports = require('export-files')(__dirname); +'use strict'; + +/** + * Module dependencies + */ + +var sortObj = require('sort-object'); +var helpers = require('export-files')(__dirname); + +/** + * Local dependencies + */ + +var logger = require('../lib/logging'); +var utils = require('../lib/utils'); + +/** + * Normalize fields in package.json + */ + +module.exports = function (file, verb) { + var str = file.contents.toString(); + var log = logger(str); + + // parse the string + var obj = JSON.parse(str); + + // run updates on package.json fields + var pkg = update(obj); + + // remove old verb from deps + pkg = helpers.devDependencies.removeVerb(pkg); + + // populate the `files` property. Not exposed on options + // currently, but can be if someone suggests a good option + var matched = require('./helpers/files')(pkg.files); + var files = verb.get('stats.files'); + pkg.files = matched(files); + + // fix the scripts property + pkg = helpers.scripts.fixMocha(pkg); + + // if should doesn't exist, remove it + if (!verb.get('data.hasShould')) { + pkg = helpers.devDependencies.removeShould(pkg); + } + + // fix the `license` and `licenses` properties + pkg = helpers.licenses.normalize(pkg); + pkg = helpers.license.normalize(pkg); + pkg = omitEmpty(pkg); + + var keys = helpers.keys.concat(diff(Object.keys(pkg), helpers.keys)); + var sorted = sortObj(pkg, keys); + var res = JSON.stringify(sorted, null, 2); + + log.results(res, 'updated properties in', file.relative); + file.contents = new Buffer(res); + return file; +} diff --git a/plugins/pkg.js b/plugins/pkg.js index d4ba364..0f8e195 100644 --- a/plugins/pkg.js +++ b/plugins/pkg.js @@ -10,15 +10,12 @@ var gutil = require('gulp-util'); var omitEmpty = require('omit-empty'); var through = require('through2'); var update = require('update-package'); -var sortObj = require('sort-object'); /** * Local dependencies */ -var helpers = require('./helpers/'); -var logger = require('../lib/logging'); -var utils = require('../lib/utils'); +var normalize = require('./helpers'); /** * virtually everything in this file is a temporary @@ -34,45 +31,8 @@ module.exports = function(verb) { } try { - if (utils.contains(file.path, 'package.json')) { - var str = file.contents.toString(); - // pass the initial string to the logger. - var log = logger(str); - - // parse the string - var obj = JSON.parse(str); - - // run updates on package.json fields - var pkg = update(obj); - - // remove old verb from deps - pkg = helpers.devDependencies.removeVerb(pkg); - - // populate the `files` property. Not exposed on options - // currently, but can be if someone suggests a good option - var matched = require('./helpers/files')(pkg.files); - var files = verb.get('stats.files'); - pkg.files = matched(files); - - // fix the scripts property - pkg = helpers.scripts.fixMocha(pkg); - - // if should doesn't exist, remove it - if (!verb.get('data.hasShould')) { - pkg = helpers.devDependencies.removeShould(pkg); - } - - // fix the `license` and `licenses` properties - pkg = helpers.licenses.normalize(pkg); - pkg = helpers.license.normalize(pkg); - pkg = omitEmpty(pkg); - - var keys = helpers.keys.concat(diff(Object.keys(pkg), helpers.keys)); - var sorted = sortObj(pkg, keys); - var res = JSON.stringify(sorted, null, 2); - - log.success(res, 'updated properties in', file.relative); - file.contents = new Buffer(res); + if (verb.exists('package.json')) { + file = normalize(file, verb); } } catch (err) { diff --git a/plugins/tests.js b/plugins/tests.js index ee64c5c..6c36f24 100644 --- a/plugins/tests.js +++ b/plugins/tests.js @@ -2,10 +2,8 @@ var gutil = require('gulp-util'); var through = require('through2'); -var logger = require('../lib/logging'); var should = require('../lib/tests/should'); var paths = require('../lib/tests/paths'); -var utils = require('../lib/utils'); module.exports = function (verb) { return function () { @@ -19,16 +17,15 @@ module.exports = function (verb) { if (verb.file('test')) { // fix `should` related info file = should(file, verb); - // fix paths file = paths(file, verb); } - } catch (err) { console.error('update:tests', err); this.emit('error', new gutil.PluginError('update:tests', err)); return cb(); } + this.push(file); cb(); }); From 450e2a84c843ad2d3b2f5f91b2d916f419f59aef Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 20 Mar 2015 20:54:32 -0400 Subject: [PATCH 047/274] cleanup --- .verb.md | 15 +++++---- README.md | 42 +++++++++++++++++++---- lib/tests/should.js | 10 +++--- lib/utils.js | 17 ++++++++++ package.json | 4 +-- plugins/banners.js | 4 ++- plugins/helpers/browser.js | 19 ----------- plugins/helpers/files.js | 26 ++++++++++---- plugins/helpers/index.js | 23 +++++++++---- plugins/helpers/keys.js | 5 ++- plugins/pkg.js | 11 ++---- {lib => plugins/readme}/verbmd.js | 56 ++++++++++++++++++++++++++----- plugins/tests.js | 1 + templates/editorconfig.js | 18 ++++++++-- transforms/index.js | 3 ++ transforms/start.js | 27 +++++++++++++++ verbfile.js | 26 +++++--------- 17 files changed, 212 insertions(+), 95 deletions(-) delete mode 100644 plugins/helpers/browser.js rename {lib => plugins/readme}/verbmd.js (64%) create mode 100644 transforms/index.js create mode 100644 transforms/start.js diff --git a/.verb.md b/.verb.md index 3dc1dcc..e865ff3 100644 --- a/.verb.md +++ b/.verb.md @@ -2,13 +2,13 @@ > {%= description %} -This is an experimental library that is not ready to use on your projects yet. But I'm using it on mine and I have to say, it's pretty amazing how much time it's saving me already. +This is an experimental library that is build 100% on top of [verb], but it's not quite ready to use on your projects. ### What's this about? -When I run `update` from the command line, it loads an object with my personal preferences, then it runs a series of [verb](https://github.com/assemble/verb) plugins that use those preferences along with "normalizers" on targeted files in the current project. +Update is a CLI tool that loads personal defaults/preferences, then runs a series of [verb](https://github.com/assemble/verb) plugins, transforms and "normalizers" on targeted files in the current project. -For example: +**For example:** - it updates the copyright dates in banners, the README and LICENSE files - it renames files to be the way I like them (like `LICENSE-MIT` => `LICENSE`) @@ -49,12 +49,13 @@ update ``` ## Run tests +{%= include("tests") %} -Install dev dependencies: +## Related -```bash -node i -d && mocha -``` +This project is built on these libraries: + +{%= join(keys(dependencies), "\n - ") %} ## Contributing Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue]({%= bugs.url %}) diff --git a/README.md b/README.md index 53b2a61..f705715 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,13 @@ > Update the year in all files in a project using glob patterns. -This is an experimental library that is not ready to use on your projects yet. But I'm using it on mine and I have to say, it's pretty amazing how much time it's saving me already. +This is an experimental library that is build 100% on top of [verb], but it's not quite ready to use on your projects. ### What's this about? -When I run `update` from the command line, it loads an object with my personal preferences, then it runs a series of [verb](https://github.com/assemble/verb) plugins that use those preferences along with "normalizers" on targeted files in the current project. +Update is a CLI tool that loads personal defaults/preferences, then runs a series of [verb](https://github.com/assemble/verb) plugins, transforms and "normalizers" on targeted files in the current project. -For example: +**For example:** - it updates the copyright dates in banners, the README and LICENSE files - it renames files to be the way I like them (like `LICENSE-MIT` => `LICENSE`) @@ -53,13 +53,41 @@ update ``` ## Run tests - -Install dev dependencies: +Install dev dependencies. ```bash -node i -d && mocha +npm i -d && npm test ``` + +## Related + +This project is built on these libraries: + +arr-diff + - array-intersection + - array-unique + - chalk + - del + - export-files + - glob + - gray-matter + - gulp-util + - has-banner + - kind-of + - log-symbols + - merge-deep + - micromatch + - omit-empty + - parse-copyright + - requires-regex + - sort-object + - through2 + - update-banner + - update-license + - update-package + - verb + ## Contributing Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](https://github.com/jonschlinkert/update/issues) @@ -76,4 +104,4 @@ Released under the MIT license *** -_This file was generated by [verb-cli](https://github.com/assemble/verb-cli) on February 27, 2015._ +_This file was generated by [update](https://github.com/jonschlinkert/update) on March 01, 2015._ diff --git a/lib/tests/should.js b/lib/tests/should.js index e651ae7..0dc31fa 100644 --- a/lib/tests/should.js +++ b/lib/tests/should.js @@ -10,14 +10,12 @@ module.exports = function should(file, verb) { var devDeps = verb.env && verb.env.devDependencies; // does `should` exists in test files? - var has = /var.*should/.test(str); + var has = /var.*should\s*=\s*require/.test(str); // if so, is it used? var used = /\.should\./.test(str); - if (has && devDeps) { - // if `should` is *not* used // - strip if from test files // - delete it from package.json @@ -25,10 +23,10 @@ module.exports = function should(file, verb) { str = stripRequires(str); delete verb.pkg.devDependencies.should; - // if `should` *is* used - // - format it properly in test files - // - ensure it's in package.json } else { + // if `should` *is* used + // - format it properly in test files + // - ensure it's in package.json str = fixRequires(str); // TODO: get the latest version from npm verb.pkg.devDependencies.should = '^0.5.0'; diff --git a/lib/utils.js b/lib/utils.js index 4355567..56d33cd 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -33,6 +33,18 @@ exports.match = function match(files) { }; }; +/** + * Try to read a directory of files. Silently catches + * any errors and returns an empty array. + */ + +exports.tryStat = function tryStat(fp) { + try { + return fs.statSync(fp); + } catch (err) {} + return []; +}; + /** * Try to read a directory of files. Silently catches * any errors and returns an empty array. @@ -156,6 +168,11 @@ exports.trailingSlash = function trailingSlash(fp, stat) { if (typeof fp !== 'string') { throw new TypeError('utils.trailingSlash() expects a string.'); } + + if (typeof stat.isFile !== 'function') { + return; + } + if (stat.isFile()) { return fp; } diff --git a/package.json b/package.json index c5cdc65..5f47e11 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "update", "description": "Update the year in all files in a project using glob patterns.", - "version": "0.3.5", + "version": "0.3.6", "homepage": "https://github.com/jonschlinkert/update", "author": { "name": "Jon Schlinkert", @@ -45,6 +45,7 @@ "log-symbols": "^1.0.2", "merge-deep": "^1.0.1", "micromatch": "^1.4.2", + "omit-empty": "^0.3.0", "parse-copyright": "^0.4.0", "requires-regex": "^0.2.0", "sort-object": "^1.0.0", @@ -54,7 +55,6 @@ "update-package": "^0.2.0", "verb": "^0.6.1" }, - "devDependencies": {}, "keywords": [ "javascript", "keys", diff --git a/plugins/banners.js b/plugins/banners.js index 2d05250..1ba3390 100644 --- a/plugins/banners.js +++ b/plugins/banners.js @@ -20,7 +20,9 @@ module.exports = function(verb) { var str = file.contents.toString(); var log = logger(str); - if (hasBanner(str) || opts.banner) { + // TODO: implement better logic for ignoring banners that shouldn't + // be stripped + if ((hasBanner(str) || opts.banner) && !/@attribution/.test(str)) { var copyright = parse(str); if (copyright && copyright.length) { file.data.copyright = copyright[0]; diff --git a/plugins/helpers/browser.js b/plugins/helpers/browser.js deleted file mode 100644 index f1c90e6..0000000 --- a/plugins/helpers/browser.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; - - -var unique = require('array-unique'); -var files = require('./files'); - -/** - * TODO: list files to browserify - */ - -module.exports = function(pkg, file) { - // populate the `browser` property - // var browser = files.toFiles(file.base, ['browser.js']); - // if (browser.length || pkg.browser && pkg.browser.length) { - // pkg.browser = unique(browser, pkg.browser); - // } - return []; -}; - diff --git a/plugins/helpers/files.js b/plugins/helpers/files.js index 11b8b7d..9bb23c6 100644 --- a/plugins/helpers/files.js +++ b/plugins/helpers/files.js @@ -1,10 +1,17 @@ 'use strict'; var fs = require('fs'); -var unique = require('array-unique'); var mm = require('micromatch'); +var unique = require('array-unique'); var utils = require('../../lib/utils'); +/** + * Default paths to look for to populate the `files` + * property of package.json + */ + +var defaults = ['index.js', 'cli.js', 'lib/', 'bin/', 'completion/', 'templates/', 'app/']; + /** * Guess at which files should be included in package.json `files`. * This isn't meant to be comprehensive, it's intended to tip you @@ -12,21 +19,26 @@ var utils = require('../../lib/utils'); */ module.exports = function(patterns, options) { - var defaults = ['index.js', 'cli.js', 'lib/', 'bin/', 'completion/', 'templates/', 'app/']; - patterns = patterns ? formatPatterns(patterns) : []; - + patterns = patterns ? ensureSlash(patterns) : []; return function (files) { return unique(mm(files, defaults, options).concat(patterns)); - } + }; }; -function formatPatterns(files) { +/** + * Format paths so that directories end in a slash + */ + +function ensureSlash(files) { var res = [], i = 0; var len; if (files && (len = files.length)) { while (len--) { var file = files[i++]; - var stat = fs.statSync(file); + if (typeof file !== 'string') { + continue; + } + var stat = file && utils.tryStat(file); res.push(utils.trailingSlash(file, stat)); } } diff --git a/plugins/helpers/index.js b/plugins/helpers/index.js index e9be7e8..c371998 100644 --- a/plugins/helpers/index.js +++ b/plugins/helpers/index.js @@ -4,15 +4,17 @@ * Module dependencies */ -var sortObj = require('sort-object'); var helpers = require('export-files')(__dirname); +var sortObj = require('sort-object'); +var omitEmpty = require('omit-empty'); +var update = require('update-package'); +var diff = require('arr-diff'); /** * Local dependencies */ -var logger = require('../lib/logging'); -var utils = require('../lib/utils'); +var logger = require('../../lib/logging'); /** * Normalize fields in package.json @@ -33,8 +35,10 @@ module.exports = function (file, verb) { // populate the `files` property. Not exposed on options // currently, but can be if someone suggests a good option - var matched = require('./helpers/files')(pkg.files); - var files = verb.get('stats.files'); + var matched = require('./files')(pkg.files); + var stats = verb.get('stats'); + var files = stats.files; + pkg.files = matched(files); // fix the scripts property @@ -42,7 +46,7 @@ module.exports = function (file, verb) { // if should doesn't exist, remove it if (!verb.get('data.hasShould')) { - pkg = helpers.devDependencies.removeShould(pkg); + // pkg = helpers.devDependencies.removeShould(pkg); } // fix the `license` and `licenses` properties @@ -50,6 +54,11 @@ module.exports = function (file, verb) { pkg = helpers.license.normalize(pkg); pkg = omitEmpty(pkg); + // if (stats.unknownHelpers.length) { + + // // console.log(stats.unknownHelpers) + // } + var keys = helpers.keys.concat(diff(Object.keys(pkg), helpers.keys)); var sorted = sortObj(pkg, keys); var res = JSON.stringify(sorted, null, 2); @@ -57,4 +66,4 @@ module.exports = function (file, verb) { log.results(res, 'updated properties in', file.relative); file.contents = new Buffer(res); return file; -} +}; diff --git a/plugins/helpers/keys.js b/plugins/helpers/keys.js index 4db53ca..6c42d3f 100644 --- a/plugins/helpers/keys.js +++ b/plugins/helpers/keys.js @@ -13,6 +13,8 @@ module.exports = [ 'version', 'homepage', 'author', + 'authors', + 'contributors', 'maintainers', 'repository', 'bugs', @@ -29,5 +31,6 @@ module.exports = [ 'scripts', 'dependencies', 'devDependencies', - 'keywords' + 'keywords', + 'verb' ]; diff --git a/plugins/pkg.js b/plugins/pkg.js index 0f8e195..b1fceae 100644 --- a/plugins/pkg.js +++ b/plugins/pkg.js @@ -5,17 +5,14 @@ */ var path = require('path'); -var diff = require('arr-diff'); var gutil = require('gulp-util'); -var omitEmpty = require('omit-empty'); var through = require('through2'); -var update = require('update-package'); /** * Local dependencies */ -var normalize = require('./helpers'); +var normalize = require('./helpers/'); /** * virtually everything in this file is a temporary @@ -31,17 +28,13 @@ module.exports = function(verb) { } try { - if (verb.exists('package.json')) { - file = normalize(file, verb); - } - + file = normalize(file, verb); } catch (err) { console.log(err); this.emit('error', new gutil.PluginError('update:pkg', err)); return cb(); } - this.push(file); cb(); }); diff --git a/lib/verbmd.js b/plugins/readme/verbmd.js similarity index 64% rename from lib/verbmd.js rename to plugins/readme/verbmd.js index 6242eda..3eb1bc4 100644 --- a/lib/verbmd.js +++ b/plugins/readme/verbmd.js @@ -1,14 +1,27 @@ 'use strict'; -var authors = [ - '{%= include("authors", {', - ' authors: [', - ' {name: \'Jon Schlinkert\', username: \'jonschlinkert\'},', - ' {name: \'Brian Woodward\', username: \'doowb\'}', - ' ]', - '}) %}' -].join('\n'); +var unique = require('array-unique'); +function unknownHelpers(str, verb) { + var helpers = Object.keys(verb._.helpers); + var async = Object.keys(verb._.asyncHelpers); + var keys = unique(helpers.concat(async)).sort(); + + var re = /\{%=\s*(\w+)\(([\s\S]+?)%}/; + var match, res = []; + var orig = str; + + while (match = re.exec(str)) { + str = str.replace(match[0], ''); + var name = match[1]; + if (keys.indexOf(name) === -1) { + res.push(name); + } + } + + verb.set('stats.unknownHelpers', res); + return orig; +} function addTravisBadge(str, stats) { if (stats && stats.hasTravis && !/badge\(.travis/.test(str)) { @@ -24,6 +37,22 @@ function fixInstall(str) { return str; } +function installGlobal(str, verb) { + // TODO: if `preferGlobal` or `bin` are in package.json, add + // {%= include("install-global") %} + return str; +} + +var authors = [ + '{%= include("authors", {', + ' authors: [', + ' {name: \'Jon Schlinkert\', username: \'jonschlinkert\'},', + ' {name: \'Brian Woodward\', username: \'doowb\'}', + ' ]', + '}) %}' +].join('\n'); + + function fixHelpers(str) { str = str.split('{%= jscomments').join('{%= apidocs'); str = str.split('{%= contrib("jon") %}').join('{%= include("author") %}'); @@ -34,6 +63,11 @@ function fixHelpers(str) { return str; } +function matchHelper(name, args) { + var str = '{%=\\s*' + name + (args || '([\\s\\S]+?)') + '\\s*%}'; + return new RegExp(str); +} + function fixCopyright(str, year) { if (!year) { return str; @@ -47,10 +81,14 @@ function runningTests(str) { return str.replace(re, '## Running tests\n{%= include("tests") %}'); } -module.exports = function (str, stats) { +module.exports = function (str, verb) { + var stats = verb.get('stats'); + str = addTravisBadge(str, stats); str = runningTests(str); str = fixInstall(str); str = fixHelpers(str); + + unknownHelpers(str, verb); return str; }; diff --git a/plugins/tests.js b/plugins/tests.js index 6c36f24..423751e 100644 --- a/plugins/tests.js +++ b/plugins/tests.js @@ -5,6 +5,7 @@ var through = require('through2'); var should = require('../lib/tests/should'); var paths = require('../lib/tests/paths'); + module.exports = function (verb) { return function () { return through.obj(function (file, enc, cb) { diff --git a/templates/editorconfig.js b/templates/editorconfig.js index 1b6b157..8f3815e 100644 --- a/templates/editorconfig.js +++ b/templates/editorconfig.js @@ -4,16 +4,28 @@ module.exports = [ '', '[*]', 'indent_style = space', - 'indent_size = 2', 'end_of_line = lf', 'charset = utf-8', + 'indent_size = 2', 'trim_trailing_whitespace = true', 'insert_final_newline = true', '', + '[*.json]', + 'indent_style = space', + 'indent_size = 2', + '', + '[*.yml]', + 'indent_style = space', + 'indent_size = 2', + '', '[*.md]', + 'indent_style = space', + 'indent_size = 2', 'trim_trailing_whitespace = false', '', '[test/fixtures/*]', - 'insert_final_newline = false', - 'trim_trailing_whitespace = false' + 'trim_trailing_whitespace = false', + 'insert_final_newline = false' ].join('\n'); + + diff --git a/transforms/index.js b/transforms/index.js new file mode 100644 index 0000000..874ea55 --- /dev/null +++ b/transforms/index.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require('export-files')(__dirname); diff --git a/transforms/start.js b/transforms/start.js new file mode 100644 index 0000000..f3246dd --- /dev/null +++ b/transforms/start.js @@ -0,0 +1,27 @@ +'use strict'; + +var fs = require('fs'); +var del = require('del'); +var path = require('path'); +var verb = require('verb'); +var glob = require('glob'); +var gutil = require('gulp-util'); +var parse = require('parse-copyright'); + +var logger = require('../lib/logging'); +var plugins = require('../plugins')(verb); +var verbmd = require('../plugins/readme/verbmd'); +var utils = require('../lib/utils'); +var log = logger({nocompare: true}); + +module.exports = function (verb) { + verb.set('stats.hasTravis', verb.exists('.travis.yml')); + var verbfile = verb.files('.verb*'); + + if (verbfile.length) { + var fp = verbfile[0]; + var str = utils.antimatter(fp); + str = verbmd(str, verb); + utils.writeFile(fp, str); + } +}; diff --git a/verbfile.js b/verbfile.js index 0eb853f..101997b 100644 --- a/verbfile.js +++ b/verbfile.js @@ -7,23 +7,15 @@ var verb = require('verb'); var gutil = require('gulp-util'); var parse = require('parse-copyright'); var logger = require('./lib/logging'); -var log = logger({nocompare: true}); var plugins = require('./plugins')(verb); -var verbmd = require('./lib/verbmd'); +var verbmd = require('./plugins/readme/verbmd'); var utils = require('./lib/utils'); var glob = require('glob'); +var pkg = require(__dirname + '/package.json'); +var log = logger({nocompare: true}); -verb.transform('_init', function (verb) { - verb.set('stats.hasTravis', verb.exists('.travis.yml')); - var verbfile = verb.files('.verb*'); - if (verbfile.length) { - var fp = verbfile[0]; - var str = utils.antimatter(fp); - str = verbmd(str, verb.get('stats')); - utils.writeFile(fp, str); - } -}); +verb.transform('start', require('./transforms/start')); verb.onLoad(/./, function (file, next) { file.render = false; @@ -31,10 +23,10 @@ verb.onLoad(/./, function (file, next) { next(); }); -verb.onLoad(/\.js$/, function (file, next) { - file.data.copyright = parse(file.content); - next(); -}); +// verb.onLoad(/\.js$/, function (file, next) { +// file.data.copyright = parse(file.content); +// next(); +// }); verb.copy('.verbrc.md', function (file) { file.path = '.verb.md'; @@ -127,7 +119,7 @@ verb.task('dotfiles', function () { if (exists.length) { del(exists, cb); - log.info('deleted', exists.join(', ')); + log.deleted('deleted', exists.join(', ')); } }) }); From 8ef00eb4a74790f318a974ae3d7e674375e51a93 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Thu, 2 Apr 2015 21:22:18 -0400 Subject: [PATCH 048/274] adds bower plugin --- plugins/bower.js | 25 +++++++++++++++++++++++++ verbfile.js | 39 ++++++++++++++++++++++----------------- 2 files changed, 47 insertions(+), 17 deletions(-) create mode 100644 plugins/bower.js diff --git a/plugins/bower.js b/plugins/bower.js new file mode 100644 index 0000000..acb60bd --- /dev/null +++ b/plugins/bower.js @@ -0,0 +1,25 @@ +'use strict'; + +/** + * Module dependencies + */ + +var gutil = require('gulp-util'); +var through = require('through2'); +var sync = require('sync-pkg'); + +module.exports = function bower_(verb) { + return function() { + return through.obj(function (file, enc, cb) { + this.push(file); + return cb(); + }, function (cb) { + var file = new gutil.File({path: 'bower.json'}); + var data = JSON.stringify(sync(verb.env), null, 2); + + file.contents = new Buffer(data); + this.push(file); + cb(); + }); + }; +}; diff --git a/verbfile.js b/verbfile.js index 101997b..40fe848 100644 --- a/verbfile.js +++ b/verbfile.js @@ -3,42 +3,44 @@ var fs = require('fs'); var del = require('del'); var path = require('path'); -var verb = require('verb'); +var verb = require('../../verb/verb'); +var debug = require('debug')('update:tasks'); var gutil = require('gulp-util'); var parse = require('parse-copyright'); -var logger = require('./lib/logging'); var plugins = require('./plugins')(verb); var verbmd = require('./plugins/readme/verbmd'); var utils = require('./lib/utils'); var glob = require('glob'); var pkg = require(__dirname + '/package.json'); +var logger = require('./lib/logging'); var log = logger({nocompare: true}); verb.transform('start', require('./transforms/start')); - verb.onLoad(/./, function (file, next) { file.render = false; file.readme = false; next(); }); -// verb.onLoad(/\.js$/, function (file, next) { -// file.data.copyright = parse(file.content); -// next(); -// }); +verb.onLoad(/\.js$/, function (file, next) { + file.data.copyright = parse(file.content); + next(); +}); verb.copy('.verbrc.md', function (file) { + debug('copy .verbrc.md'); file.path = '.verb.md'; log.success('renamed', file.relative); return path.dirname(file.relative); }); -var singleTest = false; +var hasOneTestfile = false; if (verb.files('test{,*.js,/*.js').length) { - singleTest = true; - verb.set('singleTest', true); + hasOneTestfile = true; + verb.set('hasOneTestfile', true); verb.copy('test/test.js', function (file) { + debug('copy test.js'); file.path = 'test.js'; log.success('moved', file.path); return file.base; @@ -46,12 +48,14 @@ if (verb.files('test{,*.js,/*.js').length) { } verb.copy('LICENSE-MIT', function (file) { + debug('copy LICENSE-MIT'); file.path = 'LICENSE'; log.success('renamed', file.relative); return path.dirname(file.relative); }); verb.task('banners', function () { + debug('banners task'); verb.src(['*.js', 'test/*.js', 'lib/*.js'], {render: false}) .pipe(plugins.banners()) .on('error', gutil.log) @@ -91,7 +95,7 @@ verb.task('tests', function () { verb.task('license', function () { verb.src('LICENSE{,-MIT}', {render: false}) - .pipe(plugins.license()) + // .pipe(plugins.license()) .on('error', gutil.log) .pipe(verb.dest(function (file) { file.path = 'LICENSE'; @@ -113,7 +117,7 @@ verb.task('dotfiles', function () { var res = utils.exists(files); var exists = res.EXISTS; - if (verb.get('singleTest')) { + if (verb.get('hasOneTestfile')) { exists.push('test'); } @@ -124,13 +128,14 @@ verb.task('dotfiles', function () { }) }); -verb.task('pkg', function () { - verb.src('package.json', {render: false}) +verb.task('json', function () { + verb.src('{bower,package}.json', {render: false}) .pipe(plugins.pkg()) + .pipe(plugins.bower()) .on('error', gutil.log) .pipe(verb.dest('.')) .on('end', function () { - log.success(true, 'package.json'); + log.success(true, 'updated package.json'); }); }); @@ -139,7 +144,7 @@ verb.task('readme', function () { .pipe(verb.dest('.')) .on('error', gutil.log) .on('end', function () { - log.success(true, 'updated.'); + log.success(true, 'updated readme.'); }); }); @@ -150,7 +155,7 @@ verb.task('default', [ 'travis', 'jshint', 'license', - 'pkg', + 'json', 'readme' ]); From 51deccfd27a1165f8ac1a7c35b2cec1b94cd8600 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Thu, 2 Apr 2015 21:22:35 -0400 Subject: [PATCH 049/274] fix metadata --- package.json | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 5f47e11..93ef53a 100644 --- a/package.json +++ b/package.json @@ -7,11 +7,17 @@ "name": "Jon Schlinkert", "url": "https://github.com/jonschlinkert" }, - "repository": "jonschlinkert/update", + "repository": { + "type": "git", + "url": "git://github.com/jonschlinkert/update.git" + }, "bugs": { "url": "https://github.com/jonschlinkert/update/issues" }, - "license": "MIT", + "license": { + "type": "MIT", + "url": "https://github.com/jonschlinkert/update/blob/master/LICENSE" + }, "files": [ "cli.js", "lib/", @@ -32,33 +38,36 @@ }, "dependencies": { "arr-diff": "^1.0.1", - "array-intersection": "^0.1.1", - "array-unique": "^0.1.1", + "array-unique": "^0.2.1", "chalk": "^1.0.0", + "debug": "^2.1.3", "del": "^1.1.1", - "export-files": "^1.1.0", - "glob": "^4.4.1", - "gray-matter": "^1.2.5", + "export-files": "^2.0.1", + "gray-matter": "^2.0.0", "gulp-util": "^3.0.4", "has-banner": "^0.2.0", "kind-of": "^1.1.0", "log-symbols": "^1.0.2", "merge-deep": "^1.0.1", - "micromatch": "^1.4.2", - "omit-empty": "^0.3.0", + "micromatch": "^2.1.5", + "omit-empty": "^0.3.1", "parse-copyright": "^0.4.0", "requires-regex": "^0.2.0", "sort-object": "^1.0.0", + "sync-pkg": "^0.4.0", "through2": "^0.6.3", "update-banner": "^0.1.1", "update-license": "^0.3.0", "update-package": "^0.2.0", "verb": "^0.6.1" }, + "devDependencies": { + "glob": "^5.0.3" + }, "keywords": [ "javascript", "keys", "object", "sort" ] -} \ No newline at end of file +} From 15c041b764b75e75f6963e79a33c00656360eb58 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Thu, 2 Apr 2015 21:22:50 -0400 Subject: [PATCH 050/274] remove junk --- transforms/start.js | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/transforms/start.js b/transforms/start.js index f3246dd..1b8e356 100644 --- a/transforms/start.js +++ b/transforms/start.js @@ -1,20 +1,12 @@ 'use strict'; -var fs = require('fs'); -var del = require('del'); -var path = require('path'); -var verb = require('verb'); -var glob = require('glob'); -var gutil = require('gulp-util'); -var parse = require('parse-copyright'); - -var logger = require('../lib/logging'); -var plugins = require('../plugins')(verb); +var debug = require('debug')('update:transform'); var verbmd = require('../plugins/readme/verbmd'); var utils = require('../lib/utils'); -var log = logger({nocompare: true}); module.exports = function (verb) { + debug('transform: start'); + verb.set('stats.hasTravis', verb.exists('.travis.yml')); var verbfile = verb.files('.verb*'); From 96682222a59cc3c9182ce22543d3b676713b31c4 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Thu, 2 Apr 2015 21:23:42 -0400 Subject: [PATCH 051/274] add patterns --- .editorconfig | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/.editorconfig b/.editorconfig index 2eed47c..32dd133 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,15 +3,25 @@ root = true [*] indent_style = space -indent_size = 2 end_of_line = lf charset = utf-8 +indent_size = 2 trim_trailing_whitespace = true insert_final_newline = true +[*.json] +indent_style = space +indent_size = 2 + +[*.yml] +indent_style = space +indent_size = 2 + [*.md] +indent_style = space +indent_size = 2 trim_trailing_whitespace = false [test/fixtures/*] -insert_final_newline = false -trim_trailing_whitespace = false \ No newline at end of file +trim_trailing_whitespace = false +insert_final_newline = false \ No newline at end of file From c5003e05e803f9eed0b0dcf407d32cec9e36ee1e Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Thu, 2 Apr 2015 21:23:59 -0400 Subject: [PATCH 052/274] debug statements --- lib/tests/paths.js | 1 + lib/tests/should.js | 2 ++ lib/utils.js | 10 ++++++++++ plugins/banners.js | 4 ++++ plugins/dotfiles.js | 2 ++ plugins/editorconfig.js | 4 +++- plugins/gitignore.js | 4 +++- plugins/helpers/devDependencies.js | 4 ++++ plugins/helpers/files.js | 4 +++- plugins/helpers/license.js | 4 +++- plugins/helpers/licenses.js | 5 ++++- plugins/helpers/scripts.js | 3 +++ plugins/index.js | 1 + plugins/jshint.js | 4 +++- plugins/license.js | 14 ++++++++------ plugins/pkg.js | 7 ++++--- plugins/readme/verbmd.js | 5 +++++ plugins/tests.js | 5 +++-- plugins/travis.js | 5 +++-- 19 files changed, 69 insertions(+), 19 deletions(-) diff --git a/lib/tests/paths.js b/lib/tests/paths.js index 34aad32..f76e1bf 100644 --- a/lib/tests/paths.js +++ b/lib/tests/paths.js @@ -1,6 +1,7 @@ 'use strict'; var regex = require('requires-regex'); +var debug = require('debug')('update:lib'); module.exports = function paths(file, verb) { var str = file.contents.toString(); diff --git a/lib/tests/should.js b/lib/tests/should.js index 0dc31fa..07a7e05 100644 --- a/lib/tests/should.js +++ b/lib/tests/should.js @@ -1,8 +1,10 @@ 'use strict'; +var debug = require('debug')('update:lib'); var logger = require('../logging'); module.exports = function should(file, verb) { + debug('lib/tests/should.js'); var str = file.contents.toString(); var log = logger(str); diff --git a/lib/utils.js b/lib/utils.js index 56d33cd..768ea38 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,6 +1,7 @@ 'use strict'; var fs = require('fs'); +var debug = require('debug')('update:utils'); var path = require('path'); var mm = require('micromatch'); var matter = require('gray-matter'); @@ -11,6 +12,7 @@ var logger = require('./logging'); */ exports.readFile = function readFile(fp) { + debug('utils:readFile: %s', fp); return fs.readFileSync(fp, 'utf8'); }; @@ -28,6 +30,8 @@ exports.writeFile = function writeFile(fp, str) { */ exports.match = function match(files) { + debug('utils:match: %j', files); + return function(pattern, options) { return mm(files, pattern, options); }; @@ -51,6 +55,7 @@ exports.tryStat = function tryStat(fp) { */ exports.tryReaddir = function tryReaddir(fp) { + debug('utils:tryReaddir: %s', fp); try { return fs.readdirSync(fp); } catch (err) {} @@ -66,6 +71,8 @@ exports.tryReaddir = function tryReaddir(fp) { */ exports.exists = function exists(files) { + debug('utils:exists: %j', files); + if (!files) { throw new Error('utils.exists() expects an array or string.'); } @@ -150,6 +157,7 @@ exports.contains = function contains(str, ch) { */ exports.repo = function repo(str) { + debug('utils:repo: %s', str); if (typeof str !== 'string') { throw new TypeError('utils.repo() expects a string.'); } @@ -165,6 +173,8 @@ exports.repo = function repo(str) { */ exports.trailingSlash = function trailingSlash(fp, stat) { + debug('utils:trailingSlash: %s', fp); + if (typeof fp !== 'string') { throw new TypeError('utils.trailingSlash() expects a string.'); } diff --git a/plugins/banners.js b/plugins/banners.js index 1ba3390..853ad0c 100644 --- a/plugins/banners.js +++ b/plugins/banners.js @@ -1,6 +1,7 @@ 'use strict'; var through = require('through2'); +var debug = require('debug')('update:plugin'); var parse = require('parse-copyright'); var banner = require('update-banner'); var hasBanner = require('has-banner'); @@ -8,10 +9,13 @@ var merge = require('merge-deep'); var logger = require('../lib/logging'); module.exports = function(verb) { + debug('banners plugin'); return function(options) { var opts = merge({}, options); return through.obj(function (file, enc, cb) { + debug('banners plugin file: %j', file.path); + if (file.isNull() || !file.isBuffer()) { this.push(file); return cb(); diff --git a/plugins/dotfiles.js b/plugins/dotfiles.js index 2e382a5..fbc3779 100644 --- a/plugins/dotfiles.js +++ b/plugins/dotfiles.js @@ -1,9 +1,11 @@ 'use strict'; +var debug = require('debug')('update:plugin'); var gutil = require('gulp-util'); var through = require('through2'); module.exports = function(verb) { + debug('dotfiles plugin'); return function() { return through.obj(function (file, enc, cb) { this.push(file); diff --git a/plugins/editorconfig.js b/plugins/editorconfig.js index 142dbfb..c9539be 100644 --- a/plugins/editorconfig.js +++ b/plugins/editorconfig.js @@ -1,10 +1,12 @@ 'use strict'; +var debug = require('debug')('update:plugin'); var gutil = require('gulp-util'); var through = require('through2'); // var ec = require('editorconfig'); -module.exports = function(verb) { +module.exports = function editorconfig_(verb) { + debug('editorconfig plugin'); return function() { return through.obj(function (file, enc, cb) { this.push(file); diff --git a/plugins/gitignore.js b/plugins/gitignore.js index b6f4448..dea572f 100644 --- a/plugins/gitignore.js +++ b/plugins/gitignore.js @@ -1,12 +1,14 @@ 'use strict'; +var debug = require('debug')('update:plugin'); var gutil = require('gulp-util'); var difference = require('arr-diff'); var through = require('through2'); var utils = require('../lib/utils'); var logger = require('../lib/logging'); -module.exports = function gitignore(verb) { +module.exports = function gitignore_(verb) { + debug('gitignore plugin'); return function() { return through.obj(function (file, enc, cb) { if (file.isNull() || !file.isBuffer()) { diff --git a/plugins/helpers/devDependencies.js b/plugins/helpers/devDependencies.js index 5e63420..4b5ccb4 100644 --- a/plugins/helpers/devDependencies.js +++ b/plugins/helpers/devDependencies.js @@ -1,10 +1,14 @@ 'use strict'; +var debug = require('debug')('update:helpers'); + /** * Remove the old verb */ exports.removeVerb = function(pkg) { + debug('removeVerb'); + if (pkg && pkg.devDependencies && pkg.devDependencies['verb-tag-jscomments']) { delete pkg.devDependencies['verb-tag-jscomments']; delete pkg.devDependencies.verb; diff --git a/plugins/helpers/files.js b/plugins/helpers/files.js index 9bb23c6..fca67d8 100644 --- a/plugins/helpers/files.js +++ b/plugins/helpers/files.js @@ -1,7 +1,7 @@ 'use strict'; -var fs = require('fs'); var mm = require('micromatch'); +var debug = require('debug')('update:helpers'); var unique = require('array-unique'); var utils = require('../../lib/utils'); @@ -19,6 +19,7 @@ var defaults = ['index.js', 'cli.js', 'lib/', 'bin/', 'completion/', 'templates/ */ module.exports = function(patterns, options) { + debug('helpers:files %j', patterns); patterns = patterns ? ensureSlash(patterns) : []; return function (files) { return unique(mm(files, defaults, options).concat(patterns)); @@ -30,6 +31,7 @@ module.exports = function(patterns, options) { */ function ensureSlash(files) { + var res = [], i = 0; var len; if (files && (len = files.length)) { diff --git a/plugins/helpers/license.js b/plugins/helpers/license.js index 3baecdc..b034e55 100644 --- a/plugins/helpers/license.js +++ b/plugins/helpers/license.js @@ -1,9 +1,11 @@ 'use strict'; var typeOf = require('kind-of'); +var debug = require('debug')('update:helpers'); var utils = require('../../lib/utils'); -exports.normalize = function normalize(pkg) { +exports.normalize = function license_(pkg) { + debug('helpers:license %j', pkg); var license = pkg.license; if (!license) { return pkg; } diff --git a/plugins/helpers/licenses.js b/plugins/helpers/licenses.js index cfbc1bf..a020cd5 100644 --- a/plugins/helpers/licenses.js +++ b/plugins/helpers/licenses.js @@ -1,6 +1,9 @@ 'use strict'; -exports.normalize = function normalize(pkg) { +var debug = require('debug')('update:helpers'); + +exports.normalize = function licenses_(pkg) { + debug('helpers:licenses %j', pkg); var licenses = pkg.licenses; if (Array.isArray(licenses) && licenses.length === 1) { diff --git a/plugins/helpers/scripts.js b/plugins/helpers/scripts.js index da06aa6..ee72dad 100644 --- a/plugins/helpers/scripts.js +++ b/plugins/helpers/scripts.js @@ -1,10 +1,13 @@ 'use strict'; +var debug = require('debug')('update:helpers'); + /** * Remove the old verb */ exports.fixMocha = function(pkg) { + debug('helpers:fixMocha %j', pkg); if (pkg.scripts && pkg.scripts.test && /mocha --?r/i.test(pkg.scripts.test)) { pkg.scripts.test = 'mocha'; } diff --git a/plugins/index.js b/plugins/index.js index 631b477..605dc35 100644 --- a/plugins/index.js +++ b/plugins/index.js @@ -5,6 +5,7 @@ module.exports = function (verb) { return { banners : require('./banners.js')(verb), + bower : require('./bower.js')(verb), dotfiles : require('./dotfiles.js')(verb), editorconfig : require('./editorconfig.js')(verb), gitignore : require('./gitignore.js')(verb), diff --git a/plugins/jshint.js b/plugins/jshint.js index d6b0cd0..c5d2ac5 100644 --- a/plugins/jshint.js +++ b/plugins/jshint.js @@ -1,5 +1,6 @@ 'use strict'; +var debug = require('debug')('update:plugin'); var gutil = require('gulp-util'); var through = require('through2'); var merge = require('merge-deep'); @@ -7,7 +8,8 @@ var sortObj = require('sort-object'); var logger = require('../lib/logging'); var utils = require('../lib/utils'); -module.exports = function jshint(verb) { +module.exports = function jshint_(verb) { + debug('jshint plugin'); return function() { return through.obj(function (file, enc, cb) { if (file.isNull() || !file.isBuffer()) { diff --git a/plugins/license.js b/plugins/license.js index 95a5eeb..5ef5e43 100644 --- a/plugins/license.js +++ b/plugins/license.js @@ -1,12 +1,14 @@ 'use strict'; +var debug = require('debug')('update:plugin'); var gutil = require('gulp-util'); var through = require('through2'); -var update = require('update-license'); -var utils = require('../lib/utils'); -var logger = require('../lib/logging'); +var license = require('update-license'); +// var utils = require('../lib/utils'); +// var logger = require('../lib/logging'); -module.exports = function license(verb) { +module.exports = function license_(verb) { + debug('license plugin'); return function() { return through.obj(function (file, enc, cb) { if (file.isNull() || !file.isBuffer()) { @@ -18,13 +20,13 @@ module.exports = function license(verb) { if (utils.contains(file.path, 'LICENSE')) { var str = file.contents.toString(); var log = logger(str); - str = update(str); + str = license(str); log.success(str, 'updated patterns in', file.relative); file.contents = new Buffer(str); } } catch (err) { console.log(err); - this.emit('error', new gutil.PluginError('update:license', err)); + this.emit('error', new gutil.PluginError('[update] license plugin:', err)); return cb(); } diff --git a/plugins/pkg.js b/plugins/pkg.js index b1fceae..138c737 100644 --- a/plugins/pkg.js +++ b/plugins/pkg.js @@ -4,6 +4,7 @@ * Module dependencies */ +var debug = require('debug')('update:plugin'); var path = require('path'); var gutil = require('gulp-util'); var through = require('through2'); @@ -19,14 +20,15 @@ var normalize = require('./helpers/'); * hack until I get update-package straightened out. */ -module.exports = function(verb) { +module.exports = function pkg_(verb) { + debug('pkg plugin'); + return function() { return through.obj(function (file, enc, cb) { if (file.isNull() || !file.isBuffer() || path.basename(file.path) !== 'package.json') { this.push(file); return cb(); } - try { file = normalize(file, verb); } catch (err) { @@ -34,7 +36,6 @@ module.exports = function(verb) { this.emit('error', new gutil.PluginError('update:pkg', err)); return cb(); } - this.push(file); cb(); }); diff --git a/plugins/readme/verbmd.js b/plugins/readme/verbmd.js index 3eb1bc4..f589e4f 100644 --- a/plugins/readme/verbmd.js +++ b/plugins/readme/verbmd.js @@ -1,8 +1,10 @@ 'use strict'; +var debug = require('debug')('update:readme'); var unique = require('array-unique'); function unknownHelpers(str, verb) { + debug('unknownHelpers'); var helpers = Object.keys(verb._.helpers); var async = Object.keys(verb._.asyncHelpers); var keys = unique(helpers.concat(async)).sort(); @@ -24,6 +26,8 @@ function unknownHelpers(str, verb) { } function addTravisBadge(str, stats) { + debug('addTravisBadge'); + if (stats && stats.hasTravis && !/badge\(.travis/.test(str)) { str = str.split('{%= badge("fury") %}').join('{%= badge("fury") %} {%= badge("travis") %}'); } @@ -31,6 +35,7 @@ function addTravisBadge(str, stats) { } function fixInstall(str) { + debug('fixInstall'); var re = /## Install[\s\n]+{%= include/g; str = str.replace(re, '{%= include'); str = str.split('{%= include("install") %}').join('{%= include("install-npm", {save: true}) %}'); diff --git a/plugins/tests.js b/plugins/tests.js index 423751e..6fb99ce 100644 --- a/plugins/tests.js +++ b/plugins/tests.js @@ -1,12 +1,13 @@ 'use strict'; +var debug = require('debug')('update:plugin'); var gutil = require('gulp-util'); var through = require('through2'); var should = require('../lib/tests/should'); var paths = require('../lib/tests/paths'); - -module.exports = function (verb) { +module.exports = function tests_(verb) { + debug('tests plugin'); return function () { return through.obj(function (file, enc, cb) { if (file.isNull() || !file.isBuffer()) { diff --git a/plugins/travis.js b/plugins/travis.js index f1a854c..9222c24 100644 --- a/plugins/travis.js +++ b/plugins/travis.js @@ -1,5 +1,6 @@ 'use strict'; +var debug = require('debug')('update:plugin'); var gutil = require('gulp-util'); var through = require('through2'); var logger = require('../lib/logging'); @@ -9,7 +10,8 @@ var logger = require('../lib/logging'); * if the file is missing but tests exist. */ -module.exports = function travis(verb) { +module.exports = function travis_(verb) { + debug('travis plugin'); return function () { return through.obj(function (file, enc, cb) { this.push(file); @@ -27,7 +29,6 @@ module.exports = function travis(verb) { this.push(file); log.success(true, 'writing', file.relative); } - cb(); }); }; From a2b53ede436bb88fa430164690939cb0997fe9c3 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Thu, 2 Apr 2015 21:24:29 -0400 Subject: [PATCH 053/274] verbfile --- .verb.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.verb.md b/.verb.md index e865ff3..e4ed5d2 100644 --- a/.verb.md +++ b/.verb.md @@ -48,17 +48,16 @@ From the command line, run: update ``` -## Run tests -{%= include("tests") %} - ## Related - This project is built on these libraries: {%= join(keys(dependencies), "\n - ") %} ## Contributing -Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue]({%= bugs.url %}) +{%= include("contributing") %} + +## Running tests +{%= include("test") %} ## Author {%= include("author") %} From fac3434fd326a0d8cf8810836e5ae4c951c6a198 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 25 Apr 2015 12:07:29 -0400 Subject: [PATCH 054/274] rename to updatefile.js --- updatefile.js | 15 +++++ verbfile.js | 163 -------------------------------------------------- 2 files changed, 15 insertions(+), 163 deletions(-) create mode 100644 updatefile.js delete mode 100644 verbfile.js diff --git a/updatefile.js b/updatefile.js new file mode 100644 index 0000000..5fcafa8 --- /dev/null +++ b/updatefile.js @@ -0,0 +1,15 @@ + +var update = require('./'); +var del = require('del'); + +update.task('default', function () { + update.src('*.*') + // .pipe(update.dest('./actual')) + // .on('end', function (cb) { + // process.nextTick(function () { + // del('actual', cb); + // }) + // }); +}); + +update.run(); diff --git a/verbfile.js b/verbfile.js deleted file mode 100644 index 40fe848..0000000 --- a/verbfile.js +++ /dev/null @@ -1,163 +0,0 @@ -'use strict'; - -var fs = require('fs'); -var del = require('del'); -var path = require('path'); -var verb = require('../../verb/verb'); -var debug = require('debug')('update:tasks'); -var gutil = require('gulp-util'); -var parse = require('parse-copyright'); -var plugins = require('./plugins')(verb); -var verbmd = require('./plugins/readme/verbmd'); -var utils = require('./lib/utils'); -var glob = require('glob'); -var pkg = require(__dirname + '/package.json'); -var logger = require('./lib/logging'); -var log = logger({nocompare: true}); - - -verb.transform('start', require('./transforms/start')); -verb.onLoad(/./, function (file, next) { - file.render = false; - file.readme = false; - next(); -}); - -verb.onLoad(/\.js$/, function (file, next) { - file.data.copyright = parse(file.content); - next(); -}); - -verb.copy('.verbrc.md', function (file) { - debug('copy .verbrc.md'); - file.path = '.verb.md'; - log.success('renamed', file.relative); - return path.dirname(file.relative); -}); - -var hasOneTestfile = false; -if (verb.files('test{,*.js,/*.js').length) { - hasOneTestfile = true; - verb.set('hasOneTestfile', true); - verb.copy('test/test.js', function (file) { - debug('copy test.js'); - file.path = 'test.js'; - log.success('moved', file.path); - return file.base; - }); -} - -verb.copy('LICENSE-MIT', function (file) { - debug('copy LICENSE-MIT'); - file.path = 'LICENSE'; - log.success('renamed', file.relative); - return path.dirname(file.relative); -}); - -verb.task('banners', function () { - debug('banners task'); - verb.src(['*.js', 'test/*.js', 'lib/*.js'], {render: false}) - .pipe(plugins.banners()) - .on('error', gutil.log) - .pipe(verb.dest(function (file) { - return path.dirname(file.path); - })); -}); - -verb.task('jshint', function () { - verb.src('.jshintrc', {render: false}) - .pipe(plugins.jshint()) - .on('error', gutil.log) - .pipe(verb.dest(function (file) { - file.path = '.jshintrc'; - return path.dirname(file.path); - })); -}); - -verb.task('travis', function () { - verb.src('.travis.yml', {render: false}) - .pipe(plugins.travis()) - .on('error', gutil.log) - .pipe(verb.dest(function (file) { - file.path = '.travis.yml'; - return path.dirname(file.path); - })); -}); - -verb.task('tests', function () { - verb.src(['test.js', 'test/*.js'], {render: false}) - .pipe(plugins.tests()) - .on('error', gutil.log) - .pipe(verb.dest(function (file) { - return path.dirname(file.path); - })); -}); - -verb.task('license', function () { - verb.src('LICENSE{,-MIT}', {render: false}) - // .pipe(plugins.license()) - .on('error', gutil.log) - .pipe(verb.dest(function (file) { - file.path = 'LICENSE'; - return path.dirname(file.path); - })); -}); - -verb.task('dotfiles', function () { - verb.src('.git*', {render: false, dot: true}) - .pipe(plugins.editorconfig()) - .pipe(plugins.gitignore()) - .pipe(plugins.dotfiles()) - .on('error', gutil.log) - .pipe(verb.dest(function (file) { - return path.dirname(file.path); - })) - .on('end', function (cb) { - var files = ['.npmignore', 'test/mocha.opts', '.verbrc.md', 'LICENSE-MIT']; - var res = utils.exists(files); - - var exists = res.EXISTS; - if (verb.get('hasOneTestfile')) { - exists.push('test'); - } - - if (exists.length) { - del(exists, cb); - log.deleted('deleted', exists.join(', ')); - } - }) -}); - -verb.task('json', function () { - verb.src('{bower,package}.json', {render: false}) - .pipe(plugins.pkg()) - .pipe(plugins.bower()) - .on('error', gutil.log) - .pipe(verb.dest('.')) - .on('end', function () { - log.success(true, 'updated package.json'); - }); -}); - -verb.task('readme', function () { - verb.src('.verb.md') - .pipe(verb.dest('.')) - .on('error', gutil.log) - .on('end', function () { - log.success(true, 'updated readme.'); - }); -}); - -verb.task('default', [ - 'banners', - 'tests', - 'dotfiles', - 'travis', - 'jshint', - 'license', - 'json', - 'readme' -]); - -// verb.diff(); -verb.run(); From 0bcf8001a2d91c252bc4a0b43b36a523bf3f62b9 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 25 Apr 2015 12:07:49 -0400 Subject: [PATCH 055/274] update cli files --- bin/update.js | 133 ++++++++++++++++++++++++++++++++++++++++++ cli.js | 5 -- completion/README.md | 21 +++++++ completion/bash | 27 +++++++++ completion/fish | 12 ++++ completion/powershell | 62 ++++++++++++++++++++ completion/zsh | 25 ++++++++ 7 files changed, 280 insertions(+), 5 deletions(-) create mode 100755 bin/update.js delete mode 100755 cli.js create mode 100644 completion/README.md create mode 100644 completion/bash create mode 100644 completion/fish create mode 100644 completion/powershell create mode 100644 completion/zsh diff --git a/bin/update.js b/bin/update.js new file mode 100755 index 0000000..6434a56 --- /dev/null +++ b/bin/update.js @@ -0,0 +1,133 @@ +#!/usr/bin/env node + +var path = require('path'); +var chalk = require('chalk'); +var prettyTime = require('pretty-hrtime'); +var completion = require('../lib/utils/completion'); +var taskTree = require('../lib/utils/task-tree'); +var update = require('..'); + +var argv = require('minimist')(process.argv.slice(2)); +update.extend('args', argv); + +var stack = argv._; +var name = stack.shift(); +var tasks = stack.length ? stack : ['default']; + +var updater = update.updater(name); +var file = updater.module; + +if (file) { + var cwd = path.dirname(file); + update.set('updater.cwd', cwd); + update.emit('loaded'); + + var instance = require(file); + + process.nextTick(function () { + instance.start.apply(instance, tasks); + }.bind(instance)); +} + +// exit with 0 or 1 +var failed = false; +process.once('exit', function(code) { + if (code === 0 && failed) { + exit(1); + } +}); + +update.on('last', function () { + var args; + if (argv.set) { + args = argv.set.split('='); + update.store.set.apply(update.store, args); + } + + if (argv.has) { + args = argv.has.split('='); + update.store.has.apply(update.store, args); + } + + if (argv.omit) { + args = argv.omit.split('='); + update.store.omit.apply(update.store, args); + } + + if (argv.del) { + update.store.delete({force: true}); + } +}); + +update.on('err', function () { + failed = true; +}); + +update.on('task_start', function (e) { + console.log('starting', '\'' + chalk.cyan(e.task) + '\''); +}); + +update.on('task_stop', function (e) { + var time = prettyTime(e.hrDuration); + console.log('finished', '\'' + chalk.cyan(e.task) + '\'', 'after', chalk.magenta(time)); +}); + +update.on('task_err', function (e) { + var msg = formatError(e); + var time = prettyTime(e.hrDuration); + console.log(chalk.cyan(e.task), chalk.red('errored after'), chalk.magenta(time)); + console.log(msg); +}); + +update.on('task_not_found', function (err) { + console.log(chalk.red('task \'' + err.task + '\' is not in your updatefile')); + console.log('please check the documentation for proper updatefile formatting'); + exit(1); +}); + +function logTasks(env, instance) { + var tree = taskTree(instance.tasks); + tree.label = 'Tasks for ' + tildify(instance.module); + archy(tree).split('\n').forEach(function (v) { + if (v.trim().length === 0) { + return; + } + console.log(v); + }); +} + +// format orchestrator errors +function formatError(e) { + if (!e.err) { + return e.message; + } + + // PluginError + if (typeof e.err.showStack === 'boolean') { + return e.err.toString(); + } + + // normal error + if (e.err.stack) { + return e.err.stack; + } + + // unknown (string, number, etc.) + return new Error(String(e.err)).stack; +} + + +// fix stdout truncation on windows +function exit(code) { + if (process.platform === 'win32' && process.stdout.bufferSize) { + process.stdout.once('drain', function() { + process.exit(code); + }); + return; + } + process.exit(code); +} + +if (!argv._.length) { + update.emit('loaded'); +} diff --git a/cli.js b/cli.js deleted file mode 100755 index 83a317a..0000000 --- a/cli.js +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env node - -'use strict'; - -require('./verbfile'); \ No newline at end of file diff --git a/completion/README.md b/completion/README.md new file mode 100644 index 0000000..66fdd6b --- /dev/null +++ b/completion/README.md @@ -0,0 +1,21 @@ +# Completion for update + +> Borrowed from gulp, thanks to gulp and the grunt team and Tyler Kellen for creating this. + +To enable tasks auto-completion in shell you should add `eval "$(update --completion=shell)"` in your `.shellrc` file. + +## Bash + +Add `eval "$(update --completion=bash)"` to `~/.bashrc`. + +## Zsh + +Add `eval "$(update --completion=zsh)"` to `~/.zshrc`. + +## Powershell + +Add `Invoke-Expression ((update --completion=powershell) -join [System.Environment]::NewLine)` to `$PROFILE`. + +## Fish + +Add `update --completion=fish | source` to `~/.config/fish/config.fish`. diff --git a/completion/bash b/completion/bash new file mode 100644 index 0000000..9b29802 --- /dev/null +++ b/completion/bash @@ -0,0 +1,27 @@ +#!/bin/bash + +# Borrowed from grunt-cli via gulp ;) +# http://gruntjs.com/ +# +# Copyright (c) 2012 Tyler Kellen, contributors +# Licensed under the MIT license. +# https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT + +# Usage: +# +# To enable bash completion for update, add the following line (minus the +# leading #, which is the bash comment character) to your ~/.bashrc file: +# +# eval "$(update --completion=bash)" + +# Enable bash autocompletion. +function _update_completions() { + # The currently-being-completed word. + local cur="${COMP_WORDS[COMP_CWORD]}" + #Grab tasks + local compls=$(update --tasks-simple) + # Tell complete what stuff to show. + COMPREPLY=($(compgen -W "$compls" -- "$cur")) +} + +complete -o default -F _update_completions update diff --git a/completion/fish b/completion/fish new file mode 100644 index 0000000..18931b8 --- /dev/null +++ b/completion/fish @@ -0,0 +1,12 @@ +#!/usr/bin/env fish + +# Borrowed from gulp: + +# Usage: +# +# To enable fish completion for update, add the following line to +# your ~/.config/fish/config.fish file: +# +# update --completion=fish | source + +complete -c update -a "(update --tasks-simple)" -f diff --git a/completion/powershell b/completion/powershell new file mode 100644 index 0000000..92eeab9 --- /dev/null +++ b/completion/powershell @@ -0,0 +1,62 @@ +# Copyright (c) 2014 Jason Jarrett +# +# Borrowed from gulp +# Tab completion for the `update` +# +# Usage: +# +# To enable powershell completion for update you need to be running +# at least PowerShell v3 or greater and add the below to your $PROFILE +# +# Invoke-Expression ((update --completion=powershell) -join [System.Environment]::NewLine) +# +# + +$update_completion_Process = { + param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) + + + # Load up an assembly to read the updatefile's sha1 + if(-not $global:VerbSHA1Managed) { + [Reflection.Assembly]::LoadWithPartialName("System.Security") | out-null + $global:VerbSHA1Managed = new-Object System.Security.Cryptography.SHA1Managed + } + + # setup a global (in-memory) cache + if(-not $global:VerbfileShaCache) { + $global:VerbfileShaCache = @{}; + } + + $cache = $global:VerbfileShaCache; + + # Get the updatefile's sha1 + $sha1updateFile = (resolve-path updatefile.js -ErrorAction Ignore | %{ + $file = [System.IO.File]::Open($_.Path, "open", "read") + [string]::join('', ($global:VerbSHA1Managed.ComputeHash($file) | %{ $_.ToString("x2") })) + $file.Dispose() + }) + + # lookup the sha1 for previously cached task lists. + if($cache.ContainsKey($sha1updateFile)){ + $tasks = $cache[$sha1updateFile]; + } else { + $tasks = (update --tasks-simple).split("`n"); + $cache[$sha1updateFile] = $tasks; + } + + + $tasks | + where { $_.startswith($commandName) } + Sort-Object | + foreach { New-Object System.Management.Automation.CompletionResult $_, $_, 'ParameterValue', ('{0}' -f $_) } +} + +if (-not $global:options) { + $global:options = @{ + CustomArgumentCompleters = @{}; + NativeArgumentCompleters = @{} + } +} + +$global:options['NativeArgumentCompleters']['update'] = $update_completion_Process +$function:tabexpansion2 = $function:tabexpansion2 -replace 'End\r\n{','End { if ($null -ne $options) { $options += $global:options} else {$options = $global:options}' diff --git a/completion/zsh b/completion/zsh new file mode 100644 index 0000000..780b682 --- /dev/null +++ b/completion/zsh @@ -0,0 +1,25 @@ +#!/bin/zsh + +# Borrowed from grunt-cli, by way of gulp +# http://gruntjs.com/ +# +# Copyright (c) 2012 Tyler Kellen, contributors +# Licensed under the MIT license. +# https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT + +# Usage: +# +# To enable zsh completion for update, add the following line (minus the +# leading #, which is the zsh comment character) to your ~/.zshrc file: +# +# eval "$(update --completion=zsh)" + +# Enable zsh autocompletion. +function _update_completion() { + # Grab tasks + compls=$(update --tasks-simple) + completions=(${=compls}) + compadd -- $completions +} + +compdef _update_completion update From b9022660c3e9749a46a384d4f83879e69a9ca1b6 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 25 Apr 2015 12:09:27 -0400 Subject: [PATCH 056/274] lint --- .editorconfig | 15 +++------------ .gitattributes | 12 ++---------- .gitignore | 15 ++++++++++----- .jshintrc | 1 - .travis.yml | 8 +++----- 5 files changed, 18 insertions(+), 33 deletions(-) diff --git a/.editorconfig b/.editorconfig index 32dd133..de49bd8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,19 +9,10 @@ indent_size = 2 trim_trailing_whitespace = true insert_final_newline = true -[*.json] -indent_style = space -indent_size = 2 - -[*.yml] -indent_style = space -indent_size = 2 - [*.md] -indent_style = space -indent_size = 2 trim_trailing_whitespace = false +insert_final_newline = false -[test/fixtures/*] +[{test,fixtures,actual}/**] trim_trailing_whitespace = false -insert_final_newline = false \ No newline at end of file +insert_final_newline = false diff --git a/.gitattributes b/.gitattributes index 4a3f1d3..e25e130 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,10 +1,2 @@ -# Enforce Unix newlines -* text eol=lf - -# binaries -*.ai binary -*.psd binary -*.jpg binary -*.gif binary -*.png binary -*.jpeg binary \ No newline at end of file +* text=auto +/test/** text eol=lf diff --git a/.gitignore b/.gitignore index 1082f89..80a228c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,15 @@ +*.DS_Store +*.sublime-* +_gh_pages +bower_components node_modules npm-debug.log - -tmp +actual +test/actual temp +tmp TODO.md vendor - -*.sublime-* -*.DS_Store \ No newline at end of file +.idea +benchmark +coverage diff --git a/.jshintrc b/.jshintrc index 01134d6..6e5a84a 100644 --- a/.jshintrc +++ b/.jshintrc @@ -1,7 +1,6 @@ { "asi": false, "boss": true, - "camelcase": true, "curly": true, "eqeqeq": true, "eqnull": true, diff --git a/.travis.yml b/.travis.yml index efe94c0..dedfc07 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,6 @@ sudo: false language: node_js node_js: - - "0.10" - - "0.12" - - "iojs" -git: - depth: 10 \ No newline at end of file + - 'iojs' + - '0.12' + - '0.10' From b2438d4493bd9ce4459bf7b24e1173220635aa3e Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 25 Apr 2015 12:09:43 -0400 Subject: [PATCH 057/274] clean up lib --- lib/_starters/normalizer.js | 10 -- lib/_starters/plugin.js | 13 --- lib/copy.js | 8 -- lib/copyFiles.js | 11 -- lib/delete.js | 18 ---- lib/logging.js | 35 ------- lib/mkdir.js | 37 ------- lib/move.js | 14 --- lib/rename.js | 34 ------- lib/utils.js | 193 ------------------------------------ 10 files changed, 373 deletions(-) delete mode 100644 lib/_starters/normalizer.js delete mode 100644 lib/_starters/plugin.js delete mode 100644 lib/copy.js delete mode 100644 lib/copyFiles.js delete mode 100644 lib/delete.js delete mode 100644 lib/logging.js delete mode 100644 lib/mkdir.js delete mode 100644 lib/move.js delete mode 100644 lib/rename.js delete mode 100644 lib/utils.js diff --git a/lib/_starters/normalizer.js b/lib/_starters/normalizer.js deleted file mode 100644 index 1e5dd97..0000000 --- a/lib/_starters/normalizer.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; - -module.exports = function paths(file, verb) { - var str = file.contents.toString(); - - // do stuff to `file` and `verb` - - file.contents = new Buffer(str); - return file; -}; diff --git a/lib/_starters/plugin.js b/lib/_starters/plugin.js deleted file mode 100644 index 0cc7d02..0000000 --- a/lib/_starters/plugin.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -var through = require('through2'); - -module.exports = function(options) { - return through.obj(function (file, enc, cb) { - var str = file.contents.toString(); - - file.contents = new Buffer(str); - this.push(file); - cb(); - }); -}; diff --git a/lib/copy.js b/lib/copy.js deleted file mode 100644 index 0f0915f..0000000 --- a/lib/copy.js +++ /dev/null @@ -1,8 +0,0 @@ -'use strict'; - -var fs = require('fs'); - -module.exports = function copy(src, dest) { - var res = fs.createWriteStream(dest); - fs.createReadStream(src).pipe(res); -}; \ No newline at end of file diff --git a/lib/copyFiles.js b/lib/copyFiles.js deleted file mode 100644 index dfdf33a..0000000 --- a/lib/copyFiles.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -var path = require('path'); -var copy = require('./copy'); - -module.exports = function copyFiles(files, dest) { - for (var i = files.length - 1; i >= 0; i--) { - var fp = files[i]; - copy(fp, path.join(dest, path.basename(fp))); - } -}; \ No newline at end of file diff --git a/lib/delete.js b/lib/delete.js deleted file mode 100644 index 6c9c902..0000000 --- a/lib/delete.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -var fs = require('fs'); -var chalk = require('chalk'); -var symbol = require('log-symbols'); -var del = require('del'); -var bold = chalk.bold; - -module.exports = function _delete(fp, opts) { - if (!fs.existsSync(fp)) { - if (opts && opts.silent !== true) { - console.log(symbol.error, bold(fp), 'does not exist.'); - } - } else { - del.sync(fp, opts && opts.force); - console.log(symbol.success, 'deleted', bold(fp)); - } -}; diff --git a/lib/logging.js b/lib/logging.js deleted file mode 100644 index 6bf5a80..0000000 --- a/lib/logging.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict'; - -var chalk = require('chalk'); -var symbol = require('log-symbols'); -var merge = require('merge-deep'); - -module.exports = function (str, options) { - if (typeof str === 'object') { - options = str; - } - - options = merge({}, options); - var log = {}; - - log.success = function (updated, msg, fp) { - if (options.nocompare || updated !== str) { - var text = chalk.gray(msg.trim()) + (fp ? (' ' + fp) : ''); - console.log(' ' + symbol.success + ' ' + text); - } - }; - - log.results = log.success; - - log.info = function (msg, fp) { - var text = chalk.gray(msg.trim()) + (fp ? (' ' + fp) : ''); - console.log(' ' + symbol.info + ' ' + text); - }; - - log.deleted = function (msg, fp) { - var text = chalk.gray(msg.trim()) + (fp ? (' ' + fp) : ''); - console.log(' ' + symbol.success + ' ' + text); - }; - - return log; -}; diff --git a/lib/mkdir.js b/lib/mkdir.js deleted file mode 100644 index 2c637b1..0000000 --- a/lib/mkdir.js +++ /dev/null @@ -1,37 +0,0 @@ -/*! - * update - * - * Copyright (c) 2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - -'use strict'; - -var fs = require('fs'); -var path = require('path'); - -module.exports = {}; - -/** - * Make the given directory and intermediates - * if they don't already exist. - * - * @param {String} `dirpath` - * @param {Number} `mode` - * @return {String} - * @api private - */ - -module.exports.sync = function mkdir(dir, mode) { - mode = mode || parseInt('0777', 8) & (~process.umask()); - if (!fs.existsSync(dir)) { - var parent = path.dirname(dir); - - if (fs.existsSync(parent)) { - fs.mkdirSync(dir, mode); - } else { - mkdir(parent); - fs.mkdirSync(dir, mode); - } - } -}; diff --git a/lib/move.js b/lib/move.js deleted file mode 100644 index 5e245a4..0000000 --- a/lib/move.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -var fs = require('fs'); -var path = require('path'); -var rename = require('./rename'); -var mkdir = require('./mkdir'); - -module.exports = function move(src, dest, force) { - var dir = path.dirname(dest); - if (!fs.existsSync(dir)) { - mkdir(dir); - } - rename(src, dest, force); -}; diff --git a/lib/rename.js b/lib/rename.js deleted file mode 100644 index 0653ea2..0000000 --- a/lib/rename.js +++ /dev/null @@ -1,34 +0,0 @@ -'use strict'; - -var fs = require('fs'); -var path = require('path'); -var chalk = require('chalk'); -var symbol = require('log-symbols'); -var bold = chalk.bold; - -for (var key in symbol) { - if (symbol.hasOwnProperty(key)) { - symbol[key] = ' ' + symbol[key] + ' '; - } -} - -module.exports = function rename(src, dest, opts) { - opts = opts || {}; - - if (path.resolve(src) === path.resolve(dest)) { - if (opts.silent !== true) { - console.log(symbol.warning, bold(src), 'and', bold(dest), 'are the same path.'); - } - } else if (!fs.existsSync(src)) { - if (opts.silent !== true) { - console.log(symbol.error, bold(src), 'does not exist.'); - } - } else if (fs.existsSync(dest) && opts.force !== true) { - if (opts.silent !== true) { - console.log(symbol.warning, bold(dest), 'already exists (to force, pass `true` as the last argument).'); - } - } else { - console.log(symbol.success, 'renamed', bold(src), '=>', bold(dest)); - fs.renameSync(src, dest); - } -}; \ No newline at end of file diff --git a/lib/utils.js b/lib/utils.js deleted file mode 100644 index 768ea38..0000000 --- a/lib/utils.js +++ /dev/null @@ -1,193 +0,0 @@ -'use strict'; - -var fs = require('fs'); -var debug = require('debug')('update:utils'); -var path = require('path'); -var mm = require('micromatch'); -var matter = require('gray-matter'); -var logger = require('./logging'); - -/** - * Read a file - */ - -exports.readFile = function readFile(fp) { - debug('utils:readFile: %s', fp); - return fs.readFileSync(fp, 'utf8'); -}; - -/** - * Write a file - */ - -exports.writeFile = function writeFile(fp, str) { - return fs.writeFileSync(fp, str); -}; - -/** - * Creates a matching function to use against - * the list of given files. - */ - -exports.match = function match(files) { - debug('utils:match: %j', files); - - return function(pattern, options) { - return mm(files, pattern, options); - }; -}; - -/** - * Try to read a directory of files. Silently catches - * any errors and returns an empty array. - */ - -exports.tryStat = function tryStat(fp) { - try { - return fs.statSync(fp); - } catch (err) {} - return []; -}; - -/** - * Try to read a directory of files. Silently catches - * any errors and returns an empty array. - */ - -exports.tryReaddir = function tryReaddir(fp) { - debug('utils:tryReaddir: %s', fp); - try { - return fs.readdirSync(fp); - } catch (err) {} - return []; -}; - -/** - * Pass a file or an array of filepaths that "might" exist, - * and an object is returned with `ENOENT` and `EXISTS` - * arrays. - * - * @param {Array|String} - */ - -exports.exists = function exists(files) { - debug('utils:exists: %j', files); - - if (!files) { - throw new Error('utils.exists() expects an array or string.'); - } - - files = Array.isArray(files) ? files : [files]; - var len = files.length; - var res = {ENOENT: new Array(len), EXISTS: files.slice()}; - - while (len--) { - var fp = path.resolve(files[len]); - if (!fs.existsSync(fp)) { - res.EXISTS.splice(len, 1); - res.ENOENT[len] = fp; - } - } - return res; -}; - -/** - * Stringify the `contents` property of a vinyl file. - * - * @param {Object} `file` - * @return {String} - */ - -exports.replace = function replace(str, a, b) { - if (typeof str !== 'string' || typeof a !== 'string' || typeof b !== 'string') { - throw new TypeError('utils.replace() expects a string.'); - } - return str.split(a).join(b); -}; - -/** - * Strip front matter from a string. - * - * @param {String} `fp` - * @return {String} - */ - -exports.antimatter = function antimatter(fp) { - if (typeof fp !== 'string' ) { - throw new TypeError('utils.antimatter() expects a string, got:', fp); - } - - var str = exports.readFile(fp); - var log = logger(str); - var obj = matter(str); - var keys = Object.keys(obj.data); - if (keys.length) { - str = obj.content.replace(/^\s+/, ''); - log.success(str, 'stripped deprecated front-matter tags in', fp); - } - return str; -}; - -/** - * Return true if `file.path` contains the given - * string. We know if it passes through the stream - * that the file exists. - * - * @param {Object} `file` - * @param {String} `str` - * @return {Boolean} - */ - -exports.contains = function contains(str, ch) { - if (typeof str !== 'string' ) { - throw new TypeError('utils.contains() first arg should be a string, not:', str); - } - - if (typeof ch !== 'string') { - throw new TypeError('utils.contains() second arg should be a string, not:', ch); - } - return str.indexOf(ch) !== -1; -}; - -/** - * Get a `username/name` github url string. - * - * @param {String} `url` - * @return {String} - */ - -exports.repo = function repo(str) { - debug('utils:repo: %s', str); - if (typeof str !== 'string') { - throw new TypeError('utils.repo() expects a string.'); - } - return str.replace(/\w+:\/\/github.com\/(.*?)(?:\.git)?$/, '$1'); -}; - -/** - * Add a trailing slash to a file path. - * - * @param {String} `fp` - * @param {Object} `stat` - * @return {String} - */ - -exports.trailingSlash = function trailingSlash(fp, stat) { - debug('utils:trailingSlash: %s', fp); - - if (typeof fp !== 'string') { - throw new TypeError('utils.trailingSlash() expects a string.'); - } - - if (typeof stat.isFile !== 'function') { - return; - } - - if (stat.isFile()) { - return fp; - } - if (fp && fp[fp.length - 1] !== '/') { - return fp + '/'; - } - return fp; -}; From f2edcf1ad1016b8a1ca010c14fb8891fb9be360e Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 25 Apr 2015 12:10:07 -0400 Subject: [PATCH 058/274] adds Update class --- index.js | 269 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 269 insertions(+) create mode 100644 index.js diff --git a/index.js b/index.js new file mode 100644 index 0000000..79c7875 --- /dev/null +++ b/index.js @@ -0,0 +1,269 @@ +/*! + * update + * + * Copyright (c) 2015, Jon Schlinkert. + * Licensed under the MIT License. + */ + +'use strict'; + +var path = require('path'); +var extend = require('lodash')._.extend; +var through = require('through2'); +var Template = require('template'); +var Task = require('orchestrator'); +var vfs = require('vinyl-fs'); + +/** + * Local dependencies + */ + +var plugins = require('./lib/plugins'); +var session = require('./lib/session'); +var stack = require('./lib/stack'); +var init = require('./lib/init'); + +/** + * Initialize `Update`. + * + * ```js + * var app = new Update(); + * ``` + * + * @api public + */ + +function Update(opts) { + Template.call(this, opts); + Task.call(this, opts); + this.transforms = this.transforms || {}; + this.session = session; + init.call(this, this); +} + +extend(Update.prototype, Task.prototype); +Template.extend(Update.prototype); + +/** + * Glob patterns or filepaths to source files. + * + * ```js + * app.src('*.js') + * ``` + * + * **Example usage** + * + * ```js + * app.task('web-app', function() { + * app.src('templates/*') + * app.dest(process.cwd()) + * }); + * ``` + * + * @param {String|Array} `glob` Glob patterns or file paths to source files. + * @param {Object} `options` Options or locals to merge into the context and/or pass to `src` plugins + * @api public + */ + +Update.prototype.src = function(glob, opts) { + return stack.src(this, glob, opts); +}; + +/** + * Glob patterns or filepaths to templates stored in the + * `./templates` directory of an updater. + * + * ```js + * app.templates('*.js') + * ``` + * + * **Example usage** + * + * ```js + * app.task('licenses', function() { + * app.templates('templates/licenses/*') + * app.dest(process.cwd()) + * }); + * ``` + * + * @param {String|Array} `glob` Glob patterns or file paths to source files. + * @param {Object} `options` Options or locals to merge into the context and/or pass to `src` plugins + * @api public + */ + +Update.prototype.templates = function(glob, opts) { + return stack.templates(this, glob, opts); +}; + +/** + * Specify a destination for processed files. + * + * ```js + * app.dest('dist', {ext: '.xml'}) + * ``` + * + * **Example usage** + * + * ```js + * app.task('foo', function() { + * app.src('templates/*') + * app.dest('dist', {ext: '.xml'}) + * }); + * ``` + * + * @param {String|Function} `dest` File path or rename function. + * @param {Object} `options` Options or locals to merge into the context and/or pass to `dest` plugins + * @api public + */ + +Update.prototype.dest = function(dest, opts) { + dest = path.resolve((opts && opts.cwd) || process.cwd(), dest); + return stack.dest(this, dest, opts); +}; + +/** + * Copy a `glob` of files to the specified `dest`. + * + * ```js + * app.copy('assets/**', 'dist'); + * ``` + * + * @param {String|Array} `glob` + * @param {String|Function} `dest` + * @return {Stream} Stream to allow doing additional work. + */ + +Update.prototype.copy = function(glob, dest, opts) { + return vfs.src(this, glob).pipe(vfs.dest(dest, opts)); +}; + +/** + * Define a task. + * + * ```js + * app.task('docs', function() { + * app.src('*.js').pipe(app.dest('.')); + * }); + * ``` + * + * @param {String} `name` + * @param {Function} `fn` + * @api public + */ + +Update.prototype.task = Update.prototype.add; + +/** + * Get the name of the currently running task. This is + * primarily used inside plugins. + * + * ```js + * app.gettask(); + * ``` + * + * @return {String} `task` The currently running task. + * @api public + */ + +Update.prototype.gettask = function() { + var name = this.session.get('task'); + return typeof name != 'undefined' + ? 'task_' + name + : 'file'; +}; + +/** + * Used in plugins to get a template from the current session. + * + * ```js + * var template = getFile(id, file); + * ``` + * + * @return {String} `id` Pass the task-id from the current session. + * @return {Object} `file` Vinyl file object. Must have an `id` property that matches the `id` of the session. + * @api public + */ + +Update.prototype.getFile = function(file) { + var collection = this.inflections[this.gettask()]; + return this.views[collection][file.id]; +}; + +/** + * Set or get a generator function by `name`. + * + * ```js + * // set an updater + * app.updater('foo', require('updater-foo')); + * + * // get an updater + * var foo = app.updater('foo'); + * ``` + * @param {String} `name` + * @param {Function} `fn` The updater plugin function + * @api public + */ + +Update.prototype.updater = function(name, fn) { + if (arguments.length === 1 && typeof name === 'string') { + return this.updaters[name]; + } + this.updaters[name] = fn; + return this; +}; + +/** + * Run an array of tasks. + * + * ```js + * app.run(['foo', 'bar']); + * ``` + * + * @param {Array} `tasks` + * @api private + */ + +Update.prototype.run = function() { + var tasks = arguments.length ? arguments : ['default']; + + process.nextTick(function () { + this.start.apply(this, tasks); + }.bind(this)); +}; + +/** + * Re-run the specified task(s) when a file changes. + * + * ```js + * app.task('watch', function() { + * app.watch('docs/*.md', ['docs']); + * }); + * ``` + * + * @param {String|Array} `glob` Filepaths or glob patterns. + * @param {Function} `fn` Task(s) to watch. + * @api public + */ + +Update.prototype.watch = function(glob, opts, fn) { + if (Array.isArray(opts) || typeof opts === 'function') { + fn = opts; opts = null; + } + + if (!Array.isArray(fn)) vfs.watch(glob, opts, fn); + return vfs.watch(glob, opts, function () { + this.start.apply(this, fn); + }.bind(this)); +}; + +/** + * Expose the `Update` class on `update.Update` + */ + +Update.prototype.Update = Update; + +/** + * Expose our instance of `update` + */ + +module.exports = new Update(); From 7f8fef259c3a76ae9feeea226ced80e1a621c66c Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 25 Apr 2015 12:10:27 -0400 Subject: [PATCH 059/274] adds docs for creating an updater --- recipes/create-an-updater.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 recipes/create-an-updater.md diff --git a/recipes/create-an-updater.md b/recipes/create-an-updater.md new file mode 100644 index 0000000..1088282 --- /dev/null +++ b/recipes/create-an-updater.md @@ -0,0 +1,24 @@ +# Create an updater + +> Updaters follow the same signature as gulp plugins + +**Example** + +```js +function foo(options) { + return through.obj(function (file, enc, cb) { + var str = file.contents.toString(); + // do stuff + file.contents = new Buffer(file.contents); + this.push(file); + cb(); + }); +} +``` + +## Publish your updater + +1. Name your project following the convention: `updater-*` +2. Don't use dots in the name (e.g `.js`) +3. Make sure you add `updater` to the keywords in package.json +4. Tweet about your updater! \ No newline at end of file From b731367be03f980c20eb5891effd43f04fbda920 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 25 Apr 2015 12:10:47 -0400 Subject: [PATCH 060/274] clean out most of the junk in lib, we'll start over --- plugins/banners.js | 43 ------------- plugins/bower.js | 25 -------- plugins/dotfiles.js | 21 ------- plugins/editorconfig.js | 24 -------- plugins/gitignore.js | 55 ----------------- plugins/helpers/dependencies.js | 5 -- plugins/helpers/devDependencies.js | 42 ------------- plugins/helpers/files.js | 48 --------------- plugins/helpers/index.js | 69 --------------------- plugins/helpers/keys.js | 36 ----------- plugins/helpers/license.js | 22 ------- plugins/helpers/licenses.js | 14 ----- plugins/helpers/repo.js | 12 ---- plugins/helpers/scripts.js | 15 ----- plugins/index.js | 18 ------ plugins/jshint.js | 61 ------------------ plugins/license.js | 37 ----------- plugins/pkg.js | 43 ------------- plugins/readme/verbmd.js | 99 ------------------------------ plugins/tests.js | 35 ----------- plugins/travis.js | 35 ----------- templates/editorconfig.js | 31 ---------- templates/gitattributes.js | 12 ---- templates/travis.js | 10 --- transforms/index.js | 3 - transforms/start.js | 19 ------ 26 files changed, 834 deletions(-) delete mode 100644 plugins/banners.js delete mode 100644 plugins/bower.js delete mode 100644 plugins/dotfiles.js delete mode 100644 plugins/editorconfig.js delete mode 100644 plugins/gitignore.js delete mode 100644 plugins/helpers/dependencies.js delete mode 100644 plugins/helpers/devDependencies.js delete mode 100644 plugins/helpers/files.js delete mode 100644 plugins/helpers/index.js delete mode 100644 plugins/helpers/keys.js delete mode 100644 plugins/helpers/license.js delete mode 100644 plugins/helpers/licenses.js delete mode 100644 plugins/helpers/repo.js delete mode 100644 plugins/helpers/scripts.js delete mode 100644 plugins/index.js delete mode 100644 plugins/jshint.js delete mode 100644 plugins/license.js delete mode 100644 plugins/pkg.js delete mode 100644 plugins/readme/verbmd.js delete mode 100644 plugins/tests.js delete mode 100644 plugins/travis.js delete mode 100644 templates/editorconfig.js delete mode 100644 templates/gitattributes.js delete mode 100644 templates/travis.js delete mode 100644 transforms/index.js delete mode 100644 transforms/start.js diff --git a/plugins/banners.js b/plugins/banners.js deleted file mode 100644 index 853ad0c..0000000 --- a/plugins/banners.js +++ /dev/null @@ -1,43 +0,0 @@ -'use strict'; - -var through = require('through2'); -var debug = require('debug')('update:plugin'); -var parse = require('parse-copyright'); -var banner = require('update-banner'); -var hasBanner = require('has-banner'); -var merge = require('merge-deep'); -var logger = require('../lib/logging'); - -module.exports = function(verb) { - debug('banners plugin'); - return function(options) { - var opts = merge({}, options); - - return through.obj(function (file, enc, cb) { - debug('banners plugin file: %j', file.path); - - if (file.isNull() || !file.isBuffer()) { - this.push(file); - return cb(); - } - - var str = file.contents.toString(); - var log = logger(str); - - // TODO: implement better logic for ignoring banners that shouldn't - // be stripped - if ((hasBanner(str) || opts.banner) && !/@attribution/.test(str)) { - var copyright = parse(str); - if (copyright && copyright.length) { - file.data.copyright = copyright[0]; - } - str = banner(str, file.data); - log.success(str, 'updated banners in', file.relative); - } - - file.contents = new Buffer(str); - this.push(file); - cb(); - }); - }; -}; diff --git a/plugins/bower.js b/plugins/bower.js deleted file mode 100644 index acb60bd..0000000 --- a/plugins/bower.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -/** - * Module dependencies - */ - -var gutil = require('gulp-util'); -var through = require('through2'); -var sync = require('sync-pkg'); - -module.exports = function bower_(verb) { - return function() { - return through.obj(function (file, enc, cb) { - this.push(file); - return cb(); - }, function (cb) { - var file = new gutil.File({path: 'bower.json'}); - var data = JSON.stringify(sync(verb.env), null, 2); - - file.contents = new Buffer(data); - this.push(file); - cb(); - }); - }; -}; diff --git a/plugins/dotfiles.js b/plugins/dotfiles.js deleted file mode 100644 index fbc3779..0000000 --- a/plugins/dotfiles.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -var debug = require('debug')('update:plugin'); -var gutil = require('gulp-util'); -var through = require('through2'); - -module.exports = function(verb) { - debug('dotfiles plugin'); - return function() { - return through.obj(function (file, enc, cb) { - this.push(file); - cb(); - }, function (cb) { - var file = new gutil.File({path: '.gitattributes'}); - file.contents = new Buffer(require('../templates/gitattributes')); - this.push(file); - cb(); - }); - }; -}; - diff --git a/plugins/editorconfig.js b/plugins/editorconfig.js deleted file mode 100644 index c9539be..0000000 --- a/plugins/editorconfig.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict'; - -var debug = require('debug')('update:plugin'); -var gutil = require('gulp-util'); -var through = require('through2'); -// var ec = require('editorconfig'); - -module.exports = function editorconfig_(verb) { - debug('editorconfig plugin'); - return function() { - return through.obj(function (file, enc, cb) { - this.push(file); - cb(); - }, function (cb) { - var file = new gutil.File({ - contents: new Buffer(require('../templates/editorconfig')), - path: '.editorconfig' - }); - this.push(file); - cb(); - }); - }; -}; - diff --git a/plugins/gitignore.js b/plugins/gitignore.js deleted file mode 100644 index dea572f..0000000 --- a/plugins/gitignore.js +++ /dev/null @@ -1,55 +0,0 @@ -'use strict'; - -var debug = require('debug')('update:plugin'); -var gutil = require('gulp-util'); -var difference = require('arr-diff'); -var through = require('through2'); -var utils = require('../lib/utils'); -var logger = require('../lib/logging'); - -module.exports = function gitignore_(verb) { - debug('gitignore plugin'); - return function() { - return through.obj(function (file, enc, cb) { - if (file.isNull() || !file.isBuffer()) { - this.push(file); - return cb(); - } - - try { - if (utils.contains(file.path, '.gitignore')) { - // just get these started - var patterns = ['*.DS_Store']; - var str = file.contents.toString(); - var log = logger(str); - - var lines = str.split('\n').map(trim); - var diff = difference(patterns, lines); - - if (lines[0].charAt(0) === '#') { - var comment = lines.slice(0, 1); - var rest = lines.slice(1); - lines = comment.concat(diff).concat(rest); - } else { - lines = diff.concat(lines); - } - - str = lines.join('\n'); - log.success(str, 'updated patterns in', file.relative); - file.contents = new Buffer(str); - } - } catch (err) { - this.emit('error', new gutil.PluginError('update:gitignore', err)); - return cb(); - } - - this.push(file); - cb(); - }); - }; -}; - - -function trim(str) { - return str.trim(); -} diff --git a/plugins/helpers/dependencies.js b/plugins/helpers/dependencies.js deleted file mode 100644 index 961d595..0000000 --- a/plugins/helpers/dependencies.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict'; - -/** - * PLACEHOLDER - */ diff --git a/plugins/helpers/devDependencies.js b/plugins/helpers/devDependencies.js deleted file mode 100644 index 4b5ccb4..0000000 --- a/plugins/helpers/devDependencies.js +++ /dev/null @@ -1,42 +0,0 @@ -'use strict'; - -var debug = require('debug')('update:helpers'); - -/** - * Remove the old verb - */ - -exports.removeVerb = function(pkg) { - debug('removeVerb'); - - if (pkg && pkg.devDependencies && pkg.devDependencies['verb-tag-jscomments']) { - delete pkg.devDependencies['verb-tag-jscomments']; - delete pkg.devDependencies.verb; - } - - if (pkg && pkg.devDependencies && pkg.devDependencies.verb) { - var version = stripNonNumber(pkg.devDependencies.verb).split('.'); - if (version[1] < 3) { - delete pkg.devDependencies.verb; - } else if (version[1] >= 3) { - pkg.devDependencies.verb = '^0.5.0'; - } - } - return pkg; -}; - -function stripNonNumber(str) { - return str.replace(/^[\D\s]+/, ''); -} - -/** - * Remove `should` when it's not being used - * in tests. - */ - -exports.removeShould = function(pkg) { - if (pkg && pkg.devDependencies) { - delete pkg.devDependencies.should; - } - return pkg; -}; diff --git a/plugins/helpers/files.js b/plugins/helpers/files.js deleted file mode 100644 index fca67d8..0000000 --- a/plugins/helpers/files.js +++ /dev/null @@ -1,48 +0,0 @@ -'use strict'; - -var mm = require('micromatch'); -var debug = require('debug')('update:helpers'); -var unique = require('array-unique'); -var utils = require('../../lib/utils'); - -/** - * Default paths to look for to populate the `files` - * property of package.json - */ - -var defaults = ['index.js', 'cli.js', 'lib/', 'bin/', 'completion/', 'templates/', 'app/']; - -/** - * Guess at which files should be included in package.json `files`. - * This isn't meant to be comprehensive, it's intended to tip you - * off that you need to fill in the field. - */ - -module.exports = function(patterns, options) { - debug('helpers:files %j', patterns); - patterns = patterns ? ensureSlash(patterns) : []; - return function (files) { - return unique(mm(files, defaults, options).concat(patterns)); - }; -}; - -/** - * Format paths so that directories end in a slash - */ - -function ensureSlash(files) { - - var res = [], i = 0; - var len; - if (files && (len = files.length)) { - while (len--) { - var file = files[i++]; - if (typeof file !== 'string') { - continue; - } - var stat = file && utils.tryStat(file); - res.push(utils.trailingSlash(file, stat)); - } - } - return res; -} diff --git a/plugins/helpers/index.js b/plugins/helpers/index.js deleted file mode 100644 index c371998..0000000 --- a/plugins/helpers/index.js +++ /dev/null @@ -1,69 +0,0 @@ -'use strict'; - -/** - * Module dependencies - */ - -var helpers = require('export-files')(__dirname); -var sortObj = require('sort-object'); -var omitEmpty = require('omit-empty'); -var update = require('update-package'); -var diff = require('arr-diff'); - -/** - * Local dependencies - */ - -var logger = require('../../lib/logging'); - -/** - * Normalize fields in package.json - */ - -module.exports = function (file, verb) { - var str = file.contents.toString(); - var log = logger(str); - - // parse the string - var obj = JSON.parse(str); - - // run updates on package.json fields - var pkg = update(obj); - - // remove old verb from deps - pkg = helpers.devDependencies.removeVerb(pkg); - - // populate the `files` property. Not exposed on options - // currently, but can be if someone suggests a good option - var matched = require('./files')(pkg.files); - var stats = verb.get('stats'); - var files = stats.files; - - pkg.files = matched(files); - - // fix the scripts property - pkg = helpers.scripts.fixMocha(pkg); - - // if should doesn't exist, remove it - if (!verb.get('data.hasShould')) { - // pkg = helpers.devDependencies.removeShould(pkg); - } - - // fix the `license` and `licenses` properties - pkg = helpers.licenses.normalize(pkg); - pkg = helpers.license.normalize(pkg); - pkg = omitEmpty(pkg); - - // if (stats.unknownHelpers.length) { - - // // console.log(stats.unknownHelpers) - // } - - var keys = helpers.keys.concat(diff(Object.keys(pkg), helpers.keys)); - var sorted = sortObj(pkg, keys); - var res = JSON.stringify(sorted, null, 2); - - log.results(res, 'updated properties in', file.relative); - file.contents = new Buffer(res); - return file; -}; diff --git a/plugins/helpers/keys.js b/plugins/helpers/keys.js deleted file mode 100644 index 6c42d3f..0000000 --- a/plugins/helpers/keys.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * This is not comprehensive, it's just a start - * and can be customized by the user. - * - * Order of preference. `keywords` goes last in - * order to keep most keys on the same screen when - * reviewing properties - */ - -module.exports = [ - 'name', - 'description', - 'version', - 'homepage', - 'author', - 'authors', - 'contributors', - 'maintainers', - 'repository', - 'bugs', - 'license', - 'licenses', - 'files', - 'browser', - 'main', - 'private', - 'preferGlobal', - 'bin', - 'engineStrict', - 'engines', - 'scripts', - 'dependencies', - 'devDependencies', - 'keywords', - 'verb' -]; diff --git a/plugins/helpers/license.js b/plugins/helpers/license.js deleted file mode 100644 index b034e55..0000000 --- a/plugins/helpers/license.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; - -var typeOf = require('kind-of'); -var debug = require('debug')('update:helpers'); -var utils = require('../../lib/utils'); - -exports.normalize = function license_(pkg) { - debug('helpers:license %j', pkg); - var license = pkg.license; - - if (!license) { return pkg; } - var type = typeOf(license); - - if (type === 'string') { - license = {type: license}; - } - - if (type === 'object' && license.url && utils.contains(license.url, 'LICENSE-MIT')) { - license.url = license.url.split('LICENSE-MIT').join('LICENSE'); - } - return pkg; -}; diff --git a/plugins/helpers/licenses.js b/plugins/helpers/licenses.js deleted file mode 100644 index a020cd5..0000000 --- a/plugins/helpers/licenses.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -var debug = require('debug')('update:helpers'); - -exports.normalize = function licenses_(pkg) { - debug('helpers:licenses %j', pkg); - var licenses = pkg.licenses; - - if (Array.isArray(licenses) && licenses.length === 1) { - pkg.license = pkg.licenses[0]; - delete pkg.licenses; - } - return pkg; -}; diff --git a/plugins/helpers/repo.js b/plugins/helpers/repo.js deleted file mode 100644 index 0227b7f..0000000 --- a/plugins/helpers/repo.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -var utils = require('../../lib/utils'); - -/** - * Get a `username/name` github url string. - * - * @param {String} `url` - * @return {String} - */ - -exports.repo = utils.repo; diff --git a/plugins/helpers/scripts.js b/plugins/helpers/scripts.js deleted file mode 100644 index ee72dad..0000000 --- a/plugins/helpers/scripts.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -var debug = require('debug')('update:helpers'); - -/** - * Remove the old verb - */ - -exports.fixMocha = function(pkg) { - debug('helpers:fixMocha %j', pkg); - if (pkg.scripts && pkg.scripts.test && /mocha --?r/i.test(pkg.scripts.test)) { - pkg.scripts.test = 'mocha'; - } - return pkg; -}; diff --git a/plugins/index.js b/plugins/index.js deleted file mode 100644 index 605dc35..0000000 --- a/plugins/index.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -// module.exports = require('export-files')(__dirname); - -module.exports = function (verb) { - return { - banners : require('./banners.js')(verb), - bower : require('./bower.js')(verb), - dotfiles : require('./dotfiles.js')(verb), - editorconfig : require('./editorconfig.js')(verb), - gitignore : require('./gitignore.js')(verb), - jshint : require('./jshint.js')(verb), - license : require('./license.js')(verb), - pkg : require('./pkg.js')(verb), - tests : require('./tests.js')(verb), - travis : require('./travis.js')(verb) - }; -}; diff --git a/plugins/jshint.js b/plugins/jshint.js deleted file mode 100644 index c5d2ac5..0000000 --- a/plugins/jshint.js +++ /dev/null @@ -1,61 +0,0 @@ -'use strict'; - -var debug = require('debug')('update:plugin'); -var gutil = require('gulp-util'); -var through = require('through2'); -var merge = require('merge-deep'); -var sortObj = require('sort-object'); -var logger = require('../lib/logging'); -var utils = require('../lib/utils'); - -module.exports = function jshint_(verb) { - debug('jshint plugin'); - return function() { - return through.obj(function (file, enc, cb) { - if (file.isNull() || !file.isBuffer()) { - this.push(file); - return cb(); - } - - try { - if (utils.contains(file.path, 'jshint')) { - var str = file.contents.toString(); - var obj = JSON.parse(str); - var log = logger(str); - - delete obj.globals; - - // TODO: move to user preferences - obj = sortObj(merge(obj, { - "asi": false, - "boss": true, - "curly": true, - "eqeqeq": true, - "eqnull": true, - "esnext": true, - "immed": true, - "latedef": false, - "laxcomma": false, - "mocha": true, - "newcap": true, - "noarg": true, - "node": true, - "sub": true, - "undef": true, - "unused": true - })); - var res = JSON.stringify(obj, null, 2); - log.success(res, 'updated properties in', file.relative); - file.contents = new Buffer(res); - } - } catch (err) { - console.log('jshint:', err); - this.emit('error', new gutil.PluginError('update:jshint', err)); - return cb(); - } - - this.push(file); - cb(); - }); - }; -}; diff --git a/plugins/license.js b/plugins/license.js deleted file mode 100644 index 5ef5e43..0000000 --- a/plugins/license.js +++ /dev/null @@ -1,37 +0,0 @@ -'use strict'; - -var debug = require('debug')('update:plugin'); -var gutil = require('gulp-util'); -var through = require('through2'); -var license = require('update-license'); -// var utils = require('../lib/utils'); -// var logger = require('../lib/logging'); - -module.exports = function license_(verb) { - debug('license plugin'); - return function() { - return through.obj(function (file, enc, cb) { - if (file.isNull() || !file.isBuffer()) { - this.push(file); - return cb(); - } - - try { - if (utils.contains(file.path, 'LICENSE')) { - var str = file.contents.toString(); - var log = logger(str); - str = license(str); - log.success(str, 'updated patterns in', file.relative); - file.contents = new Buffer(str); - } - } catch (err) { - console.log(err); - this.emit('error', new gutil.PluginError('[update] license plugin:', err)); - return cb(); - } - - this.push(file); - cb(); - }); - }; -}; diff --git a/plugins/pkg.js b/plugins/pkg.js deleted file mode 100644 index 138c737..0000000 --- a/plugins/pkg.js +++ /dev/null @@ -1,43 +0,0 @@ -'use strict'; - -/** - * Module dependencies - */ - -var debug = require('debug')('update:plugin'); -var path = require('path'); -var gutil = require('gulp-util'); -var through = require('through2'); - -/** - * Local dependencies - */ - -var normalize = require('./helpers/'); - -/** - * virtually everything in this file is a temporary - * hack until I get update-package straightened out. - */ - -module.exports = function pkg_(verb) { - debug('pkg plugin'); - - return function() { - return through.obj(function (file, enc, cb) { - if (file.isNull() || !file.isBuffer() || path.basename(file.path) !== 'package.json') { - this.push(file); - return cb(); - } - try { - file = normalize(file, verb); - } catch (err) { - console.log(err); - this.emit('error', new gutil.PluginError('update:pkg', err)); - return cb(); - } - this.push(file); - cb(); - }); - }; -}; diff --git a/plugins/readme/verbmd.js b/plugins/readme/verbmd.js deleted file mode 100644 index f589e4f..0000000 --- a/plugins/readme/verbmd.js +++ /dev/null @@ -1,99 +0,0 @@ -'use strict'; - -var debug = require('debug')('update:readme'); -var unique = require('array-unique'); - -function unknownHelpers(str, verb) { - debug('unknownHelpers'); - var helpers = Object.keys(verb._.helpers); - var async = Object.keys(verb._.asyncHelpers); - var keys = unique(helpers.concat(async)).sort(); - - var re = /\{%=\s*(\w+)\(([\s\S]+?)%}/; - var match, res = []; - var orig = str; - - while (match = re.exec(str)) { - str = str.replace(match[0], ''); - var name = match[1]; - if (keys.indexOf(name) === -1) { - res.push(name); - } - } - - verb.set('stats.unknownHelpers', res); - return orig; -} - -function addTravisBadge(str, stats) { - debug('addTravisBadge'); - - if (stats && stats.hasTravis && !/badge\(.travis/.test(str)) { - str = str.split('{%= badge("fury") %}').join('{%= badge("fury") %} {%= badge("travis") %}'); - } - return str; -} - -function fixInstall(str) { - debug('fixInstall'); - var re = /## Install[\s\n]+{%= include/g; - str = str.replace(re, '{%= include'); - str = str.split('{%= include("install") %}').join('{%= include("install-npm", {save: true}) %}'); - return str; -} - -function installGlobal(str, verb) { - // TODO: if `preferGlobal` or `bin` are in package.json, add - // {%= include("install-global") %} - return str; -} - -var authors = [ - '{%= include("authors", {', - ' authors: [', - ' {name: \'Jon Schlinkert\', username: \'jonschlinkert\'},', - ' {name: \'Brian Woodward\', username: \'doowb\'}', - ' ]', - '}) %}' -].join('\n'); - - -function fixHelpers(str) { - str = str.split('{%= jscomments').join('{%= apidocs'); - str = str.split('{%= contrib("jon") %}').join('{%= include("author") %}'); - str = str.split('{%= comments').join('{%= apidocs'); - str = str.split('{%= contrib("contributing") %}').join('{%= include("contributing") %}'); - str = str.split('{%= contrib("author") %}').join('{%= include("author") %}'); - str = str.split('{%= contrib("authors") %}').join(authors); - return str; -} - -function matchHelper(name, args) { - var str = '{%=\\s*' + name + (args || '([\\s\\S]+?)') + '\\s*%}'; - return new RegExp(str); -} - -function fixCopyright(str, year) { - if (!year) { - return str; - } - var segs = str.split('{%= copyright() %}'); - return segs.join('{%= copyright({year: ' + (year || '2015') + '}) %}'); -} - -function runningTests(str) { - var re = /## Run tests\n\n```bash\nnpm test\n```/; - return str.replace(re, '## Running tests\n{%= include("tests") %}'); -} - -module.exports = function (str, verb) { - var stats = verb.get('stats'); - - str = addTravisBadge(str, stats); - str = runningTests(str); - str = fixInstall(str); - str = fixHelpers(str); - - unknownHelpers(str, verb); - return str; -}; diff --git a/plugins/tests.js b/plugins/tests.js deleted file mode 100644 index 6fb99ce..0000000 --- a/plugins/tests.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict'; - -var debug = require('debug')('update:plugin'); -var gutil = require('gulp-util'); -var through = require('through2'); -var should = require('../lib/tests/should'); -var paths = require('../lib/tests/paths'); - -module.exports = function tests_(verb) { - debug('tests plugin'); - return function () { - return through.obj(function (file, enc, cb) { - if (file.isNull() || !file.isBuffer()) { - this.push(file); - return cb(); - } - - try { - if (verb.file('test')) { - // fix `should` related info - file = should(file, verb); - // fix paths - file = paths(file, verb); - } - } catch (err) { - console.error('update:tests', err); - this.emit('error', new gutil.PluginError('update:tests', err)); - return cb(); - } - - this.push(file); - cb(); - }); - }; -}; diff --git a/plugins/travis.js b/plugins/travis.js deleted file mode 100644 index 9222c24..0000000 --- a/plugins/travis.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict'; - -var debug = require('debug')('update:plugin'); -var gutil = require('gulp-util'); -var through = require('through2'); -var logger = require('../lib/logging'); - -/** - * this plugin adds `.travis.yml` using a template - * if the file is missing but tests exist. - */ - -module.exports = function travis_(verb) { - debug('travis plugin'); - return function () { - return through.obj(function (file, enc, cb) { - this.push(file); - cb(); - }, function (cb) { - - if (!verb.exists('.travis.yml') && verb.exists('test*')) { - var log = logger({nocompare: true}); - var tmpl = require('../templates/travis'); - var file = new gutil.File({ - contents: new Buffer(tmpl), - path: '.travis.yml' - }); - - this.push(file); - log.success(true, 'writing', file.relative); - } - cb(); - }); - }; -}; diff --git a/templates/editorconfig.js b/templates/editorconfig.js deleted file mode 100644 index 8f3815e..0000000 --- a/templates/editorconfig.js +++ /dev/null @@ -1,31 +0,0 @@ -module.exports = [ - '# http://editorconfig.org', - 'root = true', - '', - '[*]', - 'indent_style = space', - 'end_of_line = lf', - 'charset = utf-8', - 'indent_size = 2', - 'trim_trailing_whitespace = true', - 'insert_final_newline = true', - '', - '[*.json]', - 'indent_style = space', - 'indent_size = 2', - '', - '[*.yml]', - 'indent_style = space', - 'indent_size = 2', - '', - '[*.md]', - 'indent_style = space', - 'indent_size = 2', - 'trim_trailing_whitespace = false', - '', - '[test/fixtures/*]', - 'trim_trailing_whitespace = false', - 'insert_final_newline = false' -].join('\n'); - - diff --git a/templates/gitattributes.js b/templates/gitattributes.js deleted file mode 100644 index c130085..0000000 --- a/templates/gitattributes.js +++ /dev/null @@ -1,12 +0,0 @@ -module.exports = [ - '# Enforce Unix newlines', - '* text eol=lf', - '', - '# binaries', - '*.ai binary', - '*.psd binary', - '*.jpg binary', - '*.gif binary', - '*.png binary', - '*.jpeg binary' -].join('\n'); diff --git a/templates/travis.js b/templates/travis.js deleted file mode 100644 index 65633ba..0000000 --- a/templates/travis.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = [ - 'sudo: false', - 'language: node_js', - 'node_js:', - ' - "0.10"', - ' - "0.12"', - ' - "iojs"', - 'git:', - ' depth: 10' -].join('\n'); diff --git a/transforms/index.js b/transforms/index.js deleted file mode 100644 index 874ea55..0000000 --- a/transforms/index.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -module.exports = require('export-files')(__dirname); diff --git a/transforms/start.js b/transforms/start.js deleted file mode 100644 index 1b8e356..0000000 --- a/transforms/start.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; - -var debug = require('debug')('update:transform'); -var verbmd = require('../plugins/readme/verbmd'); -var utils = require('../lib/utils'); - -module.exports = function (verb) { - debug('transform: start'); - - verb.set('stats.hasTravis', verb.exists('.travis.yml')); - var verbfile = verb.files('.verb*'); - - if (verbfile.length) { - var fp = verbfile[0]; - var str = utils.antimatter(fp); - str = verbmd(str, verb); - utils.writeFile(fp, str); - } -}; From 27f59fabdc286803b5adec20d116d64a08ab7078 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 25 Apr 2015 12:11:57 -0400 Subject: [PATCH 061/274] boilerplate: - loaders - middleware - plugins - helpers - transforms - utils --- lib/helpers/async.js | 11 ++ lib/init.js | 25 +++++ lib/loaders/base.js | 23 ++++ lib/loaders/index.js | 4 + lib/loaders/task.js | 12 +++ lib/middleware/cwd.js | 13 +++ lib/middleware/dest.js | 11 ++ lib/middleware/dotfiles.js | 28 +++++ lib/middleware/index.js | 3 + lib/middleware/props.js | 13 +++ lib/middleware/src.js | 22 ++++ lib/plugins/dest.js | 44 ++++++++ lib/plugins/index.js | 3 + lib/plugins/init.js | 79 ++++++++++++++ lib/plugins/process.js | 40 +++++++ lib/plugins/render.js | 76 +++++++++++++ lib/session.js | 7 ++ lib/stack.js | 81 ++++++++++++++ lib/tests/paths.js | 21 ---- lib/tests/should.js | 54 ---------- lib/transforms/env/index.js | 3 + lib/transforms/index.js | 3 + lib/transforms/init/argv.js | 9 ++ lib/transforms/init/config.js | 52 +++++++++ lib/transforms/init/create.js | 12 +++ lib/transforms/init/cwd.js | 28 +++++ lib/transforms/init/data.js | 15 +++ lib/transforms/init/engines.js | 11 ++ lib/transforms/init/helpers.js | 11 ++ lib/transforms/init/index.js | 3 + lib/transforms/init/load.js | 32 ++++++ lib/transforms/init/loaders.js | 12 +++ lib/transforms/init/middleware.js | 47 ++++++++ lib/transforms/init/options.js | 21 ++++ lib/transforms/init/runner.js | 14 +++ lib/transforms/session.js | 11 ++ lib/transforms/updaters/index.js | 7 ++ lib/transforms/updaters/updaters.js | 27 +++++ lib/utils/completion.js | 18 ++++ lib/utils/error.js | 159 ++++++++++++++++++++++++++++ lib/utils/index.js | 3 + lib/utils/task-tree.js | 18 ++++ 42 files changed, 1011 insertions(+), 75 deletions(-) create mode 100644 lib/helpers/async.js create mode 100644 lib/init.js create mode 100644 lib/loaders/base.js create mode 100644 lib/loaders/index.js create mode 100644 lib/loaders/task.js create mode 100644 lib/middleware/cwd.js create mode 100644 lib/middleware/dest.js create mode 100644 lib/middleware/dotfiles.js create mode 100644 lib/middleware/index.js create mode 100644 lib/middleware/props.js create mode 100644 lib/middleware/src.js create mode 100644 lib/plugins/dest.js create mode 100644 lib/plugins/index.js create mode 100644 lib/plugins/init.js create mode 100644 lib/plugins/process.js create mode 100644 lib/plugins/render.js create mode 100644 lib/session.js create mode 100644 lib/stack.js delete mode 100644 lib/tests/paths.js delete mode 100644 lib/tests/should.js create mode 100644 lib/transforms/env/index.js create mode 100644 lib/transforms/index.js create mode 100644 lib/transforms/init/argv.js create mode 100644 lib/transforms/init/config.js create mode 100644 lib/transforms/init/create.js create mode 100644 lib/transforms/init/cwd.js create mode 100644 lib/transforms/init/data.js create mode 100644 lib/transforms/init/engines.js create mode 100644 lib/transforms/init/helpers.js create mode 100644 lib/transforms/init/index.js create mode 100644 lib/transforms/init/load.js create mode 100644 lib/transforms/init/loaders.js create mode 100644 lib/transforms/init/middleware.js create mode 100644 lib/transforms/init/options.js create mode 100644 lib/transforms/init/runner.js create mode 100644 lib/transforms/session.js create mode 100644 lib/transforms/updaters/index.js create mode 100644 lib/transforms/updaters/updaters.js create mode 100644 lib/utils/completion.js create mode 100644 lib/utils/error.js create mode 100644 lib/utils/index.js create mode 100644 lib/utils/task-tree.js diff --git a/lib/helpers/async.js b/lib/helpers/async.js new file mode 100644 index 0000000..6a88604 --- /dev/null +++ b/lib/helpers/async.js @@ -0,0 +1,11 @@ +'use strict'; + +var helper = require('async-helper-base'); + +/** + * Transform for loading default async helpers + */ + +module.exports = function async_(app) { + app.asyncHelper('include', helper('include')); +}; diff --git a/lib/init.js b/lib/init.js new file mode 100644 index 0000000..ae0f48a --- /dev/null +++ b/lib/init.js @@ -0,0 +1,25 @@ +'use strict'; + +var transforms = require('./transforms'); +var updaters = transforms.updaters; +var init = transforms.init; +var env = transforms.env; + +/** + * Load initialization transforms: + * | runner + * | loaders + * | create + * | options + * | middleware + * | plugins + * | load + * | engines + * | helpers (load last) + */ + +module.exports = function init_(app) { + app.transform('updaters', updaters); + app.transform('session', init.session); + app.transform('store', init.store); +}; diff --git a/lib/loaders/base.js b/lib/loaders/base.js new file mode 100644 index 0000000..155a87d --- /dev/null +++ b/lib/loaders/base.js @@ -0,0 +1,23 @@ +'use strict'; + +var mapFiles = require('map-files'); +var utils = require('../utils'); + +module.exports = function base_(args) { + var hasOpts = typeof args[1] === 'object'; + if (hasOpts) args[1].renameKey = utils.renameKey; + if (hasOpts && args[1].hasOwnProperty('content')) { + return normalize(args); + } + return mapFiles.apply(mapFiles, utils.arrayify(args)); +}; + +function normalize(args) { + var fp = args[0]; + var o = args[1]; + var name = utils.basename(fp); + o.path = fp; + var res = {}; + res[name] = o; + return res; +} diff --git a/lib/loaders/index.js b/lib/loaders/index.js new file mode 100644 index 0000000..f62f793 --- /dev/null +++ b/lib/loaders/index.js @@ -0,0 +1,4 @@ +'use strict'; + +module.exports = require('export-files')(__dirname); + diff --git a/lib/loaders/task.js b/lib/loaders/task.js new file mode 100644 index 0000000..3e494bd --- /dev/null +++ b/lib/loaders/task.js @@ -0,0 +1,12 @@ +'use strict'; + +var path = require('path'); + +module.exports = function task_(file) { + var name = path.basename(file.path, path.extname(file.path)); + file.content = file.contents.toString(); + file.id = name; + var template = {}; + template[name] = file; + return template; +}; diff --git a/lib/middleware/cwd.js b/lib/middleware/cwd.js new file mode 100644 index 0000000..b71eec4 --- /dev/null +++ b/lib/middleware/cwd.js @@ -0,0 +1,13 @@ +'use strict'; + +/** + * Prime the `file` object with properties that + * can be extended in plugins. + */ + +module.exports = function cwd_(app) { + return function (file, next) { + file.cwd = file.data.cwd || app.cwd || file.cwd || '.'; + next(); + }; +}; diff --git a/lib/middleware/dest.js b/lib/middleware/dest.js new file mode 100644 index 0000000..83730b6 --- /dev/null +++ b/lib/middleware/dest.js @@ -0,0 +1,11 @@ +'use strict'; + +/** + * Prime `file.data.dest`. The rest of the path logic for + * `dest` is handled in the pipeline by the `dest` plugin. + */ + +module.exports = function dest_(file, next) { + file.data.dest = file.data.dest || {}; + next(); +}; diff --git a/lib/middleware/dotfiles.js b/lib/middleware/dotfiles.js new file mode 100644 index 0000000..64b686e --- /dev/null +++ b/lib/middleware/dotfiles.js @@ -0,0 +1,28 @@ +'use strict'; + +var path = require('path'); + +/** + * Rename dotfile templates. + */ + +module.exports = function (app) { + var dotfiles = app.disabled('dotfiles'); + + return function dotfiles_(file, next) { + if (dotfiles) return next(); + + dotfiles = app.disabled('dotfiles'); + if (dotfiles) return next(); + + if (file.path.indexOf('templates/dotfiles') !== -1) { + var dirname = path.dirname(file.path); + var basename = path.basename(file.path); + if (basename[0] !== '.') { + basename = '.' + basename; + } + file.path = path.join(dirname, basename); + } + next(); + } +} diff --git a/lib/middleware/index.js b/lib/middleware/index.js new file mode 100644 index 0000000..874ea55 --- /dev/null +++ b/lib/middleware/index.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require('export-files')(__dirname); diff --git a/lib/middleware/props.js b/lib/middleware/props.js new file mode 100644 index 0000000..d97fdba --- /dev/null +++ b/lib/middleware/props.js @@ -0,0 +1,13 @@ +'use strict'; + +/** + * Prime the `file` object with properties that + * can be extended in plugins. + */ + +module.exports = function props_(file, next) { + file.options = file.options || {}; + file.locals = file.locals || {}; + file.data = file.data || {}; + next(); +}; diff --git a/lib/middleware/src.js b/lib/middleware/src.js new file mode 100644 index 0000000..2bc15d8 --- /dev/null +++ b/lib/middleware/src.js @@ -0,0 +1,22 @@ +'use strict'; + +var path = require('path'); + +/** + * Set properties on `file.data.src` to use in plugins, + * other middleware, helpers and templates. + */ + +module.exports = function src_(file, next) { + var orig = file.options.originalPath; + var parsed = path.parse(orig); + + file.data.src = file.data.src || {}; + + file.data.src.path = orig; + file.data.src.dirname = parsed.dir; + file.data.src.filename = parsed.name; + file.data.src.basename = parsed.base; + file.data.src.extname = parsed.ext; + next(); +}; diff --git a/lib/plugins/dest.js b/lib/plugins/dest.js new file mode 100644 index 0000000..a010ced --- /dev/null +++ b/lib/plugins/dest.js @@ -0,0 +1,44 @@ +'use strict'; + +/** + * Module dependencies. + */ + +var path = require('path'); +var through = require('through2'); +var utils = require('../utils'); + +/** + * Add dest properties to `file.data` + */ + +module.exports = function dest_(destDir) { + return through.obj(function (file, enc, cb) { + if (file.isNull()) { + this.push(file); + return cb(); + } + if (file.isStream()) { + this.emit('error', new utils.PluginError('update-init:', 'Streaming is not supported.')); + return cb(); + } + + try { + var dest = file.data.dest || {}; + dest.relative = file.relative; + dest.dirname = file.dest || path.dirname(file.path); + dest.extname = path.extname(file.relative); + dest.basename = path.basename(file.relative); + dest.filename = utils.basename(dest.basename, dest.extname); + dest.path = path.join(dest.dirname, dest.basename); + + file.data.dest = dest; + this.push(file); + return cb(); + } catch (err) { + console.error(err); + this.emit('error', new utils.PluginError('generate-dest:', err)); + return cb(); + } + }); +}; diff --git a/lib/plugins/index.js b/lib/plugins/index.js new file mode 100644 index 0000000..874ea55 --- /dev/null +++ b/lib/plugins/index.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require('export-files')(__dirname); diff --git a/lib/plugins/init.js b/lib/plugins/init.js new file mode 100644 index 0000000..9de577c --- /dev/null +++ b/lib/plugins/init.js @@ -0,0 +1,79 @@ +'use strict'; + +var through = require('through2'); +var chalk = require('chalk'); +var File = require('vinyl'); +var utils = require('../utils'); + +/** + * Expose `render` plugin + */ + +module.exports = plugin('update'); + +function plugin(appname) { + return function init_() { + var app = this, + id = this.gettask(); + + // create a template type for vinyl files and give it a loader + if (!app.hasOwnProperty(id)) { + app.create(id, ['task']); + } + + return through.obj(function (file, enc, cb) { + if (file.isNull()) { + this.push(file); + return cb(); + } + + if (file.isStream()) { + this.emit('error', new utils.PluginError('update-init:', 'Streaming is not supported.')); + return cb(); + } + + // Convert vinyl file to app template and add to collection + app[id](file); + cb(); + }, function (cb) { + var plural = app.inflections[id]; + + // Convert template back to vinyl file and push into stream + pushToStream(app.views[plural], this, function (template) { + var file = new File({ + path: template.path + }); + + for (var key in template) { + if (template.hasOwnProperty(key)) { + file[key] = template[key]; + } + } + + file.contents = new Buffer(template.content); + app.handle('collections', file, handleError(file, 'collections')); + return file; + }); + cb(); + }); + } +} + +function pushToStream(collection, stream, fn) { + var i = 0; + for (var key in collection) { + if (collection.hasOwnProperty(key)) { + var file = collection[key]; + stream.push(fn ? fn(file, i++) : file); + } + } +} + +function handleError(template, method) { + return function (err) { + if (err) { + console.error(chalk.red('Error running ' + method + ' middleware for', template.path)); + console.error(chalk.red(err)); + } + }; +} diff --git a/lib/plugins/process.js b/lib/plugins/process.js new file mode 100644 index 0000000..aa053ad --- /dev/null +++ b/lib/plugins/process.js @@ -0,0 +1,40 @@ +'use strict'; + +var through = require('through2'); +var utils_ = require('utils'); +var utils = require('../utils'); + +/** + * Expose `process` plugin + */ + +module.exports = plugin('update'); + +function plugin(appname) { + return function (locals, options) { + var app = this; + return function (file, enc, cb) { + var collection = app.inflections[locals.id]; + var template = app.views[collection][file.id]; + + template.content = file.contents.toString(); + var context = utils_.extend({}, locals, file.locals); + + try { + var stream = this; + app.render(template, context, function (err, content) { + if (err) { + stream.emit('error', new utils.PluginError(appname + '-process', err)); + return cb(err); + } + file.contents = new Buffer(content); + stream.push(file); + cb(); + }); + } catch (err) { + this.emit('error', new utils.PluginError(appname + '-process', err)); + return cb(); + } + }; + }; +}; diff --git a/lib/plugins/render.js b/lib/plugins/render.js new file mode 100644 index 0000000..3a128e5 --- /dev/null +++ b/lib/plugins/render.js @@ -0,0 +1,76 @@ +'use strict'; + +/** + * Module dependencies. + */ + +var PluginError = require('../utils/error'); +var through = require('through2'); +var utils = require('utils'); + +/** + * Expose `render` plugin + */ + +module.exports = plugin('update'); + +function plugin(appname) { + return function render_(locals) { + var app = this; + locals = locals || {}; + locals.options = locals.options || {}; + + return through.obj(function (file, enc, cb) { + if (file.isNull()) { + this.push(file); + return cb(); + } + if (file.isStream()) { + this.emit('error', new PluginError(appname + '-render', 'Streaming is not supported.')); + return cb(); + } + + var template = app.getFile(file); + template.content = file.contents.toString(); + + locals = utils.merge({}, locals, file.locals); + locals.options = utils.merge({}, locals.options, app.options); + + if (norender(app, file.ext, file, locals)) { + this.push(file); + return cb(); + } + + try { + var stream = this; + template.render(locals, function(err, content) { + if (err) { + stream.emit('error', new PluginError(appname + '-render', err)); + cb(err); + return; + } + + file.contents = new Buffer(content); + stream.push(file); + cb(); + }); + + } catch (err) { + this.emit('error', new PluginError(appname + '-render', err)); + return cb(); + } + }); + } +} + +/** + * Push the `file` through if the user has specfied + * not to render it. + */ + +function norender(app, ext, file, locals) { + return !app.engines.hasOwnProperty(ext) + || app.enabled('norender') || app.disabled('render') + || file.norender === true || file.render === false + || locals.norender === true || locals.render === false; +} diff --git a/lib/session.js b/lib/session.js new file mode 100644 index 0000000..22fa4dd --- /dev/null +++ b/lib/session.js @@ -0,0 +1,7 @@ +'use strict'; + +/** + * Module dependencies. + */ + +module.exports = require('session-cache')('update'); diff --git a/lib/stack.js b/lib/stack.js new file mode 100644 index 0000000..31eac54 --- /dev/null +++ b/lib/stack.js @@ -0,0 +1,81 @@ +'use strict'; + +/** + * Module dependencies. + */ + +var through = require('through2'); +var sessionify = require('sessionify'); +var vfs = require('vinyl-fs'); +var es = require('event-stream'); +var _ = require('lodash'); + +/** + * Local dependencies + */ + +var plugins = require('./plugins'); +var session = require('./session'); + +/** + * Create a plugin stack to be run by `src` or `dest` + */ + +function createStack(app, plugins) { + var stack = []; + plugins.forEach(function (plugin) { + if (plugin == null) { + stack.push(through.obj()); + } else { + stack.push(plugin); + } + }); + var res = es.pipe.apply(es, stack); + return sessionify(res, session); +} + +/** + * Default `src` plugins to run. + */ + +exports.src = function (app, glob, opts) { + opts = _.extend({}, app.options, opts); + opts.cwd = app.get('updater.cwd'); + session.set('src', opts); + + return createStack(app, [ + vfs. src(glob, opts), + plugins.init.call(app, opts) + ]); +}; + +/** + * Default `template` plugins to run. + */ + +exports.templates = function (app, glob, opts) { + opts = _.extend({}, app.options, opts); + opts.cwd = app.get('updater.cwd') + '/templates'; + session.set('templates', opts); + + return createStack(app, [ + vfs.src(glob, opts), + plugins.init.call(app, opts) + ]); +}; + +/** + * Default `dest` plugins to run. + */ + +exports.dest = function (app, dest, options) { + var srcOpts = session.get('src') || session.get('templates') || {}; + var opts = _.extend({}, app.options, srcOpts, options); + var locals = opts.locals; + + return createStack(app, [ + plugins.dest.call(app, dest, opts), + plugins.render.call(app, opts, locals), + vfs.dest(dest, opts) + ]); +}; diff --git a/lib/tests/paths.js b/lib/tests/paths.js deleted file mode 100644 index f76e1bf..0000000 --- a/lib/tests/paths.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -var regex = require('requires-regex'); -var debug = require('debug')('update:lib'); - -module.exports = function paths(file, verb) { - var str = file.contents.toString(); - - // body... // test.js is at the root, let's make sure - // the path to `index.js` is correct - if (file.path.indexOf('test/') === -1) { - str.replace(regex(), function ($1, $2, $3, fp) { - if (fp && fp === '../' || fp === '..') { - str = str.replace(fp, './'); - } - }); - } - - file.contents = new Buffer(str); - return file; -}; diff --git a/lib/tests/should.js b/lib/tests/should.js deleted file mode 100644 index 07a7e05..0000000 --- a/lib/tests/should.js +++ /dev/null @@ -1,54 +0,0 @@ -'use strict'; - -var debug = require('debug')('update:lib'); -var logger = require('../logging'); - -module.exports = function should(file, verb) { - debug('lib/tests/should.js'); - var str = file.contents.toString(); - var log = logger(str); - - // does package.json have `devDependencies`? - var devDeps = verb.env && verb.env.devDependencies; - - // does `should` exists in test files? - var has = /var.*should\s*=\s*require/.test(str); - - // if so, is it used? - var used = /\.should\./.test(str); - - if (has && devDeps) { - // if `should` is *not* used - // - strip if from test files - // - delete it from package.json - if (!used) { - str = stripRequires(str); - delete verb.pkg.devDependencies.should; - - } else { - // if `should` *is* used - // - format it properly in test files - // - ensure it's in package.json - str = fixRequires(str); - // TODO: get the latest version from npm - verb.pkg.devDependencies.should = '^0.5.0'; - } - } - - log.results(str, 'updated "should" statements in', file.relative); - file.contents = new Buffer(str); - return file; -}; - - -var re = /var should[^\n]+/; - -function fixRequires(str) { - return str.replace(re, 'require(\'should\');'); -} - -function stripRequires(str) { - return str.replace(re, ''); -} - - diff --git a/lib/transforms/env/index.js b/lib/transforms/env/index.js new file mode 100644 index 0000000..874ea55 --- /dev/null +++ b/lib/transforms/env/index.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require('export-files')(__dirname); diff --git a/lib/transforms/index.js b/lib/transforms/index.js new file mode 100644 index 0000000..f1c2a3d --- /dev/null +++ b/lib/transforms/index.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require('export-dirs')(__dirname); diff --git a/lib/transforms/init/argv.js b/lib/transforms/init/argv.js new file mode 100644 index 0000000..b2f950e --- /dev/null +++ b/lib/transforms/init/argv.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * Prime `app.cache.argv` + */ + +module.exports = function argv_(app) { + app.cache.argv = app.cache.argv || []; +}; diff --git a/lib/transforms/init/config.js b/lib/transforms/init/config.js new file mode 100644 index 0000000..cae1615 --- /dev/null +++ b/lib/transforms/init/config.js @@ -0,0 +1,52 @@ +'use strict'; + +var Store = require('data-store'); +var chalk = require('chalk'); + +/** + * Create a global data config for user values. + * + * To get and set values, do the following: + * + * - set: `--set one=abc` or `--set one` + * - get: `--set one` or `--get one,two,three` + */ + +module.exports = function config_() { + var config = this.config = new Store('app'); + var del = this.get('argv.del'); + var set = this.get('argv.set'); + var get = this.get('argv.get'); + + if (set) { + var args = set.split('='); + if (args.length === 2) { + config.set(args[0], args[1]); + } else { + config.set(args[0], true); + } + } + + if (get) { + if (get === true || get === 'true') { + console.log(chalk.cyan('config config:'), chalk.bold(JSON.stringify(config.data))); + } else if (get.indexOf(',') !== -1) { + get.split(',').forEach(function (val) { + console.log(val, '=', chalk.bold(JSON.stringify(config.get(val)))); + }); + } else { + console.log(get, '=', chalk.bold(JSON.stringify(config.get(get)))); + } + } + + if (del) { + if (del.indexOf(',') !== -1) { + del.split(',').forEach(function (val) { + config.omit(val); + }); + } else { + config.omit(del); + } + console.log('deleted:', chalk.bold(del)); + } +}; diff --git a/lib/transforms/init/create.js b/lib/transforms/init/create.js new file mode 100644 index 0000000..c5a9076 --- /dev/null +++ b/lib/transforms/init/create.js @@ -0,0 +1,12 @@ +'use strict'; + +/** + * Create built-in template types, using the `base` loader + */ + +module.exports = function create_(app) { + app.create('example', {isRenderable: true}, ['base']); + app.create('include', {isRenderable: true}, ['base']); + app.create('badge', {isRenderable: true}, ['base']); + app.create('doc', {isRenderable: true}, ['base']); +}; diff --git a/lib/transforms/init/cwd.js b/lib/transforms/init/cwd.js new file mode 100644 index 0000000..5248c1f --- /dev/null +++ b/lib/transforms/init/cwd.js @@ -0,0 +1,28 @@ +'use strict'; + +/** + * Get/set the current working directory + * + * ```js + * console.log(app.cwd); + * //=> /dev/foo/bar/ + * ``` + * Or set: + * + * ```js + * app.cwd = 'foo'; + * ``` + */ + +module.exports = function cwd_(app) { + var cwd = app.option('cwd') || process.cwd(); + + Object.defineProperty(app, 'cwd', { + get: function () { + return cwd; + }, + set: function (val) { + cwd = val; + } + }); +}; diff --git a/lib/transforms/init/data.js b/lib/transforms/init/data.js new file mode 100644 index 0000000..38c9c5f --- /dev/null +++ b/lib/transforms/init/data.js @@ -0,0 +1,15 @@ +'use strict'; + +var path = require('path'); +var utils = require('../../utils'); + +/** + * Prime `app.cache.data` with empty package.json fields that + * will be over-written by the user's environment. + */ + +module.exports = function data_(app) { + app.data('../../templates/pkg.json', function (fp) { + return utils.tryRequire(path.resolve(__dirname, fp)); + }); +}; diff --git a/lib/transforms/init/engines.js b/lib/transforms/init/engines.js new file mode 100644 index 0000000..87acd84 --- /dev/null +++ b/lib/transforms/init/engines.js @@ -0,0 +1,11 @@ +'use strict'; + +/** + * Load built-in engines + */ + +module.exports = function engines_(app) { + app.engine('*', require('engine-lodash'), { + delims: ['<%', '%>'] + }); +}; diff --git a/lib/transforms/init/helpers.js b/lib/transforms/init/helpers.js new file mode 100644 index 0000000..8943be7 --- /dev/null +++ b/lib/transforms/init/helpers.js @@ -0,0 +1,11 @@ +'use strict'; + +/** + * Load default helpers + */ + +module.exports = function helpers_(app) { + require('../../helpers/sync')(app); + require('../../helpers/async')(app); + require('../../helpers/collections')(app); +}; diff --git a/lib/transforms/init/index.js b/lib/transforms/init/index.js new file mode 100644 index 0000000..874ea55 --- /dev/null +++ b/lib/transforms/init/index.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require('export-files')(__dirname); diff --git a/lib/transforms/init/load.js b/lib/transforms/init/load.js new file mode 100644 index 0000000..cbdab1e --- /dev/null +++ b/lib/transforms/init/load.js @@ -0,0 +1,32 @@ +'use strict'; + +var path = require('path'); +var cwd = require('cwd'); + +/** + * Load built-in template types + */ + +module.exports = function load_(app) { + var includes = tryRequire(app.config.get('includes'), './includes'); + app.includes('**/*.md', { cwd: includes, cache: true }); +}; + +function tryRequire(name, fallback) { + if (typeof name === 'string') { + try { + return require(name); + } catch(err) { + try { + return require(path.resolve(name)); + } catch(err) {} + return path.resolve(fallback); + } + } else { + try { + return require(fallback); + } catch(err) { + return path.resolve(fallback); + } + } +} diff --git a/lib/transforms/init/loaders.js b/lib/transforms/init/loaders.js new file mode 100644 index 0000000..f48c0c2 --- /dev/null +++ b/lib/transforms/init/loaders.js @@ -0,0 +1,12 @@ +'use strict'; + +var loaders = require('../../loaders'); + +/** + * Load built-in loaders + */ + +module.exports = function base_(app) { + app.loader('base', [loaders.base]); + app.loader('task', [loaders.task]); +}; diff --git a/lib/transforms/init/middleware.js b/lib/transforms/init/middleware.js new file mode 100644 index 0000000..0ac9821 --- /dev/null +++ b/lib/transforms/init/middleware.js @@ -0,0 +1,47 @@ +'use strict'; + +var utils = require('../../utils/'); + +/** + * Initialize default middleware + */ + +module.exports = function middleware_(app) { + app.onLoad(/\.js$/, utils.parallel([ + require('../../middleware/copyright')(app), + require('../../middleware/todos')(app), + ]), error('.onLoad (js):')); + + app.onLoad(/\.md$/, utils.series([ + require('../../middleware/conflict')(app), + require('../../middleware/copyright')(app), + require('../../middleware/props'), + require('../../middleware/cwd')(app), + require('../../middleware/engine'), + require('../../middleware/src'), + require('../../middleware/dest'), + require('../../middleware/ext'), + require('../../middleware/lint')(app), + require('template-toc')(app), + utils.escape, + ]), error('.onLoad (md):')); + + app.preRender(/\.md$/, utils.parallel([ + require('../../middleware/lint')(app), + require('../../middleware/multi-toc'), + require('../../middleware/readme'), + ]), error('.preRender (md):')); + + app.postRender(/\.md$/, utils.parallel([ + utils.unescape, + require('../../middleware/lint-after')(app), + require('../../middleware/diff')(app) + ]), error('.postRender:')); +}; + +function error(method) { + return function (err, file, next) { + if (err) console.log(method, err); + next(); + }; +} diff --git a/lib/transforms/init/options.js b/lib/transforms/init/options.js new file mode 100644 index 0000000..d07facc --- /dev/null +++ b/lib/transforms/init/options.js @@ -0,0 +1,21 @@ +'use strict'; + +/** + * Define default options. + */ + +module.exports = function options_(app) { + app.option({ + toc: {append: '\n\n_(Table of contents generated by [update])_'} + }); + + // engines + app.option('view engine', 'md'); + + // delimiters + app.option('layoutDelims', ['{%', '%}']); + app.option('escapeDelims', { + from: ['<%%', '%>'], + to: ['<%', '%>'] + }); +}; diff --git a/lib/transforms/init/runner.js b/lib/transforms/init/runner.js new file mode 100644 index 0000000..6c224f8 --- /dev/null +++ b/lib/transforms/init/runner.js @@ -0,0 +1,14 @@ +'use strict'; + +/** + * The current `app.runner` + */ + +module.exports = function runner_(app) { + app.data({ + runner: { + name: 'app-cli', + url: 'https://github.com/assemble/app-cli' + } + }); +}; diff --git a/lib/transforms/session.js b/lib/transforms/session.js new file mode 100644 index 0000000..63edb92 --- /dev/null +++ b/lib/transforms/session.js @@ -0,0 +1,11 @@ +'use strict'; + +var session = require('../session'); + +/** + * The first transform to be run at init. + */ + +module.exports = function session_(app) { + app.session = session; +}; diff --git a/lib/transforms/updaters/index.js b/lib/transforms/updaters/index.js new file mode 100644 index 0000000..0fbff4b --- /dev/null +++ b/lib/transforms/updaters/index.js @@ -0,0 +1,7 @@ +'use strict'; + +var transforms = require('export-files')(__dirname); + +module.exports = function updaters_(update) { + this.transform('updaters', transforms.updaters); +}; diff --git a/lib/transforms/updaters/updaters.js b/lib/transforms/updaters/updaters.js new file mode 100644 index 0000000..3e8aedd --- /dev/null +++ b/lib/transforms/updaters/updaters.js @@ -0,0 +1,27 @@ +'use strict'; + +var resolveUp = require('resolve-up'); +var path = require('path'); +var _ = require('lodash'); + +/** + * Load updaters onto the `updaters` object + */ + +module.exports = function updaters_() { + this.updaters = this.updaters || {}; + var pattern = this.option('updater pattern') || 'updater-*'; + console.log(pattern) + + _.transform(resolveUp(pattern), function (acc, dir) { + var pkg = require(path.resolve(dir, 'package.json')); + if (!pkg.main) return acc; + + var fp = path.resolve(dir, pkg.main); + var res = {}; + res.module = require.resolve(path.resolve(fp)); + res.pkg = pkg; + var name = pkg.name.split('updater-').join(''); + acc[name] = res; + }, this.updaters); +}; diff --git a/lib/utils/completion.js b/lib/utils/completion.js new file mode 100644 index 0000000..9ada3e4 --- /dev/null +++ b/lib/utils/completion.js @@ -0,0 +1,18 @@ +'use strict'; + +var fs = require('fs'); +var path = require('path'); + +module.exports = function (name) { + if (typeof name !== 'string') { + throw new Error('Missing completion type'); + } + var file = path.join(__dirname, '../../completion', name); + try { + console.log(fs.readFileSync(file, 'utf8')); + process.exit(0); + } catch (err) { + console.log('echo "[update] autocompletion rules for', '\'' + name + '\'', 'not found"'); + process.exit(5); + } +}; diff --git a/lib/utils/error.js b/lib/utils/error.js new file mode 100644 index 0000000..ef61bca --- /dev/null +++ b/lib/utils/error.js @@ -0,0 +1,159 @@ +'use strict'; + +var util = require('util'); +var chalk = require('chalk'); +var utils = require('utils'); + +/** + * Based on gulp-util PluginError + * https://github.com/wearefractal/gulp-util + * MIT License + */ + +var nonEnumerable = ['name', 'message', 'stack']; +var ignore = nonEnumerable.concat(['plugin', 'showStack', 'showProperties', '__safety', '_stack']); +var properties = ['name', 'message', 'fileName', 'lineNumber', 'stack', 'showStack', 'showProperties', 'plugin']; + +function PluginError(plugin, message, options) { + if (!(this instanceof PluginError)) { + throw new Error('Call PluginError using new'); + } + + Error.call(this); + var opts = setDefaults(plugin, message, options); + var self = this; + + // if opts has an error, grab details from it + if (typeof opts.error === 'object') { + var keys = Object.keys(opts.error); + + // These properties are not enumerable, so we have to add them explicitly. + utils.unique(keys.concat(nonEnumerable)).forEach(function(prop) { + self[prop] = opts.error[prop]; + }); + } + + // opts object can override + properties.forEach(function(prop) { + if (prop in opts) this[prop] = opts[prop]; + }, this); + + // defaults + if (!this.name) { + this.name = 'Error'; + } + + if (!this.stack) { + var safety = {}; + + /** + * Error.captureStackTrace appends a stack property + * which relies on the toString method of the object + * it is applied to. + * + * Since we are using our own toString method which + * controls when to display the stack trace, if we don't + * go through this safety object we'll get stack + * overflow problems. + */ + + safety.toString = function() { + return this._messageWithDetails() + '\nStack:'; + }.bind(this); + + // console.log(arguments.callee) + Error.captureStackTrace(safety, arguments.callee || this.constructor); + this.__safety = safety; + } + if (!this.plugin) throw new Error('Missing plugin name'); + if (!this.message) throw new Error('Missing error message'); +} + +util.inherits(PluginError, Error); + + +PluginError.prototype._messageWithDetails = function() { + var msg = 'Message:\n ' + this.message; + var details = this._messageDetails(); + if (details !== '') msg += '\n' + details; + return msg; +}; + +PluginError.prototype._messageDetails = function() { + if (!this.showProperties) return ''; + var self = this; + + var keys = Object.keys(this); + var props = utils.difference(keys, ignore); + + var len = props.length; + if (len === 0) return ''; + + var res = [], i = 0; + while (len--) { + var prop = props[i++]; + res += ' '; + res += prop; + res += ': '; + res += self[prop]; + res += '\n'; + } + return 'Details:\n' + res; +}; + +PluginError.prototype.toString = function () { + var detailsWithStack = function(stack) { + return this._messageWithDetails() + '\nStack:\n' + stack; + }.bind(this); + + if (this.hasOwnProperty('showStack')) { + // If there is no wrapped error, use the stack + // captured in the PluginError ctor + if (this.hasOwnProperty('__safety')) { + return message(this.__safety.stack, this); + } + if (this.hasOwnProperty('_stack')) { + return message(detailsWithStack(this._stack), this); + } + // Stack from wrapped error + return message(detailsWithStack(this.stack), this); + } + return message(this._messageWithDetails(), this); +}; + +function message(msg, thisArg) { + var sig = chalk.red(thisArg.name); + sig += ' in plugin '; + sig += '"'; + sig += chalk.cyan(thisArg.plugin); + sig += '"'; + sig += '\n'; + sig += msg; + return sig; +} + +function setDefaults(plugin, message, opts) { + if (typeof plugin === 'object') { + return defaults(plugin); + } + + opts = opts || {}; + if (message instanceof Error) { + opts.error = message; + return defaults(opts); + } + + if (typeof message === 'object') { + return defaults(message); + } + + opts.message = message; + opts.plugin = plugin; + return defaults(opts); +} + +function defaults (opts) { + return utils.extend({showStack: false, showProperties: true}, opts); +} + +module.exports = PluginError; diff --git a/lib/utils/index.js b/lib/utils/index.js new file mode 100644 index 0000000..874ea55 --- /dev/null +++ b/lib/utils/index.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require('export-files')(__dirname); diff --git a/lib/utils/task-tree.js b/lib/utils/task-tree.js new file mode 100644 index 0000000..63b0bea --- /dev/null +++ b/lib/utils/task-tree.js @@ -0,0 +1,18 @@ +'use strict'; + +/** + * Borrowed from gulp + */ + +module.exports = function (tasks) { + return Object.keys(tasks) + .reduce(function (prev, task) { + prev.nodes.push({ + label: task, + nodes: tasks[task].dep + }); + return prev; + }, { + nodes: [] + }); +}; From f8c67aba35e766c81f8f47f2de685807d3aabf22 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 25 Apr 2015 12:13:14 -0400 Subject: [PATCH 062/274] fix copyright --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index fa30c4c..65f90ac 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014-2015, Jon Schlinkert. +Copyright (c) 2015, Jon Schlinkert. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 76c1cf74f5a6eeba1b797ff8fe1d5866f61ea7f1 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 25 Apr 2015 12:13:50 -0400 Subject: [PATCH 063/274] generate readme --- .verb.md | 25 ++++-- README.md | 250 ++++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 231 insertions(+), 44 deletions(-) diff --git a/.verb.md b/.verb.md index e4ed5d2..3e9bb65 100644 --- a/.verb.md +++ b/.verb.md @@ -2,6 +2,16 @@ > {%= description %} + + +{%= include("install-npm", {save: true}) %} + +## Usage + +```js +var update = require('{%= name %}'); +``` + This is an experimental library that is build 100% on top of [verb], but it's not quite ready to use on your projects. ### What's this about? @@ -48,22 +58,23 @@ From the command line, run: update ``` -## Related -This project is built on these libraries: +## API +{%= apidocs("index.js") %} -{%= join(keys(dependencies), "\n - ") %} +## Related projects +{%%= related([]) %} + +## Running tests +{%= include("tests") %} ## Contributing {%= include("contributing") %} -## Running tests -{%= include("test") %} - ## Author {%= include("author") %} ## License -{%= copyright({year: 2014}) %} +{%= copyright() %} {%= license() %} *** diff --git a/README.md b/README.md index f705715..1e315fe 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,36 @@ # update [![NPM version](https://badge.fury.io/js/update.svg)](http://badge.fury.io/js/update) [![Build Status](https://travis-ci.org/jonschlinkert/update.svg)](https://travis-ci.org/jonschlinkert/update) -> Update the year in all files in a project using glob patterns. +> Lint and update your project to meet your standards. + + + +- [Usage](#usage) + * [What's this about?](#what-s-this-about-) + * [The goal](#the-goal) + * [Next](#next) +- [CLI](#cli) +- [API](#api) +- [Related projects](#related-projects) +- [Running tests](#running-tests) +- [Contributing](#contributing) +- [Author](#author) +- [License](#license) + +_(Table of contents generated by [verb])_ + + + +## Install with [npm](npmjs.org) + +```bash +npm i update --save +``` + +## Usage + +```js +var update = require('update'); +``` This is an experimental library that is build 100% on top of [verb], but it's not quite ready to use on your projects. @@ -52,56 +82,202 @@ From the command line, run: update ``` -## Run tests -Install dev dependencies. +## API -```bash -npm i -d && npm test +### [Update](index.js#L36) + +Initialize `Update`. + +**Example** + +```js +var app = new Update(); +``` + +### [.src](index.js#L68) + +Glob patterns or filepaths to source files. + +**Example usage** + +**Params** + +* `glob` **{String|Array}**: Glob patterns or file paths to source files. +* `options` **{Object}**: Options or locals to merge into the context and/or pass to `src` plugins + +**Examples** + +```js +app.src('*.js') +``` + +```js +app.task('web-app', function() { + app.src('templates/*') + app.dest(process.cwd()) +}); +``` + +### [.templates](index.js#L94) + +Glob patterns or filepaths to templates stored in the `./templates` directory of an updater. + +**Example usage** + +**Params** + +* `glob` **{String|Array}**: Glob patterns or file paths to source files. +* `options` **{Object}**: Options or locals to merge into the context and/or pass to `src` plugins + +**Examples** + +```js +app.templates('*.js') +``` + +```js +app.task('licenses', function() { + app.templates('templates/licenses/*') + app.dest(process.cwd()) +}); +``` + +### [.dest](index.js#L119) + +Specify a destination for processed files. + +**Example usage** + +**Params** + +* `dest` **{String|Function}**: File path or rename function. +* `options` **{Object}**: Options or locals to merge into the context and/or pass to `dest` plugins + +**Examples** + +```js +app.dest('dist', {ext: '.xml'}) +``` + +```js +app.task('foo', function() { + app.src('templates/*') + app.dest('dist', {ext: '.xml'}) +}); ``` +### [.task](index.js#L154) -## Related - -This project is built on these libraries: - -arr-diff - - array-intersection - - array-unique - - chalk - - del - - export-files - - glob - - gray-matter - - gulp-util - - has-banner - - kind-of - - log-symbols - - merge-deep - - micromatch - - omit-empty - - parse-copyright - - requires-regex - - sort-object - - through2 - - update-banner - - update-license - - update-package - - verb +Define a task. + +**Params** + +* `name` **{String}** +* `fn` **{Function}** + +**Example** + +```js +app.task('docs', function() { + app.src('*.js').pipe(app.dest('.')); +}); +``` + +### [.gettask](index.js#L168) + +Get the name of the currently running task. This is primarily used inside plugins. + +* `returns` **{String}** `task`: The currently running task. + +**Example** + +```js +app.gettask(); +``` + +### [.getFile](index.js#L187) + +Used in plugins to get a template from the current session. + +* `returns` **{String}** `id`: Pass the task-id from the current session. +* `returns` **{Object}** `file`: Vinyl file object. Must have an `id` property that matches the `id` of the session. + +**Example** + +```js +var template = getFile(id, file); +``` + +### [.updater](index.js#L207) + +Set or get a generator function by `name`. + +**Params** + +* `name` **{String}** +* `fn` **{Function}**: The updater plugin function + +**Example** + +```js +// set an updater +app.updater('foo', require('updater-foo')); + +// get an updater +var foo = app.updater('foo'); +``` + +### [.watch](index.js#L248) + +Re-run the specified task(s) when a file changes. + +**Params** + +* `glob` **{String|Array}**: Filepaths or glob patterns. +* `fn` **{Function}**: Task(s) to watch. + +**Example** + +```js +app.task('watch', function() { + app.watch('docs/*.md', ['docs']); +}); +``` + +## Related projects + +{%= related([]) %} + +## Running tests + +Install dev dependencies: + +```bash +npm i -d && npm test +``` ## Contributing + Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](https://github.com/jonschlinkert/update/issues) ## Author **Jon Schlinkert** - + + [github/jonschlinkert](https://github.com/jonschlinkert) -+ [twitter/jonschlinkert](http://twitter.com/jonschlinkert) ++ [twitter/jonschlinkert](http://twitter.com/jonschlinkert) ## License -Copyright (c) 2014-2015 Jon Schlinkert -Released under the MIT license + +Copyright (c) 2015 Jon Schlinkert +Released under the MIT license. *** -_This file was generated by [update](https://github.com/jonschlinkert/update) on March 01, 2015._ +_This file was generated by [verb-cli](https://github.com/assemble/verb-cli) on April 25, 2015._ + + + +[verb]: https://github.com/assemble/verb +[template]: https://github.com/jonschlinkert/template +[assemble]: http://assemble.io \ No newline at end of file From d4180aa9c0e41be3cbeea42a8f9af98b62e34874 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 25 Apr 2015 12:14:14 -0400 Subject: [PATCH 064/274] fix description, deps --- package.json | 65 +++++++++++++++++++++++----------------------------- 1 file changed, 29 insertions(+), 36 deletions(-) diff --git a/package.json b/package.json index 93ef53a..5ca35d1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "update", - "description": "Update the year in all files in a project using glob patterns.", - "version": "0.3.6", + "description": "Lint and update your project to meet your standards.", + "version": "0.1.0", "homepage": "https://github.com/jonschlinkert/update", "author": { "name": "Jon Schlinkert", @@ -20,16 +20,16 @@ }, "files": [ "cli.js", - "lib/", - "templates/", - "verbfile.js", - "plugins/" + "index.js", + "bin/", + "lib/" ], - "main": "index.js", "preferGlobal": true, + "engineStrict": true, "bin": { - "update": "./cli.js" + "update": "bin/update.js" }, + "main": "index.js", "engines": { "node": ">=0.10.0" }, @@ -37,37 +37,30 @@ "test": "mocha" }, "dependencies": { - "arr-diff": "^1.0.1", - "array-unique": "^0.2.1", + "async-helper-base": "^0.2.0", "chalk": "^1.0.0", - "debug": "^2.1.3", - "del": "^1.1.1", + "cwd": "^0.6.0", + "data-store": "^0.4.1", + "engine-lodash": "^0.6.3", + "event-stream": "^3.3.0", + "export-dirs": "^0.2.4", "export-files": "^2.0.1", - "gray-matter": "^2.0.0", - "gulp-util": "^3.0.4", - "has-banner": "^0.2.0", - "kind-of": "^1.1.0", - "log-symbols": "^1.0.2", - "merge-deep": "^1.0.1", - "micromatch": "^2.1.5", - "omit-empty": "^0.3.1", - "parse-copyright": "^0.4.0", - "requires-regex": "^0.2.0", - "sort-object": "^1.0.0", - "sync-pkg": "^0.4.0", + "lodash": "^3.7.0", + "map-files": "^0.7.4", + "minimist": "^1.1.1", + "orchestrator": "^0.3.7", + "pretty-hrtime": "^1.0.0", + "resolve-up": "^0.1.1", + "session-cache": "^0.1.3", + "sessionify": "^0.1.0", + "template": "^0.13.1", + "template-toc": "^0.3.3", "through2": "^0.6.3", - "update-banner": "^0.1.1", - "update-license": "^0.3.0", - "update-package": "^0.2.0", - "verb": "^0.6.1" + "utils": "^0.2.1", + "vinyl": "^0.4.6", + "vinyl-fs": "^1.0.0" }, "devDependencies": { - "glob": "^5.0.3" - }, - "keywords": [ - "javascript", - "keys", - "object", - "sort" - ] + "del": "^1.1.1" + } } From fd6bbb895e00bef9c581e1df11fcfb93d2c7a30d Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 27 Apr 2015 05:36:01 -0400 Subject: [PATCH 065/274] generate readme --- README.md | 167 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 110 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 1e315fe..cc79222 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,20 @@ -# update [![NPM version](https://badge.fury.io/js/update.svg)](http://badge.fury.io/js/update) [![Build Status](https://travis-ci.org/jonschlinkert/update.svg)](https://travis-ci.org/jonschlinkert/update) +# update [![NPM version](https://badge.fury.io/js/update.svg)](http://badge.fury.io/js/update) [![Build Status](https://travis-ci.org/jonschlinkert/update.svg)](https://travis-ci.org/jonschlinkert/update) > Lint and update your project to meet your standards. -- [Usage](#usage) - * [What's this about?](#what-s-this-about-) - * [The goal](#the-goal) - * [Next](#next) -- [CLI](#cli) -- [API](#api) -- [Related projects](#related-projects) -- [Running tests](#running-tests) -- [Contributing](#contributing) -- [Author](#author) -- [License](#license) +* [Usage](#usage) - [What's this about?](#what-s-this-about-) + - [The goal](#the-goal) + - [Next](#next) + +* [CLI](#cli) +* [API](#api) +* [Related projects](#related-projects) +* [Running tests](#running-tests) +* [Contributing](#contributing) +* [Author](#author) +* [License](#license) _(Table of contents generated by [verb])_ @@ -32,7 +32,7 @@ npm i update --save var update = require('update'); ``` -This is an experimental library that is build 100% on top of [verb], but it's not quite ready to use on your projects. +This is an experimental library that is build 100% on top of [verb], but it's not quite ready to use on your projects. ### What's this about? @@ -40,12 +40,12 @@ Update is a CLI tool that loads personal defaults/preferences, then runs a serie **For example:** -- it updates the copyright dates in banners, the README and LICENSE files -- it renames files to be the way I like them (like `LICENSE-MIT` => `LICENSE`) -- it updates banners to ensure they have the correct project URL and license information -- it updates `.jshintrc` with my lastest preferences -- it updates `.editorconfig` with my latest preferences -- it updates `package.json` properties with my latest preferences +* it updates the copyright dates in banners, the README and LICENSE files +* it renames files to be the way I like them (like `LICENSE-MIT` => `LICENSE`) +* it updates banners to ensure they have the correct project URL and license information +* it updates `.jshintrc` with my lastest preferences +* it updates `.editorconfig` with my latest preferences +* it updates `package.json` properties with my latest preferences etc... this is maybe 20% of what it does currently. There are some bugs to work out, but I can tell this project is going to be worth spending time on. It's already paying off. @@ -53,20 +53,19 @@ etc... this is maybe 20% of what it does currently. There are some bugs to work The goal is to be able to easily update and normalize existing projects from the command line using a compbination of: -- **global defaults** for project metadata, like your github username, license preference, and other properties that change very little if at all from project-to-project. -- **normalizers** that will normalize and update virtually anything in the project to: - + use your (latest) preference - + meet the (latest) standards for whatever piece of metadata is being updated. For example, it would lint your `.travis.md` files to ensure that `iojs` has been added to the `node_js` versions. +* **global defaults** for project metadata, like your github username, license preference, and other properties that change very little if at all from project-to-project. +* **normalizers** that will normalize and update virtually anything in the project to: - use your (latest) preference + - meet the (latest) standards for whatever piece of metadata is being updated. For example, it would lint your `.travis.md` files to ensure that `iojs` has been added to the `node_js` versions. ### Next After I completely understand how this should work, I'll: -1. remove all logic and preferences that are specific to my own projects. -2. Split everything out into plugins, helpers, utils etc. -3. Try to make everything compatible with gulp plugins, so we don't need to think about another format. +* * remove all logic and preferences that are specific to my own projects. +* Split everything out into plugins, helpers, utils etc. +* Try to make everything compatible with gulp plugins, so we don't need to think about another format. -For now, however, this project is not at all idiomatic. A lot of the logic is pretty opinionated, there is a lot of duplication, and some of the plugins are just sloppy. As a rule-of-thumb I like to get things working as a POC before I spend time cleaning up code. +For now, however, this project is not at all idiomatic. A lot of the logic is pretty opinionated, there is a lot of duplication, and some of the plugins are just sloppy. As a rule-of-thumb I like to get things working as a POC before I spend time cleaning up code. ## Install globally with [npm](npmjs.org): @@ -94,7 +93,7 @@ Initialize `Update`. var app = new Update(); ``` -### [.src](index.js#L68) +### [.src](index.js#L67) Glob patterns or filepaths to source files. @@ -102,8 +101,8 @@ Glob patterns or filepaths to source files. **Params** -* `glob` **{String|Array}**: Glob patterns or file paths to source files. -* `options` **{Object}**: Options or locals to merge into the context and/or pass to `src` plugins +* `glob` **{String|Array}**: Glob patterns or file paths to source files. +* `options` **{Object}**: Options or locals to merge into the context and/or pass to `src` plugins **Examples** @@ -118,7 +117,7 @@ app.task('web-app', function() { }); ``` -### [.templates](index.js#L94) +### [.templates](index.js#L93) Glob patterns or filepaths to templates stored in the `./templates` directory of an updater. @@ -126,8 +125,8 @@ Glob patterns or filepaths to templates stored in the `./templates` directory of **Params** -* `glob` **{String|Array}**: Glob patterns or file paths to source files. -* `options` **{Object}**: Options or locals to merge into the context and/or pass to `src` plugins +* `glob` **{String|Array}**: Glob patterns or file paths to source files. +* `options` **{Object}**: Options or locals to merge into the context and/or pass to `src` plugins **Examples** @@ -142,7 +141,7 @@ app.task('licenses', function() { }); ``` -### [.dest](index.js#L119) +### [.dest](index.js#L118) Specify a destination for processed files. @@ -150,8 +149,8 @@ Specify a destination for processed files. **Params** -* `dest` **{String|Function}**: File path or rename function. -* `options` **{Object}**: Options or locals to merge into the context and/or pass to `dest` plugins +* `dest` **{String|Function}**: File path or rename function. +* `options` **{Object}**: Options or locals to merge into the context and/or pass to `dest` plugins **Examples** @@ -166,14 +165,14 @@ app.task('foo', function() { }); ``` -### [.task](index.js#L154) +### [.task](index.js#L175) Define a task. **Params** -* `name` **{String}** -* `fn` **{Function}** +* `name` **{String}** +* `fn` **{Function}** **Example** @@ -183,39 +182,93 @@ app.task('docs', function() { }); ``` -### [.gettask](index.js#L168) +### [.getTask](index.js#L190) + +Get the id from the current task. Used in plugins to get the current session. + +* `returns` **{String}** `task`: The currently running task. + +**Example** + +```js +var id = verb.getTask(); +verb.views[id]; +``` + +### [.getCollection](index.js#L211) -Get the name of the currently running task. This is primarily used inside plugins. +Get the collection name (inflection) of the given template type. -* `returns` **{String}** `task`: The currently running task. +* `returns` **{String}** `name`: Singular name of the collection to get **Example** ```js -app.gettask(); +var collection = verb.getCollection('page'); +// gets the `pages` collection +//=> {a: {}, b: {}, ...} ``` -### [.getFile](index.js#L187) +### [.getViews](index.js#L228) Used in plugins to get a template from the current session. -* `returns` **{String}** `id`: Pass the task-id from the current session. -* `returns` **{Object}** `file`: Vinyl file object. Must have an `id` property that matches the `id` of the session. +* `returns` **{String}** `id`: Pass the task-id from the current session. +* `returns` **{Object}** `file`: Vinyl file object. Must have an `id` property that matches the `id` of the session. + +**Example** + +```js +var views = app.getViews(); +``` + +### [.getFile](index.js#L244) + +Get a template (file) from the current session in a stream. + +* `returns` **{Object}** `file`: Vinyl file object. Must have an `id` property that matches the `id` of the session. + +**Example** + +```js +var file = app.getFile(file); +``` + +### [.pushToStream](index.js#L261) + +Get a template from the current session, convert it to a vinyl file, and push it into the stream. + +**Params** + +* `stream` **{Stream}**: Vinyl stream +* `id` **{String}**: Get the session `id` using `app.getTask()` + +**Example** + +```js +app.pushToStream(file); +``` + +### [.taskFiles](index.js#L278) + +`taskFiles` is a session-context-specific getter that returns the collection of files from the current `task`. + +* `returns` **{Object}**: Get the files from the current task. **Example** ```js -var template = getFile(id, file); +var files = verb.taskFiles; ``` -### [.updater](index.js#L207) +### [.updater](index.js#L301) Set or get a generator function by `name`. **Params** -* `name` **{String}** -* `fn` **{Function}**: The updater plugin function +* `name` **{String}** +* `fn` **{Function}**: The updater plugin function **Example** @@ -227,14 +280,14 @@ app.updater('foo', require('updater-foo')); var foo = app.updater('foo'); ``` -### [.watch](index.js#L248) +### [.watch](index.js#L342) Re-run the specified task(s) when a file changes. **Params** -* `glob` **{String|Array}**: Filepaths or glob patterns. -* `fn` **{Function}**: Task(s) to watch. +* `glob` **{String|Array}**: Filepaths or glob patterns. +* `fn` **{Function}**: Task(s) to watch. **Example** @@ -246,7 +299,7 @@ app.task('watch', function() { ## Related projects -{%= related([]) %} +{%= related([]) %} ## Running tests @@ -274,10 +327,10 @@ Released under the MIT license. *** -_This file was generated by [verb-cli](https://github.com/assemble/verb-cli) on April 25, 2015._ +_This file was generated by [verb-cli](https://github.com/assemble/verb-cli) on April 27, 2015._ -[verb]: https://github.com/assemble/verb +[assemble]: http://assemble.io [template]: https://github.com/jonschlinkert/template -[assemble]: http://assemble.io \ No newline at end of file +[verb]: https://github.com/assemble/verb \ No newline at end of file From 6699821d1900393f1a9e38bb9772a90e29e4c3f6 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 27 Apr 2015 05:36:16 -0400 Subject: [PATCH 066/274] update rules --- .gitignore | 1 + .jshintrc | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 80a228c..de081d3 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ vendor .idea benchmark coverage +support diff --git a/.jshintrc b/.jshintrc index 6e5a84a..6b00ad6 100644 --- a/.jshintrc +++ b/.jshintrc @@ -1,12 +1,13 @@ { "asi": false, "boss": true, - "curly": true, + "curly": false, "eqeqeq": true, "eqnull": true, "esnext": true, "immed": true, "latedef": false, + "laxbreak": true, "laxcomma": false, "mocha": true, "newcap": true, From 2f06e2d9749337b48e6fcb0d780cd58f429f6c49 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 27 Apr 2015 05:36:39 -0400 Subject: [PATCH 067/274] externalize error handling --- lib/utils/error.js | 159 --------------------------------------------- 1 file changed, 159 deletions(-) delete mode 100644 lib/utils/error.js diff --git a/lib/utils/error.js b/lib/utils/error.js deleted file mode 100644 index ef61bca..0000000 --- a/lib/utils/error.js +++ /dev/null @@ -1,159 +0,0 @@ -'use strict'; - -var util = require('util'); -var chalk = require('chalk'); -var utils = require('utils'); - -/** - * Based on gulp-util PluginError - * https://github.com/wearefractal/gulp-util - * MIT License - */ - -var nonEnumerable = ['name', 'message', 'stack']; -var ignore = nonEnumerable.concat(['plugin', 'showStack', 'showProperties', '__safety', '_stack']); -var properties = ['name', 'message', 'fileName', 'lineNumber', 'stack', 'showStack', 'showProperties', 'plugin']; - -function PluginError(plugin, message, options) { - if (!(this instanceof PluginError)) { - throw new Error('Call PluginError using new'); - } - - Error.call(this); - var opts = setDefaults(plugin, message, options); - var self = this; - - // if opts has an error, grab details from it - if (typeof opts.error === 'object') { - var keys = Object.keys(opts.error); - - // These properties are not enumerable, so we have to add them explicitly. - utils.unique(keys.concat(nonEnumerable)).forEach(function(prop) { - self[prop] = opts.error[prop]; - }); - } - - // opts object can override - properties.forEach(function(prop) { - if (prop in opts) this[prop] = opts[prop]; - }, this); - - // defaults - if (!this.name) { - this.name = 'Error'; - } - - if (!this.stack) { - var safety = {}; - - /** - * Error.captureStackTrace appends a stack property - * which relies on the toString method of the object - * it is applied to. - * - * Since we are using our own toString method which - * controls when to display the stack trace, if we don't - * go through this safety object we'll get stack - * overflow problems. - */ - - safety.toString = function() { - return this._messageWithDetails() + '\nStack:'; - }.bind(this); - - // console.log(arguments.callee) - Error.captureStackTrace(safety, arguments.callee || this.constructor); - this.__safety = safety; - } - if (!this.plugin) throw new Error('Missing plugin name'); - if (!this.message) throw new Error('Missing error message'); -} - -util.inherits(PluginError, Error); - - -PluginError.prototype._messageWithDetails = function() { - var msg = 'Message:\n ' + this.message; - var details = this._messageDetails(); - if (details !== '') msg += '\n' + details; - return msg; -}; - -PluginError.prototype._messageDetails = function() { - if (!this.showProperties) return ''; - var self = this; - - var keys = Object.keys(this); - var props = utils.difference(keys, ignore); - - var len = props.length; - if (len === 0) return ''; - - var res = [], i = 0; - while (len--) { - var prop = props[i++]; - res += ' '; - res += prop; - res += ': '; - res += self[prop]; - res += '\n'; - } - return 'Details:\n' + res; -}; - -PluginError.prototype.toString = function () { - var detailsWithStack = function(stack) { - return this._messageWithDetails() + '\nStack:\n' + stack; - }.bind(this); - - if (this.hasOwnProperty('showStack')) { - // If there is no wrapped error, use the stack - // captured in the PluginError ctor - if (this.hasOwnProperty('__safety')) { - return message(this.__safety.stack, this); - } - if (this.hasOwnProperty('_stack')) { - return message(detailsWithStack(this._stack), this); - } - // Stack from wrapped error - return message(detailsWithStack(this.stack), this); - } - return message(this._messageWithDetails(), this); -}; - -function message(msg, thisArg) { - var sig = chalk.red(thisArg.name); - sig += ' in plugin '; - sig += '"'; - sig += chalk.cyan(thisArg.plugin); - sig += '"'; - sig += '\n'; - sig += msg; - return sig; -} - -function setDefaults(plugin, message, opts) { - if (typeof plugin === 'object') { - return defaults(plugin); - } - - opts = opts || {}; - if (message instanceof Error) { - opts.error = message; - return defaults(opts); - } - - if (typeof message === 'object') { - return defaults(message); - } - - opts.message = message; - opts.plugin = plugin; - return defaults(opts); -} - -function defaults (opts) { - return utils.extend({showStack: false, showProperties: true}, opts); -} - -module.exports = PluginError; From d94cfa0b515c6f48543eb93112ac38f37ff455d2 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 27 Apr 2015 07:23:42 -0400 Subject: [PATCH 068/274] re-render readme --- .verb.md | 4 ++-- README.md | 12 +++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.verb.md b/.verb.md index 3e9bb65..4766191 100644 --- a/.verb.md +++ b/.verb.md @@ -43,8 +43,8 @@ The goal is to be able to easily update and normalize existing projects from the After I completely understand how this should work, I'll: 1. remove all logic and preferences that are specific to my own projects. -2. Split everything out into plugins, helpers, utils etc. -3. Try to make everything compatible with gulp plugins, so we don't need to think about another format. +1. Split everything out into plugins, helpers, utils etc. +1. Try to make everything compatible with gulp plugins, so we don't need to think about another format. For now, however, this project is not at all idiomatic. A lot of the logic is pretty opinionated, there is a lot of duplication, and some of the plugins are just sloppy. As a rule-of-thumb I like to get things working as a POC before I spend time cleaning up code. diff --git a/README.md b/README.md index cc79222..a9e7e78 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@ -* [Usage](#usage) - [What's this about?](#what-s-this-about-) +* [Usage](#usage) + - [What's this about?](#what-s-this-about-) - [The goal](#the-goal) - [Next](#next) @@ -54,16 +55,17 @@ etc... this is maybe 20% of what it does currently. There are some bugs to work The goal is to be able to easily update and normalize existing projects from the command line using a compbination of: * **global defaults** for project metadata, like your github username, license preference, and other properties that change very little if at all from project-to-project. -* **normalizers** that will normalize and update virtually anything in the project to: - use your (latest) preference +* **normalizers** that will normalize and update virtually anything in the project to: + - use your (latest) preference - meet the (latest) standards for whatever piece of metadata is being updated. For example, it would lint your `.travis.md` files to ensure that `iojs` has been added to the `node_js` versions. ### Next After I completely understand how this should work, I'll: -* * remove all logic and preferences that are specific to my own projects. -* Split everything out into plugins, helpers, utils etc. -* Try to make everything compatible with gulp plugins, so we don't need to think about another format. +1. remove all logic and preferences that are specific to my own projects. +2. Split everything out into plugins, helpers, utils etc. +3. Try to make everything compatible with gulp plugins, so we don't need to think about another format. For now, however, this project is not at all idiomatic. A lot of the logic is pretty opinionated, there is a lot of duplication, and some of the plugins are just sloppy. As a rule-of-thumb I like to get things working as a POC before I spend time cleaning up code. From 6ff8a5a887bf367474cc6a65a2929beed5cf76fe Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 27 Apr 2015 08:30:33 -0400 Subject: [PATCH 069/274] adds utils --- lib/utils/index.js | 287 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 286 insertions(+), 1 deletion(-) diff --git a/lib/utils/index.js b/lib/utils/index.js index 874ea55..b8d0707 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -1,3 +1,288 @@ 'use strict'; -module.exports = require('export-files')(__dirname); +/** + * Module dependencies + */ + +var fs = require('fs'); +var path = require('path'); +var chalk = require('chalk'); +var async = require('async'); +var relative = require('relative'); +var mm = require('micromatch'); + +/** + * File cache + */ + +var cache = {}; + +/** + * Expose `utils` + */ + +var utils = module.exports = require('export-files')(__dirname); + + +/** + * Run middleware in series + */ + +utils.series = function series(fns) { + return function (file, cb) { + async.eachSeries(fns, function (fn, next) { + fn(file, next); + }, cb); + }; +}; + +utils.pushToStream = function pushToStream(collection, stream, fn) { + var i = 0; + for (var key in collection) { + if (collection.hasOwnProperty(key)) { + var file = collection[key]; + stream.push(fn ? fn(file, i++) : file); + } + } +}; + +/** + * Cast `val` to an array. + */ + +utils.arrayify = function arrayify(val) { + var isArray = Array.isArray(val); + if (typeof val !== 'string' && !isArray) { + throw new Error('utils.arrayify() expects a string or array.'); + } + return isArray ? val : [val]; +}; + +/** + * Try to require a file, fail silently. Encapsulating try-catches + * also helps with v8 optimizations. + * + * @api private + */ + +utils.tryRequire = function tryRequire(fp) { + if (typeof fp === 'undefined') { + throw new Error('utils.tryRequire() expects a string.'); + } + + var key = 'tryRequire:' + fp; + if (cache.hasOwnProperty(key)) { + return cache[key]; + } + + try { + return (cache[key] = require(path.resolve(fp))); + } catch(err) { + console.error(chalk.red('verb cannot find'), chalk.bold(fp), err); + } + return {}; +}; + +/** + * Recursively try to read directories. + */ + +utils.tryReaddirs = function tryReaddirs(dir, ignored) { + if (typeof dir === 'undefined') { + throw new Error('utils.tryReaddirs() expects a string.'); + } + + var files = utils.tryReaddir(dir); + var res = [], len = files.length, i = 0; + + var isIgnored = utils.matchesAny(ignored); + while (len--) { + var fp = relative(path.resolve(dir, files[i++])); + if (isIgnored(fp)) continue; + + var stat = utils.tryStats(fp); + if (!stat) continue; + + if (stat.isDirectory()) { + if (fp.indexOf('.git') !== -1) { + continue; + } + res.push.apply(res, tryReaddirs(fp, ignored)); + } + res.push(fp); + } + return res; +}; + +/** + * Try to read a directory of files. Silently catches + * any errors and returns an empty array. + */ + +utils.tryStats = function tryStats(fp, verbose) { + if (typeof fp === 'undefined') { + throw new Error('utils.tryStats() expects a string.'); + } + + try { + return fs.statSync(fp); + } catch (err) { + if (verbose) console.log(err); + } + return null; +}; + +/** + * Get the basename of a file path, excluding extension. + * + * @param {String} `fp` + * @param {String} `ext` Optionally pass the extension. + */ + +utils.basename = function basename(fp, ext) { + return fp.substr(0, fp.length - (ext || path.extname(fp)).length); +}; + +/** + * Default `renameKey` function. + */ + +utils.renameKey = function renameKey(fp, acc, opts) { + fp = relative.toBase(opts.cwd, fp); + return utils.basename(fp); +}; + +/** + * Get the extension from a string, or the first string + * in an array (like glob patterns) + */ + +utils.getExt = function getExt(str) { + str = Array.isArray(str) ? str[0] : str; + return str.slice(str.lastIndexOf('.')); +}; + +/** + * Ensure that a file extension is formatted properly. + * + * @param {String} `ext` + */ + +utils.formatExt = function formatExt(ext) { + if (ext && ext[0] !== '.') ext = '.' + ext; + return ext; +}; + +/** + * Try to call `fn` on `filepath`, either returning the + * result when successful, or failing silently and + * returning null. + */ + +utils.tryCatch = function tryCatch(fn, fp) { + try { + return fn(path.resolve(fp)); + } catch(err) {} + return null; +}; + +/** + * Try to resolve the given path, or fail silently + */ + +utils.tryRequire = function tryRequire(fp) { + return utils.tryCatch(require, fp); +}; + +/** + * Try to resolve the given path, or fail silently + */ + +utils.tryResolve = function tryResolve(fp) { + return utils.tryCatch(require.resolve, fp); +}; + +/** + * Try to read a file, or fail silently + */ + +utils.tryRead = function tryRead(fp) { + return utils.tryCatch(utils.readFile, fp); +}; + +/** + * Try to read a directory of files. Silently catches + * any errors and returns an empty array. + */ + +utils.tryStat = function tryStat(fp) { + return utils.tryCatch(fs.statSync, fp); +}; + +/** + * Read a file + */ + +utils.readFile = function readFile(fp) { + return fs.readFileSync(fp, 'utf8'); +}; + +/** + * Write a file + */ + +utils.writeFile = function writeFile(fp, str) { + return fs.writeFileSync(fp, str); +}; + +/** + * Creates a matching function to use against + * the list of given files. + */ + +utils.match = function match(files) { + return function(pattern, options) { + return mm(files, pattern, options); + }; +}; + +/** + * Read a file + */ + +utils.tryReadJson = function tryReadJson(fp) { + try { + return JSON.parse(utils.readFile(fp)); + } catch(err) {} + return null; +}; + +/** + * Try to read a directory of files. Silently catches + * any errors and returns an empty array. + */ + +utils.tryReaddir = function tryReaddir(fp) { + try { + return fs.readdirSync(fp); + } catch (err) {} + return []; +}; + +/** + * Escape delimiters + */ + +utils.escape = function escape(file, next) { + file.content = file.content.split('{%%').join('__LEFT_DELIM__'); + next(); +}; + +/** + * Unescape delimiters + */ + +utils.unescape = function unescape(file, next) { + file.content = file.content.split('__LEFT_DELIM__').join('{%'); + next(); +}; From 6a1340404b1ef64b98756687cbe79973d776778c Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 27 Apr 2015 08:30:48 -0400 Subject: [PATCH 070/274] adds modifiers index, no transforms yet --- lib/transforms/modifiers/index.js | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 lib/transforms/modifiers/index.js diff --git a/lib/transforms/modifiers/index.js b/lib/transforms/modifiers/index.js new file mode 100644 index 0000000..874ea55 --- /dev/null +++ b/lib/transforms/modifiers/index.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require('export-files')(__dirname); From 6c8b88daf29a80ea843c556017e6eb7b519318ce Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 27 Apr 2015 08:37:00 -0400 Subject: [PATCH 071/274] adds/updates a ton of transforms --- lib/transforms/env/author.js | 17 +++++++++ lib/transforms/{init => env}/cwd.js | 0 lib/transforms/env/git.js | 15 ++++++++ lib/transforms/env/github.js | 20 +++++++++++ lib/transforms/env/paths.js | 9 +++++ lib/transforms/env/pkg.js | 17 +++++++++ lib/transforms/env/tmpl.js | 27 ++++++++++++++ lib/transforms/env/user.js | 10 ++++++ lib/transforms/env/username.js | 22 ++++++++++++ lib/transforms/init/argv.js | 3 +- lib/transforms/init/config.js | 29 +++++++++++---- lib/transforms/init/create.js | 8 ++--- .../init/{options.js => defaults.js} | 7 +++- lib/transforms/init/load.js | 28 ++------------- lib/transforms/init/metadata.js | 20 +++++++++++ lib/transforms/init/middleware.js | 35 ++++--------------- 16 files changed, 200 insertions(+), 67 deletions(-) create mode 100644 lib/transforms/env/author.js rename lib/transforms/{init => env}/cwd.js (100%) create mode 100644 lib/transforms/env/git.js create mode 100644 lib/transforms/env/github.js create mode 100644 lib/transforms/env/paths.js create mode 100644 lib/transforms/env/pkg.js create mode 100644 lib/transforms/env/tmpl.js create mode 100644 lib/transforms/env/user.js create mode 100644 lib/transforms/env/username.js rename lib/transforms/init/{options.js => defaults.js} (64%) create mode 100644 lib/transforms/init/metadata.js diff --git a/lib/transforms/env/author.js b/lib/transforms/env/author.js new file mode 100644 index 0000000..88837c2 --- /dev/null +++ b/lib/transforms/env/author.js @@ -0,0 +1,17 @@ +'use strict'; + +var author = require('parse-author'); + +/** + * Called in the `init` transform. Adds an `author` + * property to the context, or normalizes the existing one. + */ + +module.exports = function author_(app) { + var res = app.get('data.author'); + if (res && typeof res === 'string') { + app.data({author: author(res)}); + } else { + app.data({author: {}}) + } +}; diff --git a/lib/transforms/init/cwd.js b/lib/transforms/env/cwd.js similarity index 100% rename from lib/transforms/init/cwd.js rename to lib/transforms/env/cwd.js diff --git a/lib/transforms/env/git.js b/lib/transforms/env/git.js new file mode 100644 index 0000000..c0fee38 --- /dev/null +++ b/lib/transforms/env/git.js @@ -0,0 +1,15 @@ +'use strict'; + +var username = require('git-user-name'); + +/** + * Called in the `username` transform, if a `username` + * cannot be determined from easier means, this attempts + * to get the `user.name` from global `.git config` + */ + +module.exports = function username_(app) { + if (!app.get('data.git.username')) { + app.set('data.git.username', username()); + } +}; diff --git a/lib/transforms/env/github.js b/lib/transforms/env/github.js new file mode 100644 index 0000000..a55568a --- /dev/null +++ b/lib/transforms/env/github.js @@ -0,0 +1,20 @@ +'use strict'; + +var parse = require('parse-github-url'); + +/** + * Adds a `github` property to the context. + * Called in the `init` transform. + */ + +module.exports = function github_(app) { + var repo = app.get('data.repository'); + var url = (repo && typeof repo === 'object') + ? repo.url + : repo; + + var github = parse(url); + if (github && Object.keys(github).length) { + app.data({github: github}); + } +}; diff --git a/lib/transforms/env/paths.js b/lib/transforms/env/paths.js new file mode 100644 index 0000000..b587a8b --- /dev/null +++ b/lib/transforms/env/paths.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * Prime the `update.paths` object. + */ + +module.exports = function paths_(app) { + if (!app.has('paths')) app.paths = {}; +}; diff --git a/lib/transforms/env/pkg.js b/lib/transforms/env/pkg.js new file mode 100644 index 0000000..2b9406f --- /dev/null +++ b/lib/transforms/env/pkg.js @@ -0,0 +1,17 @@ +'use strict'; + +var utils = require('../../utils'); +var cache = {}; + +/** + * Extend the package.json object onto `update.cache.data`. + * Called in the `init` transform. + */ + +module.exports = function pkg_(app) { + var filename = app.option('config') || 'package.json'; + + app.data(filename, function (fp) { + return cache[fp] || (cache[fp] = utils.tryRequire(fp)); + }); +}; diff --git a/lib/transforms/env/tmpl.js b/lib/transforms/env/tmpl.js new file mode 100644 index 0000000..1e299e6 --- /dev/null +++ b/lib/transforms/env/tmpl.js @@ -0,0 +1,27 @@ +'use strict'; + +var path = require('path'); + +/** + * Get/set the current working directory + * + * ```js + * console.log(app.templates); + * //=> /dev/foo/bar/ + * ``` + * Or set: + * + * ```js + * app.templates = 'foo'; + * ``` + */ + +module.exports = function templates_(app) { + var dir = app.option('templates'); + + if (typeof dir === 'undefined') { + dir = path.join(process.cwd(), 'templates'); + } + + app.set('paths.templates'); +}; diff --git a/lib/transforms/env/user.js b/lib/transforms/env/user.js new file mode 100644 index 0000000..dc33a45 --- /dev/null +++ b/lib/transforms/env/user.js @@ -0,0 +1,10 @@ +'use strict'; + +/** + * Called in the `init` transform. Adds `user` and `username` + * for the current project to the context. + */ + +module.exports = function user_(app) { + app.set('data.username', app.get('data.github.username')); +}; diff --git a/lib/transforms/env/username.js b/lib/transforms/env/username.js new file mode 100644 index 0000000..e357d3f --- /dev/null +++ b/lib/transforms/env/username.js @@ -0,0 +1,22 @@ +'use strict'; + +var github = require('parse-github-url'); + +/** + * If the `git` transform was not able to find anything, + * this attempts to generate a username from other fields. + * + * Called in the `init` transform. + */ + +module.exports = function username_(app) { + if (!app.get('data.github.username')) { + var author = app.get('data.author'); + if (typeof author.url === 'string' && /\/github/.test(author.url)) { + var parsed = github(author.url); + var user = (parsed && parsed.user) || ''; + app.set('data.github.username', user); + app.set('data.username', user); + } + } +}; diff --git a/lib/transforms/init/argv.js b/lib/transforms/init/argv.js index b2f950e..11d69cc 100644 --- a/lib/transforms/init/argv.js +++ b/lib/transforms/init/argv.js @@ -1,7 +1,8 @@ 'use strict'; /** - * Prime `app.cache.argv` + * Prime the `update.cache.argv` object. Used for setting values + * that are passed from the command line. */ module.exports = function argv_(app) { diff --git a/lib/transforms/init/config.js b/lib/transforms/init/config.js index cae1615..ae1d7af 100644 --- a/lib/transforms/init/config.js +++ b/lib/transforms/init/config.js @@ -2,24 +2,30 @@ var Store = require('data-store'); var chalk = require('chalk'); +var _ = require('lodash'); /** - * Create a global data config for user values. + * Create a global config store for user values. * * To get and set values, do the following: * * - set: `--set one=abc` or `--set one` * - get: `--set one` or `--get one,two,three` + * - del: `--del` (deletes the entire store) + * + * Called in the `init` transform. */ -module.exports = function config_() { - var config = this.config = new Store('app'); - var del = this.get('argv.del'); - var set = this.get('argv.set'); - var get = this.get('argv.get'); +module.exports = function config_(app) { + var config = app.config = new Store('update'); + var del = app.get('argv.del'); + var set = app.get('argv.set'); + var union = app.get('argv.union'); + var get = app.get('argv.get'); + var args; if (set) { - var args = set.split('='); + args = set.split('='); if (args.length === 2) { config.set(args[0], args[1]); } else { @@ -27,6 +33,15 @@ module.exports = function config_() { } } + if (union) { + args = union.split('='); + if (args.length > 1) { + var val = config.get(args[1]); + args[2] = args[2].split(','); + config.set(args[1], _.union(val, args[2])); + } + } + if (get) { if (get === true || get === 'true') { console.log(chalk.cyan('config config:'), chalk.bold(JSON.stringify(config.data))); diff --git a/lib/transforms/init/create.js b/lib/transforms/init/create.js index c5a9076..0d5139a 100644 --- a/lib/transforms/init/create.js +++ b/lib/transforms/init/create.js @@ -4,9 +4,7 @@ * Create built-in template types, using the `base` loader */ -module.exports = function create_(app) { - app.create('example', {isRenderable: true}, ['base']); - app.create('include', {isRenderable: true}, ['base']); - app.create('badge', {isRenderable: true}, ['base']); - app.create('doc', {isRenderable: true}, ['base']); +module.exports = function create_() { + this.create('include', {isRenderable: true}, ['base']); + this.create('dotfile', {isRenderable: true}, ['base']); }; diff --git a/lib/transforms/init/options.js b/lib/transforms/init/defaults.js similarity index 64% rename from lib/transforms/init/options.js rename to lib/transforms/init/defaults.js index d07facc..78f3af0 100644 --- a/lib/transforms/init/options.js +++ b/lib/transforms/init/defaults.js @@ -12,8 +12,13 @@ module.exports = function options_(app) { // engines app.option('view engine', 'md'); + // routing + app.option('router methods', ['onInit', 'onRender']); + app.enable('case sensitive routing'); + app.enable('strict routing'); + // delimiters - app.option('layoutDelims', ['{%', '%}']); + app.option('layoutDelims', ['{%=', '%}']); app.option('escapeDelims', { from: ['<%%', '%>'], to: ['<%', '%>'] diff --git a/lib/transforms/init/load.js b/lib/transforms/init/load.js index cbdab1e..71b3e70 100644 --- a/lib/transforms/init/load.js +++ b/lib/transforms/init/load.js @@ -1,32 +1,10 @@ 'use strict'; -var path = require('path'); -var cwd = require('cwd'); - /** - * Load built-in template types + * Load built-in templates */ module.exports = function load_(app) { - var includes = tryRequire(app.config.get('includes'), './includes'); - app.includes('**/*.md', { cwd: includes, cache: true }); + app.includes('templates/**/*.*', { cwd: process.cwd()}); + app.dotfiles('templates/**/_*', { cwd: process.cwd()}); }; - -function tryRequire(name, fallback) { - if (typeof name === 'string') { - try { - return require(name); - } catch(err) { - try { - return require(path.resolve(name)); - } catch(err) {} - return path.resolve(fallback); - } - } else { - try { - return require(fallback); - } catch(err) { - return path.resolve(fallback); - } - } -} diff --git a/lib/transforms/init/metadata.js b/lib/transforms/init/metadata.js new file mode 100644 index 0000000..4268fa3 --- /dev/null +++ b/lib/transforms/init/metadata.js @@ -0,0 +1,20 @@ +'use strict'; + +var path = require('path'); + +/** + * Adds Updates's package.json data to `update.metadata`. + * + * Called in the `init` transform. + */ + +module.exports = function metadata_(app) { + Object.defineProperty(app, 'metadata', { + get: function () { + return require(path.resolve(__dirname, '../../..', 'package.json')); + }, + set: function () { + console.log('`update.metadata` is read-only and cannot be modified.'); + } + }); +}; diff --git a/lib/transforms/init/middleware.js b/lib/transforms/init/middleware.js index 0ac9821..00b6d24 100644 --- a/lib/transforms/init/middleware.js +++ b/lib/transforms/init/middleware.js @@ -1,42 +1,21 @@ 'use strict'; -var utils = require('../../utils/'); +var utils = require('../../utils'); /** * Initialize default middleware */ -module.exports = function middleware_(app) { - app.onLoad(/\.js$/, utils.parallel([ - require('../../middleware/copyright')(app), - require('../../middleware/todos')(app), - ]), error('.onLoad (js):')); - - app.onLoad(/\.md$/, utils.series([ - require('../../middleware/conflict')(app), - require('../../middleware/copyright')(app), +module.exports = function middleware_() { + this.onLoad(/./, utils.series([ require('../../middleware/props'), - require('../../middleware/cwd')(app), require('../../middleware/engine'), + require('../../middleware/cwd')(this), require('../../middleware/src'), require('../../middleware/dest'), - require('../../middleware/ext'), - require('../../middleware/lint')(app), - require('template-toc')(app), - utils.escape, - ]), error('.onLoad (md):')); - - app.preRender(/\.md$/, utils.parallel([ - require('../../middleware/lint')(app), - require('../../middleware/multi-toc'), - require('../../middleware/readme'), - ]), error('.preRender (md):')); - - app.postRender(/\.md$/, utils.parallel([ - utils.unescape, - require('../../middleware/lint-after')(app), - require('../../middleware/diff')(app) - ]), error('.postRender:')); + // require('../../middleware/ext'), + // require('../../middleware/dotfiles')(this), + ]), error('.onLoad (.):')); }; function error(method) { From ac1eafa4eb4efaf85b07e6aa962fe0fde9046315 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 27 Apr 2015 08:37:09 -0400 Subject: [PATCH 072/274] adds middleware --- lib/middleware/dotfiles.js | 4 ++-- lib/middleware/engine.js | 10 ++++++++++ lib/middleware/ext.js | 14 ++++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 lib/middleware/engine.js create mode 100644 lib/middleware/ext.js diff --git a/lib/middleware/dotfiles.js b/lib/middleware/dotfiles.js index 64b686e..e5a786b 100644 --- a/lib/middleware/dotfiles.js +++ b/lib/middleware/dotfiles.js @@ -24,5 +24,5 @@ module.exports = function (app) { file.path = path.join(dirname, basename); } next(); - } -} + }; +}; diff --git a/lib/middleware/engine.js b/lib/middleware/engine.js new file mode 100644 index 0000000..88f358e --- /dev/null +++ b/lib/middleware/engine.js @@ -0,0 +1,10 @@ +'use strict'; + +/** + * Set the engine to use + */ + +module.exports = function engine_(file, next) { + file.options.engine = file.options.engine || file.ext || '.md'; + next(); +}; diff --git a/lib/middleware/ext.js b/lib/middleware/ext.js new file mode 100644 index 0000000..d132fec --- /dev/null +++ b/lib/middleware/ext.js @@ -0,0 +1,14 @@ +'use strict'; + +var path = require('path'); +var utils = require('../utils'); + +/** + * Prime the `file` object with properties that + * can be extended in plugins. + */ + +module.exports = function ext_(file, next) { + file.ext = utils.formatExt(file.ext || path.extname(file.path)); + next(); +}; From 047ab8695e0fdc7827c5bb28abaf3c439b787f17 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 27 Apr 2015 08:37:23 -0400 Subject: [PATCH 073/274] adds default helpers --- lib/helpers/collections.js | 33 +++++++++++++++++++++++++++++++++ lib/helpers/sync.js | 9 +++++++++ 2 files changed, 42 insertions(+) create mode 100644 lib/helpers/collections.js create mode 100644 lib/helpers/sync.js diff --git a/lib/helpers/collections.js b/lib/helpers/collections.js new file mode 100644 index 0000000..37eb502 --- /dev/null +++ b/lib/helpers/collections.js @@ -0,0 +1,33 @@ +'use strict'; + +var _ = require('lodash'); + +/** + * Transform for loading helper collections + * + * - Loads template-helpers + * - Loads logging-helpers + * - Exposes markdown-utils as helpers + * - exposes path helpers on the `path.` property + * + * ```js + * <%= mdu.link(author.name, author.url) %> + * //=> [Jon Schlinkert](https://github.com/jonschlinkert) + * + * <%= path.extname("foo.md") %> + * //=> '.md' + * ``` + */ + +module.exports = function collections_(verb) { + verb.helpers({console: console}); + verb.helpers(require('logging-helpers')); + + // namespaced helpers + verb.helpers({mdu: require('markdown-utils')}); + + // remove `path` helpers from root and add them to `path.` + var helpers = require('template-helpers'); + verb.helpers(_.omit(helpers._, Object.keys(helpers.path))); + verb.helpers({path: helpers.path}); +}; diff --git a/lib/helpers/sync.js b/lib/helpers/sync.js new file mode 100644 index 0000000..5e40dc4 --- /dev/null +++ b/lib/helpers/sync.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * Transform for loading default sync helpers + */ + +module.exports = function sync_(app) { + app.helper('date', require('helper-date')); +}; From e7903c8027e77d469ae728e3160c2bb6023ba10c Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 27 Apr 2015 08:37:33 -0400 Subject: [PATCH 074/274] update plugin stack, use merge --- lib/stack.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/stack.js b/lib/stack.js index 31eac54..10d3b5d 100644 --- a/lib/stack.js +++ b/lib/stack.js @@ -39,7 +39,7 @@ function createStack(app, plugins) { */ exports.src = function (app, glob, opts) { - opts = _.extend({}, app.options, opts); + opts = _.merge({}, app.options, opts); opts.cwd = app.get('updater.cwd'); session.set('src', opts); @@ -55,7 +55,7 @@ exports.src = function (app, glob, opts) { exports.templates = function (app, glob, opts) { opts = _.extend({}, app.options, opts); - opts.cwd = app.get('updater.cwd') + '/templates'; + opts.cwd = app.get('updater.templates'); session.set('templates', opts); return createStack(app, [ @@ -68,14 +68,13 @@ exports.templates = function (app, glob, opts) { * Default `dest` plugins to run. */ -exports.dest = function (app, dest, options) { +exports.dest = function (app, dest, opts) { var srcOpts = session.get('src') || session.get('templates') || {}; - var opts = _.extend({}, app.options, srcOpts, options); - var locals = opts.locals; + opts = _.merge({}, app.options, srcOpts, opts); return createStack(app, [ - plugins.dest.call(app, dest, opts), - plugins.render.call(app, opts, locals), + plugins.dest.call(app, dest, opts, opts.locals), + plugins.render.call(app, opts, opts.locals), vfs.dest(dest, opts) ]); }; From 3ee468c1eb13cb8a0cf4fcc0cd0c3157626b6e5b Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 27 Apr 2015 08:37:55 -0400 Subject: [PATCH 075/274] clean up, use `.pushToStream()` method --- lib/plugins/init.js | 59 +++++++-------------------------------------- 1 file changed, 9 insertions(+), 50 deletions(-) diff --git a/lib/plugins/init.js b/lib/plugins/init.js index 9de577c..632ab59 100644 --- a/lib/plugins/init.js +++ b/lib/plugins/init.js @@ -1,22 +1,18 @@ 'use strict'; +var PluginError = require('plugin-error'); var through = require('through2'); -var chalk = require('chalk'); -var File = require('vinyl'); -var utils = require('../utils'); -/** - * Expose `render` plugin - */ +module.exports = plugin('update', 'init'); -module.exports = plugin('update'); +function plugin(appname, name) { + var pluginname = appname + '-' + name + ':'; -function plugin(appname) { return function init_() { - var app = this, - id = this.gettask(); + var app = this; + var id = this.getTask(); - // create a template type for vinyl files and give it a loader + // create a template type for vinyl files and assign a loader if (!app.hasOwnProperty(id)) { app.create(id, ['task']); } @@ -26,9 +22,8 @@ function plugin(appname) { this.push(file); return cb(); } - if (file.isStream()) { - this.emit('error', new utils.PluginError('update-init:', 'Streaming is not supported.')); + this.emit('error', new PluginError(pluginname, 'Streaming is not supported.')); return cb(); } @@ -36,44 +31,8 @@ function plugin(appname) { app[id](file); cb(); }, function (cb) { - var plural = app.inflections[id]; - - // Convert template back to vinyl file and push into stream - pushToStream(app.views[plural], this, function (template) { - var file = new File({ - path: template.path - }); - - for (var key in template) { - if (template.hasOwnProperty(key)) { - file[key] = template[key]; - } - } - - file.contents = new Buffer(template.content); - app.handle('collections', file, handleError(file, 'collections')); - return file; - }); + app.pushToStream(id, this); cb(); }); - } -} - -function pushToStream(collection, stream, fn) { - var i = 0; - for (var key in collection) { - if (collection.hasOwnProperty(key)) { - var file = collection[key]; - stream.push(fn ? fn(file, i++) : file); - } - } -} - -function handleError(template, method) { - return function (err) { - if (err) { - console.error(chalk.red('Error running ' + method + ' middleware for', template.path)); - console.error(chalk.red(err)); - } }; } From a892108892996d5c18a96d269827ac62e9c0f240 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 27 Apr 2015 08:38:14 -0400 Subject: [PATCH 076/274] use `app` --- lib/transforms/updaters/index.js | 4 ++-- lib/transforms/updaters/updaters.js | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/transforms/updaters/index.js b/lib/transforms/updaters/index.js index 0fbff4b..7573e21 100644 --- a/lib/transforms/updaters/index.js +++ b/lib/transforms/updaters/index.js @@ -2,6 +2,6 @@ var transforms = require('export-files')(__dirname); -module.exports = function updaters_(update) { - this.transform('updaters', transforms.updaters); +module.exports = function updaters_(app) { + app.transform('updaters', transforms.updaters); }; diff --git a/lib/transforms/updaters/updaters.js b/lib/transforms/updaters/updaters.js index 3e8aedd..0bebf92 100644 --- a/lib/transforms/updaters/updaters.js +++ b/lib/transforms/updaters/updaters.js @@ -8,10 +8,9 @@ var _ = require('lodash'); * Load updaters onto the `updaters` object */ -module.exports = function updaters_() { - this.updaters = this.updaters || {}; - var pattern = this.option('updater pattern') || 'updater-*'; - console.log(pattern) +module.exports = function updaters_(app) { + app.updaters = app.updaters || {}; + var pattern = app.option('updater pattern') || 'updater-*'; _.transform(resolveUp(pattern), function (acc, dir) { var pkg = require(path.resolve(dir, 'package.json')); @@ -23,5 +22,5 @@ module.exports = function updaters_() { res.pkg = pkg; var name = pkg.name.split('updater-').join(''); acc[name] = res; - }, this.updaters); + }, app.updaters); }; From 8b8d4763d204ddf06d4d9894fdf8456a054523b0 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 27 Apr 2015 08:38:30 -0400 Subject: [PATCH 077/274] fix plugin --- lib/plugins/process.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/plugins/process.js b/lib/plugins/process.js index aa053ad..b0169a3 100644 --- a/lib/plugins/process.js +++ b/lib/plugins/process.js @@ -1,8 +1,8 @@ 'use strict'; +var PluginError = require('plugin-error'); var through = require('through2'); -var utils_ = require('utils'); -var utils = require('../utils'); +var utils = require('utils'); /** * Expose `process` plugin @@ -11,20 +11,20 @@ var utils = require('../utils'); module.exports = plugin('update'); function plugin(appname) { - return function (locals, options) { + return function (locals) { var app = this; - return function (file, enc, cb) { + return through.obj(function (file, enc, cb) { var collection = app.inflections[locals.id]; var template = app.views[collection][file.id]; template.content = file.contents.toString(); - var context = utils_.extend({}, locals, file.locals); + var context = utils.extend({}, locals, file.locals); try { var stream = this; app.render(template, context, function (err, content) { if (err) { - stream.emit('error', new utils.PluginError(appname + '-process', err)); + stream.emit('error', new PluginError(appname + '-process', err)); return cb(err); } file.contents = new Buffer(content); @@ -32,9 +32,9 @@ function plugin(appname) { cb(); }); } catch (err) { - this.emit('error', new utils.PluginError(appname + '-process', err)); + this.emit('error', new PluginError(appname + '-process', err)); return cb(); } - }; + }); }; -}; +} From 2a5bc8332b0e4f82c00fe7e6e3969d710c53e53e Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 27 Apr 2015 08:38:58 -0400 Subject: [PATCH 078/274] wrap, to make it easier to externalize --- lib/plugins/dest.js | 70 ++++++++++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/lib/plugins/dest.js b/lib/plugins/dest.js index a010ced..ae03002 100644 --- a/lib/plugins/dest.js +++ b/lib/plugins/dest.js @@ -6,39 +6,49 @@ var path = require('path'); var through = require('through2'); +var PluginError = require('plugin-error'); var utils = require('../utils'); /** * Add dest properties to `file.data` */ -module.exports = function dest_(destDir) { - return through.obj(function (file, enc, cb) { - if (file.isNull()) { - this.push(file); - return cb(); - } - if (file.isStream()) { - this.emit('error', new utils.PluginError('update-init:', 'Streaming is not supported.')); - return cb(); - } - - try { - var dest = file.data.dest || {}; - dest.relative = file.relative; - dest.dirname = file.dest || path.dirname(file.path); - dest.extname = path.extname(file.relative); - dest.basename = path.basename(file.relative); - dest.filename = utils.basename(dest.basename, dest.extname); - dest.path = path.join(dest.dirname, dest.basename); - - file.data.dest = dest; - this.push(file); - return cb(); - } catch (err) { - console.error(err); - this.emit('error', new utils.PluginError('generate-dest:', err)); - return cb(); - } - }); -}; +module.exports = plugin('verb', 'dest'); + +function plugin(appname, name) { + var pluginname = appname + '-' + name; + + return function dest_(destDir) { + return through.obj(function (file, enc, cb) { + if (file.isNull()) { + this.push(file); + return cb(); + } + if (file.isStream()) { + this.emit('error', new PluginError(pluginname, 'Streaming is not supported.')); + return cb(); + } + + try { + var dest = file.data.dest || {}; + if (typeof destDir === 'function') { + dest.dirname = destDir(file); + } else { + dest.dirname = path.dirname(file.path); + } + dest.relative = file.relative; + dest.extname = path.extname(file.relative); + dest.basename = path.basename(file.relative); + dest.filename = utils.basename(dest.basename, dest.extname); + dest.path = path.join(dest.dirname, dest.basename); + + file.data.dest = dest; + this.push(file); + return cb(); + } catch (err) { + this.emit('error', new PluginError(pluginname, err)); + return cb(); + } + }); + }; +} From 9cb36d57dab0f5b0f73542da2c050f4d6da2f354 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 27 Apr 2015 08:39:18 -0400 Subject: [PATCH 079/274] minor edits --- lib/loaders/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/loaders/index.js b/lib/loaders/index.js index f62f793..69c8b1c 100644 --- a/lib/loaders/index.js +++ b/lib/loaders/index.js @@ -1,4 +1,6 @@ 'use strict'; -module.exports = require('export-files')(__dirname); - +module.exports = { + base: require('./base'), + task: require('./task') +}; From fffeb29de8ded138307324b7d187434a49724da1 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 27 Apr 2015 08:40:27 -0400 Subject: [PATCH 080/274] ensure that `args` is an array --- lib/loaders/base.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/loaders/base.js b/lib/loaders/base.js index 155a87d..877ad4e 100644 --- a/lib/loaders/base.js +++ b/lib/loaders/base.js @@ -4,12 +4,13 @@ var mapFiles = require('map-files'); var utils = require('../utils'); module.exports = function base_(args) { + args = utils.arrayify(args); var hasOpts = typeof args[1] === 'object'; if (hasOpts) args[1].renameKey = utils.renameKey; if (hasOpts && args[1].hasOwnProperty('content')) { return normalize(args); } - return mapFiles.apply(mapFiles, utils.arrayify(args)); + return mapFiles.apply(mapFiles, args); }; function normalize(args) { From b277f511bd3e5219758438baab711d8305953fdc Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 27 Apr 2015 08:40:33 -0400 Subject: [PATCH 081/274] clean up --- lib/helpers/async.js | 4 ++-- lib/plugins/render.js | 36 +++++++++++++++++++----------------- lib/utils/index.js | 2 +- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/lib/helpers/async.js b/lib/helpers/async.js index 6a88604..fdd00c6 100644 --- a/lib/helpers/async.js +++ b/lib/helpers/async.js @@ -6,6 +6,6 @@ var helper = require('async-helper-base'); * Transform for loading default async helpers */ -module.exports = function async_(app) { - app.asyncHelper('include', helper('include')); +module.exports = function async_(verb) { + verb.asyncHelper('include', helper('include')); }; diff --git a/lib/plugins/render.js b/lib/plugins/render.js index 3a128e5..b5f37cc 100644 --- a/lib/plugins/render.js +++ b/lib/plugins/render.js @@ -4,19 +4,22 @@ * Module dependencies. */ -var PluginError = require('../utils/error'); +var _ = require('lodash'); +var PluginError = require('plugin-error'); var through = require('through2'); -var utils = require('utils'); /** * Expose `render` plugin */ -module.exports = plugin('update'); +module.exports = plugin('verb', 'render'); + +function plugin(appname, name) { + var pluginname = appname + '-' + name + ':'; -function plugin(appname) { return function render_(locals) { var app = this; + locals = locals || {}; locals.options = locals.options || {}; @@ -26,41 +29,40 @@ function plugin(appname) { return cb(); } if (file.isStream()) { - this.emit('error', new PluginError(appname + '-render', 'Streaming is not supported.')); + this.emit('error', new PluginError(pluginname, 'Streaming is not supported.')); return cb(); } - var template = app.getFile(file); - template.content = file.contents.toString(); - - locals = utils.merge({}, locals, file.locals); - locals.options = utils.merge({}, locals.options, app.options); + locals = _.merge({}, locals, file.locals); + locals.options = _.merge({}, app.options, locals.options); if (norender(app, file.ext, file, locals)) { this.push(file); return cb(); } + var template = app.getFile(file); + template.content = file.contents.toString(); + try { var stream = this; - template.render(locals, function(err, content) { + template.render(locals, function (err, content) { if (err) { - stream.emit('error', new PluginError(appname + '-render', err)); + stream.emit('error', new PluginError(pluginname, err)); cb(err); return; } - file.contents = new Buffer(content); stream.push(file); - cb(); + return cb(); }); } catch (err) { - this.emit('error', new PluginError(appname + '-render', err)); + this.emit('error', new PluginError(pluginname, err)); return cb(); } }); - } + }; } /** @@ -70,7 +72,7 @@ function plugin(appname) { function norender(app, ext, file, locals) { return !app.engines.hasOwnProperty(ext) - || app.enabled('norender') || app.disabled('render') + || app.isTrue('norender') || app.isFalse('render') || file.norender === true || file.render === false || locals.norender === true || locals.render === false; } diff --git a/lib/utils/index.js b/lib/utils/index.js index b8d0707..e817c41 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -78,7 +78,7 @@ utils.tryRequire = function tryRequire(fp) { try { return (cache[key] = require(path.resolve(fp))); } catch(err) { - console.error(chalk.red('verb cannot find'), chalk.bold(fp), err); + console.error(chalk.red('update cannot find'), chalk.bold(fp), err); } return {}; }; From 0c7a90b7f361bc74c96cc38c5f005382f8548098 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 27 Apr 2015 08:40:46 -0400 Subject: [PATCH 082/274] initialize transforms --- lib/init.js | 40 +++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/lib/init.js b/lib/init.js index ae0f48a..38609b4 100644 --- a/lib/init.js +++ b/lib/init.js @@ -6,7 +6,8 @@ var init = transforms.init; var env = transforms.env; /** - * Load initialization transforms: + * Load initialization transforms + * * | runner * | loaders * | create @@ -15,11 +16,40 @@ var env = transforms.env; * | plugins * | load * | engines - * | helpers (load last) + * | helpers - load helpers last */ - module.exports = function init_(app) { app.transform('updaters', updaters); - app.transform('session', init.session); - app.transform('store', init.store); + + app.transform('metadata', init.metadata); + app.transform('args', init.argv); + // app.transform('ignore', init.ignore); + // app.transform('files', env.files); + + app.transform('env', env.env); + app.transform('pkg', env.pkg); + app.transform('paths', env.paths); + app.transform('cwd', env.cwd); + app.transform('keys', env.keys); + app.transform('author', env.author); + app.transform('user', env.user); + app.transform('username', env.username); + app.transform('github', env.github); + + app.on('init', function () { + app.transform('config', init.config); + app.transform('runner', init.runner); + app.transform('defaults', init.defaults); + app.transform('loaders', init.loaders); + app.transform('create', init.create); + app.transform('engines', init.engines); + app.transform('middleware', init.middleware); + app.transform('helpers', init.helpers); + app.transform('load', init.load); + app.emit('loaded'); + }); + + app.on('loaded', function () { + app.transform('helpers', init.helpers); + }); }; From 4b6df1cfd1a5806a2e378a3c693bbe597a5bbe76 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 27 Apr 2015 08:40:59 -0400 Subject: [PATCH 083/274] set variables after the instance --- bin/update.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/bin/update.js b/bin/update.js index 6434a56..1ac07d8 100755 --- a/bin/update.js +++ b/bin/update.js @@ -8,21 +8,27 @@ var taskTree = require('../lib/utils/task-tree'); var update = require('..'); var argv = require('minimist')(process.argv.slice(2)); -update.extend('args', argv); var stack = argv._; var name = stack.shift(); var tasks = stack.length ? stack : ['default']; -var updater = update.updater(name); +var updater = typeof name !== 'undefined' + ? update.updater(name) + : exit(0); + +// var updater = update.updater(name); var file = updater.module; if (file) { var cwd = path.dirname(file); - update.set('updater.cwd', cwd); - update.emit('loaded'); var instance = require(file); + instance.set('updater.cwd', cwd); + instance.set('updater.templates', cwd + '/templates'); + instance.emit('init'); + instance.emit('loaded'); + instance.extend('argv', argv); process.nextTick(function () { instance.start.apply(instance, tasks); From d0a9394ec133a43a6ee6acff31b5b30bde321d46 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 27 Apr 2015 08:41:27 -0400 Subject: [PATCH 084/274] adds awesome methods. --- index.js | 120 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 108 insertions(+), 12 deletions(-) diff --git a/index.js b/index.js index 79c7875..9d5d0ca 100644 --- a/index.js +++ b/index.js @@ -8,11 +8,12 @@ 'use strict'; var path = require('path'); -var extend = require('lodash')._.extend; var through = require('through2'); var Template = require('template'); +var toVinyl = require('to-vinyl'); var Task = require('orchestrator'); var vfs = require('vinyl-fs'); +var _ = require('lodash'); /** * Local dependencies @@ -21,6 +22,7 @@ var vfs = require('vinyl-fs'); var plugins = require('./lib/plugins'); var session = require('./lib/session'); var stack = require('./lib/stack'); +var utils = require('./lib/utils'); var init = require('./lib/init'); /** @@ -36,12 +38,11 @@ var init = require('./lib/init'); function Update(opts) { Template.call(this, opts); Task.call(this, opts); - this.transforms = this.transforms || {}; this.session = session; init.call(this, this); } -extend(Update.prototype, Task.prototype); +_.extend(Update.prototype, Task.prototype); Template.extend(Update.prototype); /** @@ -134,7 +135,29 @@ Update.prototype.dest = function(dest, opts) { */ Update.prototype.copy = function(glob, dest, opts) { - return vfs.src(this, glob).pipe(vfs.dest(dest, opts)); + return stack.templates(this, glob, {cwd: cwd}) + .pipe(this.process(opts)) + .pipe(vfs.dest(dest, opts)); +}; + +/** + * Plugin for processing templates using any registered engine. + * If this plugin is NOT used, engines will be selected based + * on file extension. + * + * ```js + * app.process(); + * ``` + * + * @param {String|Array} `glob` + * @param {String|Function} `dest` + * @return {Stream} Stream to allow doing additional work. + */ + +Update.prototype.process = function(locals, options) { + locals = _.merge({id: this.gettask()}, this.cache.data, locals); + locals.options = _.merge({}, this.options, options, locals.options); + return through.obj(plugins.process.call(this, locals, options)); }; /** @@ -154,29 +177,49 @@ Update.prototype.copy = function(glob, dest, opts) { Update.prototype.task = Update.prototype.add; /** - * Get the name of the currently running task. This is - * primarily used inside plugins. + * Get the id from the current task. Used in plugins to get + * the current session. * * ```js - * app.gettask(); + * var id = verb.getTask(); + * verb.views[id]; * ``` * * @return {String} `task` The currently running task. * @api public */ -Update.prototype.gettask = function() { +Update.prototype.getTask = function() { var name = this.session.get('task'); - return typeof name != 'undefined' + return typeof name !== 'undefined' ? 'task_' + name : 'file'; }; +/** + * Get the collection name (inflection) of the given + * template type. + * + * ```js + * var collection = verb.getCollection('page'); + * // gets the `pages` collection + * //=> {a: {}, b: {}, ...} + * ``` + * + * @return {String} `name` Singular name of the collection to get + * @api public + */ + +Update.prototype.getCollection = function(name) { + var plural = this.inflections[name]; + return this.views[plural]; +}; + /** * Used in plugins to get a template from the current session. * * ```js - * var template = getFile(id, file); + * var views = app.getViews(); * ``` * * @return {String} `id` Pass the task-id from the current session. @@ -184,11 +227,64 @@ Update.prototype.gettask = function() { * @api public */ +Update.prototype.getViews = function() { + var collection = this.inflections[this.getTask()]; + return this.views[collection]; +}; + +/** + * Get a template (file) from the current session in a stream. + * + * ```js + * var file = app.getFile(file); + * ``` + * + * @return {Object} `file` Vinyl file object. Must have an `id` property that matches the `id` of the session. + * @api public + */ + Update.prototype.getFile = function(file) { - var collection = this.inflections[this.gettask()]; - return this.views[collection][file.id]; + return this.getViews()[file.id]; +}; + +/** + * Get a template from the current session, convert it to a vinyl + * file, and push it into the stream. + * + * ```js + * app.pushToStream(file); + * ``` + * + * @param {Stream} `stream` Vinyl stream + * @param {String} `id` Get the session `id` using `app.getTask()` + * @api public + */ + +Update.prototype.pushToStream = function(id, stream) { + return utils.pushToStream(this.getCollection(id), stream, toVinyl); }; +/** + * `taskFiles` is a session-context-specific getter that + * returns the collection of files from the current `task`. + * + * ```js + * var files = verb.taskFiles; + * ``` + * + * @name .taskFiles + * @return {Object} Get the files from the current task. + * @api public + */ + +Object.defineProperty(Update.prototype, 'taskFiles', { + configurable: true, + enumerable: false, + get: function () { + return this.views[this.inflections[this.getTask()]]; + } +}); + /** * Set or get a generator function by `name`. * From cfd8da681a4c3dbceabd5378d98e7025b4f81866 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 27 Apr 2015 08:41:51 -0400 Subject: [PATCH 085/274] add deps for new utils/transforms/helpers/plugins --- package.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 5ca35d1..9022f24 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "test": "mocha" }, "dependencies": { + "async": "^0.9.0", "async-helper-base": "^0.2.0", "chalk": "^1.0.0", "cwd": "^0.6.0", @@ -45,19 +46,29 @@ "event-stream": "^3.3.0", "export-dirs": "^0.2.4", "export-files": "^2.0.1", + "git-user-name": "^1.1.2", + "helper-date": "^0.2.1", "lodash": "^3.7.0", + "logging-helpers": "^0.4.0", "map-files": "^0.7.4", + "markdown-utils": "^0.6.0", + "micromatch": "^2.1.6", "minimist": "^1.1.1", "orchestrator": "^0.3.7", + "parse-author": "^0.2.0", + "parse-github-url": "^0.1.0", + "plugin-error": "^0.1.0", "pretty-hrtime": "^1.0.0", + "relative": "^3.0.0", "resolve-up": "^0.1.1", "session-cache": "^0.1.3", "sessionify": "^0.1.0", "template": "^0.13.1", + "template-helpers": "^0.3.2", "template-toc": "^0.3.3", "through2": "^0.6.3", + "to-vinyl": "^0.1.2", "utils": "^0.2.1", - "vinyl": "^0.4.6", "vinyl-fs": "^1.0.0" }, "devDependencies": { From ecbbad67cf87aaabeb52239682511ecce5f953f9 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 27 Apr 2015 08:45:33 -0400 Subject: [PATCH 086/274] move `createStack` --- lib/stack.js | 23 ++++------------------- lib/utils/index.js | 30 ++++++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/lib/stack.js b/lib/stack.js index 10d3b5d..3800bb9 100644 --- a/lib/stack.js +++ b/lib/stack.js @@ -4,10 +4,7 @@ * Module dependencies. */ -var through = require('through2'); -var sessionify = require('sessionify'); var vfs = require('vinyl-fs'); -var es = require('event-stream'); var _ = require('lodash'); /** @@ -16,24 +13,12 @@ var _ = require('lodash'); var plugins = require('./plugins'); var session = require('./session'); +var utils = require('./utils'); /** * Create a plugin stack to be run by `src` or `dest` */ -function createStack(app, plugins) { - var stack = []; - plugins.forEach(function (plugin) { - if (plugin == null) { - stack.push(through.obj()); - } else { - stack.push(plugin); - } - }); - var res = es.pipe.apply(es, stack); - return sessionify(res, session); -} - /** * Default `src` plugins to run. */ @@ -43,7 +28,7 @@ exports.src = function (app, glob, opts) { opts.cwd = app.get('updater.cwd'); session.set('src', opts); - return createStack(app, [ + return utils.createStack(app, [ vfs. src(glob, opts), plugins.init.call(app, opts) ]); @@ -58,7 +43,7 @@ exports.templates = function (app, glob, opts) { opts.cwd = app.get('updater.templates'); session.set('templates', opts); - return createStack(app, [ + return utils.createStack(app, [ vfs.src(glob, opts), plugins.init.call(app, opts) ]); @@ -72,7 +57,7 @@ exports.dest = function (app, dest, opts) { var srcOpts = session.get('src') || session.get('templates') || {}; opts = _.merge({}, app.options, srcOpts, opts); - return createStack(app, [ + return utils.createStack(app, [ plugins.dest.call(app, dest, opts, opts.locals), plugins.render.call(app, opts, opts.locals), vfs.dest(dest, opts) diff --git a/lib/utils/index.js b/lib/utils/index.js index e817c41..c73fa38 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -6,10 +6,20 @@ var fs = require('fs'); var path = require('path'); -var chalk = require('chalk'); var async = require('async'); -var relative = require('relative'); +var chalk = require('chalk'); +var es = require('event-stream'); var mm = require('micromatch'); +var relative = require('relative'); +var sessionify = require('sessionify'); +var through = require('through2'); + +/** + * Local dependencies + */ + + +var session = require('./session'); /** * File cache @@ -23,6 +33,22 @@ var cache = {}; var utils = module.exports = require('export-files')(__dirname); +/** + * Create a plugin stack + */ + +utils.createStack = function createStack(app, plugins) { + var stack = []; + plugins.forEach(function (plugin) { + if (plugin == null) { + stack.push(through.obj()); + } else { + stack.push(plugin); + } + }); + var res = es.pipe.apply(es, stack); + return sessionify(res, session); +}; /** * Run middleware in series From bbbe116eb528d406aeece039c91af908187496c3 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 27 Apr 2015 08:45:42 -0400 Subject: [PATCH 087/274] lint --- lib/transforms/env/author.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/transforms/env/author.js b/lib/transforms/env/author.js index 88837c2..fe2e4c3 100644 --- a/lib/transforms/env/author.js +++ b/lib/transforms/env/author.js @@ -12,6 +12,6 @@ module.exports = function author_(app) { if (res && typeof res === 'string') { app.data({author: author(res)}); } else { - app.data({author: {}}) + app.data({author: {}}); } }; From aa3b9d28604abc0187a5be2b85f311861683a433 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 27 Apr 2015 12:38:07 -0400 Subject: [PATCH 088/274] adds `plugin` method --- index.js | 43 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index 9d5d0ca..76745eb 100644 --- a/index.js +++ b/index.js @@ -45,6 +45,40 @@ function Update(opts) { _.extend(Update.prototype, Task.prototype); Template.extend(Update.prototype); +/** + * Glob patterns or filepaths to source files. + * + * ```js + * app.src('*.js') + * ``` + * + * **Example usage** + * + * ```js + * app.task('web-app', function() { + * app.src('templates/*') + * app.dest(process.cwd()) + * }); + * ``` + * + * @param {String|Array} `glob` Glob patterns or file paths to source files. + * @param {Object} `options` Options or locals to merge into the context and/or pass to `src` plugins + * @api public + */ + +Update.prototype.plugin = function(name, fn) { + if (!fn) return this.plugins[name]; + if (name && typeof name === 'object') { + for (var key in name) { + this.plugin(key, name[key]); + } + } else { + this.plugins[name] = fn; + } + console.log(this) + return this; +}; + /** * Glob patterns or filepaths to source files. * @@ -118,7 +152,6 @@ Update.prototype.templates = function(glob, opts) { */ Update.prototype.dest = function(dest, opts) { - dest = path.resolve((opts && opts.cwd) || process.cwd(), dest); return stack.dest(this, dest, opts); }; @@ -135,8 +168,8 @@ Update.prototype.dest = function(dest, opts) { */ Update.prototype.copy = function(glob, dest, opts) { - return stack.templates(this, glob, {cwd: cwd}) - .pipe(this.process(opts)) + return stack.templates(this, glob, opts) + // .pipe(this.process(opts)) .pipe(vfs.dest(dest, opts)); }; @@ -155,9 +188,9 @@ Update.prototype.copy = function(glob, dest, opts) { */ Update.prototype.process = function(locals, options) { - locals = _.merge({id: this.gettask()}, this.cache.data, locals); + locals = _.merge({id: this.getTask()}, this.cache.data, locals); locals.options = _.merge({}, this.options, options, locals.options); - return through.obj(plugins.process.call(this, locals, options)); + return plugins.process.call(this, locals, options); }; /** From 1467e92fc16458b0061b4db8bd0a939c57c03a07 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 27 Apr 2015 12:38:19 -0400 Subject: [PATCH 089/274] clean up --- bin/update.js | 9 ++++----- index.js | 1 - updatefile.js | 9 +-------- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/bin/update.js b/bin/update.js index 1ac07d8..7cc7cb3 100755 --- a/bin/update.js +++ b/bin/update.js @@ -17,13 +17,12 @@ var updater = typeof name !== 'undefined' ? update.updater(name) : exit(0); -// var updater = update.updater(name); -var file = updater.module; +var fp = updater.module; -if (file) { - var cwd = path.dirname(file); +if (fp) { + var cwd = path.dirname(fp); + var instance = require(fp); - var instance = require(file); instance.set('updater.cwd', cwd); instance.set('updater.templates', cwd + '/templates'); instance.emit('init'); diff --git a/index.js b/index.js index 76745eb..744b86c 100644 --- a/index.js +++ b/index.js @@ -75,7 +75,6 @@ Update.prototype.plugin = function(name, fn) { } else { this.plugins[name] = fn; } - console.log(this) return this; }; diff --git a/updatefile.js b/updatefile.js index 5fcafa8..8a03154 100644 --- a/updatefile.js +++ b/updatefile.js @@ -1,15 +1,8 @@ - var update = require('./'); var del = require('del'); update.task('default', function () { - update.src('*.*') - // .pipe(update.dest('./actual')) - // .on('end', function (cb) { - // process.nextTick(function () { - // del('actual', cb); - // }) - // }); + del('actual', cb); }); update.run(); From 146e9dea3b1fa35311c1276413e865bbe91c1d6c Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 27 Apr 2015 12:39:31 -0400 Subject: [PATCH 090/274] add `git` and `plugins` transforms --- lib/init.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/init.js b/lib/init.js index 38609b4..00194ae 100644 --- a/lib/init.js +++ b/lib/init.js @@ -22,6 +22,7 @@ module.exports = function init_(app) { app.transform('updaters', updaters); app.transform('metadata', init.metadata); + app.transform('plugins', init.plugins); app.transform('args', init.argv); // app.transform('ignore', init.ignore); // app.transform('files', env.files); @@ -31,6 +32,7 @@ module.exports = function init_(app) { app.transform('paths', env.paths); app.transform('cwd', env.cwd); app.transform('keys', env.keys); + app.transform('git', env.git); app.transform('author', env.author); app.transform('user', env.user); app.transform('username', env.username); From 4dad4ee60843e7e888088e6b8e38431da513dc1c Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 27 Apr 2015 12:39:49 -0400 Subject: [PATCH 091/274] try to get paths working properly --- lib/middleware/cwd.js | 2 +- lib/stack.js | 9 ++++++--- lib/transforms/env/cwd.js | 2 ++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/middleware/cwd.js b/lib/middleware/cwd.js index b71eec4..9abcfac 100644 --- a/lib/middleware/cwd.js +++ b/lib/middleware/cwd.js @@ -7,7 +7,7 @@ module.exports = function cwd_(app) { return function (file, next) { - file.cwd = file.data.cwd || app.cwd || file.cwd || '.'; + file.cwd = file.data.cwd || app.cwd || file.cwd || process.cwd(); next(); }; }; diff --git a/lib/stack.js b/lib/stack.js index 3800bb9..2423698 100644 --- a/lib/stack.js +++ b/lib/stack.js @@ -4,6 +4,7 @@ * Module dependencies. */ +var path = require('path'); var vfs = require('vinyl-fs'); var _ = require('lodash'); @@ -29,7 +30,7 @@ exports.src = function (app, glob, opts) { session.set('src', opts); return utils.createStack(app, [ - vfs. src(glob, opts), + vfs.src(glob, opts), plugins.init.call(app, opts) ]); }; @@ -39,8 +40,8 @@ exports.src = function (app, glob, opts) { */ exports.templates = function (app, glob, opts) { - opts = _.extend({}, app.options, opts); - opts.cwd = app.get('updater.templates'); + opts = _.merge({}, app.options, opts); + opts.cwd = app.get('updater.cwd'); session.set('templates', opts); return utils.createStack(app, [ @@ -56,6 +57,8 @@ exports.templates = function (app, glob, opts) { exports.dest = function (app, dest, opts) { var srcOpts = session.get('src') || session.get('templates') || {}; opts = _.merge({}, app.options, srcOpts, opts); + opts.cwd = process.cwd(); + dest = path.resolve(opts.cwd, dest); return utils.createStack(app, [ plugins.dest.call(app, dest, opts, opts.locals), diff --git a/lib/transforms/env/cwd.js b/lib/transforms/env/cwd.js index 5248c1f..b637d01 100644 --- a/lib/transforms/env/cwd.js +++ b/lib/transforms/env/cwd.js @@ -25,4 +25,6 @@ module.exports = function cwd_(app) { cwd = val; } }); + + app.set('paths.cwd', cwd); }; From e17614ebab75b89a9b8512a70d4456d74bc7dbc6 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 27 Apr 2015 12:40:17 -0400 Subject: [PATCH 092/274] fix session path --- lib/utils/index.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/utils/index.js b/lib/utils/index.js index c73fa38..bf1a792 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -13,13 +13,13 @@ var mm = require('micromatch'); var relative = require('relative'); var sessionify = require('sessionify'); var through = require('through2'); +var Vinyl = require('vinyl'); /** * Local dependencies */ - -var session = require('./session'); +var session = require('../session'); /** * File cache @@ -62,12 +62,16 @@ utils.series = function series(fns) { }; }; +/** + * Push a collection of templates into the stream (as vinyl files) + */ + utils.pushToStream = function pushToStream(collection, stream, fn) { var i = 0; for (var key in collection) { if (collection.hasOwnProperty(key)) { var file = collection[key]; - stream.push(fn ? fn(file, i++) : file); + stream.push(fn ? fn(file, Vinyl, i++) : file); } } }; From 179248ecd527380aeed43de72231ddb3bcf7a963 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 27 Apr 2015 12:40:29 -0400 Subject: [PATCH 093/274] add plugins transform --- lib/transforms/init/plugins.js | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 lib/transforms/init/plugins.js diff --git a/lib/transforms/init/plugins.js b/lib/transforms/init/plugins.js new file mode 100644 index 0000000..76a419c --- /dev/null +++ b/lib/transforms/init/plugins.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * Prime the `app.plugins` object. + */ + +module.exports = function plugins_(app) { + app.plugins = app.plugins || {}; +}; From 019e7290363c88565335faf616982387021ec0b1 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 27 Apr 2015 12:40:44 -0400 Subject: [PATCH 094/274] minor edits to user/username --- lib/transforms/env/user.js | 2 +- lib/transforms/env/username.js | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/transforms/env/user.js b/lib/transforms/env/user.js index dc33a45..ab807b0 100644 --- a/lib/transforms/env/user.js +++ b/lib/transforms/env/user.js @@ -6,5 +6,5 @@ */ module.exports = function user_(app) { - app.set('data.username', app.get('data.github.username')); + app.set('data.username', app.get('data.git.username')); }; diff --git a/lib/transforms/env/username.js b/lib/transforms/env/username.js index e357d3f..dbc62b2 100644 --- a/lib/transforms/env/username.js +++ b/lib/transforms/env/username.js @@ -15,8 +15,10 @@ module.exports = function username_(app) { if (typeof author.url === 'string' && /\/github/.test(author.url)) { var parsed = github(author.url); var user = (parsed && parsed.user) || ''; - app.set('data.github.username', user); - app.set('data.username', user); + if (user) { + app.set('data.github.username', user); + app.set('data.username', user); + } } } }; From ce3272b053b69e4277df25ab111b61cb44cf9214 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 25 May 2015 12:27:37 -0400 Subject: [PATCH 095/274] start updating from app --- bin/update.js | 44 +++-- index.js | 283 +++++++++++++++------------- lib/init.js | 44 ++--- lib/loaders/file.js | 3 + lib/loaders/index.js | 5 +- lib/loaders/task.js | 12 -- lib/middleware/data.js | 10 + lib/middleware/dest.js | 2 +- lib/middleware/ext.js | 13 +- lib/middleware/lint.js | 15 ++ lib/middleware/matter.js | 13 ++ lib/middleware/props.js | 2 +- lib/middleware/src.js | 22 ++- lib/plugins/dest.js | 2 +- lib/plugins/init.js | 4 +- lib/plugins/lint.js | 12 ++ lib/plugins/process.js | 2 +- lib/plugins/render.js | 100 +++------- lib/transforms/init/loaders.js | 2 +- lib/transforms/updaters/updaters.js | 20 +- lib/utils/completion.js | 4 + package.json | 6 +- 22 files changed, 335 insertions(+), 285 deletions(-) create mode 100644 lib/loaders/file.js delete mode 100644 lib/loaders/task.js create mode 100644 lib/middleware/data.js create mode 100644 lib/middleware/lint.js create mode 100644 lib/middleware/matter.js create mode 100644 lib/plugins/lint.js diff --git a/bin/update.js b/bin/update.js index 7cc7cb3..41905a9 100755 --- a/bin/update.js +++ b/bin/update.js @@ -5,19 +5,27 @@ var chalk = require('chalk'); var prettyTime = require('pretty-hrtime'); var completion = require('../lib/utils/completion'); var taskTree = require('../lib/utils/task-tree'); -var update = require('..'); +var app = require('..'); var argv = require('minimist')(process.argv.slice(2)); - var stack = argv._; -var name = stack.shift(); -var tasks = stack.length ? stack : ['default']; +var len = stack.length, i = 0; + +while (len--) { + var name = stack[i++]; + var plugin = app.updater(name); + console.log(plugin); + // app.updater(name); +} + +var name = stack[0]; var updater = typeof name !== 'undefined' - ? update.updater(name) + ? app.updater(name) : exit(0); -var fp = updater.module; + // console.log(updater) +var fp = updater.updatefile; if (fp) { var cwd = path.dirname(fp); @@ -30,7 +38,7 @@ if (fp) { instance.extend('argv', argv); process.nextTick(function () { - instance.start.apply(instance, tasks); + instance.start.apply(instance, ['default']); }.bind(instance)); } @@ -42,49 +50,49 @@ process.once('exit', function(code) { } }); -update.on('last', function () { +app.on('last', function () { var args; if (argv.set) { args = argv.set.split('='); - update.store.set.apply(update.store, args); + app.store.set.apply(app.store, args); } if (argv.has) { args = argv.has.split('='); - update.store.has.apply(update.store, args); + app.store.has.apply(app.store, args); } if (argv.omit) { args = argv.omit.split('='); - update.store.omit.apply(update.store, args); + app.store.omit.apply(app.store, args); } if (argv.del) { - update.store.delete({force: true}); + app.store.delete({force: true}); } }); -update.on('err', function () { +app.on('err', function () { failed = true; }); -update.on('task_start', function (e) { +app.on('task_start', function (e) { console.log('starting', '\'' + chalk.cyan(e.task) + '\''); }); -update.on('task_stop', function (e) { +app.on('task_stop', function (e) { var time = prettyTime(e.hrDuration); console.log('finished', '\'' + chalk.cyan(e.task) + '\'', 'after', chalk.magenta(time)); }); -update.on('task_err', function (e) { +app.on('task_err', function (e) { var msg = formatError(e); var time = prettyTime(e.hrDuration); console.log(chalk.cyan(e.task), chalk.red('errored after'), chalk.magenta(time)); console.log(msg); }); -update.on('task_not_found', function (err) { +app.on('task_not_found', function (err) { console.log(chalk.red('task \'' + err.task + '\' is not in your updatefile')); console.log('please check the documentation for proper updatefile formatting'); exit(1); @@ -134,5 +142,5 @@ function exit(code) { } if (!argv._.length) { - update.emit('loaded'); + app.emit('loaded'); } diff --git a/index.js b/index.js index 744b86c..d0c076c 100644 --- a/index.js +++ b/index.js @@ -1,23 +1,17 @@ -/*! - * update - * - * Copyright (c) 2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - 'use strict'; -var path = require('path'); +var typeOf = require('kind-of'); +var es = require('event-stream'); var through = require('through2'); var Template = require('template'); var toVinyl = require('to-vinyl'); var Task = require('orchestrator'); +var tutils = require('template-utils')._; var vfs = require('vinyl-fs'); var _ = require('lodash'); -/** - * Local dependencies - */ +var render = require('template-render'); +var init = require('template-init'); var plugins = require('./lib/plugins'); var session = require('./lib/session'); @@ -26,56 +20,66 @@ var utils = require('./lib/utils'); var init = require('./lib/init'); /** - * Initialize `Update`. + * Initialize `App` * - * ```js - * var app = new Update(); - * ``` - * - * @api public + * @param {Object} `context` + * @api private */ -function Update(opts) { - Template.call(this, opts); - Task.call(this, opts); +function App() { + Template.apply(this, arguments); + Task.apply(this, arguments); this.session = session; - init.call(this, this); + this.plugins = {}; + init(this); } -_.extend(Update.prototype, Task.prototype); -Template.extend(Update.prototype); +_.extend(App.prototype, Task.prototype); +Template.extend(App.prototype); /** - * Glob patterns or filepaths to source files. - * - * ```js - * app.src('*.js') - * ``` + * Register a plugin by `name` * - * **Example usage** - * - * ```js - * app.task('web-app', function() { - * app.src('templates/*') - * app.dest(process.cwd()) - * }); - * ``` + * @param {String} `name` + * @param {Function} `fn` + * @api public + */ + +App.prototype.plugin = function(name, fn) { + if (arguments.length === 1) { + return this.plugins[name]; + } + if (typeof fn === 'function') { + fn = fn.bind(this); + } + this.plugins[name] = fn; + return this; +}; + +/** + * Create a plugin pipeline from an array of plugins. * - * @param {String|Array} `glob` Glob patterns or file paths to source files. - * @param {Object} `options` Options or locals to merge into the context and/or pass to `src` plugins + * @param {Array} `plugins` Each plugin is a function that returns a stream, or the name of a registered plugin. + * @param {Object} `options` + * @return {Stream} * @api public */ -Update.prototype.plugin = function(name, fn) { - if (!fn) return this.plugins[name]; - if (name && typeof name === 'object') { - for (var key in name) { - this.plugin(key, name[key]); +App.prototype.pipeline = function(plugins, options) { + var res = []; + for (var i = 0; i < plugins.length; i++) { + var val = plugins[i]; + if (typeOf(val) === 'function') { + res.push(val.call(this, options)); + } else if (typeOf(val) === 'object') { + res.push(val); + } else if (this.plugins.hasOwnProperty(val) && !this.isFalse('plugin ' + val)) { + res.push(this.plugins[val].call(this, options)); + } else { + res.push(through.obj()); } - } else { - this.plugins[name] = fn; } - return this; + return es.pipe.apply(es, res); }; /** @@ -85,22 +89,20 @@ Update.prototype.plugin = function(name, fn) { * app.src('*.js') * ``` * - * **Example usage** - * - * ```js - * app.task('web-app', function() { - * app.src('templates/*') - * app.dest(process.cwd()) - * }); - * ``` - * * @param {String|Array} `glob` Glob patterns or file paths to source files. * @param {Object} `options` Options or locals to merge into the context and/or pass to `src` plugins * @api public */ -Update.prototype.src = function(glob, opts) { - return stack.src(this, glob, opts); +App.prototype.src = function(glob, opts) { + opts = _.merge({}, this.options, opts); + session.set('src', opts); + var app = this; + + return this.combine([ + vfs.src(glob, opts), + app.plugin('init')(app) + ], opts); }; /** @@ -111,21 +113,12 @@ Update.prototype.src = function(glob, opts) { * app.templates('*.js') * ``` * - * **Example usage** - * - * ```js - * app.task('licenses', function() { - * app.templates('templates/licenses/*') - * app.dest(process.cwd()) - * }); - * ``` - * * @param {String|Array} `glob` Glob patterns or file paths to source files. * @param {Object} `options` Options or locals to merge into the context and/or pass to `src` plugins * @api public */ -Update.prototype.templates = function(glob, opts) { +App.prototype.templates = function(glob, opts) { return stack.templates(this, glob, opts); }; @@ -136,22 +129,22 @@ Update.prototype.templates = function(glob, opts) { * app.dest('dist', {ext: '.xml'}) * ``` * - * **Example usage** - * - * ```js - * app.task('foo', function() { - * app.src('templates/*') - * app.dest('dist', {ext: '.xml'}) - * }); - * ``` - * * @param {String|Function} `dest` File path or rename function. - * @param {Object} `options` Options or locals to merge into the context and/or pass to `dest` plugins + * @param {Object} `options` Options or locals to pass to `dest` plugins * @api public */ -Update.prototype.dest = function(dest, opts) { - return stack.dest(this, dest, opts); +App.prototype.dest = function(dest, opts) { + var srcOpts = session.get('src') || {}; + opts = _.merge({}, this.options, srcOpts, opts); + var app = this; + + return this.combine([ + app.plugin('paths')(dest, opts), + app.plugin('lint'), + app.plugin('render')(opts), + app.plugin('dest')(dest, opts), + ], opts); }; /** @@ -166,7 +159,7 @@ Update.prototype.dest = function(dest, opts) { * @return {Stream} Stream to allow doing additional work. */ -Update.prototype.copy = function(glob, dest, opts) { +App.prototype.copy = function(glob, dest, opts) { return stack.templates(this, glob, opts) // .pipe(this.process(opts)) .pipe(vfs.dest(dest, opts)); @@ -186,7 +179,7 @@ Update.prototype.copy = function(glob, dest, opts) { * @return {Stream} Stream to allow doing additional work. */ -Update.prototype.process = function(locals, options) { +App.prototype.process = function(locals, options) { locals = _.merge({id: this.getTask()}, this.cache.data, locals); locals.options = _.merge({}, this.options, options, locals.options); return plugins.process.call(this, locals, options); @@ -197,7 +190,8 @@ Update.prototype.process = function(locals, options) { * * ```js * app.task('docs', function() { - * app.src('*.js').pipe(app.dest('.')); + * app.src(['foo.js', 'bar/*.js']) + * .pipe(app.dest('./')); * }); * ``` * @@ -206,34 +200,34 @@ Update.prototype.process = function(locals, options) { * @api public */ -Update.prototype.task = Update.prototype.add; +App.prototype.task = App.prototype.add; /** - * Get the id from the current task. Used in plugins to get - * the current session. + * Get the name of the current task-session. This is + * used in plugins to lookup data or views created in + * a task. * * ```js - * var id = verb.getTask(); - * verb.views[id]; + * var id = app.getTask(); + * var views = app.views[id]; * ``` * - * @return {String} `task` The currently running task. + * @return {String} `task` The name of the currently running task. * @api public */ -Update.prototype.getTask = function() { +App.prototype.getTask = function() { var name = this.session.get('task'); return typeof name !== 'undefined' ? 'task_' + name - : 'file'; + : 'taskFile'; }; /** - * Get the collection name (inflection) of the given - * template type. + * Get a view collection by its singular-form `name`. * * ```js - * var collection = verb.getCollection('page'); + * var collection = app.getCollection('page'); * // gets the `pages` collection * //=> {a: {}, b: {}, ...} * ``` @@ -242,41 +236,32 @@ Update.prototype.getTask = function() { * @api public */ -Update.prototype.getCollection = function(name) { - var plural = this.inflections[name]; - return this.views[plural]; -}; +App.prototype.getCollection = function(name) { + if (typeof name === 'undefined') { + name = this.getTask(); + } -/** - * Used in plugins to get a template from the current session. - * - * ```js - * var views = app.getViews(); - * ``` - * - * @return {String} `id` Pass the task-id from the current session. - * @return {Object} `file` Vinyl file object. Must have an `id` property that matches the `id` of the session. - * @api public - */ + if (this.views.hasOwnProperty(name)) { + return this.views[name]; + } -Update.prototype.getViews = function() { - var collection = this.inflections[this.getTask()]; - return this.views[collection]; + name = this.inflections[name]; + return this.views[name]; }; /** - * Get a template (file) from the current session in a stream. + * Get a file from the current session. * * ```js * var file = app.getFile(file); * ``` * - * @return {Object} `file` Vinyl file object. Must have an `id` property that matches the `id` of the session. + * @return {Object} `file` Vinyl file object. Must have an `id` property. * @api public */ -Update.prototype.getFile = function(file) { - return this.getViews()[file.id]; +App.prototype.getFile = function(file, id) { + return this.getCollection(id)[file.id]; }; /** @@ -292,28 +277,28 @@ Update.prototype.getFile = function(file) { * @api public */ -Update.prototype.pushToStream = function(id, stream) { - return utils.pushToStream(this.getCollection(id), stream, toVinyl); +App.prototype.pushToStream = function(id, stream) { + return tutils.pushToStream(this.getCollection(id), stream, toVinyl); }; /** * `taskFiles` is a session-context-specific getter that - * returns the collection of files from the current `task`. + * returns the collection of files from the currently running `task`. * * ```js - * var files = verb.taskFiles; + * var taskFiles = app.taskFiles; * ``` * * @name .taskFiles - * @return {Object} Get the files from the current task. + * @return {Object} Get the files from the currently running task. * @api public */ -Object.defineProperty(Update.prototype, 'taskFiles', { +Object.defineProperty(App.prototype, 'taskFiles', { configurable: true, - enumerable: false, + enumerable: true, get: function () { - return this.views[this.inflections[this.getTask()]]; + return this.getCollection(); } }); @@ -332,7 +317,7 @@ Object.defineProperty(Update.prototype, 'taskFiles', { * @api public */ -Update.prototype.updater = function(name, fn) { +App.prototype.updater = function(name, fn) { if (arguments.length === 1 && typeof name === 'string') { return this.updaters[name]; } @@ -340,6 +325,27 @@ Update.prototype.updater = function(name, fn) { return this; }; +/** + * Register a plugin that can be arbitrarily pushed into a + * plugin stack. + * + * ```js + * app.plugin('foo', require('plugin-foo')); + * ``` + * + * @param {String} `name` Plugin name + * @param {Function} `fn` Plugin function, must return a vinyl stream. + * @api public + */ + +App.prototype.plugin = function(name, fn) { + if (arguments.length === 1 && typeof name === 'string') { + return this.plugins[name]; + } + this.plugins[name] = fn; + return this; +}; + /** * Run an array of tasks. * @@ -351,14 +357,28 @@ Update.prototype.updater = function(name, fn) { * @api private */ -Update.prototype.run = function() { +App.prototype.run = function() { var tasks = arguments.length ? arguments : ['default']; - process.nextTick(function () { this.start.apply(this, tasks); }.bind(this)); }; +/** + * Wrapper around Task._runTask to enable `sessions`. + * + * @param {Object} `task` Task to run + * @api private + */ + +App.prototype._runTask = function(task) { + var app = this; + app.session.run(function () { + app.session.set('task', task.name); + Task.prototype._runTask.call(app, task); + }); +}; + /** * Re-run the specified task(s) when a file changes. * @@ -373,25 +393,24 @@ Update.prototype.run = function() { * @api public */ -Update.prototype.watch = function(glob, opts, fn) { +App.prototype.watch = function(glob, opts, fn) { if (Array.isArray(opts) || typeof opts === 'function') { fn = opts; opts = null; } - - if (!Array.isArray(fn)) vfs.watch(glob, opts, fn); + if (!Array.isArray(fn)) return vfs.watch(glob, opts, fn); return vfs.watch(glob, opts, function () { this.start.apply(this, fn); }.bind(this)); }; /** - * Expose the `Update` class on `update.Update` + * Expose the `App` class on `update.App` */ -Update.prototype.Update = Update; +App.prototype.App = App; /** * Expose our instance of `update` */ -module.exports = new Update(); +module.exports = new App(); diff --git a/lib/init.js b/lib/init.js index 00194ae..3641dd1 100644 --- a/lib/init.js +++ b/lib/init.js @@ -1,57 +1,47 @@ 'use strict'; -var transforms = require('./transforms'); -var updaters = transforms.updaters; -var init = transforms.init; -var env = transforms.env; +var init = require('./transforms/'); /** * Load initialization transforms * - * | runner + * | config * | loaders - * | create + * | templates * | options * | middleware * | plugins * | load * | engines - * | helpers - load helpers last + * | helpers */ -module.exports = function init_(app) { - app.transform('updaters', updaters); +module.exports = function(app) { app.transform('metadata', init.metadata); app.transform('plugins', init.plugins); - app.transform('args', init.argv); - // app.transform('ignore', init.ignore); - // app.transform('files', env.files); - app.transform('env', env.env); - app.transform('pkg', env.pkg); - app.transform('paths', env.paths); - app.transform('cwd', env.cwd); - app.transform('keys', env.keys); - app.transform('git', env.git); - app.transform('author', env.author); - app.transform('user', env.user); - app.transform('username', env.username); - app.transform('github', env.github); + app.once('loaded', function () { + app.transform('cwd', init.cwd); + app.transform('paths', init.paths); + app.emit('env'); + }); - app.on('init', function () { - app.transform('config', init.config); + app.once('env', function () { + app.transform('options', init.options); app.transform('runner', init.runner); - app.transform('defaults', init.defaults); + app.transform('argv', init.argv); + app.transform('config', init.config); app.transform('loaders', init.loaders); app.transform('create', init.create); app.transform('engines', init.engines); app.transform('middleware', init.middleware); app.transform('helpers', init.helpers); app.transform('load', init.load); - app.emit('loaded'); + app.emit('init'); }); - app.on('loaded', function () { + app.once('init', function () { app.transform('helpers', init.helpers); + app.emit('last'); }); }; diff --git a/lib/loaders/file.js b/lib/loaders/file.js new file mode 100644 index 0000000..dbbf862 --- /dev/null +++ b/lib/loaders/file.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require('init-file-loader'); diff --git a/lib/loaders/index.js b/lib/loaders/index.js index 69c8b1c..874ea55 100644 --- a/lib/loaders/index.js +++ b/lib/loaders/index.js @@ -1,6 +1,3 @@ 'use strict'; -module.exports = { - base: require('./base'), - task: require('./task') -}; +module.exports = require('export-files')(__dirname); diff --git a/lib/loaders/task.js b/lib/loaders/task.js deleted file mode 100644 index 3e494bd..0000000 --- a/lib/loaders/task.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -var path = require('path'); - -module.exports = function task_(file) { - var name = path.basename(file.path, path.extname(file.path)); - file.content = file.contents.toString(); - file.id = name; - var template = {}; - template[name] = file; - return template; -}; diff --git a/lib/middleware/data.js b/lib/middleware/data.js new file mode 100644 index 0000000..e738d30 --- /dev/null +++ b/lib/middleware/data.js @@ -0,0 +1,10 @@ +'use strict'; + +/** + * Prime the `file.data` object. + */ + +module.exports = function(file, next) { + file.data = file.data || {}; + next(); +}; diff --git a/lib/middleware/dest.js b/lib/middleware/dest.js index 83730b6..e00b3ce 100644 --- a/lib/middleware/dest.js +++ b/lib/middleware/dest.js @@ -5,7 +5,7 @@ * `dest` is handled in the pipeline by the `dest` plugin. */ -module.exports = function dest_(file, next) { +module.exports = function(file, next) { file.data.dest = file.data.dest || {}; next(); }; diff --git a/lib/middleware/ext.js b/lib/middleware/ext.js index d132fec..7d90f84 100644 --- a/lib/middleware/ext.js +++ b/lib/middleware/ext.js @@ -1,14 +1,17 @@ 'use strict'; var path = require('path'); -var utils = require('../utils'); +var utils = require('template-utils')._; /** - * Prime the `file` object with properties that - * can be extended in plugins. + * Ensure that `ext` is on the file object. */ -module.exports = function ext_(file, next) { - file.ext = utils.formatExt(file.ext || path.extname(file.path)); +module.exports = function(file, next) { + file.ext = file.ext || file.data.src.ext || path.extname(file.path); + + if (typeof file.ext === 'string') { + file.ext = utils.formatExt(file.ext); + } next(); }; diff --git a/lib/middleware/lint.js b/lib/middleware/lint.js new file mode 100644 index 0000000..ebdb6a7 --- /dev/null +++ b/lib/middleware/lint.js @@ -0,0 +1,15 @@ +'use strict'; + +var lint = require('lint-templates'); + +/** + * Lint `file.content` for missing helpers + * or data properties. + */ + +module.exports = function (app) { + return function(file, next) { + lint(app, file); + next(); + }; +}; diff --git a/lib/middleware/matter.js b/lib/middleware/matter.js new file mode 100644 index 0000000..ca4a08f --- /dev/null +++ b/lib/middleware/matter.js @@ -0,0 +1,13 @@ +'use strict'; + +var parser = require('parser-front-matter'); + +/** + * Default middleware for parsing front-matter + */ + +module.exports = function matter(app) { + return function (file, next) { + return parser.parse(file, next); + }; +}; diff --git a/lib/middleware/props.js b/lib/middleware/props.js index d97fdba..2fb109a 100644 --- a/lib/middleware/props.js +++ b/lib/middleware/props.js @@ -5,7 +5,7 @@ * can be extended in plugins. */ -module.exports = function props_(file, next) { +module.exports = function(file, next) { file.options = file.options || {}; file.locals = file.locals || {}; file.data = file.data || {}; diff --git a/lib/middleware/src.js b/lib/middleware/src.js index 2bc15d8..f0db1a6 100644 --- a/lib/middleware/src.js +++ b/lib/middleware/src.js @@ -1,22 +1,36 @@ 'use strict'; +var parse = require('parse-filepath'); var path = require('path'); /** * Set properties on `file.data.src` to use in plugins, - * other middleware, helpers and templates. + * other middleware, helpers, templates etc. */ -module.exports = function src_(file, next) { +module.exports = function(file, next) { var orig = file.options.originalPath; - var parsed = path.parse(orig); file.data.src = file.data.src || {}; - file.data.src.path = orig; + + // look for native node.js `path.parse` method first + var parsed = typeof path.parse === 'function' + ? path.parse(orig) + : parse(orig); + file.data.src.dirname = parsed.dir; + file.data.src.relative = file.relative; file.data.src.filename = parsed.name; file.data.src.basename = parsed.base; file.data.src.extname = parsed.ext; + file.data.src.ext = parsed.ext.slice(1); + + file.data.process = {}; + file.data.process.cwd = function () { + return process.cwd(); + }; + + file.data.resolve = path.resolve; next(); }; diff --git a/lib/plugins/dest.js b/lib/plugins/dest.js index ae03002..0c9b8b3 100644 --- a/lib/plugins/dest.js +++ b/lib/plugins/dest.js @@ -18,7 +18,7 @@ module.exports = plugin('verb', 'dest'); function plugin(appname, name) { var pluginname = appname + '-' + name; - return function dest_(destDir) { + return function destPlugin(destDir) { return through.obj(function (file, enc, cb) { if (file.isNull()) { this.push(file); diff --git a/lib/plugins/init.js b/lib/plugins/init.js index 632ab59..ee39b62 100644 --- a/lib/plugins/init.js +++ b/lib/plugins/init.js @@ -8,13 +8,13 @@ module.exports = plugin('update', 'init'); function plugin(appname, name) { var pluginname = appname + '-' + name + ':'; - return function init_() { + return function initPlugin() { var app = this; var id = this.getTask(); // create a template type for vinyl files and assign a loader if (!app.hasOwnProperty(id)) { - app.create(id, ['task']); + app.create(id, ['file']); } return through.obj(function (file, enc, cb) { diff --git a/lib/plugins/lint.js b/lib/plugins/lint.js new file mode 100644 index 0000000..1012887 --- /dev/null +++ b/lib/plugins/lint.js @@ -0,0 +1,12 @@ +'use strict'; + +var through = require('through2'); +var lint = require('lint-templates'); + +module.exports = function(app) { + return through.obj(function (file, enc, cb) { + lint(app, file); + this.push(file); + return cb(); + }); +}; diff --git a/lib/plugins/process.js b/lib/plugins/process.js index b0169a3..79bcdf4 100644 --- a/lib/plugins/process.js +++ b/lib/plugins/process.js @@ -11,7 +11,7 @@ var utils = require('utils'); module.exports = plugin('update'); function plugin(appname) { - return function (locals) { + return function processPlugin(locals) { var app = this; return through.obj(function (file, enc, cb) { var collection = app.inflections[locals.id]; diff --git a/lib/plugins/render.js b/lib/plugins/render.js index b5f37cc..aeb050b 100644 --- a/lib/plugins/render.js +++ b/lib/plugins/render.js @@ -1,78 +1,32 @@ 'use strict'; -/** - * Module dependencies. - */ - -var _ = require('lodash'); -var PluginError = require('plugin-error'); var through = require('through2'); - -/** - * Expose `render` plugin - */ - -module.exports = plugin('verb', 'render'); - -function plugin(appname, name) { - var pluginname = appname + '-' + name + ':'; - - return function render_(locals) { - var app = this; - - locals = locals || {}; - locals.options = locals.options || {}; - - return through.obj(function (file, enc, cb) { - if (file.isNull()) { - this.push(file); - return cb(); - } - if (file.isStream()) { - this.emit('error', new PluginError(pluginname, 'Streaming is not supported.')); - return cb(); - } - - locals = _.merge({}, locals, file.locals); - locals.options = _.merge({}, app.options, locals.options); - - if (norender(app, file.ext, file, locals)) { - this.push(file); - return cb(); - } - - var template = app.getFile(file); - template.content = file.contents.toString(); - - try { - var stream = this; - template.render(locals, function (err, content) { - if (err) { - stream.emit('error', new PluginError(pluginname, err)); - cb(err); - return; - } - file.contents = new Buffer(content); - stream.push(file); - return cb(); - }); - - } catch (err) { - this.emit('error', new PluginError(pluginname, err)); - return cb(); +var extend = require('extend-shallow'); +var PluginError = require('plugin-error'); +var utils = require('../utils'); + +module.exports = function(options) { + var locals = options.locals; + var app = this; + + return through.obj(function (file, enc, cb) { + if (utils.norender(app, file, locals)) { + this.push(file); + return cb(); + } + + file.content = file.contents.toString(); + var stream = this; + + locals = extend({}, file.data, locals); + app.render(file, locals, function (err, res) { + if (err) { + stream.emit('error', new PluginError('render-plugin', err)); + return cb(err); } + file.contents = new Buffer(res); + stream.push(file); + return cb(); }); - }; -} - -/** - * Push the `file` through if the user has specfied - * not to render it. - */ - -function norender(app, ext, file, locals) { - return !app.engines.hasOwnProperty(ext) - || app.isTrue('norender') || app.isFalse('render') - || file.norender === true || file.render === false - || locals.norender === true || locals.render === false; -} + }); +}; diff --git a/lib/transforms/init/loaders.js b/lib/transforms/init/loaders.js index f48c0c2..ddba5e5 100644 --- a/lib/transforms/init/loaders.js +++ b/lib/transforms/init/loaders.js @@ -8,5 +8,5 @@ var loaders = require('../../loaders'); module.exports = function base_(app) { app.loader('base', [loaders.base]); - app.loader('task', [loaders.task]); + app.loader('file', [loaders.file]); }; diff --git a/lib/transforms/updaters/updaters.js b/lib/transforms/updaters/updaters.js index 0bebf92..9542bcf 100644 --- a/lib/transforms/updaters/updaters.js +++ b/lib/transforms/updaters/updaters.js @@ -1,5 +1,6 @@ 'use strict'; +var glob = require('globby'); var resolveUp = require('resolve-up'); var path = require('path'); var _ = require('lodash'); @@ -18,9 +19,26 @@ module.exports = function updaters_(app) { var fp = path.resolve(dir, pkg.main); var res = {}; - res.module = require.resolve(path.resolve(fp)); + + res.plugins = plugins(pattern, dir); + res.updatefile = require.resolve(dir); + res.module = node_modules(fp); res.pkg = pkg; var name = pkg.name.split('updater-').join(''); acc[name] = res; + return acc; }, app.updaters); }; + +function node_modules(dir) { + var fp = path.resolve(path.dirname(dir), 'node_modules/update'); + return require.resolve(fp); +} + +function plugins(pattern, dir) { + var cwd = path.resolve(dir, 'node_modules'); + return glob.sync(pattern, {cwd: cwd}).reduce(function (acc, fp) { + acc[fp] = require.resolve(path.resolve(cwd, fp)); + return acc; + }, {}); +} diff --git a/lib/utils/completion.js b/lib/utils/completion.js index 9ada3e4..906eb44 100644 --- a/lib/utils/completion.js +++ b/lib/utils/completion.js @@ -3,6 +3,10 @@ var fs = require('fs'); var path = require('path'); +/** + * Borrowed from gulp + */ + module.exports = function (name) { if (typeof name !== 'string') { throw new Error('Missing completion type'); diff --git a/package.json b/package.json index 9022f24..fa8beb3 100644 --- a/package.json +++ b/package.json @@ -40,14 +40,15 @@ "async": "^0.9.0", "async-helper-base": "^0.2.0", "chalk": "^1.0.0", - "cwd": "^0.6.0", "data-store": "^0.4.1", "engine-lodash": "^0.6.3", "event-stream": "^3.3.0", "export-dirs": "^0.2.4", "export-files": "^2.0.1", "git-user-name": "^1.1.2", + "globby": "^2.0.0", "helper-date": "^0.2.1", + "init-file-loader": "^0.1.1", "lodash": "^3.7.0", "logging-helpers": "^0.4.0", "map-files": "^0.7.4", @@ -56,6 +57,7 @@ "minimist": "^1.1.1", "orchestrator": "^0.3.7", "parse-author": "^0.2.0", + "parse-filepath": "^0.5.0", "parse-github-url": "^0.1.0", "plugin-error": "^0.1.0", "pretty-hrtime": "^1.0.0", @@ -65,10 +67,10 @@ "sessionify": "^0.1.0", "template": "^0.13.1", "template-helpers": "^0.3.2", - "template-toc": "^0.3.3", "through2": "^0.6.3", "to-vinyl": "^0.1.2", "utils": "^0.2.1", + "vinyl": "^0.4.6", "vinyl-fs": "^1.0.0" }, "devDependencies": { From 4cd0672b2e45bf3588659dec339e4a6bf1656272 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 25 May 2015 12:53:02 -0400 Subject: [PATCH 096/274] updated --- index.js | 4 - lib/init.js | 3 +- lib/middleware/cwd.js | 13 -- lib/middleware/matter.js | 4 +- lib/transforms/env/cwd.js | 7 +- lib/transforms/init/argv.js | 4 +- lib/transforms/init/config.js | 66 +----- lib/transforms/init/config/del.js | 26 +++ lib/transforms/init/config/get.js | 30 +++ lib/transforms/init/config/index.js | 21 ++ lib/transforms/init/config/option.js | 38 ++++ lib/transforms/init/config/set.js | 28 +++ lib/transforms/init/config/union.js | 30 +++ lib/transforms/init/create.js | 5 +- lib/transforms/init/cwd.js | 28 +++ lib/transforms/init/defaults.js | 19 +- lib/transforms/init/load.js | 2 +- lib/transforms/init/loaders.js | 9 +- lib/transforms/init/metadata.js | 7 +- lib/transforms/init/middleware.js | 42 ++-- lib/transforms/init/plugins.js | 27 ++- lib/transforms/init/runner.js | 8 +- lib/transforms/session.js | 9 +- lib/utils/index.js | 312 ++------------------------- package.json | 19 +- updatefile.js | 5 +- 26 files changed, 318 insertions(+), 448 deletions(-) delete mode 100644 lib/middleware/cwd.js create mode 100644 lib/transforms/init/config/del.js create mode 100644 lib/transforms/init/config/get.js create mode 100644 lib/transforms/init/config/index.js create mode 100644 lib/transforms/init/config/option.js create mode 100644 lib/transforms/init/config/set.js create mode 100644 lib/transforms/init/config/union.js create mode 100644 lib/transforms/init/cwd.js diff --git a/index.js b/index.js index d0c076c..d851bee 100644 --- a/index.js +++ b/index.js @@ -10,13 +10,9 @@ var tutils = require('template-utils')._; var vfs = require('vinyl-fs'); var _ = require('lodash'); -var render = require('template-render'); -var init = require('template-init'); - var plugins = require('./lib/plugins'); var session = require('./lib/session'); var stack = require('./lib/stack'); -var utils = require('./lib/utils'); var init = require('./lib/init'); /** diff --git a/lib/init.js b/lib/init.js index 3641dd1..7979256 100644 --- a/lib/init.js +++ b/lib/init.js @@ -3,8 +3,7 @@ var init = require('./transforms/'); /** - * Load initialization transforms - * + * Load transforms * | config * | loaders * | templates diff --git a/lib/middleware/cwd.js b/lib/middleware/cwd.js deleted file mode 100644 index 9abcfac..0000000 --- a/lib/middleware/cwd.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -/** - * Prime the `file` object with properties that - * can be extended in plugins. - */ - -module.exports = function cwd_(app) { - return function (file, next) { - file.cwd = file.data.cwd || app.cwd || file.cwd || process.cwd(); - next(); - }; -}; diff --git a/lib/middleware/matter.js b/lib/middleware/matter.js index ca4a08f..feb4e50 100644 --- a/lib/middleware/matter.js +++ b/lib/middleware/matter.js @@ -1,6 +1,7 @@ 'use strict'; var parser = require('parser-front-matter'); +var extend = require('extend-shallow'); /** * Default middleware for parsing front-matter @@ -8,6 +9,7 @@ var parser = require('parser-front-matter'); module.exports = function matter(app) { return function (file, next) { - return parser.parse(file, next); + var opts = extend({}, app.options.matter, file.options); + return parser.parse(file, opts, next); }; }; diff --git a/lib/transforms/env/cwd.js b/lib/transforms/env/cwd.js index b637d01..e0249aa 100644 --- a/lib/transforms/env/cwd.js +++ b/lib/transforms/env/cwd.js @@ -1,7 +1,7 @@ 'use strict'; /** - * Get/set the current working directory + * Getter/setter for the current working directory. * * ```js * console.log(app.cwd); @@ -14,10 +14,11 @@ * ``` */ -module.exports = function cwd_(app) { +module.exports = function(app) { var cwd = app.option('cwd') || process.cwd(); Object.defineProperty(app, 'cwd', { + configurable: true, get: function () { return cwd; }, @@ -25,6 +26,4 @@ module.exports = function cwd_(app) { cwd = val; } }); - - app.set('paths.cwd', cwd); }; diff --git a/lib/transforms/init/argv.js b/lib/transforms/init/argv.js index 11d69cc..a8131d6 100644 --- a/lib/transforms/init/argv.js +++ b/lib/transforms/init/argv.js @@ -1,10 +1,10 @@ 'use strict'; /** - * Prime the `update.cache.argv` object. Used for setting values + * Prime the `app.cache.argv` object. Used for getting/setting values * that are passed from the command line. */ -module.exports = function argv_(app) { +module.exports = function(app) { app.cache.argv = app.cache.argv || []; }; diff --git a/lib/transforms/init/config.js b/lib/transforms/init/config.js index ae1d7af..0f623c1 100644 --- a/lib/transforms/init/config.js +++ b/lib/transforms/init/config.js @@ -1,67 +1,3 @@ 'use strict'; -var Store = require('data-store'); -var chalk = require('chalk'); -var _ = require('lodash'); - -/** - * Create a global config store for user values. - * - * To get and set values, do the following: - * - * - set: `--set one=abc` or `--set one` - * - get: `--set one` or `--get one,two,three` - * - del: `--del` (deletes the entire store) - * - * Called in the `init` transform. - */ - -module.exports = function config_(app) { - var config = app.config = new Store('update'); - var del = app.get('argv.del'); - var set = app.get('argv.set'); - var union = app.get('argv.union'); - var get = app.get('argv.get'); - var args; - - if (set) { - args = set.split('='); - if (args.length === 2) { - config.set(args[0], args[1]); - } else { - config.set(args[0], true); - } - } - - if (union) { - args = union.split('='); - if (args.length > 1) { - var val = config.get(args[1]); - args[2] = args[2].split(','); - config.set(args[1], _.union(val, args[2])); - } - } - - if (get) { - if (get === true || get === 'true') { - console.log(chalk.cyan('config config:'), chalk.bold(JSON.stringify(config.data))); - } else if (get.indexOf(',') !== -1) { - get.split(',').forEach(function (val) { - console.log(val, '=', chalk.bold(JSON.stringify(config.get(val)))); - }); - } else { - console.log(get, '=', chalk.bold(JSON.stringify(config.get(get)))); - } - } - - if (del) { - if (del.indexOf(',') !== -1) { - del.split(',').forEach(function (val) { - config.omit(val); - }); - } else { - config.omit(del); - } - console.log('deleted:', chalk.bold(del)); - } -}; +module.exports = require('export-files')(__dirname + '/config'); diff --git a/lib/transforms/init/config/del.js b/lib/transforms/init/config/del.js new file mode 100644 index 0000000..b668d23 --- /dev/null +++ b/lib/transforms/init/config/del.js @@ -0,0 +1,26 @@ +'use strict'; + +var chalk = require('chalk'); + +/** + * Delete a value from the config store. + * + * ```sh + * $ --del foo + * ``` + */ + +module.exports = function(app) { + var config = app.config; + var del = app.get('argv.del'); + if (del) { + if (del.indexOf(',') !== -1) { + del.split(',').forEach(function (val) { + config.omit(val); + }); + } else { + config.omit(del); + } + console.log('deleted:', chalk.bold(del)); + } +}; diff --git a/lib/transforms/init/config/get.js b/lib/transforms/init/config/get.js new file mode 100644 index 0000000..8b0616c --- /dev/null +++ b/lib/transforms/init/config/get.js @@ -0,0 +1,30 @@ +'use strict'; + +var chalk = require('chalk'); + +/** + * Get a value from the config store. + * + * ```sh + * $ --get one + * # or + * $ --get one,two,three + * ``` + */ + +module.exports = function(app) { + var config = app.config; + var get = app.get('argv.get'); + + if (get) { + if (get === true || get === 'true') { + console.log(chalk.cyan('config config:'), chalk.bold(JSON.stringify(config.data))); + } else if (get.indexOf(',') !== -1) { + get.split(',').forEach(function (val) { + console.log(val, '=', chalk.bold(JSON.stringify(config.get(val)))); + }); + } else { + console.log(get, '=', chalk.bold(JSON.stringify(config.get(get)))); + } + } +}; diff --git a/lib/transforms/init/config/index.js b/lib/transforms/init/config/index.js new file mode 100644 index 0000000..a444a26 --- /dev/null +++ b/lib/transforms/init/config/index.js @@ -0,0 +1,21 @@ +'use strict'; + +var Config = require('data-store'); +var config = require('export-files')(__dirname); + +/** + * Initialize a global config store, for persisting data + * that may be reused across projects. + * + * Initialized in the `init` transform. + */ + +module.exports = function(app) { + app.config = new Config('app'); + + app.transform('set', config.set); + app.transform('get', config.get); + app.transform('del', config.del); + app.transform('option', config.option); + app.transform('union', config.union); +}; diff --git a/lib/transforms/init/config/option.js b/lib/transforms/init/config/option.js new file mode 100644 index 0000000..85035f8 --- /dev/null +++ b/lib/transforms/init/config/option.js @@ -0,0 +1,38 @@ +'use strict'; + +/** + * Persist a value on the config store. + * + * ```sh + * $ --option one=abc + * #=> {one: 'abc'} + * + * $ --option one + * #=> {one: true} + * ``` + */ + +module.exports = function(app) { + var config = app.config; + var args; + + var option = app.get('argv.option'); + if (option) { + args = option.split('='); + if (args.length === 2) { + config.set(args[0], args[1]); + } else { + config.set(args[0], true); + } + } + + var enable = app.get('argv.enable'); + if (enable) { + config.set(enable, true); + } + + var disable = app.get('argv.disable'); + if (disable) { + config.set(disable, false); + } +}; diff --git a/lib/transforms/init/config/set.js b/lib/transforms/init/config/set.js new file mode 100644 index 0000000..773aec9 --- /dev/null +++ b/lib/transforms/init/config/set.js @@ -0,0 +1,28 @@ +'use strict'; + +/** + * Persist a value on the config store. + * + * ```sh + * $ --set one=abc + * #=> {one: 'abc'} + * + * $ --set one + * #=> {one: true} + * ``` + */ + +module.exports = function(app) { + var config = app.config; + var args; + + var set = app.get('argv.set'); + if (set) { + args = set.split('='); + if (args.length === 2) { + config.set(args[0], args[1]); + } else { + config.set(args[0], true); + } + } +}; diff --git a/lib/transforms/init/config/union.js b/lib/transforms/init/config/union.js new file mode 100644 index 0000000..1925f5c --- /dev/null +++ b/lib/transforms/init/config/union.js @@ -0,0 +1,30 @@ +'use strict'; + +var union = require('arr-union'); + +/** + * Get a value from the config store. + * + * ```sh + * $ --union one=a,b,c + * #=> {one: ['a', 'b', 'c']} + * + * $ --union one=d + * #=> {one: ['a', 'b', 'c', 'd']} + * ``` + */ + +module.exports = function(app) { + var config = app.config; + var args; + + var arr = app.get('argv.union'); + if (arr) { + args = arr.split('='); + if (args.length > 1) { + var val = config.get(args[1]); + args[2] = args[2].split(','); + config.set(args[1], union(val, args[2])); + } + } +}; diff --git a/lib/transforms/init/create.js b/lib/transforms/init/create.js index 0d5139a..1ab98b3 100644 --- a/lib/transforms/init/create.js +++ b/lib/transforms/init/create.js @@ -1,10 +1,11 @@ 'use strict'; /** - * Create built-in template types, using the `base` loader + * Initialize built-in template types with + * the `base` loader. */ -module.exports = function create_() { +module.exports = function() { this.create('include', {isRenderable: true}, ['base']); this.create('dotfile', {isRenderable: true}, ['base']); }; diff --git a/lib/transforms/init/cwd.js b/lib/transforms/init/cwd.js new file mode 100644 index 0000000..388ba2c --- /dev/null +++ b/lib/transforms/init/cwd.js @@ -0,0 +1,28 @@ +'use strict'; + +/** + * Getter/setter for the current working directory. + * + * ```js + * console.log(app.cwd); + * //=> /dev/foo/bar/ + * ``` + * Or set: + * + * ```js + * app.cwd = 'foo'; + * ``` + */ + +module.exports = function(app) { + var cwd = app.option('cwd') || process.cwd(); + + Object.defineProperty(app, 'cwd', { + get: function () { + return cwd; + }, + set: function (val) { + cwd = val; + } + }); +}; diff --git a/lib/transforms/init/defaults.js b/lib/transforms/init/defaults.js index 78f3af0..8507f1a 100644 --- a/lib/transforms/init/defaults.js +++ b/lib/transforms/init/defaults.js @@ -1,26 +1,15 @@ 'use strict'; /** - * Define default options. + * Initialize default options. */ module.exports = function options_(app) { app.option({ toc: {append: '\n\n_(Table of contents generated by [update])_'} }); - - // engines app.option('view engine', 'md'); - - // routing - app.option('router methods', ['onInit', 'onRender']); - app.enable('case sensitive routing'); - app.enable('strict routing'); - - // delimiters - app.option('layoutDelims', ['{%=', '%}']); - app.option('escapeDelims', { - from: ['<%%', '%>'], - to: ['<%', '%>'] - }); + app.disable('default routes'); + app.option('layoutDelims', ['{%', '%}']); + app.option('escapeDelims', ['<%%', '<%']); }; diff --git a/lib/transforms/init/load.js b/lib/transforms/init/load.js index 71b3e70..ebfee0b 100644 --- a/lib/transforms/init/load.js +++ b/lib/transforms/init/load.js @@ -1,7 +1,7 @@ 'use strict'; /** - * Load built-in templates + * Load templates for built-in template types. */ module.exports = function load_(app) { diff --git a/lib/transforms/init/loaders.js b/lib/transforms/init/loaders.js index ddba5e5..455e82f 100644 --- a/lib/transforms/init/loaders.js +++ b/lib/transforms/init/loaders.js @@ -1,12 +1,13 @@ 'use strict'; -var loaders = require('../../loaders'); +var base = require('base-loader'); +var task = require('init-file-loader'); /** * Load built-in loaders */ -module.exports = function base_(app) { - app.loader('base', [loaders.base]); - app.loader('file', [loaders.file]); +module.exports = function(app) { + app.loader('base', [base]); + app.loader('task', [task]); }; diff --git a/lib/transforms/init/metadata.js b/lib/transforms/init/metadata.js index 4268fa3..d0b1cd1 100644 --- a/lib/transforms/init/metadata.js +++ b/lib/transforms/init/metadata.js @@ -3,18 +3,17 @@ var path = require('path'); /** - * Adds Updates's package.json data to `update.metadata`. - * + * Sets App's package.json data on `app.metadata`. * Called in the `init` transform. */ -module.exports = function metadata_(app) { +module.exports = function(app) { Object.defineProperty(app, 'metadata', { get: function () { return require(path.resolve(__dirname, '../../..', 'package.json')); }, set: function () { - console.log('`update.metadata` is read-only and cannot be modified.'); + console.log('`update.metadata` is read-only and should not be modified.'); } }); }; diff --git a/lib/transforms/init/middleware.js b/lib/transforms/init/middleware.js index 00b6d24..b84d782 100644 --- a/lib/transforms/init/middleware.js +++ b/lib/transforms/init/middleware.js @@ -1,26 +1,40 @@ 'use strict'; -var utils = require('../../utils'); +var chalk = require('chalk'); +var mu = require('middleware-utils'); +var tu = require('template-utils').utils; +var middleware = require('../../middleware'); +var err = mu.error, regex; /** * Initialize default middleware */ -module.exports = function middleware_() { - this.onLoad(/./, utils.series([ - require('../../middleware/props'), - require('../../middleware/engine'), - require('../../middleware/cwd')(this), - require('../../middleware/src'), - require('../../middleware/dest'), - // require('../../middleware/ext'), - // require('../../middleware/dotfiles')(this), - ]), error('.onLoad (.):')); +module.exports = function(app) { + // use extensions from engines to create route regex + if (typeof regex === 'undefined') { + regex = tu.extensionRe(Object.keys(app.engines)); + } + + app.onLoad(regex, mu.series([ + middleware.matter(app), + ])) + .onLoad(/./, mu.series([ + middleware.data, + middleware.src, + middleware.ext, + ])); }; -function error(method) { - return function (err, file, next) { - if (err) console.log(method, err); +function debugFile(method, output) { + return function(file, next) { + if (!output) return next(); + console.log(chalk.yellow('//', method + ' ----------------')); + console.log(chalk.bold('file.path:'), file.path); + console.log(chalk.cyan('file.data:'), file.data); + console.log(chalk.gray('file.opts:'), file.options); + console.log(chalk.yellow('// end -----------------------')); + console.log(); next(); }; } diff --git a/lib/transforms/init/plugins.js b/lib/transforms/init/plugins.js index 76a419c..34fb7d0 100644 --- a/lib/transforms/init/plugins.js +++ b/lib/transforms/init/plugins.js @@ -1,9 +1,30 @@ 'use strict'; +var render = require('template-render'); +var paths = require('gulp-dest-paths'); +var init = require('template-init'); +var vfs = require('vinyl-fs'); +var plugins = require('../../plugins'); + /** - * Prime the `app.plugins` object. + * Enable default plugins. */ -module.exports = function plugins_(app) { - app.plugins = app.plugins || {}; +module.exports = function(app) { + app.plugin('init', init(app)); + app.plugin('lint', plugins.lint(app)); + app.plugin('paths', paths); + app.plugin('render', render(app)); + app.plugin('src', vfs.src); + app.plugin('dest', vfs.dest); + + // default `src` plugins + app.enable('plugin src'); + app.enable('plugin init'); + + // default `plugin dest`s + app.enable('plugin paths'); + app.enable('plugin lint'); + app.enable('plugin render'); + app.enable('plugin dest'); }; diff --git a/lib/transforms/init/runner.js b/lib/transforms/init/runner.js index 6c224f8..5ebe56c 100644 --- a/lib/transforms/init/runner.js +++ b/lib/transforms/init/runner.js @@ -1,14 +1,14 @@ 'use strict'; /** - * The current `app.runner` + * Metadata for the current `app.runner` */ -module.exports = function runner_(app) { +module.exports = function(app) { app.data({ runner: { - name: 'app-cli', - url: 'https://github.com/assemble/app-cli' + name: 'update', + url: 'https://github.com/jonschlinkert/update' } }); }; diff --git a/lib/transforms/session.js b/lib/transforms/session.js index 63edb92..a6f11c5 100644 --- a/lib/transforms/session.js +++ b/lib/transforms/session.js @@ -1,11 +1,10 @@ 'use strict'; -var session = require('../session'); - /** - * The first transform to be run at init. + * Expose `session` on app, for getting the + * current session from a running task. */ -module.exports = function session_(app) { - app.session = session; +module.exports = function() { + this.session = require('../session'); }; diff --git a/lib/utils/index.js b/lib/utils/index.js index bf1a792..fafc040 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -1,318 +1,38 @@ 'use strict'; /** - * Module dependencies + * Expose `utils/` modules on `utils` */ -var fs = require('fs'); -var path = require('path'); -var async = require('async'); -var chalk = require('chalk'); -var es = require('event-stream'); -var mm = require('micromatch'); -var relative = require('relative'); -var sessionify = require('sessionify'); -var through = require('through2'); -var Vinyl = require('vinyl'); +var utils = require('export-files')(__dirname); /** - * Local dependencies - */ - -var session = require('../session'); - -/** - * File cache - */ - -var cache = {}; - -/** - * Expose `utils` - */ - -var utils = module.exports = require('export-files')(__dirname); - -/** - * Create a plugin stack - */ - -utils.createStack = function createStack(app, plugins) { - var stack = []; - plugins.forEach(function (plugin) { - if (plugin == null) { - stack.push(through.obj()); - } else { - stack.push(plugin); - } - }); - var res = es.pipe.apply(es, stack); - return sessionify(res, session); -}; - -/** - * Run middleware in series - */ - -utils.series = function series(fns) { - return function (file, cb) { - async.eachSeries(fns, function (fn, next) { - fn(file, next); - }, cb); - }; -}; - -/** - * Push a collection of templates into the stream (as vinyl files) - */ - -utils.pushToStream = function pushToStream(collection, stream, fn) { - var i = 0; - for (var key in collection) { - if (collection.hasOwnProperty(key)) { - var file = collection[key]; - stream.push(fn ? fn(file, Vinyl, i++) : file); - } - } -}; - -/** - * Cast `val` to an array. + * Detect if the user has specfied not to render a vinyl template. + * + * @return {Boolean} */ -utils.arrayify = function arrayify(val) { - var isArray = Array.isArray(val); - if (typeof val !== 'string' && !isArray) { - throw new Error('utils.arrayify() expects a string or array.'); - } - return isArray ? val : [val]; +utils.norender = function norender(app, file, locals) { + return app.isTrue('norender') || app.isFalse('render') + || file.norender === true || file.render === false + || locals.norender === true || locals.render === false; }; /** - * Try to require a file, fail silently. Encapsulating try-catches - * also helps with v8 optimizations. + * Coerce value to an array * * @api private */ -utils.tryRequire = function tryRequire(fp) { - if (typeof fp === 'undefined') { - throw new Error('utils.tryRequire() expects a string.'); - } - - var key = 'tryRequire:' + fp; - if (cache.hasOwnProperty(key)) { - return cache[key]; - } - - try { - return (cache[key] = require(path.resolve(fp))); - } catch(err) { - console.error(chalk.red('update cannot find'), chalk.bold(fp), err); - } - return {}; -}; - -/** - * Recursively try to read directories. - */ - -utils.tryReaddirs = function tryReaddirs(dir, ignored) { - if (typeof dir === 'undefined') { - throw new Error('utils.tryReaddirs() expects a string.'); - } - - var files = utils.tryReaddir(dir); - var res = [], len = files.length, i = 0; - - var isIgnored = utils.matchesAny(ignored); - while (len--) { - var fp = relative(path.resolve(dir, files[i++])); - if (isIgnored(fp)) continue; - - var stat = utils.tryStats(fp); - if (!stat) continue; - - if (stat.isDirectory()) { - if (fp.indexOf('.git') !== -1) { - continue; - } - res.push.apply(res, tryReaddirs(fp, ignored)); - } - res.push(fp); - } - return res; -}; - -/** - * Try to read a directory of files. Silently catches - * any errors and returns an empty array. - */ - -utils.tryStats = function tryStats(fp, verbose) { - if (typeof fp === 'undefined') { - throw new Error('utils.tryStats() expects a string.'); - } - - try { - return fs.statSync(fp); - } catch (err) { - if (verbose) console.log(err); +utils.arrayify = function arrayify(val) { + if (typeof val !== 'string' && !Array.isArray(val)) { + throw new TypeError('app#utils.arrayify expects val to be a string or array.'); } - return null; + return !Array.isArray(val) ? [val] : val; }; /** - * Get the basename of a file path, excluding extension. - * - * @param {String} `fp` - * @param {String} `ext` Optionally pass the extension. - */ - -utils.basename = function basename(fp, ext) { - return fp.substr(0, fp.length - (ext || path.extname(fp)).length); -}; - -/** - * Default `renameKey` function. - */ - -utils.renameKey = function renameKey(fp, acc, opts) { - fp = relative.toBase(opts.cwd, fp); - return utils.basename(fp); -}; - -/** - * Get the extension from a string, or the first string - * in an array (like glob patterns) - */ - -utils.getExt = function getExt(str) { - str = Array.isArray(str) ? str[0] : str; - return str.slice(str.lastIndexOf('.')); -}; - -/** - * Ensure that a file extension is formatted properly. - * - * @param {String} `ext` - */ - -utils.formatExt = function formatExt(ext) { - if (ext && ext[0] !== '.') ext = '.' + ext; - return ext; -}; - -/** - * Try to call `fn` on `filepath`, either returning the - * result when successful, or failing silently and - * returning null. - */ - -utils.tryCatch = function tryCatch(fn, fp) { - try { - return fn(path.resolve(fp)); - } catch(err) {} - return null; -}; - -/** - * Try to resolve the given path, or fail silently - */ - -utils.tryRequire = function tryRequire(fp) { - return utils.tryCatch(require, fp); -}; - -/** - * Try to resolve the given path, or fail silently - */ - -utils.tryResolve = function tryResolve(fp) { - return utils.tryCatch(require.resolve, fp); -}; - -/** - * Try to read a file, or fail silently - */ - -utils.tryRead = function tryRead(fp) { - return utils.tryCatch(utils.readFile, fp); -}; - -/** - * Try to read a directory of files. Silently catches - * any errors and returns an empty array. - */ - -utils.tryStat = function tryStat(fp) { - return utils.tryCatch(fs.statSync, fp); -}; - -/** - * Read a file - */ - -utils.readFile = function readFile(fp) { - return fs.readFileSync(fp, 'utf8'); -}; - -/** - * Write a file - */ - -utils.writeFile = function writeFile(fp, str) { - return fs.writeFileSync(fp, str); -}; - -/** - * Creates a matching function to use against - * the list of given files. - */ - -utils.match = function match(files) { - return function(pattern, options) { - return mm(files, pattern, options); - }; -}; - -/** - * Read a file - */ - -utils.tryReadJson = function tryReadJson(fp) { - try { - return JSON.parse(utils.readFile(fp)); - } catch(err) {} - return null; -}; - -/** - * Try to read a directory of files. Silently catches - * any errors and returns an empty array. - */ - -utils.tryReaddir = function tryReaddir(fp) { - try { - return fs.readdirSync(fp); - } catch (err) {} - return []; -}; - -/** - * Escape delimiters - */ - -utils.escape = function escape(file, next) { - file.content = file.content.split('{%%').join('__LEFT_DELIM__'); - next(); -}; - -/** - * Unescape delimiters + * Expose `utils` */ -utils.unescape = function unescape(file, next) { - file.content = file.content.split('__LEFT_DELIM__').join('{%'); - next(); -}; +module.exports = utils; diff --git a/package.json b/package.json index fa8beb3..9b2a0e1 100644 --- a/package.json +++ b/package.json @@ -37,43 +37,48 @@ "test": "mocha" }, "dependencies": { - "async": "^0.9.0", + "arr-union": "^2.0.1", "async-helper-base": "^0.2.0", + "base-loader": "^0.1.0", "chalk": "^1.0.0", "data-store": "^0.4.1", "engine-lodash": "^0.6.3", "event-stream": "^3.3.0", "export-dirs": "^0.2.4", "export-files": "^2.0.1", + "extend-shallow": "^1.1.4", "git-user-name": "^1.1.2", "globby": "^2.0.0", + "gulp-dest-paths": "^0.1.1", "helper-date": "^0.2.1", "init-file-loader": "^0.1.1", + "kind-of": "^1.1.0", + "lint-templates": "^0.1.2", "lodash": "^3.7.0", "logging-helpers": "^0.4.0", - "map-files": "^0.7.4", "markdown-utils": "^0.6.0", - "micromatch": "^2.1.6", + "middleware-utils": "^0.1.2", "minimist": "^1.1.1", "orchestrator": "^0.3.7", "parse-author": "^0.2.0", "parse-filepath": "^0.5.0", "parse-github-url": "^0.1.0", + "parser-front-matter": "^1.2.1", "plugin-error": "^0.1.0", "pretty-hrtime": "^1.0.0", - "relative": "^3.0.0", "resolve-up": "^0.1.1", "session-cache": "^0.1.3", - "sessionify": "^0.1.0", "template": "^0.13.1", "template-helpers": "^0.3.2", + "template-init": "^0.4.1", + "template-render": "^0.5.1", + "template-utils": "^0.6.2", "through2": "^0.6.3", "to-vinyl": "^0.1.2", "utils": "^0.2.1", - "vinyl": "^0.4.6", "vinyl-fs": "^1.0.0" }, "devDependencies": { "del": "^1.1.1" } -} +} \ No newline at end of file diff --git a/updatefile.js b/updatefile.js index 8a03154..69b560d 100644 --- a/updatefile.js +++ b/updatefile.js @@ -1,8 +1,9 @@ var update = require('./'); var del = require('del'); -update.task('default', function () { - del('actual', cb); +update.task('default', function (cb) { + console.log('deleting...'); + del('foooo', cb); }); update.run(); From 4fe8868a99e58d13829a77626b4b6f288faa2260 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 25 May 2015 13:53:18 -0400 Subject: [PATCH 097/274] remove loaders --- lib/loaders/base.js | 24 ------------------------ lib/loaders/file.js | 3 --- lib/loaders/index.js | 3 --- updatefile.js | 13 +++++++++---- 4 files changed, 9 insertions(+), 34 deletions(-) delete mode 100644 lib/loaders/base.js delete mode 100644 lib/loaders/file.js delete mode 100644 lib/loaders/index.js diff --git a/lib/loaders/base.js b/lib/loaders/base.js deleted file mode 100644 index 877ad4e..0000000 --- a/lib/loaders/base.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict'; - -var mapFiles = require('map-files'); -var utils = require('../utils'); - -module.exports = function base_(args) { - args = utils.arrayify(args); - var hasOpts = typeof args[1] === 'object'; - if (hasOpts) args[1].renameKey = utils.renameKey; - if (hasOpts && args[1].hasOwnProperty('content')) { - return normalize(args); - } - return mapFiles.apply(mapFiles, args); -}; - -function normalize(args) { - var fp = args[0]; - var o = args[1]; - var name = utils.basename(fp); - o.path = fp; - var res = {}; - res[name] = o; - return res; -} diff --git a/lib/loaders/file.js b/lib/loaders/file.js deleted file mode 100644 index dbbf862..0000000 --- a/lib/loaders/file.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -module.exports = require('init-file-loader'); diff --git a/lib/loaders/index.js b/lib/loaders/index.js deleted file mode 100644 index 874ea55..0000000 --- a/lib/loaders/index.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -module.exports = require('export-files')(__dirname); diff --git a/updatefile.js b/updatefile.js index 69b560d..c69569a 100644 --- a/updatefile.js +++ b/updatefile.js @@ -1,9 +1,14 @@ var update = require('./'); var del = require('del'); -update.task('default', function (cb) { - console.log('deleting...'); - del('foooo', cb); +update.task('default', function () { + console.log('default...'); }); -update.run(); +update.task('one', function () { + console.log('one...'); +}); + +update.task('two', function () { + console.log('two...'); +}); From bebb45b6a5376fe5b349abf020d94739cd5cdd4d Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 20 Oct 2015 06:32:52 -0400 Subject: [PATCH 098/274] refactor to use assemble-core --- .editorconfig | 6 +- .gitattributes | 12 +- .gitignore | 1 - .jshintrc | 2 +- .travis.yml | 11 +- .verb.md | 57 +- README.md | 338 ----- bin/update.js | 146 --- completion/README.md | 21 - completion/bash | 27 - completion/fish | 12 - completion/powershell | 62 - completion/zsh | 25 - gulpfile.js | 34 + index.js | 420 +----- lib/helpers/async.js | 11 - lib/helpers/collections.js | 33 - lib/helpers/sync.js | 9 - lib/{middleware => }/index.js | 2 - lib/init.js | 46 - lib/locals.js | 23 + lib/middleware/data.js | 10 - lib/middleware/dest.js | 11 - lib/middleware/dotfiles.js | 28 - lib/middleware/engine.js | 10 - lib/middleware/ext.js | 17 - lib/middleware/lint.js | 15 - lib/middleware/matter.js | 15 - lib/middleware/props.js | 13 - lib/middleware/src.js | 36 - lib/plugins/config.js | 45 + lib/plugins/dest.js | 54 - lib/plugins/index.js | 2 - lib/plugins/init.js | 38 - lib/plugins/lint.js | 12 - lib/plugins/locals.js | 19 + lib/plugins/process.js | 40 - lib/plugins/questions.js | 34 + lib/plugins/reloadViews.js | 34 + lib/plugins/render.js | 32 - lib/plugins/store.js | 15 + lib/session.js | 7 - lib/stack.js | 68 - lib/transforms/env/author.js | 17 - lib/transforms/env/cwd.js | 29 - lib/transforms/env/git.js | 15 - lib/transforms/env/github.js | 20 - lib/transforms/env/index.js | 3 - lib/transforms/env/paths.js | 9 - lib/transforms/env/pkg.js | 17 - lib/transforms/env/tmpl.js | 27 - lib/transforms/env/user.js | 10 - lib/transforms/env/username.js | 24 - lib/transforms/index.js | 3 - lib/transforms/init/argv.js | 10 - lib/transforms/init/config.js | 3 - lib/transforms/init/config/del.js | 26 - lib/transforms/init/config/get.js | 30 - lib/transforms/init/config/index.js | 21 - lib/transforms/init/config/option.js | 38 - lib/transforms/init/config/set.js | 28 - lib/transforms/init/config/union.js | 30 - lib/transforms/init/create.js | 11 - lib/transforms/init/cwd.js | 28 - lib/transforms/init/data.js | 15 - lib/transforms/init/defaults.js | 15 - lib/transforms/init/engines.js | 11 - lib/transforms/init/helpers.js | 11 - lib/transforms/init/index.js | 3 - lib/transforms/init/load.js | 10 - lib/transforms/init/loaders.js | 13 - lib/transforms/init/metadata.js | 19 - lib/transforms/init/middleware.js | 40 - lib/transforms/init/plugins.js | 30 - lib/transforms/init/runner.js | 14 - lib/transforms/modifiers/index.js | 3 - lib/transforms/session.js | 10 - lib/transforms/updaters/index.js | 7 - lib/transforms/updaters/updaters.js | 44 - lib/updaters/copyright.js | 8 + lib/updaters/license.js | 44 + lib/updaters/rename.js | 39 + lib/utils.js | 24 + lib/utils/completion.js | 22 - lib/utils/index.js | 38 - lib/utils/task-tree.js | 18 - package.json | 153 ++- recipes/create-an-updater.md | 24 - test/app.applyLayout.js | 85 ++ test/app.collection.compile.js | 50 + test/app.collection.js | 176 +++ test/app.collection.render.js | 155 +++ test/app.compile.js | 52 + test/app.copy.js | 30 + test/app.create.js | 176 +++ test/app.data.js | 72 ++ test/app.dest.js | 1104 ++++++++++++++++ test/app.engines.js | 159 +++ test/app.events.js | 50 + test/app.get-set.js | 72 ++ test/app.handle.js | 23 + test/app.handlers.js | 128 ++ test/app.js | 130 ++ test/app.list.compile.js | 44 + test/app.list.render.js | 157 +++ test/app.lookups.js | 112 ++ test/app.middleware.js | 60 + test/app.option.js | 104 ++ test/app.render.js | 88 ++ test/app.route.js | 93 ++ test/app.src.js | 295 +++++ test/app.task.js | 156 +++ test/app.use.js | 281 ++++ test/app.view.compile.js | 38 + test/app.view.render.js | 92 ++ test/app.watch.js | 42 + test/collection.engines.js | 177 +++ test/collection.events.js | 27 + test/collection.js | 537 ++++++++ test/collection.options.js | 25 + test/collection.render.js | 138 ++ test/collection.use.js | 156 +++ test/dest.js | 908 +++++++++++++ test/fixtures/bom-utf16be.txt | Bin 0 -> 244 bytes test/fixtures/bom-utf16le.txt | Bin 0 -> 244 bytes test/fixtures/bom-utf8.txt | 1 + test/fixtures/copy/example.txt | 1 + test/fixtures/data/a.json | 3 + test/fixtures/data/alert.json | 7 + test/fixtures/data/b.json | 3 + test/fixtures/data/c.json | 3 + test/fixtures/data/data.json | 3 + test/fixtures/data/test.json | 4 + .../front-matter/autodetect-no-lang.md | 5 + test/fixtures/front-matter/autodetect-yaml.md | 5 + test/fixtures/front-matter/lang-yaml.md | 5 + test/fixtures/generic/run.dmc | 1 + test/fixtures/generic/test.dmc | 1 + test/fixtures/helpers/a.js | 3 + test/fixtures/helpers/b.js | 3 + test/fixtures/helpers/c.js | 3 + test/fixtures/helpers/obj.js | 9 + test/fixtures/noext/license | 21 + test/fixtures/pages/a.hbs | 1 + test/fixtures/pages/b.hbs | 1 + test/fixtures/pages/c.hbs | 1 + test/fixtures/posts/a.txt | 4 + test/fixtures/posts/b.txt | 4 + test/fixtures/posts/c.txt | 4 + test/fixtures/templates/a.tmpl | 1 + test/fixtures/templates/b.tmpl | 1 + test/fixtures/templates/c.tmpl | 1 + test/fixtures/test-symlink | 1 + test/fixtures/test-symlink-dir/suchempty | 1 + test/fixtures/test.coffee | 1 + test/fixtures/vinyl/bom-utf16be.txt | Bin 0 -> 244 bytes test/fixtures/vinyl/bom-utf16le.txt | Bin 0 -> 244 bytes test/fixtures/vinyl/bom-utf8.txt | 1 + test/fixtures/vinyl/test-symlink | 1 + test/fixtures/vinyl/test-symlink-dir | 1 + test/fixtures/vinyl/test.coffee | 1 + test/fixtures/vinyl/wow/suchempty | 1 + test/fixtures/watch/test.txt | 1 + test/fixtures/wow/suchempty | 1 + test/group.js | 136 ++ test/handlers.js | 46 + test/helpers.js | 693 ++++++++++ test/item.js | 1066 +++++++++++++++ test/layouts.js | 127 ++ test/list.js | 679 ++++++++++ test/list.render.js | 137 ++ test/mergePartials.js | 108 ++ test/partials.js | 202 +++ test/questions.js | 58 + test/renameKey.js | 350 +++++ test/render.js | 70 + test/routes.js | 98 ++ test/store.js | 233 ++++ test/support/ignore.js | 6 + test/support/index.js | 75 ++ test/support/spy.js | 27 + test/view.content.js | 29 + test/view.events.js | 28 + test/view.js | 1151 +++++++++++++++++ test/view.methods.js | 40 + test/view.option.js | 29 + test/view.render.js | 54 + test/view.set.js | 34 + test/view.use.js | 60 + test/viewTypes.js | 33 + test/views.js | 445 +++++++ updatefile.js | 14 - 192 files changed, 12581 insertions(+), 2415 deletions(-) delete mode 100644 README.md delete mode 100755 bin/update.js delete mode 100644 completion/README.md delete mode 100644 completion/bash delete mode 100644 completion/fish delete mode 100644 completion/powershell delete mode 100644 completion/zsh create mode 100644 gulpfile.js delete mode 100644 lib/helpers/async.js delete mode 100644 lib/helpers/collections.js delete mode 100644 lib/helpers/sync.js rename lib/{middleware => }/index.js (77%) delete mode 100644 lib/init.js create mode 100644 lib/locals.js delete mode 100644 lib/middleware/data.js delete mode 100644 lib/middleware/dest.js delete mode 100644 lib/middleware/dotfiles.js delete mode 100644 lib/middleware/engine.js delete mode 100644 lib/middleware/ext.js delete mode 100644 lib/middleware/lint.js delete mode 100644 lib/middleware/matter.js delete mode 100644 lib/middleware/props.js delete mode 100644 lib/middleware/src.js create mode 100644 lib/plugins/config.js delete mode 100644 lib/plugins/dest.js delete mode 100644 lib/plugins/init.js delete mode 100644 lib/plugins/lint.js create mode 100644 lib/plugins/locals.js delete mode 100644 lib/plugins/process.js create mode 100644 lib/plugins/questions.js create mode 100644 lib/plugins/reloadViews.js delete mode 100644 lib/plugins/render.js create mode 100644 lib/plugins/store.js delete mode 100644 lib/session.js delete mode 100644 lib/stack.js delete mode 100644 lib/transforms/env/author.js delete mode 100644 lib/transforms/env/cwd.js delete mode 100644 lib/transforms/env/git.js delete mode 100644 lib/transforms/env/github.js delete mode 100644 lib/transforms/env/index.js delete mode 100644 lib/transforms/env/paths.js delete mode 100644 lib/transforms/env/pkg.js delete mode 100644 lib/transforms/env/tmpl.js delete mode 100644 lib/transforms/env/user.js delete mode 100644 lib/transforms/env/username.js delete mode 100644 lib/transforms/index.js delete mode 100644 lib/transforms/init/argv.js delete mode 100644 lib/transforms/init/config.js delete mode 100644 lib/transforms/init/config/del.js delete mode 100644 lib/transforms/init/config/get.js delete mode 100644 lib/transforms/init/config/index.js delete mode 100644 lib/transforms/init/config/option.js delete mode 100644 lib/transforms/init/config/set.js delete mode 100644 lib/transforms/init/config/union.js delete mode 100644 lib/transforms/init/create.js delete mode 100644 lib/transforms/init/cwd.js delete mode 100644 lib/transforms/init/data.js delete mode 100644 lib/transforms/init/defaults.js delete mode 100644 lib/transforms/init/engines.js delete mode 100644 lib/transforms/init/helpers.js delete mode 100644 lib/transforms/init/index.js delete mode 100644 lib/transforms/init/load.js delete mode 100644 lib/transforms/init/loaders.js delete mode 100644 lib/transforms/init/metadata.js delete mode 100644 lib/transforms/init/middleware.js delete mode 100644 lib/transforms/init/plugins.js delete mode 100644 lib/transforms/init/runner.js delete mode 100644 lib/transforms/modifiers/index.js delete mode 100644 lib/transforms/session.js delete mode 100644 lib/transforms/updaters/index.js delete mode 100644 lib/transforms/updaters/updaters.js create mode 100644 lib/updaters/copyright.js create mode 100644 lib/updaters/license.js create mode 100644 lib/updaters/rename.js create mode 100644 lib/utils.js delete mode 100644 lib/utils/completion.js delete mode 100644 lib/utils/index.js delete mode 100644 lib/utils/task-tree.js delete mode 100644 recipes/create-an-updater.md create mode 100644 test/app.applyLayout.js create mode 100644 test/app.collection.compile.js create mode 100644 test/app.collection.js create mode 100644 test/app.collection.render.js create mode 100644 test/app.compile.js create mode 100644 test/app.copy.js create mode 100644 test/app.create.js create mode 100644 test/app.data.js create mode 100644 test/app.dest.js create mode 100644 test/app.engines.js create mode 100644 test/app.events.js create mode 100644 test/app.get-set.js create mode 100644 test/app.handle.js create mode 100644 test/app.handlers.js create mode 100644 test/app.js create mode 100644 test/app.list.compile.js create mode 100644 test/app.list.render.js create mode 100644 test/app.lookups.js create mode 100644 test/app.middleware.js create mode 100644 test/app.option.js create mode 100644 test/app.render.js create mode 100644 test/app.route.js create mode 100644 test/app.src.js create mode 100644 test/app.task.js create mode 100644 test/app.use.js create mode 100644 test/app.view.compile.js create mode 100644 test/app.view.render.js create mode 100644 test/app.watch.js create mode 100644 test/collection.engines.js create mode 100644 test/collection.events.js create mode 100644 test/collection.js create mode 100644 test/collection.options.js create mode 100644 test/collection.render.js create mode 100644 test/collection.use.js create mode 100644 test/dest.js create mode 100644 test/fixtures/bom-utf16be.txt create mode 100644 test/fixtures/bom-utf16le.txt create mode 100644 test/fixtures/bom-utf8.txt create mode 100644 test/fixtures/copy/example.txt create mode 100644 test/fixtures/data/a.json create mode 100644 test/fixtures/data/alert.json create mode 100644 test/fixtures/data/b.json create mode 100644 test/fixtures/data/c.json create mode 100644 test/fixtures/data/data.json create mode 100644 test/fixtures/data/test.json create mode 100644 test/fixtures/front-matter/autodetect-no-lang.md create mode 100644 test/fixtures/front-matter/autodetect-yaml.md create mode 100644 test/fixtures/front-matter/lang-yaml.md create mode 100644 test/fixtures/generic/run.dmc create mode 100644 test/fixtures/generic/test.dmc create mode 100644 test/fixtures/helpers/a.js create mode 100644 test/fixtures/helpers/b.js create mode 100644 test/fixtures/helpers/c.js create mode 100644 test/fixtures/helpers/obj.js create mode 100644 test/fixtures/noext/license create mode 100644 test/fixtures/pages/a.hbs create mode 100644 test/fixtures/pages/b.hbs create mode 100644 test/fixtures/pages/c.hbs create mode 100644 test/fixtures/posts/a.txt create mode 100644 test/fixtures/posts/b.txt create mode 100644 test/fixtures/posts/c.txt create mode 100644 test/fixtures/templates/a.tmpl create mode 100644 test/fixtures/templates/b.tmpl create mode 100644 test/fixtures/templates/c.tmpl create mode 120000 test/fixtures/test-symlink create mode 100644 test/fixtures/test-symlink-dir/suchempty create mode 100644 test/fixtures/test.coffee create mode 100644 test/fixtures/vinyl/bom-utf16be.txt create mode 100644 test/fixtures/vinyl/bom-utf16le.txt create mode 100644 test/fixtures/vinyl/bom-utf8.txt create mode 120000 test/fixtures/vinyl/test-symlink create mode 120000 test/fixtures/vinyl/test-symlink-dir create mode 100644 test/fixtures/vinyl/test.coffee create mode 100644 test/fixtures/vinyl/wow/suchempty create mode 100644 test/fixtures/watch/test.txt create mode 100644 test/fixtures/wow/suchempty create mode 100644 test/group.js create mode 100644 test/handlers.js create mode 100644 test/helpers.js create mode 100644 test/item.js create mode 100644 test/layouts.js create mode 100644 test/list.js create mode 100644 test/list.render.js create mode 100644 test/mergePartials.js create mode 100644 test/partials.js create mode 100644 test/questions.js create mode 100644 test/renameKey.js create mode 100644 test/render.js create mode 100644 test/routes.js create mode 100644 test/store.js create mode 100644 test/support/ignore.js create mode 100644 test/support/index.js create mode 100644 test/support/spy.js create mode 100644 test/view.content.js create mode 100644 test/view.events.js create mode 100644 test/view.js create mode 100644 test/view.methods.js create mode 100644 test/view.option.js create mode 100644 test/view.render.js create mode 100644 test/view.set.js create mode 100644 test/view.use.js create mode 100644 test/viewTypes.js create mode 100644 test/views.js delete mode 100644 updatefile.js diff --git a/.editorconfig b/.editorconfig index de49bd8..1ff40e6 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,6 +13,10 @@ insert_final_newline = true trim_trailing_whitespace = false insert_final_newline = false -[{test,fixtures,actual}/**] +[{,test/}{actual,fixtures}/**] +trim_trailing_whitespace = false +insert_final_newline = false + +[templates/**] trim_trailing_whitespace = false insert_final_newline = false diff --git a/.gitattributes b/.gitattributes index e25e130..660957e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,10 @@ -* text=auto -/test/** text eol=lf +# Enforce Unix newlines +* text eol=lf + +# binaries +*.ai binary +*.psd binary +*.jpg binary +*.gif binary +*.png binary +*.jpeg binary diff --git a/.gitignore b/.gitignore index de081d3..80a228c 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,3 @@ vendor .idea benchmark coverage -support diff --git a/.jshintrc b/.jshintrc index 6b00ad6..1d9d592 100644 --- a/.jshintrc +++ b/.jshintrc @@ -16,4 +16,4 @@ "sub": true, "undef": true, "unused": true -} \ No newline at end of file +} diff --git a/.travis.yml b/.travis.yml index dedfc07..0fc9381 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,11 @@ sudo: false language: node_js node_js: - - 'iojs' - - '0.12' - - '0.10' + - "0.10" + - "0.12" + - "0.13" + - "iojs" +matrix: + fast_finish: true + allow_failures: + - node_js: "0.13" diff --git a/.verb.md b/.verb.md index 4766191..b97aa7a 100644 --- a/.verb.md +++ b/.verb.md @@ -1,67 +1,22 @@ -# {%= name %} {%= badge("fury") %} {%= badge("travis") %} +# {%= name %} {%= badge("fury") %} > {%= description %} - - +## Install {%= include("install-npm", {save: true}) %} ## Usage ```js -var update = require('{%= name %}'); -``` - -This is an experimental library that is build 100% on top of [verb], but it's not quite ready to use on your projects. - -### What's this about? - -Update is a CLI tool that loads personal defaults/preferences, then runs a series of [verb](https://github.com/assemble/verb) plugins, transforms and "normalizers" on targeted files in the current project. - -**For example:** - -- it updates the copyright dates in banners, the README and LICENSE files -- it renames files to be the way I like them (like `LICENSE-MIT` => `LICENSE`) -- it updates banners to ensure they have the correct project URL and license information -- it updates `.jshintrc` with my lastest preferences -- it updates `.editorconfig` with my latest preferences -- it updates `package.json` properties with my latest preferences - -etc... this is maybe 20% of what it does currently. There are some bugs to work out, but I can tell this project is going to be worth spending time on. It's already paying off. - -### The goal - -The goal is to be able to easily update and normalize existing projects from the command line using a compbination of: - -- **global defaults** for project metadata, like your github username, license preference, and other properties that change very little if at all from project-to-project. -- **normalizers** that will normalize and update virtually anything in the project to: - + use your (latest) preference - + meet the (latest) standards for whatever piece of metadata is being updated. For example, it would lint your `.travis.md` files to ensure that `iojs` has been added to the `node_js` versions. - -### Next - -After I completely understand how this should work, I'll: - -1. remove all logic and preferences that are specific to my own projects. -1. Split everything out into plugins, helpers, utils etc. -1. Try to make everything compatible with gulp plugins, so we don't need to think about another format. - -For now, however, this project is not at all idiomatic. A lot of the logic is pretty opinionated, there is a lot of duplication, and some of the plugins are just sloppy. As a rule-of-thumb I like to get things working as a POC before I spend time cleaning up code. - -{%= include("install-global") %} - -## CLI - -From the command line, run: - -```bash -update +var updateNext = require('{%= name %}'); ``` ## API -{%= apidocs("index.js") %} + +{%%= apidocs("index.js") %} ## Related projects + {%%= related([]) %} ## Running tests diff --git a/README.md b/README.md deleted file mode 100644 index a9e7e78..0000000 --- a/README.md +++ /dev/null @@ -1,338 +0,0 @@ -# update [![NPM version](https://badge.fury.io/js/update.svg)](http://badge.fury.io/js/update) [![Build Status](https://travis-ci.org/jonschlinkert/update.svg)](https://travis-ci.org/jonschlinkert/update) - -> Lint and update your project to meet your standards. - - - -* [Usage](#usage) - - [What's this about?](#what-s-this-about-) - - [The goal](#the-goal) - - [Next](#next) - -* [CLI](#cli) -* [API](#api) -* [Related projects](#related-projects) -* [Running tests](#running-tests) -* [Contributing](#contributing) -* [Author](#author) -* [License](#license) - -_(Table of contents generated by [verb])_ - - - -## Install with [npm](npmjs.org) - -```bash -npm i update --save -``` - -## Usage - -```js -var update = require('update'); -``` - -This is an experimental library that is build 100% on top of [verb], but it's not quite ready to use on your projects. - -### What's this about? - -Update is a CLI tool that loads personal defaults/preferences, then runs a series of [verb](https://github.com/assemble/verb) plugins, transforms and "normalizers" on targeted files in the current project. - -**For example:** - -* it updates the copyright dates in banners, the README and LICENSE files -* it renames files to be the way I like them (like `LICENSE-MIT` => `LICENSE`) -* it updates banners to ensure they have the correct project URL and license information -* it updates `.jshintrc` with my lastest preferences -* it updates `.editorconfig` with my latest preferences -* it updates `package.json` properties with my latest preferences - -etc... this is maybe 20% of what it does currently. There are some bugs to work out, but I can tell this project is going to be worth spending time on. It's already paying off. - -### The goal - -The goal is to be able to easily update and normalize existing projects from the command line using a compbination of: - -* **global defaults** for project metadata, like your github username, license preference, and other properties that change very little if at all from project-to-project. -* **normalizers** that will normalize and update virtually anything in the project to: - - use your (latest) preference - - meet the (latest) standards for whatever piece of metadata is being updated. For example, it would lint your `.travis.md` files to ensure that `iojs` has been added to the `node_js` versions. - -### Next - -After I completely understand how this should work, I'll: - -1. remove all logic and preferences that are specific to my own projects. -2. Split everything out into plugins, helpers, utils etc. -3. Try to make everything compatible with gulp plugins, so we don't need to think about another format. - -For now, however, this project is not at all idiomatic. A lot of the logic is pretty opinionated, there is a lot of duplication, and some of the plugins are just sloppy. As a rule-of-thumb I like to get things working as a POC before I spend time cleaning up code. - -## Install globally with [npm](npmjs.org): - -```bash -npm i -g update -``` - -## CLI - -From the command line, run: - -```bash -update -``` - -## API - -### [Update](index.js#L36) - -Initialize `Update`. - -**Example** - -```js -var app = new Update(); -``` - -### [.src](index.js#L67) - -Glob patterns or filepaths to source files. - -**Example usage** - -**Params** - -* `glob` **{String|Array}**: Glob patterns or file paths to source files. -* `options` **{Object}**: Options or locals to merge into the context and/or pass to `src` plugins - -**Examples** - -```js -app.src('*.js') -``` - -```js -app.task('web-app', function() { - app.src('templates/*') - app.dest(process.cwd()) -}); -``` - -### [.templates](index.js#L93) - -Glob patterns or filepaths to templates stored in the `./templates` directory of an updater. - -**Example usage** - -**Params** - -* `glob` **{String|Array}**: Glob patterns or file paths to source files. -* `options` **{Object}**: Options or locals to merge into the context and/or pass to `src` plugins - -**Examples** - -```js -app.templates('*.js') -``` - -```js -app.task('licenses', function() { - app.templates('templates/licenses/*') - app.dest(process.cwd()) -}); -``` - -### [.dest](index.js#L118) - -Specify a destination for processed files. - -**Example usage** - -**Params** - -* `dest` **{String|Function}**: File path or rename function. -* `options` **{Object}**: Options or locals to merge into the context and/or pass to `dest` plugins - -**Examples** - -```js -app.dest('dist', {ext: '.xml'}) -``` - -```js -app.task('foo', function() { - app.src('templates/*') - app.dest('dist', {ext: '.xml'}) -}); -``` - -### [.task](index.js#L175) - -Define a task. - -**Params** - -* `name` **{String}** -* `fn` **{Function}** - -**Example** - -```js -app.task('docs', function() { - app.src('*.js').pipe(app.dest('.')); -}); -``` - -### [.getTask](index.js#L190) - -Get the id from the current task. Used in plugins to get the current session. - -* `returns` **{String}** `task`: The currently running task. - -**Example** - -```js -var id = verb.getTask(); -verb.views[id]; -``` - -### [.getCollection](index.js#L211) - -Get the collection name (inflection) of the given template type. - -* `returns` **{String}** `name`: Singular name of the collection to get - -**Example** - -```js -var collection = verb.getCollection('page'); -// gets the `pages` collection -//=> {a: {}, b: {}, ...} -``` - -### [.getViews](index.js#L228) - -Used in plugins to get a template from the current session. - -* `returns` **{String}** `id`: Pass the task-id from the current session. -* `returns` **{Object}** `file`: Vinyl file object. Must have an `id` property that matches the `id` of the session. - -**Example** - -```js -var views = app.getViews(); -``` - -### [.getFile](index.js#L244) - -Get a template (file) from the current session in a stream. - -* `returns` **{Object}** `file`: Vinyl file object. Must have an `id` property that matches the `id` of the session. - -**Example** - -```js -var file = app.getFile(file); -``` - -### [.pushToStream](index.js#L261) - -Get a template from the current session, convert it to a vinyl file, and push it into the stream. - -**Params** - -* `stream` **{Stream}**: Vinyl stream -* `id` **{String}**: Get the session `id` using `app.getTask()` - -**Example** - -```js -app.pushToStream(file); -``` - -### [.taskFiles](index.js#L278) - -`taskFiles` is a session-context-specific getter that returns the collection of files from the current `task`. - -* `returns` **{Object}**: Get the files from the current task. - -**Example** - -```js -var files = verb.taskFiles; -``` - -### [.updater](index.js#L301) - -Set or get a generator function by `name`. - -**Params** - -* `name` **{String}** -* `fn` **{Function}**: The updater plugin function - -**Example** - -```js -// set an updater -app.updater('foo', require('updater-foo')); - -// get an updater -var foo = app.updater('foo'); -``` - -### [.watch](index.js#L342) - -Re-run the specified task(s) when a file changes. - -**Params** - -* `glob` **{String|Array}**: Filepaths or glob patterns. -* `fn` **{Function}**: Task(s) to watch. - -**Example** - -```js -app.task('watch', function() { - app.watch('docs/*.md', ['docs']); -}); -``` - -## Related projects - -{%= related([]) %} - -## Running tests - -Install dev dependencies: - -```bash -npm i -d && npm test -``` - -## Contributing - -Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](https://github.com/jonschlinkert/update/issues) - -## Author - -**Jon Schlinkert** - -+ [github/jonschlinkert](https://github.com/jonschlinkert) -+ [twitter/jonschlinkert](http://twitter.com/jonschlinkert) - -## License - -Copyright (c) 2015 Jon Schlinkert -Released under the MIT license. - -*** - -_This file was generated by [verb-cli](https://github.com/assemble/verb-cli) on April 27, 2015._ - - - -[assemble]: http://assemble.io -[template]: https://github.com/jonschlinkert/template -[verb]: https://github.com/assemble/verb \ No newline at end of file diff --git a/bin/update.js b/bin/update.js deleted file mode 100755 index 41905a9..0000000 --- a/bin/update.js +++ /dev/null @@ -1,146 +0,0 @@ -#!/usr/bin/env node - -var path = require('path'); -var chalk = require('chalk'); -var prettyTime = require('pretty-hrtime'); -var completion = require('../lib/utils/completion'); -var taskTree = require('../lib/utils/task-tree'); -var app = require('..'); - -var argv = require('minimist')(process.argv.slice(2)); -var stack = argv._; -var len = stack.length, i = 0; - -while (len--) { - var name = stack[i++]; - var plugin = app.updater(name); - console.log(plugin); - // app.updater(name); -} - -var name = stack[0]; - -var updater = typeof name !== 'undefined' - ? app.updater(name) - : exit(0); - - // console.log(updater) -var fp = updater.updatefile; - -if (fp) { - var cwd = path.dirname(fp); - var instance = require(fp); - - instance.set('updater.cwd', cwd); - instance.set('updater.templates', cwd + '/templates'); - instance.emit('init'); - instance.emit('loaded'); - instance.extend('argv', argv); - - process.nextTick(function () { - instance.start.apply(instance, ['default']); - }.bind(instance)); -} - -// exit with 0 or 1 -var failed = false; -process.once('exit', function(code) { - if (code === 0 && failed) { - exit(1); - } -}); - -app.on('last', function () { - var args; - if (argv.set) { - args = argv.set.split('='); - app.store.set.apply(app.store, args); - } - - if (argv.has) { - args = argv.has.split('='); - app.store.has.apply(app.store, args); - } - - if (argv.omit) { - args = argv.omit.split('='); - app.store.omit.apply(app.store, args); - } - - if (argv.del) { - app.store.delete({force: true}); - } -}); - -app.on('err', function () { - failed = true; -}); - -app.on('task_start', function (e) { - console.log('starting', '\'' + chalk.cyan(e.task) + '\''); -}); - -app.on('task_stop', function (e) { - var time = prettyTime(e.hrDuration); - console.log('finished', '\'' + chalk.cyan(e.task) + '\'', 'after', chalk.magenta(time)); -}); - -app.on('task_err', function (e) { - var msg = formatError(e); - var time = prettyTime(e.hrDuration); - console.log(chalk.cyan(e.task), chalk.red('errored after'), chalk.magenta(time)); - console.log(msg); -}); - -app.on('task_not_found', function (err) { - console.log(chalk.red('task \'' + err.task + '\' is not in your updatefile')); - console.log('please check the documentation for proper updatefile formatting'); - exit(1); -}); - -function logTasks(env, instance) { - var tree = taskTree(instance.tasks); - tree.label = 'Tasks for ' + tildify(instance.module); - archy(tree).split('\n').forEach(function (v) { - if (v.trim().length === 0) { - return; - } - console.log(v); - }); -} - -// format orchestrator errors -function formatError(e) { - if (!e.err) { - return e.message; - } - - // PluginError - if (typeof e.err.showStack === 'boolean') { - return e.err.toString(); - } - - // normal error - if (e.err.stack) { - return e.err.stack; - } - - // unknown (string, number, etc.) - return new Error(String(e.err)).stack; -} - - -// fix stdout truncation on windows -function exit(code) { - if (process.platform === 'win32' && process.stdout.bufferSize) { - process.stdout.once('drain', function() { - process.exit(code); - }); - return; - } - process.exit(code); -} - -if (!argv._.length) { - app.emit('loaded'); -} diff --git a/completion/README.md b/completion/README.md deleted file mode 100644 index 66fdd6b..0000000 --- a/completion/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Completion for update - -> Borrowed from gulp, thanks to gulp and the grunt team and Tyler Kellen for creating this. - -To enable tasks auto-completion in shell you should add `eval "$(update --completion=shell)"` in your `.shellrc` file. - -## Bash - -Add `eval "$(update --completion=bash)"` to `~/.bashrc`. - -## Zsh - -Add `eval "$(update --completion=zsh)"` to `~/.zshrc`. - -## Powershell - -Add `Invoke-Expression ((update --completion=powershell) -join [System.Environment]::NewLine)` to `$PROFILE`. - -## Fish - -Add `update --completion=fish | source` to `~/.config/fish/config.fish`. diff --git a/completion/bash b/completion/bash deleted file mode 100644 index 9b29802..0000000 --- a/completion/bash +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash - -# Borrowed from grunt-cli via gulp ;) -# http://gruntjs.com/ -# -# Copyright (c) 2012 Tyler Kellen, contributors -# Licensed under the MIT license. -# https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT - -# Usage: -# -# To enable bash completion for update, add the following line (minus the -# leading #, which is the bash comment character) to your ~/.bashrc file: -# -# eval "$(update --completion=bash)" - -# Enable bash autocompletion. -function _update_completions() { - # The currently-being-completed word. - local cur="${COMP_WORDS[COMP_CWORD]}" - #Grab tasks - local compls=$(update --tasks-simple) - # Tell complete what stuff to show. - COMPREPLY=($(compgen -W "$compls" -- "$cur")) -} - -complete -o default -F _update_completions update diff --git a/completion/fish b/completion/fish deleted file mode 100644 index 18931b8..0000000 --- a/completion/fish +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env fish - -# Borrowed from gulp: - -# Usage: -# -# To enable fish completion for update, add the following line to -# your ~/.config/fish/config.fish file: -# -# update --completion=fish | source - -complete -c update -a "(update --tasks-simple)" -f diff --git a/completion/powershell b/completion/powershell deleted file mode 100644 index 92eeab9..0000000 --- a/completion/powershell +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright (c) 2014 Jason Jarrett -# -# Borrowed from gulp -# Tab completion for the `update` -# -# Usage: -# -# To enable powershell completion for update you need to be running -# at least PowerShell v3 or greater and add the below to your $PROFILE -# -# Invoke-Expression ((update --completion=powershell) -join [System.Environment]::NewLine) -# -# - -$update_completion_Process = { - param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) - - - # Load up an assembly to read the updatefile's sha1 - if(-not $global:VerbSHA1Managed) { - [Reflection.Assembly]::LoadWithPartialName("System.Security") | out-null - $global:VerbSHA1Managed = new-Object System.Security.Cryptography.SHA1Managed - } - - # setup a global (in-memory) cache - if(-not $global:VerbfileShaCache) { - $global:VerbfileShaCache = @{}; - } - - $cache = $global:VerbfileShaCache; - - # Get the updatefile's sha1 - $sha1updateFile = (resolve-path updatefile.js -ErrorAction Ignore | %{ - $file = [System.IO.File]::Open($_.Path, "open", "read") - [string]::join('', ($global:VerbSHA1Managed.ComputeHash($file) | %{ $_.ToString("x2") })) - $file.Dispose() - }) - - # lookup the sha1 for previously cached task lists. - if($cache.ContainsKey($sha1updateFile)){ - $tasks = $cache[$sha1updateFile]; - } else { - $tasks = (update --tasks-simple).split("`n"); - $cache[$sha1updateFile] = $tasks; - } - - - $tasks | - where { $_.startswith($commandName) } - Sort-Object | - foreach { New-Object System.Management.Automation.CompletionResult $_, $_, 'ParameterValue', ('{0}' -f $_) } -} - -if (-not $global:options) { - $global:options = @{ - CustomArgumentCompleters = @{}; - NativeArgumentCompleters = @{} - } -} - -$global:options['NativeArgumentCompleters']['update'] = $update_completion_Process -$function:tabexpansion2 = $function:tabexpansion2 -replace 'End\r\n{','End { if ($null -ne $options) { $options += $global:options} else {$options = $global:options}' diff --git a/completion/zsh b/completion/zsh deleted file mode 100644 index 780b682..0000000 --- a/completion/zsh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/zsh - -# Borrowed from grunt-cli, by way of gulp -# http://gruntjs.com/ -# -# Copyright (c) 2012 Tyler Kellen, contributors -# Licensed under the MIT license. -# https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT - -# Usage: -# -# To enable zsh completion for update, add the following line (minus the -# leading #, which is the zsh comment character) to your ~/.zshrc file: -# -# eval "$(update --completion=zsh)" - -# Enable zsh autocompletion. -function _update_completion() { - # Grab tasks - compls=$(update --tasks-simple) - completions=(${=compls}) - compadd -- $completions -} - -compdef _update_completion update diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..9ae53e9 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,34 @@ +var gulp = require('gulp'); +var mocha = require('gulp-mocha'); +var istanbul = require('gulp-istanbul'); +var jshint = require('gulp-jshint'); +require('jshint-stylish'); + +var lint = ['index.js', 'lib/utils.js', 'test/*.js']; + +gulp.task('coverage', function () { + return gulp.src(lint) + .pipe(istanbul()) + .pipe(istanbul.hookRequire()); +}); + +gulp.task('mocha', ['coverage'], function () { + return gulp.src('test/*.js') + .pipe(mocha({reporter: 'spec'})) + .pipe(istanbul.writeReports()); +}); + +gulp.task('jshint', function () { + return gulp.src(lint) + .pipe(jshint()) + .pipe(jshint.reporter('jshint-stylish')) +}); + +gulp.task('default', ['mocha', 'jshint'], function (cb) { + console.log('Finished "default"'); + + // force the process to end since `verb.watch` + // holds it open in the tests + process.exit(); + cb(); +}); diff --git a/index.js b/index.js index d851bee..804c788 100644 --- a/index.js +++ b/index.js @@ -1,412 +1,54 @@ -'use strict'; - -var typeOf = require('kind-of'); -var es = require('event-stream'); -var through = require('through2'); -var Template = require('template'); -var toVinyl = require('to-vinyl'); -var Task = require('orchestrator'); -var tutils = require('template-utils')._; -var vfs = require('vinyl-fs'); -var _ = require('lodash'); - -var plugins = require('./lib/plugins'); -var session = require('./lib/session'); -var stack = require('./lib/stack'); -var init = require('./lib/init'); - -/** - * Initialize `App` - * - * @param {Object} `context` - * @api private - */ - -function App() { - Template.apply(this, arguments); - Task.apply(this, arguments); - this.session = session; - this.plugins = {}; - init(this); -} - -_.extend(App.prototype, Task.prototype); -Template.extend(App.prototype); - -/** - * Register a plugin by `name` - * - * @param {String} `name` - * @param {Function} `fn` - * @api public - */ - -App.prototype.plugin = function(name, fn) { - if (arguments.length === 1) { - return this.plugins[name]; - } - if (typeof fn === 'function') { - fn = fn.bind(this); - } - this.plugins[name] = fn; - return this; -}; - -/** - * Create a plugin pipeline from an array of plugins. +/*! + * update * - * @param {Array} `plugins` Each plugin is a function that returns a stream, or the name of a registered plugin. - * @param {Object} `options` - * @return {Stream} - * @api public + * Copyright (c) 2015, Jon Schlinkert. + * Licensed under the MIT License. */ -App.prototype.pipeline = function(plugins, options) { - var res = []; - for (var i = 0; i < plugins.length; i++) { - var val = plugins[i]; - if (typeOf(val) === 'function') { - res.push(val.call(this, options)); - } else if (typeOf(val) === 'object') { - res.push(val); - } else if (this.plugins.hasOwnProperty(val) && !this.isFalse('plugin ' + val)) { - res.push(this.plugins[val].call(this, options)); - } else { - res.push(through.obj()); - } - } - return es.pipe.apply(es, res); -}; - -/** - * Glob patterns or filepaths to source files. - * - * ```js - * app.src('*.js') - * ``` - * - * @param {String|Array} `glob` Glob patterns or file paths to source files. - * @param {Object} `options` Options or locals to merge into the context and/or pass to `src` plugins - * @api public - */ - -App.prototype.src = function(glob, opts) { - opts = _.merge({}, this.options, opts); - session.set('src', opts); - var app = this; - - return this.combine([ - vfs.src(glob, opts), - app.plugin('init')(app) - ], opts); -}; - -/** - * Glob patterns or filepaths to templates stored in the - * `./templates` directory of an updater. - * - * ```js - * app.templates('*.js') - * ``` - * - * @param {String|Array} `glob` Glob patterns or file paths to source files. - * @param {Object} `options` Options or locals to merge into the context and/or pass to `src` plugins - * @api public - */ - -App.prototype.templates = function(glob, opts) { - return stack.templates(this, glob, opts); -}; - -/** - * Specify a destination for processed files. - * - * ```js - * app.dest('dist', {ext: '.xml'}) - * ``` - * - * @param {String|Function} `dest` File path or rename function. - * @param {Object} `options` Options or locals to pass to `dest` plugins - * @api public - */ - -App.prototype.dest = function(dest, opts) { - var srcOpts = session.get('src') || {}; - opts = _.merge({}, this.options, srcOpts, opts); - var app = this; - - return this.combine([ - app.plugin('paths')(dest, opts), - app.plugin('lint'), - app.plugin('render')(opts), - app.plugin('dest')(dest, opts), - ], opts); -}; - -/** - * Copy a `glob` of files to the specified `dest`. - * - * ```js - * app.copy('assets/**', 'dist'); - * ``` - * - * @param {String|Array} `glob` - * @param {String|Function} `dest` - * @return {Stream} Stream to allow doing additional work. - */ - -App.prototype.copy = function(glob, dest, opts) { - return stack.templates(this, glob, opts) - // .pipe(this.process(opts)) - .pipe(vfs.dest(dest, opts)); -}; - -/** - * Plugin for processing templates using any registered engine. - * If this plugin is NOT used, engines will be selected based - * on file extension. - * - * ```js - * app.process(); - * ``` - * - * @param {String|Array} `glob` - * @param {String|Function} `dest` - * @return {Stream} Stream to allow doing additional work. - */ - -App.prototype.process = function(locals, options) { - locals = _.merge({id: this.getTask()}, this.cache.data, locals); - locals.options = _.merge({}, this.options, options, locals.options); - return plugins.process.call(this, locals, options); -}; - -/** - * Define a task. - * - * ```js - * app.task('docs', function() { - * app.src(['foo.js', 'bar/*.js']) - * .pipe(app.dest('./')); - * }); - * ``` - * - * @param {String} `name` - * @param {Function} `fn` - * @api public - */ - -App.prototype.task = App.prototype.add; - -/** - * Get the name of the current task-session. This is - * used in plugins to lookup data or views created in - * a task. - * - * ```js - * var id = app.getTask(); - * var views = app.views[id]; - * ``` - * - * @return {String} `task` The name of the currently running task. - * @api public - */ - -App.prototype.getTask = function() { - var name = this.session.get('task'); - return typeof name !== 'undefined' - ? 'task_' + name - : 'taskFile'; -}; - -/** - * Get a view collection by its singular-form `name`. - * - * ```js - * var collection = app.getCollection('page'); - * // gets the `pages` collection - * //=> {a: {}, b: {}, ...} - * ``` - * - * @return {String} `name` Singular name of the collection to get - * @api public - */ - -App.prototype.getCollection = function(name) { - if (typeof name === 'undefined') { - name = this.getTask(); - } - - if (this.views.hasOwnProperty(name)) { - return this.views[name]; - } - - name = this.inflections[name]; - return this.views[name]; -}; - -/** - * Get a file from the current session. - * - * ```js - * var file = app.getFile(file); - * ``` - * - * @return {Object} `file` Vinyl file object. Must have an `id` property. - * @api public - */ - -App.prototype.getFile = function(file, id) { - return this.getCollection(id)[file.id]; -}; - -/** - * Get a template from the current session, convert it to a vinyl - * file, and push it into the stream. - * - * ```js - * app.pushToStream(file); - * ``` - * - * @param {Stream} `stream` Vinyl stream - * @param {String} `id` Get the session `id` using `app.getTask()` - * @api public - */ - -App.prototype.pushToStream = function(id, stream) { - return tutils.pushToStream(this.getCollection(id), stream, toVinyl); -}; - -/** - * `taskFiles` is a session-context-specific getter that - * returns the collection of files from the currently running `task`. - * - * ```js - * var taskFiles = app.taskFiles; - * ``` - * - * @name .taskFiles - * @return {Object} Get the files from the currently running task. - * @api public - */ - -Object.defineProperty(App.prototype, 'taskFiles', { - configurable: true, - enumerable: true, - get: function () { - return this.getCollection(); - } -}); +'use strict'; -/** - * Set or get a generator function by `name`. - * - * ```js - * // set an updater - * app.updater('foo', require('updater-foo')); - * - * // get an updater - * var foo = app.updater('foo'); - * ``` - * @param {String} `name` - * @param {Function} `fn` The updater plugin function - * @api public - */ +var ask = require('assemble-ask'); +var Core = require('assemble-core'); +var plugin = require('./lib/plugins'); +var utils = require('./lib/utils'); -App.prototype.updater = function(name, fn) { - if (arguments.length === 1 && typeof name === 'string') { - return this.updaters[name]; +function Update(options) { + if (!(this instanceof Update)) { + return new Update(options); } - this.updaters[name] = fn; - return this; -}; + Core.call(this, options); + this.initUpdater(this.options); +} /** - * Register a plugin that can be arbitrarily pushed into a - * plugin stack. - * - * ```js - * app.plugin('foo', require('plugin-foo')); - * ``` - * - * @param {String} `name` Plugin name - * @param {Function} `fn` Plugin function, must return a vinyl stream. - * @api public + * Inherit assemble-core */ -App.prototype.plugin = function(name, fn) { - if (arguments.length === 1 && typeof name === 'string') { - return this.plugins[name]; - } - this.plugins[name] = fn; - return this; -}; +Core.extend(Update); /** - * Run an array of tasks. - * - * ```js - * app.run(['foo', 'bar']); - * ``` - * - * @param {Array} `tasks` - * @api private + * Initialize Updater defaults */ -App.prototype.run = function() { - var tasks = arguments.length ? arguments : ['default']; - process.nextTick(function () { - this.start.apply(this, tasks); - }.bind(this)); -}; - -/** - * Wrapper around Task._runTask to enable `sessions`. - * - * @param {Object} `task` Task to run - * @api private - */ +Update.prototype.initUpdater = function() { + this.use(plugin.locals({name: 'update'})); + this.use(plugin.store({name: 'update'})); + // this.use(plugin.config()); + // this.use(plugin.reloadViews()); + this.use(ask()); -App.prototype._runTask = function(task) { - var app = this; - app.session.run(function () { - app.session.set('task', task.name); - Task.prototype._runTask.call(app, task); + this.engine('md', require('engine-base'), { + delims: ['{%', '%}'] }); -}; - -/** - * Re-run the specified task(s) when a file changes. - * - * ```js - * app.task('watch', function() { - * app.watch('docs/*.md', ['docs']); - * }); - * ``` - * - * @param {String|Array} `glob` Filepaths or glob patterns. - * @param {Function} `fn` Task(s) to watch. - * @api public - */ -App.prototype.watch = function(glob, opts, fn) { - if (Array.isArray(opts) || typeof opts === 'function') { - fn = opts; opts = null; - } - if (!Array.isArray(fn)) return vfs.watch(glob, opts, fn); - return vfs.watch(glob, opts, function () { - this.start.apply(this, fn); - }.bind(this)); + this.onLoad(/\.md$/, function (view, next) { + utils.matter.parse(view, next); + }); }; -/** - * Expose the `App` class on `update.App` - */ - -App.prototype.App = App; /** - * Expose our instance of `update` + * Expose `Update` */ -module.exports = new App(); +module.exports = Update; diff --git a/lib/helpers/async.js b/lib/helpers/async.js deleted file mode 100644 index fdd00c6..0000000 --- a/lib/helpers/async.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -var helper = require('async-helper-base'); - -/** - * Transform for loading default async helpers - */ - -module.exports = function async_(verb) { - verb.asyncHelper('include', helper('include')); -}; diff --git a/lib/helpers/collections.js b/lib/helpers/collections.js deleted file mode 100644 index 37eb502..0000000 --- a/lib/helpers/collections.js +++ /dev/null @@ -1,33 +0,0 @@ -'use strict'; - -var _ = require('lodash'); - -/** - * Transform for loading helper collections - * - * - Loads template-helpers - * - Loads logging-helpers - * - Exposes markdown-utils as helpers - * - exposes path helpers on the `path.` property - * - * ```js - * <%= mdu.link(author.name, author.url) %> - * //=> [Jon Schlinkert](https://github.com/jonschlinkert) - * - * <%= path.extname("foo.md") %> - * //=> '.md' - * ``` - */ - -module.exports = function collections_(verb) { - verb.helpers({console: console}); - verb.helpers(require('logging-helpers')); - - // namespaced helpers - verb.helpers({mdu: require('markdown-utils')}); - - // remove `path` helpers from root and add them to `path.` - var helpers = require('template-helpers'); - verb.helpers(_.omit(helpers._, Object.keys(helpers.path))); - verb.helpers({path: helpers.path}); -}; diff --git a/lib/helpers/sync.js b/lib/helpers/sync.js deleted file mode 100644 index 5e40dc4..0000000 --- a/lib/helpers/sync.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -/** - * Transform for loading default sync helpers - */ - -module.exports = function sync_(app) { - app.helper('date', require('helper-date')); -}; diff --git a/lib/middleware/index.js b/lib/index.js similarity index 77% rename from lib/middleware/index.js rename to lib/index.js index 874ea55..23b2930 100644 --- a/lib/middleware/index.js +++ b/lib/index.js @@ -1,3 +1 @@ -'use strict'; - module.exports = require('export-files')(__dirname); diff --git a/lib/init.js b/lib/init.js deleted file mode 100644 index 7979256..0000000 --- a/lib/init.js +++ /dev/null @@ -1,46 +0,0 @@ -'use strict'; - -var init = require('./transforms/'); - -/** - * Load transforms - * | config - * | loaders - * | templates - * | options - * | middleware - * | plugins - * | load - * | engines - * | helpers - */ - -module.exports = function(app) { - app.transform('metadata', init.metadata); - app.transform('plugins', init.plugins); - - app.once('loaded', function () { - app.transform('cwd', init.cwd); - app.transform('paths', init.paths); - app.emit('env'); - }); - - app.once('env', function () { - app.transform('options', init.options); - app.transform('runner', init.runner); - app.transform('argv', init.argv); - app.transform('config', init.config); - app.transform('loaders', init.loaders); - app.transform('create', init.create); - app.transform('engines', init.engines); - app.transform('middleware', init.middleware); - app.transform('helpers', init.helpers); - app.transform('load', init.load); - app.emit('init'); - }); - - app.once('init', function () { - app.transform('helpers', init.helpers); - app.emit('last'); - }); -}; diff --git a/lib/locals.js b/lib/locals.js new file mode 100644 index 0000000..6ff09c2 --- /dev/null +++ b/lib/locals.js @@ -0,0 +1,23 @@ +'use strict'; + +var get = require('get-value'); +var set = require('set-value'); + +function Locals(name, app) { + this.cache = get(app.cache.data, name) || (app.cache.data[name] = {}); +} + +Locals.prototype.get = function(key) { + return get(this.cache, key); +}; + +Locals.prototype.set = function(key, value) { + set(this.cache, key, value); + return this; +}; + +/** + * Expose Locals + */ + +module.exports = Locals; diff --git a/lib/middleware/data.js b/lib/middleware/data.js deleted file mode 100644 index e738d30..0000000 --- a/lib/middleware/data.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; - -/** - * Prime the `file.data` object. - */ - -module.exports = function(file, next) { - file.data = file.data || {}; - next(); -}; diff --git a/lib/middleware/dest.js b/lib/middleware/dest.js deleted file mode 100644 index e00b3ce..0000000 --- a/lib/middleware/dest.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -/** - * Prime `file.data.dest`. The rest of the path logic for - * `dest` is handled in the pipeline by the `dest` plugin. - */ - -module.exports = function(file, next) { - file.data.dest = file.data.dest || {}; - next(); -}; diff --git a/lib/middleware/dotfiles.js b/lib/middleware/dotfiles.js deleted file mode 100644 index e5a786b..0000000 --- a/lib/middleware/dotfiles.js +++ /dev/null @@ -1,28 +0,0 @@ -'use strict'; - -var path = require('path'); - -/** - * Rename dotfile templates. - */ - -module.exports = function (app) { - var dotfiles = app.disabled('dotfiles'); - - return function dotfiles_(file, next) { - if (dotfiles) return next(); - - dotfiles = app.disabled('dotfiles'); - if (dotfiles) return next(); - - if (file.path.indexOf('templates/dotfiles') !== -1) { - var dirname = path.dirname(file.path); - var basename = path.basename(file.path); - if (basename[0] !== '.') { - basename = '.' + basename; - } - file.path = path.join(dirname, basename); - } - next(); - }; -}; diff --git a/lib/middleware/engine.js b/lib/middleware/engine.js deleted file mode 100644 index 88f358e..0000000 --- a/lib/middleware/engine.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; - -/** - * Set the engine to use - */ - -module.exports = function engine_(file, next) { - file.options.engine = file.options.engine || file.ext || '.md'; - next(); -}; diff --git a/lib/middleware/ext.js b/lib/middleware/ext.js deleted file mode 100644 index 7d90f84..0000000 --- a/lib/middleware/ext.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict'; - -var path = require('path'); -var utils = require('template-utils')._; - -/** - * Ensure that `ext` is on the file object. - */ - -module.exports = function(file, next) { - file.ext = file.ext || file.data.src.ext || path.extname(file.path); - - if (typeof file.ext === 'string') { - file.ext = utils.formatExt(file.ext); - } - next(); -}; diff --git a/lib/middleware/lint.js b/lib/middleware/lint.js deleted file mode 100644 index ebdb6a7..0000000 --- a/lib/middleware/lint.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -var lint = require('lint-templates'); - -/** - * Lint `file.content` for missing helpers - * or data properties. - */ - -module.exports = function (app) { - return function(file, next) { - lint(app, file); - next(); - }; -}; diff --git a/lib/middleware/matter.js b/lib/middleware/matter.js deleted file mode 100644 index feb4e50..0000000 --- a/lib/middleware/matter.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -var parser = require('parser-front-matter'); -var extend = require('extend-shallow'); - -/** - * Default middleware for parsing front-matter - */ - -module.exports = function matter(app) { - return function (file, next) { - var opts = extend({}, app.options.matter, file.options); - return parser.parse(file, opts, next); - }; -}; diff --git a/lib/middleware/props.js b/lib/middleware/props.js deleted file mode 100644 index 2fb109a..0000000 --- a/lib/middleware/props.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -/** - * Prime the `file` object with properties that - * can be extended in plugins. - */ - -module.exports = function(file, next) { - file.options = file.options || {}; - file.locals = file.locals || {}; - file.data = file.data || {}; - next(); -}; diff --git a/lib/middleware/src.js b/lib/middleware/src.js deleted file mode 100644 index f0db1a6..0000000 --- a/lib/middleware/src.js +++ /dev/null @@ -1,36 +0,0 @@ -'use strict'; - -var parse = require('parse-filepath'); -var path = require('path'); - -/** - * Set properties on `file.data.src` to use in plugins, - * other middleware, helpers, templates etc. - */ - -module.exports = function(file, next) { - var orig = file.options.originalPath; - - file.data.src = file.data.src || {}; - file.data.src.path = orig; - - // look for native node.js `path.parse` method first - var parsed = typeof path.parse === 'function' - ? path.parse(orig) - : parse(orig); - - file.data.src.dirname = parsed.dir; - file.data.src.relative = file.relative; - file.data.src.filename = parsed.name; - file.data.src.basename = parsed.base; - file.data.src.extname = parsed.ext; - file.data.src.ext = parsed.ext.slice(1); - - file.data.process = {}; - file.data.process.cwd = function () { - return process.cwd(); - }; - - file.data.resolve = path.resolve; - next(); -}; diff --git a/lib/plugins/config.js b/lib/plugins/config.js new file mode 100644 index 0000000..064fde1 --- /dev/null +++ b/lib/plugins/config.js @@ -0,0 +1,45 @@ +'use strict'; + +var Config = require('map-config'); +var lib = require('..'); +var utils = lib.utils; + +module.exports = function (options) { + return function (app) { + var config = app.locals.cache; + var keys = Object.keys(app.views); + var views = {}; + + keys.forEach(function (key) { + var instance = app[key]; + + views[key] = function (config) { + var mapper = new Config({ + options: 'option', + set: 'set', + addViews: 'addViews' + }, instance); + + console.log('loading templates from config: "' + key + '"'); + return mapper.process(config); + }; + }); + + var collections = new Config(views); + var configMap = new Config({ + plugins: function (config) { + utils.forOwn(config, function (val, key) { + var opts = utils.merge({}, app.options, val); + app.use(require(key)(opts)); + }); + }, + collections: function (config) { + return collections.process(config); + }, + helpers: 'helpers', + asyncHelpers: 'asyncHelpers' + }, app); + + configMap.process(config); + }; +}; diff --git a/lib/plugins/dest.js b/lib/plugins/dest.js deleted file mode 100644 index 0c9b8b3..0000000 --- a/lib/plugins/dest.js +++ /dev/null @@ -1,54 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var path = require('path'); -var through = require('through2'); -var PluginError = require('plugin-error'); -var utils = require('../utils'); - -/** - * Add dest properties to `file.data` - */ - -module.exports = plugin('verb', 'dest'); - -function plugin(appname, name) { - var pluginname = appname + '-' + name; - - return function destPlugin(destDir) { - return through.obj(function (file, enc, cb) { - if (file.isNull()) { - this.push(file); - return cb(); - } - if (file.isStream()) { - this.emit('error', new PluginError(pluginname, 'Streaming is not supported.')); - return cb(); - } - - try { - var dest = file.data.dest || {}; - if (typeof destDir === 'function') { - dest.dirname = destDir(file); - } else { - dest.dirname = path.dirname(file.path); - } - dest.relative = file.relative; - dest.extname = path.extname(file.relative); - dest.basename = path.basename(file.relative); - dest.filename = utils.basename(dest.basename, dest.extname); - dest.path = path.join(dest.dirname, dest.basename); - - file.data.dest = dest; - this.push(file); - return cb(); - } catch (err) { - this.emit('error', new PluginError(pluginname, err)); - return cb(); - } - }); - }; -} diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 874ea55..23b2930 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -1,3 +1 @@ -'use strict'; - module.exports = require('export-files')(__dirname); diff --git a/lib/plugins/init.js b/lib/plugins/init.js deleted file mode 100644 index ee39b62..0000000 --- a/lib/plugins/init.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -var PluginError = require('plugin-error'); -var through = require('through2'); - -module.exports = plugin('update', 'init'); - -function plugin(appname, name) { - var pluginname = appname + '-' + name + ':'; - - return function initPlugin() { - var app = this; - var id = this.getTask(); - - // create a template type for vinyl files and assign a loader - if (!app.hasOwnProperty(id)) { - app.create(id, ['file']); - } - - return through.obj(function (file, enc, cb) { - if (file.isNull()) { - this.push(file); - return cb(); - } - if (file.isStream()) { - this.emit('error', new PluginError(pluginname, 'Streaming is not supported.')); - return cb(); - } - - // Convert vinyl file to app template and add to collection - app[id](file); - cb(); - }, function (cb) { - app.pushToStream(id, this); - cb(); - }); - }; -} diff --git a/lib/plugins/lint.js b/lib/plugins/lint.js deleted file mode 100644 index 1012887..0000000 --- a/lib/plugins/lint.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -var through = require('through2'); -var lint = require('lint-templates'); - -module.exports = function(app) { - return through.obj(function (file, enc, cb) { - lint(app, file); - this.push(file); - return cb(); - }); -}; diff --git a/lib/plugins/locals.js b/lib/plugins/locals.js new file mode 100644 index 0000000..d1953fc --- /dev/null +++ b/lib/plugins/locals.js @@ -0,0 +1,19 @@ + +var lib = require('..'); +var Locals = lib.locals; +var utils = lib.utils; + +module.exports = function (config) { + config = config || {}; + var name = config.name; + if (!name) { + throw new Error('expected config.name to be a string.'); + } + + return function (app) { + var opts = app.option('update') || {}; + opts = utils.merge({}, config, opts); + this.locals = new Locals(name, this); + return this; + }; +}; diff --git a/lib/plugins/process.js b/lib/plugins/process.js deleted file mode 100644 index 79bcdf4..0000000 --- a/lib/plugins/process.js +++ /dev/null @@ -1,40 +0,0 @@ -'use strict'; - -var PluginError = require('plugin-error'); -var through = require('through2'); -var utils = require('utils'); - -/** - * Expose `process` plugin - */ - -module.exports = plugin('update'); - -function plugin(appname) { - return function processPlugin(locals) { - var app = this; - return through.obj(function (file, enc, cb) { - var collection = app.inflections[locals.id]; - var template = app.views[collection][file.id]; - - template.content = file.contents.toString(); - var context = utils.extend({}, locals, file.locals); - - try { - var stream = this; - app.render(template, context, function (err, content) { - if (err) { - stream.emit('error', new PluginError(appname + '-process', err)); - return cb(err); - } - file.contents = new Buffer(content); - stream.push(file); - cb(); - }); - } catch (err) { - this.emit('error', new PluginError(appname + '-process', err)); - return cb(); - } - }); - }; -} diff --git a/lib/plugins/questions.js b/lib/plugins/questions.js new file mode 100644 index 0000000..bd82ad1 --- /dev/null +++ b/lib/plugins/questions.js @@ -0,0 +1,34 @@ + +var lib = require('../'); +var utils = lib.utils; + +module.exports = function (options) { + return function (app) { + var opts = utils.merge({}, options, app.get('options.questions')); + this.questions = utils.questions(opts); + + /** + * Ask a question, or use a pre-existing value + * to populate the answer. + */ + + this.ask = function (locals) { + var ctx = utils.merge({}, this.cache.data, locals || {}); + return utils.ask({ + questions: this.questions, + store: this.store, + data: ctx + }); + }; + + /** + * Set a question to ask at a later point. + */ + + this.question = function () { + this.questions.set.apply(this.questions, arguments); + return this; + }; + return this; + }; +}; diff --git a/lib/plugins/reloadViews.js b/lib/plugins/reloadViews.js new file mode 100644 index 0000000..0619488 --- /dev/null +++ b/lib/plugins/reloadViews.js @@ -0,0 +1,34 @@ +'use strict'; + +/** + * Reload views when a user changes settings. + * + * Initializes event listeners to listen for events + * that indicate if something needs to be re-initialized + * based on user options. + */ + +module.exports = function (key) { + return function (app) { + this.only('reloadViews', 'option', function (key) { + reloadViews(app, key); + }); + + this.only('reloadViews', 'use', function () { + reloadViews(app); + }); + + function reloadViews(key) { + for (var name in app.views) { + if (app.views.hasOwnProperty(name)) { + var views = app.views[name]; + + if (!key || typeof app[name][key] !== 'function') { + app.create(name, app[name].options); + app[name].addViews(views); + } + } + } + } + }; +}; diff --git a/lib/plugins/render.js b/lib/plugins/render.js deleted file mode 100644 index aeb050b..0000000 --- a/lib/plugins/render.js +++ /dev/null @@ -1,32 +0,0 @@ -'use strict'; - -var through = require('through2'); -var extend = require('extend-shallow'); -var PluginError = require('plugin-error'); -var utils = require('../utils'); - -module.exports = function(options) { - var locals = options.locals; - var app = this; - - return through.obj(function (file, enc, cb) { - if (utils.norender(app, file, locals)) { - this.push(file); - return cb(); - } - - file.content = file.contents.toString(); - var stream = this; - - locals = extend({}, file.data, locals); - app.render(file, locals, function (err, res) { - if (err) { - stream.emit('error', new PluginError('render-plugin', err)); - return cb(err); - } - file.contents = new Buffer(res); - stream.push(file); - return cb(); - }); - }); -}; diff --git a/lib/plugins/store.js b/lib/plugins/store.js new file mode 100644 index 0000000..b9b6596 --- /dev/null +++ b/lib/plugins/store.js @@ -0,0 +1,15 @@ + +var lib = require('../'); +var utils = lib.utils; + +module.exports = function (config) { + config = config || {}; + var name = config.name; + + return function (app) { + var opts = app.option('store') || {}; + opts = utils.merge({}, config, opts); + this.store = utils.store(name, opts); + return this; + } +}; diff --git a/lib/session.js b/lib/session.js deleted file mode 100644 index 22fa4dd..0000000 --- a/lib/session.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -module.exports = require('session-cache')('update'); diff --git a/lib/stack.js b/lib/stack.js deleted file mode 100644 index 2423698..0000000 --- a/lib/stack.js +++ /dev/null @@ -1,68 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var path = require('path'); -var vfs = require('vinyl-fs'); -var _ = require('lodash'); - -/** - * Local dependencies - */ - -var plugins = require('./plugins'); -var session = require('./session'); -var utils = require('./utils'); - -/** - * Create a plugin stack to be run by `src` or `dest` - */ - -/** - * Default `src` plugins to run. - */ - -exports.src = function (app, glob, opts) { - opts = _.merge({}, app.options, opts); - opts.cwd = app.get('updater.cwd'); - session.set('src', opts); - - return utils.createStack(app, [ - vfs.src(glob, opts), - plugins.init.call(app, opts) - ]); -}; - -/** - * Default `template` plugins to run. - */ - -exports.templates = function (app, glob, opts) { - opts = _.merge({}, app.options, opts); - opts.cwd = app.get('updater.cwd'); - session.set('templates', opts); - - return utils.createStack(app, [ - vfs.src(glob, opts), - plugins.init.call(app, opts) - ]); -}; - -/** - * Default `dest` plugins to run. - */ - -exports.dest = function (app, dest, opts) { - var srcOpts = session.get('src') || session.get('templates') || {}; - opts = _.merge({}, app.options, srcOpts, opts); - opts.cwd = process.cwd(); - dest = path.resolve(opts.cwd, dest); - - return utils.createStack(app, [ - plugins.dest.call(app, dest, opts, opts.locals), - plugins.render.call(app, opts, opts.locals), - vfs.dest(dest, opts) - ]); -}; diff --git a/lib/transforms/env/author.js b/lib/transforms/env/author.js deleted file mode 100644 index fe2e4c3..0000000 --- a/lib/transforms/env/author.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict'; - -var author = require('parse-author'); - -/** - * Called in the `init` transform. Adds an `author` - * property to the context, or normalizes the existing one. - */ - -module.exports = function author_(app) { - var res = app.get('data.author'); - if (res && typeof res === 'string') { - app.data({author: author(res)}); - } else { - app.data({author: {}}); - } -}; diff --git a/lib/transforms/env/cwd.js b/lib/transforms/env/cwd.js deleted file mode 100644 index e0249aa..0000000 --- a/lib/transforms/env/cwd.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict'; - -/** - * Getter/setter for the current working directory. - * - * ```js - * console.log(app.cwd); - * //=> /dev/foo/bar/ - * ``` - * Or set: - * - * ```js - * app.cwd = 'foo'; - * ``` - */ - -module.exports = function(app) { - var cwd = app.option('cwd') || process.cwd(); - - Object.defineProperty(app, 'cwd', { - configurable: true, - get: function () { - return cwd; - }, - set: function (val) { - cwd = val; - } - }); -}; diff --git a/lib/transforms/env/git.js b/lib/transforms/env/git.js deleted file mode 100644 index c0fee38..0000000 --- a/lib/transforms/env/git.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -var username = require('git-user-name'); - -/** - * Called in the `username` transform, if a `username` - * cannot be determined from easier means, this attempts - * to get the `user.name` from global `.git config` - */ - -module.exports = function username_(app) { - if (!app.get('data.git.username')) { - app.set('data.git.username', username()); - } -}; diff --git a/lib/transforms/env/github.js b/lib/transforms/env/github.js deleted file mode 100644 index a55568a..0000000 --- a/lib/transforms/env/github.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; - -var parse = require('parse-github-url'); - -/** - * Adds a `github` property to the context. - * Called in the `init` transform. - */ - -module.exports = function github_(app) { - var repo = app.get('data.repository'); - var url = (repo && typeof repo === 'object') - ? repo.url - : repo; - - var github = parse(url); - if (github && Object.keys(github).length) { - app.data({github: github}); - } -}; diff --git a/lib/transforms/env/index.js b/lib/transforms/env/index.js deleted file mode 100644 index 874ea55..0000000 --- a/lib/transforms/env/index.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -module.exports = require('export-files')(__dirname); diff --git a/lib/transforms/env/paths.js b/lib/transforms/env/paths.js deleted file mode 100644 index b587a8b..0000000 --- a/lib/transforms/env/paths.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -/** - * Prime the `update.paths` object. - */ - -module.exports = function paths_(app) { - if (!app.has('paths')) app.paths = {}; -}; diff --git a/lib/transforms/env/pkg.js b/lib/transforms/env/pkg.js deleted file mode 100644 index 2b9406f..0000000 --- a/lib/transforms/env/pkg.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict'; - -var utils = require('../../utils'); -var cache = {}; - -/** - * Extend the package.json object onto `update.cache.data`. - * Called in the `init` transform. - */ - -module.exports = function pkg_(app) { - var filename = app.option('config') || 'package.json'; - - app.data(filename, function (fp) { - return cache[fp] || (cache[fp] = utils.tryRequire(fp)); - }); -}; diff --git a/lib/transforms/env/tmpl.js b/lib/transforms/env/tmpl.js deleted file mode 100644 index 1e299e6..0000000 --- a/lib/transforms/env/tmpl.js +++ /dev/null @@ -1,27 +0,0 @@ -'use strict'; - -var path = require('path'); - -/** - * Get/set the current working directory - * - * ```js - * console.log(app.templates); - * //=> /dev/foo/bar/ - * ``` - * Or set: - * - * ```js - * app.templates = 'foo'; - * ``` - */ - -module.exports = function templates_(app) { - var dir = app.option('templates'); - - if (typeof dir === 'undefined') { - dir = path.join(process.cwd(), 'templates'); - } - - app.set('paths.templates'); -}; diff --git a/lib/transforms/env/user.js b/lib/transforms/env/user.js deleted file mode 100644 index ab807b0..0000000 --- a/lib/transforms/env/user.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; - -/** - * Called in the `init` transform. Adds `user` and `username` - * for the current project to the context. - */ - -module.exports = function user_(app) { - app.set('data.username', app.get('data.git.username')); -}; diff --git a/lib/transforms/env/username.js b/lib/transforms/env/username.js deleted file mode 100644 index dbc62b2..0000000 --- a/lib/transforms/env/username.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict'; - -var github = require('parse-github-url'); - -/** - * If the `git` transform was not able to find anything, - * this attempts to generate a username from other fields. - * - * Called in the `init` transform. - */ - -module.exports = function username_(app) { - if (!app.get('data.github.username')) { - var author = app.get('data.author'); - if (typeof author.url === 'string' && /\/github/.test(author.url)) { - var parsed = github(author.url); - var user = (parsed && parsed.user) || ''; - if (user) { - app.set('data.github.username', user); - app.set('data.username', user); - } - } - } -}; diff --git a/lib/transforms/index.js b/lib/transforms/index.js deleted file mode 100644 index f1c2a3d..0000000 --- a/lib/transforms/index.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -module.exports = require('export-dirs')(__dirname); diff --git a/lib/transforms/init/argv.js b/lib/transforms/init/argv.js deleted file mode 100644 index a8131d6..0000000 --- a/lib/transforms/init/argv.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; - -/** - * Prime the `app.cache.argv` object. Used for getting/setting values - * that are passed from the command line. - */ - -module.exports = function(app) { - app.cache.argv = app.cache.argv || []; -}; diff --git a/lib/transforms/init/config.js b/lib/transforms/init/config.js deleted file mode 100644 index 0f623c1..0000000 --- a/lib/transforms/init/config.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -module.exports = require('export-files')(__dirname + '/config'); diff --git a/lib/transforms/init/config/del.js b/lib/transforms/init/config/del.js deleted file mode 100644 index b668d23..0000000 --- a/lib/transforms/init/config/del.js +++ /dev/null @@ -1,26 +0,0 @@ -'use strict'; - -var chalk = require('chalk'); - -/** - * Delete a value from the config store. - * - * ```sh - * $ --del foo - * ``` - */ - -module.exports = function(app) { - var config = app.config; - var del = app.get('argv.del'); - if (del) { - if (del.indexOf(',') !== -1) { - del.split(',').forEach(function (val) { - config.omit(val); - }); - } else { - config.omit(del); - } - console.log('deleted:', chalk.bold(del)); - } -}; diff --git a/lib/transforms/init/config/get.js b/lib/transforms/init/config/get.js deleted file mode 100644 index 8b0616c..0000000 --- a/lib/transforms/init/config/get.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict'; - -var chalk = require('chalk'); - -/** - * Get a value from the config store. - * - * ```sh - * $ --get one - * # or - * $ --get one,two,three - * ``` - */ - -module.exports = function(app) { - var config = app.config; - var get = app.get('argv.get'); - - if (get) { - if (get === true || get === 'true') { - console.log(chalk.cyan('config config:'), chalk.bold(JSON.stringify(config.data))); - } else if (get.indexOf(',') !== -1) { - get.split(',').forEach(function (val) { - console.log(val, '=', chalk.bold(JSON.stringify(config.get(val)))); - }); - } else { - console.log(get, '=', chalk.bold(JSON.stringify(config.get(get)))); - } - } -}; diff --git a/lib/transforms/init/config/index.js b/lib/transforms/init/config/index.js deleted file mode 100644 index a444a26..0000000 --- a/lib/transforms/init/config/index.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -var Config = require('data-store'); -var config = require('export-files')(__dirname); - -/** - * Initialize a global config store, for persisting data - * that may be reused across projects. - * - * Initialized in the `init` transform. - */ - -module.exports = function(app) { - app.config = new Config('app'); - - app.transform('set', config.set); - app.transform('get', config.get); - app.transform('del', config.del); - app.transform('option', config.option); - app.transform('union', config.union); -}; diff --git a/lib/transforms/init/config/option.js b/lib/transforms/init/config/option.js deleted file mode 100644 index 85035f8..0000000 --- a/lib/transforms/init/config/option.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -/** - * Persist a value on the config store. - * - * ```sh - * $ --option one=abc - * #=> {one: 'abc'} - * - * $ --option one - * #=> {one: true} - * ``` - */ - -module.exports = function(app) { - var config = app.config; - var args; - - var option = app.get('argv.option'); - if (option) { - args = option.split('='); - if (args.length === 2) { - config.set(args[0], args[1]); - } else { - config.set(args[0], true); - } - } - - var enable = app.get('argv.enable'); - if (enable) { - config.set(enable, true); - } - - var disable = app.get('argv.disable'); - if (disable) { - config.set(disable, false); - } -}; diff --git a/lib/transforms/init/config/set.js b/lib/transforms/init/config/set.js deleted file mode 100644 index 773aec9..0000000 --- a/lib/transforms/init/config/set.js +++ /dev/null @@ -1,28 +0,0 @@ -'use strict'; - -/** - * Persist a value on the config store. - * - * ```sh - * $ --set one=abc - * #=> {one: 'abc'} - * - * $ --set one - * #=> {one: true} - * ``` - */ - -module.exports = function(app) { - var config = app.config; - var args; - - var set = app.get('argv.set'); - if (set) { - args = set.split('='); - if (args.length === 2) { - config.set(args[0], args[1]); - } else { - config.set(args[0], true); - } - } -}; diff --git a/lib/transforms/init/config/union.js b/lib/transforms/init/config/union.js deleted file mode 100644 index 1925f5c..0000000 --- a/lib/transforms/init/config/union.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict'; - -var union = require('arr-union'); - -/** - * Get a value from the config store. - * - * ```sh - * $ --union one=a,b,c - * #=> {one: ['a', 'b', 'c']} - * - * $ --union one=d - * #=> {one: ['a', 'b', 'c', 'd']} - * ``` - */ - -module.exports = function(app) { - var config = app.config; - var args; - - var arr = app.get('argv.union'); - if (arr) { - args = arr.split('='); - if (args.length > 1) { - var val = config.get(args[1]); - args[2] = args[2].split(','); - config.set(args[1], union(val, args[2])); - } - } -}; diff --git a/lib/transforms/init/create.js b/lib/transforms/init/create.js deleted file mode 100644 index 1ab98b3..0000000 --- a/lib/transforms/init/create.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -/** - * Initialize built-in template types with - * the `base` loader. - */ - -module.exports = function() { - this.create('include', {isRenderable: true}, ['base']); - this.create('dotfile', {isRenderable: true}, ['base']); -}; diff --git a/lib/transforms/init/cwd.js b/lib/transforms/init/cwd.js deleted file mode 100644 index 388ba2c..0000000 --- a/lib/transforms/init/cwd.js +++ /dev/null @@ -1,28 +0,0 @@ -'use strict'; - -/** - * Getter/setter for the current working directory. - * - * ```js - * console.log(app.cwd); - * //=> /dev/foo/bar/ - * ``` - * Or set: - * - * ```js - * app.cwd = 'foo'; - * ``` - */ - -module.exports = function(app) { - var cwd = app.option('cwd') || process.cwd(); - - Object.defineProperty(app, 'cwd', { - get: function () { - return cwd; - }, - set: function (val) { - cwd = val; - } - }); -}; diff --git a/lib/transforms/init/data.js b/lib/transforms/init/data.js deleted file mode 100644 index 38c9c5f..0000000 --- a/lib/transforms/init/data.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -var path = require('path'); -var utils = require('../../utils'); - -/** - * Prime `app.cache.data` with empty package.json fields that - * will be over-written by the user's environment. - */ - -module.exports = function data_(app) { - app.data('../../templates/pkg.json', function (fp) { - return utils.tryRequire(path.resolve(__dirname, fp)); - }); -}; diff --git a/lib/transforms/init/defaults.js b/lib/transforms/init/defaults.js deleted file mode 100644 index 8507f1a..0000000 --- a/lib/transforms/init/defaults.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -/** - * Initialize default options. - */ - -module.exports = function options_(app) { - app.option({ - toc: {append: '\n\n_(Table of contents generated by [update])_'} - }); - app.option('view engine', 'md'); - app.disable('default routes'); - app.option('layoutDelims', ['{%', '%}']); - app.option('escapeDelims', ['<%%', '<%']); -}; diff --git a/lib/transforms/init/engines.js b/lib/transforms/init/engines.js deleted file mode 100644 index 87acd84..0000000 --- a/lib/transforms/init/engines.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -/** - * Load built-in engines - */ - -module.exports = function engines_(app) { - app.engine('*', require('engine-lodash'), { - delims: ['<%', '%>'] - }); -}; diff --git a/lib/transforms/init/helpers.js b/lib/transforms/init/helpers.js deleted file mode 100644 index 8943be7..0000000 --- a/lib/transforms/init/helpers.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -/** - * Load default helpers - */ - -module.exports = function helpers_(app) { - require('../../helpers/sync')(app); - require('../../helpers/async')(app); - require('../../helpers/collections')(app); -}; diff --git a/lib/transforms/init/index.js b/lib/transforms/init/index.js deleted file mode 100644 index 874ea55..0000000 --- a/lib/transforms/init/index.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -module.exports = require('export-files')(__dirname); diff --git a/lib/transforms/init/load.js b/lib/transforms/init/load.js deleted file mode 100644 index ebfee0b..0000000 --- a/lib/transforms/init/load.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; - -/** - * Load templates for built-in template types. - */ - -module.exports = function load_(app) { - app.includes('templates/**/*.*', { cwd: process.cwd()}); - app.dotfiles('templates/**/_*', { cwd: process.cwd()}); -}; diff --git a/lib/transforms/init/loaders.js b/lib/transforms/init/loaders.js deleted file mode 100644 index 455e82f..0000000 --- a/lib/transforms/init/loaders.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -var base = require('base-loader'); -var task = require('init-file-loader'); - -/** - * Load built-in loaders - */ - -module.exports = function(app) { - app.loader('base', [base]); - app.loader('task', [task]); -}; diff --git a/lib/transforms/init/metadata.js b/lib/transforms/init/metadata.js deleted file mode 100644 index d0b1cd1..0000000 --- a/lib/transforms/init/metadata.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; - -var path = require('path'); - -/** - * Sets App's package.json data on `app.metadata`. - * Called in the `init` transform. - */ - -module.exports = function(app) { - Object.defineProperty(app, 'metadata', { - get: function () { - return require(path.resolve(__dirname, '../../..', 'package.json')); - }, - set: function () { - console.log('`update.metadata` is read-only and should not be modified.'); - } - }); -}; diff --git a/lib/transforms/init/middleware.js b/lib/transforms/init/middleware.js deleted file mode 100644 index b84d782..0000000 --- a/lib/transforms/init/middleware.js +++ /dev/null @@ -1,40 +0,0 @@ -'use strict'; - -var chalk = require('chalk'); -var mu = require('middleware-utils'); -var tu = require('template-utils').utils; -var middleware = require('../../middleware'); -var err = mu.error, regex; - -/** - * Initialize default middleware - */ - -module.exports = function(app) { - // use extensions from engines to create route regex - if (typeof regex === 'undefined') { - regex = tu.extensionRe(Object.keys(app.engines)); - } - - app.onLoad(regex, mu.series([ - middleware.matter(app), - ])) - .onLoad(/./, mu.series([ - middleware.data, - middleware.src, - middleware.ext, - ])); -}; - -function debugFile(method, output) { - return function(file, next) { - if (!output) return next(); - console.log(chalk.yellow('//', method + ' ----------------')); - console.log(chalk.bold('file.path:'), file.path); - console.log(chalk.cyan('file.data:'), file.data); - console.log(chalk.gray('file.opts:'), file.options); - console.log(chalk.yellow('// end -----------------------')); - console.log(); - next(); - }; -} diff --git a/lib/transforms/init/plugins.js b/lib/transforms/init/plugins.js deleted file mode 100644 index 34fb7d0..0000000 --- a/lib/transforms/init/plugins.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict'; - -var render = require('template-render'); -var paths = require('gulp-dest-paths'); -var init = require('template-init'); -var vfs = require('vinyl-fs'); -var plugins = require('../../plugins'); - -/** - * Enable default plugins. - */ - -module.exports = function(app) { - app.plugin('init', init(app)); - app.plugin('lint', plugins.lint(app)); - app.plugin('paths', paths); - app.plugin('render', render(app)); - app.plugin('src', vfs.src); - app.plugin('dest', vfs.dest); - - // default `src` plugins - app.enable('plugin src'); - app.enable('plugin init'); - - // default `plugin dest`s - app.enable('plugin paths'); - app.enable('plugin lint'); - app.enable('plugin render'); - app.enable('plugin dest'); -}; diff --git a/lib/transforms/init/runner.js b/lib/transforms/init/runner.js deleted file mode 100644 index 5ebe56c..0000000 --- a/lib/transforms/init/runner.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -/** - * Metadata for the current `app.runner` - */ - -module.exports = function(app) { - app.data({ - runner: { - name: 'update', - url: 'https://github.com/jonschlinkert/update' - } - }); -}; diff --git a/lib/transforms/modifiers/index.js b/lib/transforms/modifiers/index.js deleted file mode 100644 index 874ea55..0000000 --- a/lib/transforms/modifiers/index.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -module.exports = require('export-files')(__dirname); diff --git a/lib/transforms/session.js b/lib/transforms/session.js deleted file mode 100644 index a6f11c5..0000000 --- a/lib/transforms/session.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; - -/** - * Expose `session` on app, for getting the - * current session from a running task. - */ - -module.exports = function() { - this.session = require('../session'); -}; diff --git a/lib/transforms/updaters/index.js b/lib/transforms/updaters/index.js deleted file mode 100644 index 7573e21..0000000 --- a/lib/transforms/updaters/index.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -var transforms = require('export-files')(__dirname); - -module.exports = function updaters_(app) { - app.transform('updaters', transforms.updaters); -}; diff --git a/lib/transforms/updaters/updaters.js b/lib/transforms/updaters/updaters.js deleted file mode 100644 index 9542bcf..0000000 --- a/lib/transforms/updaters/updaters.js +++ /dev/null @@ -1,44 +0,0 @@ -'use strict'; - -var glob = require('globby'); -var resolveUp = require('resolve-up'); -var path = require('path'); -var _ = require('lodash'); - -/** - * Load updaters onto the `updaters` object - */ - -module.exports = function updaters_(app) { - app.updaters = app.updaters || {}; - var pattern = app.option('updater pattern') || 'updater-*'; - - _.transform(resolveUp(pattern), function (acc, dir) { - var pkg = require(path.resolve(dir, 'package.json')); - if (!pkg.main) return acc; - - var fp = path.resolve(dir, pkg.main); - var res = {}; - - res.plugins = plugins(pattern, dir); - res.updatefile = require.resolve(dir); - res.module = node_modules(fp); - res.pkg = pkg; - var name = pkg.name.split('updater-').join(''); - acc[name] = res; - return acc; - }, app.updaters); -}; - -function node_modules(dir) { - var fp = path.resolve(path.dirname(dir), 'node_modules/update'); - return require.resolve(fp); -} - -function plugins(pattern, dir) { - var cwd = path.resolve(dir, 'node_modules'); - return glob.sync(pattern, {cwd: cwd}).reduce(function (acc, fp) { - acc[fp] = require.resolve(path.resolve(cwd, fp)); - return acc; - }, {}); -} diff --git a/lib/updaters/copyright.js b/lib/updaters/copyright.js new file mode 100644 index 0000000..336c980 --- /dev/null +++ b/lib/updaters/copyright.js @@ -0,0 +1,8 @@ + +var fs = require('fs'); +var copyright = require('update-copyright'); + +var str = fs.readFileSync('LICENSE', 'utf8'); +var updated = copyright(str, pkg); + +console.log(updated) diff --git a/lib/updaters/license.js b/lib/updaters/license.js new file mode 100644 index 0000000..d4454dd --- /dev/null +++ b/lib/updaters/license.js @@ -0,0 +1,44 @@ + +var fs = require('fs'); +var del = require('delete'); +var async = require('async'); +var green = require('ansi-green'); +var success = require('success-symbol'); +var copyright = require('update-copyright'); +var writeFile = require('write'); +var pkg = require('load-pkg')(); +var cwd = require('cwd'); + +function update(filepath, cb) { + var banner = 'The MIT License (MIT)\n\n'; + var fp = cwd(filepath); + + fs.readFile(fp, 'utf8', function (err, str) { + if (err) return cb(err); + + if (!/^The MIT License \(MIT\)/i.test(str)) { + str = banner + str; + } + + del(filepath, function (err) { + if (err) return cb(err); + + writeFile('LICENSE', str, function (err) { + if (err) return cb(err); + + return cb(null, 'updated'); + }); + }); + }); +} + +// update('LICENSE', function (err, res) { +// if (err) { +// return console.error(err); +// } +// var msg = ' LICENSE is already up to date.'; +// if (res === 'updated') { +// msg = ' updated LICENSE'; +// } +// console.log(green(success), msg); +// }); diff --git a/lib/updaters/rename.js b/lib/updaters/rename.js new file mode 100644 index 0000000..3fdba81 --- /dev/null +++ b/lib/updaters/rename.js @@ -0,0 +1,39 @@ + +var fs = require('fs'); +var del = require('delete'); +var async = require('async'); +var green = require('ansi-green'); +var success = require('success-symbol'); +var copyright = require('update-copyright'); +var writeFile = require('write'); +var pkg = require('load-pkg')(); +var cwd = require('cwd'); + +function renameFiles(files, cb) { + async.eachSeries(Object.keys(files), function (key, next) { + fs.rename(key, files[key], next); + }, cb); +} + +var files = { + 'LICENSE-MIT': 'LICENSE', + '.verbrc.md': '.verb.md', + 'README.md': 'readme.md' +}; + +// renameFiles(files, function(err) { +// if (err) { +// if (err.code !== 'ENOENT') { +// console.error(err); +// } +// return; +// } +// var keys = Object.keys(files); +// var len = keys.length; +// console.log(green(success), ' renamed ' + len, 'files'); +// }); + +var str = fs.readFileSync('LICENSE', 'utf8'); +var updated = copyright(str, pkg); + +console.log(updated) diff --git a/lib/utils.js b/lib/utils.js new file mode 100644 index 0000000..63d2c57 --- /dev/null +++ b/lib/utils.js @@ -0,0 +1,24 @@ +'use strict'; + +/** + * Lazily required module dependencies + */ + +var lazy = require('lazy-cache')(require); + +// type utils +lazy('mixin-deep', 'merge'); +lazy('for-own'); + +// engine/template utiles +lazy('ask-once', 'ask'); +lazy('parser-front-matter', 'matter'); +lazy('question-cache', 'questions'); +lazy('data-store', 'store'); +lazy('resolve-dir', 'resolve'); + +/** + * Expose utils + */ + +module.exports = lazy; diff --git a/lib/utils/completion.js b/lib/utils/completion.js deleted file mode 100644 index 906eb44..0000000 --- a/lib/utils/completion.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; - -var fs = require('fs'); -var path = require('path'); - -/** - * Borrowed from gulp - */ - -module.exports = function (name) { - if (typeof name !== 'string') { - throw new Error('Missing completion type'); - } - var file = path.join(__dirname, '../../completion', name); - try { - console.log(fs.readFileSync(file, 'utf8')); - process.exit(0); - } catch (err) { - console.log('echo "[update] autocompletion rules for', '\'' + name + '\'', 'not found"'); - process.exit(5); - } -}; diff --git a/lib/utils/index.js b/lib/utils/index.js deleted file mode 100644 index fafc040..0000000 --- a/lib/utils/index.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -/** - * Expose `utils/` modules on `utils` - */ - -var utils = require('export-files')(__dirname); - -/** - * Detect if the user has specfied not to render a vinyl template. - * - * @return {Boolean} - */ - -utils.norender = function norender(app, file, locals) { - return app.isTrue('norender') || app.isFalse('render') - || file.norender === true || file.render === false - || locals.norender === true || locals.render === false; -}; - -/** - * Coerce value to an array - * - * @api private - */ - -utils.arrayify = function arrayify(val) { - if (typeof val !== 'string' && !Array.isArray(val)) { - throw new TypeError('app#utils.arrayify expects val to be a string or array.'); - } - return !Array.isArray(val) ? [val] : val; -}; - -/** - * Expose `utils` - */ - -module.exports = utils; diff --git a/lib/utils/task-tree.js b/lib/utils/task-tree.js deleted file mode 100644 index 63b0bea..0000000 --- a/lib/utils/task-tree.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -/** - * Borrowed from gulp - */ - -module.exports = function (tasks) { - return Object.keys(tasks) - .reduce(function (prev, task) { - prev.nodes.push({ - label: task, - nodes: tasks[task].dep - }); - return prev; - }, { - nodes: [] - }); -}; diff --git a/package.json b/package.json index 9b2a0e1..a0ecb53 100644 --- a/package.json +++ b/package.json @@ -1,34 +1,18 @@ { - "name": "update", - "description": "Lint and update your project to meet your standards.", + "name": "update-next", + "description": "Update", "version": "0.1.0", - "homepage": "https://github.com/jonschlinkert/update", - "author": { - "name": "Jon Schlinkert", - "url": "https://github.com/jonschlinkert" - }, - "repository": { - "type": "git", - "url": "git://github.com/jonschlinkert/update.git" - }, + "homepage": "https://github.com/jonschlinkert/update-next", + "author": "Jon Schlinkert (https://github.com/jonschlinkert)", + "repository": "jonschlinkert/update-next", "bugs": { - "url": "https://github.com/jonschlinkert/update/issues" - }, - "license": { - "type": "MIT", - "url": "https://github.com/jonschlinkert/update/blob/master/LICENSE" + "url": "https://github.com/jonschlinkert/update-next/issues" }, + "license": "MIT", "files": [ - "cli.js", - "index.js", - "bin/", - "lib/" + "lib/", + "index.js" ], - "preferGlobal": true, - "engineStrict": true, - "bin": { - "update": "bin/update.js" - }, "main": "index.js", "engines": { "node": ">=0.10.0" @@ -37,48 +21,83 @@ "test": "mocha" }, "dependencies": { - "arr-union": "^2.0.1", - "async-helper-base": "^0.2.0", - "base-loader": "^0.1.0", - "chalk": "^1.0.0", - "data-store": "^0.4.1", - "engine-lodash": "^0.6.3", - "event-stream": "^3.3.0", - "export-dirs": "^0.2.4", - "export-files": "^2.0.1", - "extend-shallow": "^1.1.4", - "git-user-name": "^1.1.2", - "globby": "^2.0.0", - "gulp-dest-paths": "^0.1.1", - "helper-date": "^0.2.1", - "init-file-loader": "^0.1.1", - "kind-of": "^1.1.0", - "lint-templates": "^0.1.2", - "lodash": "^3.7.0", - "logging-helpers": "^0.4.0", - "markdown-utils": "^0.6.0", - "middleware-utils": "^0.1.2", - "minimist": "^1.1.1", - "orchestrator": "^0.3.7", - "parse-author": "^0.2.0", - "parse-filepath": "^0.5.0", - "parse-github-url": "^0.1.0", - "parser-front-matter": "^1.2.1", - "plugin-error": "^0.1.0", - "pretty-hrtime": "^1.0.0", - "resolve-up": "^0.1.1", - "session-cache": "^0.1.3", - "template": "^0.13.1", - "template-helpers": "^0.3.2", - "template-init": "^0.4.1", - "template-render": "^0.5.1", - "template-utils": "^0.6.2", - "through2": "^0.6.3", - "to-vinyl": "^0.1.2", - "utils": "^0.2.1", - "vinyl-fs": "^1.0.0" + "ansi-green": "^0.1.1", + "ask-once": "^0.5.1", + "assemble-ask": "^0.1.2", + "assemble-core": "^0.1.1", + "cwd": "^0.8.4", + "data-store": "^0.10.1", + "delete": "^0.2.1", + "export-files": "^2.1.0", + "for-own": "^0.1.3", + "get-value": "^1.2.1", + "lazy-cache": "^0.2.3", + "load-pkg": "^2.0.1", + "map-config": "^0.2.0", + "question-cache": "^0.3.1", + "resolve-dir": "^0.1.0", + "set-value": "^0.2.0", + "success-symbol": "^0.1.0", + "update-copyright": "^0.1.0", + "write": "^0.2.1" }, "devDependencies": { - "del": "^1.1.1" + "async": "^1.4.2", + "base-methods": "^0.2.14", + "buffer-equal": "0.0.1", + "consolidate": "^0.13.1", + "coveralls": "^2.11.4", + "define-property": "^0.2.5", + "engine-base": "^0.1.2", + "engine-handlebars": "^0.8.0", + "event-stream": "^3.3.1", + "graceful-fs": "^4.1.2", + "gulp": "^3.9.0", + "gulp-istanbul": "^0.10.0", + "gulp-jshint": "^1.11.2", + "gulp-mocha": "^2.1.3", + "is-buffer": "^1.1.0", + "istanbul": "^0.4.0", + "jshint-stylish": "^2.0.1", + "kind-of": "^2.0.1", + "look-up": "^0.8.1", + "mixin-deep": "^1.1.3", + "mocha": "*", + "parser-front-matter": "^1.2.5", + "resolve-glob": "^0.1.3", + "rimraf": "^2.4.3", + "should": "*", + "sinon": "^1.17.0", + "swig": "^1.4.2", + "through2": "^2.0.0", + "vinyl": "^0.5.3" + }, + "keywords": [], + "verb": { + "related": { + "list": [ + "composer", + "generate", + "boilerplate", + "scaffold", + "templates", + "verb" + ] + }, + "reflinks": [ + "scaffold", + "boilerplate", + "template", + "template", + "verb" + ] + }, + "update": { + "helpers": { + "related": "@/helper-related" + }, + "plugins": { + "@/foo": {} + } } -} \ No newline at end of file +} diff --git a/recipes/create-an-updater.md b/recipes/create-an-updater.md deleted file mode 100644 index 1088282..0000000 --- a/recipes/create-an-updater.md +++ /dev/null @@ -1,24 +0,0 @@ -# Create an updater - -> Updaters follow the same signature as gulp plugins - -**Example** - -```js -function foo(options) { - return through.obj(function (file, enc, cb) { - var str = file.contents.toString(); - // do stuff - file.contents = new Buffer(file.contents); - this.push(file); - cb(); - }); -} -``` - -## Publish your updater - -1. Name your project following the convention: `updater-*` -2. Don't use dots in the name (e.g `.js`) -3. Make sure you add `updater` to the keywords in package.json -4. Tweet about your updater! \ No newline at end of file diff --git a/test/app.applyLayout.js b/test/app.applyLayout.js new file mode 100644 index 0000000..6fdf390 --- /dev/null +++ b/test/app.applyLayout.js @@ -0,0 +1,85 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; +var page = { + content: '<%= name %>', + layout: 'default.tmpl', + locals: { + name: 'Halle' + } +}; + +describe('helpers', function () { + describe('rendering', function () { + beforeEach(function () { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.create('layout', {viewType: 'layout'}); + app.create('page'); + }); + + it('should throw an error when a layout cannot be found:', function (done) { + app.layout('fofof.tmpl', {content: '..'}); + app.page('a.tmpl', page) + .render(function (err) { + assert(err.message === 'Templates#layouts no layouts are registered, but one is defined: default.tmpl'); + done(); + }); + }); + + it('should emit an error when a layout cannot be found:', function (done) { + app.layout('fofof.tmpl', {content: '..'}); + app.on('error', function (err) { + assert(err.message === 'Templates#layouts no layouts are registered, but one is defined: default.tmpl'); + done(); + }); + + app.page('a.tmpl', page) + .render(function () { + }); + }); + + it('should throw an error - layout defined but no layouts registered:', function (done) { + app.page('a.tmpl', page) + .render(function (err) { + assert(err.message === 'Templates#layouts no layouts are registered, but one is defined: default.tmpl'); + done(); + }); + }); + + it('should emit an error - layout defined but no layouts registered:', function (done) { + app.on('error', function (err) { + assert(err.message === 'Templates#layouts no layouts are registered, but one is defined: default.tmpl'); + done(); + }); + app.page('a.tmpl', page) + .render(function () { + }); + }); + + it('should wrap a view with a layout (view.render):', function (done) { + app.layout('default.tmpl', {content: 'before {% body %} after'}); + app.page('a.tmpl', page) + .render(function (err) { + if (err) return done(err); + done(); + }); + }); + + it('should wrap a view with a layout (app.render):', function (done) { + app.layout('default.tmpl', {content: 'before {% body %} after'}); + app.page('a.tmpl', page); + + var view = app.pages.getView('a.tmpl'); + app.render(view, function (err, res) { + if (err) return done(err); + assert(res.contents.toString() === 'before Halle after'); + done(); + }); + }); + }); +}); + diff --git a/test/app.collection.compile.js b/test/app.collection.compile.js new file mode 100644 index 0000000..6bd595b --- /dev/null +++ b/test/app.collection.compile.js @@ -0,0 +1,50 @@ +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var Views = App.Views; +var views; + +describe('compile', function () { + beforeEach(function () { + views = new Views(); + }); + + it('should throw an error when an engine cannot be found:', function () { + views.addView('foo.bar', {content: '<%= name %>'}); + var page = views.getView('foo.bar'); + (function() { + views.compile(page); + }).should.throw('Views#compile cannot find an engine for: .bar'); + }); + + it('should compile a template:', function () { + views.engine('tmpl', require('engine-base')); + views.addView('a.tmpl', {path: 'a.tmpl', content: '<%= a %>', a: 'b'}); + + var page = views.getView('a.tmpl'); + var view = views.compile(page); + assert.equal(typeof view.fn, 'function'); + }); + + it('should compile a template by name:', function () { + views.engine('tmpl', require('engine-base')); + views.addView('a.tmpl', {path: 'a.tmpl', content: '<%= a %>', a: 'b'}); + + var view = views.compile('a.tmpl'); + assert.equal(typeof view.fn, 'function'); + }); + + it('should throw an error when a callback is given:', function () { + views.engine('md', require('engine-base')); + views.addView('foo.md', {content: '<%= name %>'}); + var page = views.getView('foo.md'); + (function() { + views.compile(page, function () {}); + }).should.throw('Views#compile is sync and does not take a callback function'); + + (function() { + views.compile(page, {}, function () {}); + }).should.throw('Views#compile is sync and does not take a callback function'); + }); +}); diff --git a/test/app.collection.js b/test/app.collection.js new file mode 100644 index 0000000..e866028 --- /dev/null +++ b/test/app.collection.js @@ -0,0 +1,176 @@ +require('mocha'); +require('should'); +var fs = require('fs'); +var assert = require('assert'); +var define = require('define-property'); +var support = require('./support'); +var App = support.resolve(); +var Collection = App.Collection; +var app; + +describe('collection', function () { + describe('method', function () { + beforeEach(function () { + app = new App(); + }); + + it('should expose the collection method', function () { + assert(typeof app.collection === 'function'); + }); + + it('should return a new collection', function () { + var collection = app.collection(); + assert(typeof collection === 'object'); + }); + + it('should have isCollection property', function () { + var collection = app.collection(); + assert(collection.isCollection === true); + }); + }); + + describe('adding views', function () { + beforeEach(function () { + app = new App() + .use(function () { + return function () { + define(this, 'count', { + get: function() { + return Object.keys(this.views).length; + }, + set: function () { + throw new Error('count is a read-only getter and cannot be defined.'); + } + }); + }; + }); + + app.engine('tmpl', require('engine-base')); + app.create('pages'); + }); + + it('should load a view onto the respective collection:', function () { + app.pages('test/fixtures/pages/a.hbs'); + app.views.pages.should.have.property('test/fixtures/pages/a.hbs'); + }); + + it('should allow collection methods to be chained:', function () { + app + .pages('test/fixtures/pages/a.hbs') + .pages('test/fixtures/pages/b.hbs') + .pages('test/fixtures/pages/c.hbs'); + + app.views.pages.should.have.properties([ + 'test/fixtures/pages/a.hbs', + 'test/fixtures/pages/b.hbs', + 'test/fixtures/pages/c.hbs' + ]); + }); + + it('should expose the `option` method:', function () { + app.pages.option('foo', 'bar') + .pages('test/fixtures/pages/a.hbs') + .pages('test/fixtures/pages/b.hbs') + .pages('test/fixtures/pages/c.hbs'); + + app.pages.options.should.have.property('foo', 'bar'); + app.views.pages.should.have.properties([ + 'test/fixtures/pages/a.hbs', + 'test/fixtures/pages/b.hbs', + 'test/fixtures/pages/c.hbs' + ]); + }); + + it('should expose the `option` method:', function () { + app.pages.option('foo', 'bar') + .pages('test/fixtures/pages/a.hbs') + .pages('test/fixtures/pages/b.hbs') + .pages('test/fixtures/pages/c.hbs'); + + assert(app.pages.count === 3); + }); + }); + + describe('addItem', function () { + beforeEach(function () { + app = new App(); + }); + + it('should add items to a collection', function () { + var pages = app.collection({Collection: Collection}); + pages.addItem('foo'); + pages.addItem('bar'); + pages.addItem('baz'); + + pages.items.hasOwnProperty('foo'); + pages.items.hasOwnProperty('bar'); + pages.items.hasOwnProperty('baz'); + }); + + it('should create a collection from an existing collection:', function () { + var pages = app.collection({Collection: Collection}); + pages.addItem('foo'); + pages.addItem('bar'); + pages.addItem('baz'); + + var posts = app.collection(pages); + posts.items.hasOwnProperty('foo'); + posts.items.hasOwnProperty('bar'); + posts.items.hasOwnProperty('baz'); + }); + }); + + describe('rendering views', function () { + beforeEach(function () { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.create('pages'); + }); + + it('should render a view with inherited app.render', function (done) { + app.page('test/fixtures/templates/a.tmpl') + .use(function (view) { + if (!view.contents) { + view.contents = fs.readFileSync(view.path); + } + }) + .set('data.name', 'Brian') + .render(function (err, res) { + if (err) return done(err); + assert(res.contents.toString() === 'Brian'); + done(); + }); + }); + }); +}); + +describe('collection singular method', function () { + describe('create', function () { + beforeEach(function () { + app = new App(); + }); + + it('should add a pluralized collection from singular name', function () { + app.create('page'); + assert(typeof app.views.pages === 'object'); + }); + }); + + describe('adding views', function () { + beforeEach(function () { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.create('page'); + }); + + it('should add a view to the created collection:', function () { + app.page('test/fixtures/pages/a.hbs'); + assert(typeof app.views.pages['test/fixtures/pages/a.hbs'] === 'object'); + }); + + it('should expose the `option` method:', function () { + app.pages.option('foo', 'bar'); + app.pages.options.should.have.property('foo', 'bar'); + }); + }); +}); diff --git a/test/app.collection.render.js b/test/app.collection.render.js new file mode 100644 index 0000000..6cc96a3 --- /dev/null +++ b/test/app.collection.render.js @@ -0,0 +1,155 @@ +require('mocha'); +require('should'); +var async = require('async'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var List = App.List; +var pages, app; + +describe('render', function () { + describe('rendering', function () { + beforeEach(function () { + app = App(); + pages = app.create('pages'); + app.engine('tmpl', require('engine-base')); + pages.engine('tmpl', require('engine-base')); + }); + + it('should throw an error when no callback is given:', function () { + (function() { + app.pages.render({}); + }).should.throw('Views#render is async and expects a callback function'); + }); + + it('should throw an error when an engine is not defined:', function (done) { + pages.addView('foo.bar', {content: '<%= name %>'}); + var page = pages.getView('foo.bar'); + + app.pages.render(page, function(err) { + assert(err.message === 'Views#render cannot find an engine for: .bar'); + done(); + }); + }); + + it('should use helpers defined on app to render a view:', function (done) { + var locals = {name: 'Halle'}; + app.helper('upper', function (str) { + return str.toUpperCase(str) + 'app'; + }); + + pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); + var page = pages.getView('a.tmpl'); + + app.render(page, function (err, res) { + if (err) return done(err); + + assert(res.content === 'a HALLEapp b'); + done(); + }); + }); + + it('should use helpers defined on app to render a view with collection.render:', function (done) { + var locals = {name: 'Halle'}; + app.helper('upper', function (str) { + return str.toUpperCase(str) + 'app'; + }); + + pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); + pages.helper('upper', app._.helpers.sync.upper); + var page = pages.getView('a.tmpl'); + + pages.render(page, function (err, res) { + if (err) return done(err); + + assert(res.content === 'a HALLEapp b'); + done(); + }); + }); + + it('should use helpers when rendering a view:', function (done) { + var locals = {name: 'Halle'}; + pages.helper('upper', function (str) { + return str.toUpperCase(str); + }); + + pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); + var page = pages.getView('a.tmpl'); + + pages.render(page, function (err, res) { + if (err) return done(err); + assert(res.content === 'a HALLE b'); + done(); + }); + }); + + it('should render a template when contents is a buffer:', function (done) { + pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); + var view = pages.getView('a.tmpl'); + + pages.render(view, function (err, view) { + if (err) return done(err); + assert(view.contents.toString() === 'b'); + done(); + }); + }); + + it('should render a template when content is a string:', function (done) { + pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); + var view = pages.getView('a.tmpl'); + + pages.render(view, function (err, view) { + if (err) return done(err); + assert(view.contents.toString() === 'b'); + done(); + }); + }); + + it('should render a view from its path:', function (done) { + pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); + + pages.render('a.tmpl', function (err, view) { + if (err) return done(err); + assert(view.content === 'b'); + done(); + }); + }); + + it('should use a plugin for rendering:', function (done) { + pages.engine('tmpl', require('engine-base')); + pages.option('engine', 'tmpl'); + + pages.addViews({ + 'a': {content: '<%= title %>', locals: {title: 'aaa'}}, + 'b': {content: '<%= title %>', locals: {title: 'bbb'}}, + 'c': {content: '<%= title %>', locals: {title: 'ccc'}}, + 'd': {content: '<%= title %>', locals: {title: 'ddd'}}, + 'e': {content: '<%= title %>', locals: {title: 'eee'}}, + 'f': {content: '<%= title %>', locals: {title: 'fff'}}, + 'g': {content: '<%= title %>', locals: {title: 'ggg'}}, + 'h': {content: '<%= title %>', locals: {title: 'hhh'}}, + 'i': {content: '<%= title %>', locals: {title: 'iii'}}, + 'j': {content: '<%= title %>', locals: {title: 'jjj'}}, + }); + + pages.use(function (collection) { + collection.option('pager', false); + + collection.renderEach = function (cb) { + var list = new List(collection); + async.map(list.items, function (item, next) { + collection.render(item, next); + }, cb); + }; + }); + + pages.renderEach(function (err, items) { + if (err) return done(err); + assert(items[0].content === 'aaa'); + assert(items[9].content === 'jjj'); + assert(items.length === 10); + done(); + }); + }); + }); +}); diff --git a/test/app.compile.js b/test/app.compile.js new file mode 100644 index 0000000..423c9f9 --- /dev/null +++ b/test/app.compile.js @@ -0,0 +1,52 @@ +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('compile', function () { + beforeEach(function () { + app = new App(); + app.create('page'); + }); + + it('should throw an error when an engine cannot be found:', function () { + app.page('foo.bar', {content: '<%= name %>'}); + var page = app.pages.getView('foo.bar'); + (function() { + app.compile(page); + }).should.throw('Templates#compile cannot find an engine for: .bar'); + }); + + it('should compile a template:', function () { + app.engine('tmpl', require('engine-base')); + app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>', a: 'b'}); + + var page = app.pages.getView('a.tmpl'); + var view = app.compile(page); + assert.equal(typeof view.fn, 'function'); + }); + + it('should compile a template by name:', function () { + app.engine('tmpl', require('engine-base')); + app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>', a: 'b'}); + + var view = app.compile('a.tmpl'); + assert.equal(typeof view.fn, 'function'); + }); + + it('should throw an error when a callback is given:', function () { + app.engine('md', require('engine-base')); + app.page('foo.md', {content: '<%= name %>'}); + var page = app.pages.getView('foo.md'); + (function() { + app.compile(page, function () { + }); + }).should.throw('Templates#compile is sync and does not take a callback function'); + + (function() { + app.compile(page, {}, function () { + }); + }).should.throw('Templates#compile is sync and does not take a callback function'); + }); +}); diff --git a/test/app.copy.js b/test/app.copy.js new file mode 100644 index 0000000..0e979b7 --- /dev/null +++ b/test/app.copy.js @@ -0,0 +1,30 @@ +require('mocha'); +var path = require('path'); +var assert = require('assert'); +var rimraf = require('rimraf'); +var App = require('..'); +var app; + +var fixtures = path.join(__dirname, 'fixtures/copy/*.txt'); +var outpath = path.join(__dirname, 'out-fixtures'); + +describe('copy()', function() { + beforeEach(function (done) { + rimraf(outpath, done); + app = new App(); + }); + + afterEach(function (done) { + rimraf(outpath, done); + }); + + describe('streams', function () { + it('should copy files', function (done) { + app.copy(fixtures, path.join(__dirname, 'actual')) + .on('data', function (file) { + assert.equal(typeof file, 'object'); + }) + .on('end', done); + }); + }); +}); diff --git a/test/app.create.js b/test/app.create.js new file mode 100644 index 0000000..99a15da --- /dev/null +++ b/test/app.create.js @@ -0,0 +1,176 @@ +require('mocha'); +require('should'); +var fs = require('fs'); +var path = require('path'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('create', function () { + describe('inflections', function () { + beforeEach(function () { + app = new App(); + }); + + it('should expose the create method', function () { + assert(typeof app.create === 'function'); + }); + + it('should add a collection to `views`', function () { + app.create('pages'); + assert(typeof app.views.pages === 'object'); + assert(typeof app.pages === 'function'); + }); + + it('should add a pluralized collection to `views`', function () { + app.create('page'); + assert(typeof app.views.pages === 'object'); + assert(typeof app.page === 'function'); + }); + }); + + describe('custom constructors', function () { + beforeEach(function () { + var Vinyl = require('vinyl'); + Vinyl.prototype.custom = function (key) { + this[key] = 'nonsense'; + return this; + }; + app = new App({View: Vinyl}); + app.create('pages'); + }); + + it('should create views from key-value pairs:', function () { + app.page('a.hbs', {path: 'a.hbs', content: 'a'}); + app.page('b.hbs', {path: 'b.hbs', content: 'b'}); + app.page('c.hbs', {path: 'c.hbs', content: 'c'}); + var a = app.pages.getView('a.hbs'); + a.custom('foo'); + a.foo.should.equal('nonsense'); + }); + }); + + describe('custom instances', function () { + it('should create views from custom `View` and `Views` instance/ctor:', function () { + var Vinyl = require('vinyl'); + Vinyl.prototype.read = function (file) { + return fs.readFileSync(file.path); + }; + + var Views = App.Views; + var views = new Views({View: Vinyl}); + + views.addView('a.hbs', {path: 'a.hbs', content: 'a'}); + views.addView('b.hbs', {path: 'b.hbs', content: 'b'}); + views.addView('c.hbs', {path: 'c.hbs', content: 'c'}); + + app = new App(); + app.create('pages', views); + + var a = app.pages.getView('a.hbs'); + assert(a instanceof Vinyl); + assert(Vinyl.isVinyl(a)); + assert(typeof a.read === 'function'); + + views.addView('d.hbs', {path: 'd.hbs', content: 'd'}); + var d = app.pages.getView('d.hbs'); + assert(d instanceof Vinyl); + assert(Vinyl.isVinyl(d)); + }); + }); + + describe('chaining', function () { + beforeEach(function () { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.create('page'); + }); + + it('should create views from key-value pairs:', function () { + app.page('a.hbs', {content: 'a'}); + app.page('b.hbs', {content: 'b'}); + app.page('c.hbs', {content: 'c'}); + app.views.pages.should.have.properties(['a.hbs', 'b.hbs', 'c.hbs']); + assert(app.views.pages['a.hbs'].contents.toString() === 'a'); + }); + + it('should create views from file paths:', function () { + app.page('test/fixtures/pages/a.hbs'); + app.page('test/fixtures/pages/b.hbs'); + app.page('test/fixtures/pages/c.hbs'); + + app.views.pages.should.have.properties([ + 'test/fixtures/pages/a.hbs', + 'test/fixtures/pages/b.hbs', + 'test/fixtures/pages/c.hbs' + ]); + }); + }); + + + describe('instance', function () { + beforeEach(function () { + app = new App(); + app.engine('tmpl', require('engine-base')); + }); + + it('should return the collection instance', function () { + var collection = app.create('pages'); + assert(collection instanceof App.Views); + + collection.option('renameKey', function (key) { + return path.basename(key); + }); + collection + .use(function (views) { + views.read = function (name) { + var view = this.getView(name); + if (!view.contents) { + view.contents = fs.readFileSync(view.path); + } + }; + }); + + collection.addView('test/fixtures/templates/a.tmpl'); + collection.read('a.tmpl'); + assert(collection.getView('a.tmpl').contents.toString() === '<%= name %>'); + }); + }); + + describe('viewType', function () { + beforeEach(function () { + app = new App(); + app.engine('tmpl', require('engine-base')); + }); + + it('should add collection to the given viewType', function () { + app.create('layout', {viewType: 'layout'}); + assert(app.layouts.options.viewType[0] === 'layout'); + }); + + it('should add a collection to multiple viewTypes', function () { + app.create('foo', {viewType: ['layout', 'renderable']}); + assert.deepEqual(app.foos.options.viewType, ['layout', 'renderable']); + }); + }); + + describe('events', function () { + beforeEach(function () { + app = new App(); + app.engine('tmpl', require('engine-base')); + }); + + it('should emit `create` when a collection is created:', function () { + app.on('create', function (collection) { + if (collection.options.plural === 'layouts') { + collection.options.foo = 'bar'; + } + }); + + app.create('layout'); + app.layout('one', {path: 'two', contents: '...'}); + assert(app.layouts.options.foo === 'bar'); + }); + }); +}); diff --git a/test/app.data.js b/test/app.data.js new file mode 100644 index 0000000..c2aedc6 --- /dev/null +++ b/test/app.data.js @@ -0,0 +1,72 @@ +require('mocha'); +require('should'); +var path = require('path'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('app.data', function () { + beforeEach(function () { + app = new App(); + }); + + it('should set a key-value pair on cache.data:', function () { + app.data('a', 'b'); + assert(app.cache.data.a === 'b'); + }); + + it('should set an object on cache.data:', function () { + app.data({c: 'd'}); + assert(app.cache.data.c === 'd'); + }); + + it('should load data from a file onto cache.data:', function () { + app.data('test/fixtures/data/a.json'); + assert(app.cache.data.a.one.a === 'aaa'); + }); + + it('should load a glob of data onto cache.data:', function () { + app.data('test/fixtures/data/*.json'); + assert(app.cache.data.a.one.a === 'aaa'); + assert(app.cache.data.b.two.b === 'bbb'); + assert(app.cache.data.c.three.c === 'ccc'); + }); + + it('should use `namespace` defined on global opts:', function () { + app.option('namespace', function (key) { + return 'prefix_' + path.basename(key, path.extname(key)); + }); + app.data('test/fixtures/data/*.json'); + assert(app.cache.data.prefix_a.one.a === 'aaa'); + assert(app.cache.data.prefix_b.two.b === 'bbb'); + assert(app.cache.data.prefix_c.three.c === 'ccc'); + }); + + it('should extend `cache.data`', function() { + app.data({a: 'aaa', b: 'bbb', c: 'ccc'}); + app.data({x: 'xxx', y: 'yyy', z: 'zzz'}); + assert(app.cache.data.a === 'aaa'); + assert(app.cache.data.b === 'bbb'); + assert(app.cache.data.c === 'ccc'); + assert(app.cache.data.x === 'xxx'); + assert(app.cache.data.y === 'yyy'); + assert(app.cache.data.z === 'zzz'); + }); + + it('should extend the `cache.data` object when the first param is a string.', function() { + app.data('foo', {x: 'xxx', y: 'yyy', z: 'zzz'}); + app.data('bar', {a: 'aaa', b: 'bbb', c: 'ccc'}); + assert(app.cache.data.foo.x === 'xxx'); + assert(app.cache.data.bar.a === 'aaa'); + }); + + it('should be chainable.', function() { + app + .data({x: 'xxx', y: 'yyy', z: 'zzz'}) + .data({a: 'aaa', b: 'bbb', c: 'ccc'}); + + assert(app.cache.data.x === 'xxx'); + assert(app.cache.data.a === 'aaa'); + }); +}); diff --git a/test/app.dest.js b/test/app.dest.js new file mode 100644 index 0000000..52e5584 --- /dev/null +++ b/test/app.dest.js @@ -0,0 +1,1104 @@ +var spies = require('./support/spy'); +var chmodSpy = spies.chmodSpy; +var statSpy = spies.statSpy; + +require('mocha'); +var should = require('should'); +var assert = require('assert'); +var App = require('..'); +var app; + +var path = require('path'); +var fs = require('graceful-fs'); +var rimraf = require('rimraf'); + +var bufferStream; +var bufEqual = require('buffer-equal'); +var through = require('through2'); +var File = require('vinyl'); + +var outpath = path.join(__dirname, 'out-fixtures'); + + +var wipeOut = function(cb) { + app = new App(); + rimraf(path.join(__dirname, 'out-fixtures/'), cb); + spies.setError('false'); + statSpy.reset(); + chmodSpy.reset(); +}; + +var dataWrap = function(fn) { + return function(data, enc, cb) { + fn(data); + cb(); + }; +}; + +var realMode = function(n) { + return n & 07777; +}; + +describe('dest stream', function() { + beforeEach(wipeOut); + afterEach(wipeOut); + + it('should explode on invalid folder (empty)', function(done) { + var stream; + try { + stream = app.dest(); + } catch (err) { + assert(err && typeof err === 'object'); + should.not.exist(stream); + done(); + } + }); + + it('should explode on invalid folder (empty string)', function(done) { + var stream; + try { + stream = app.dest(''); + } catch (err) { + assert(err && typeof err === 'object'); + should.not.exist(stream); + done(); + } + }); + + it('should pass through writes with cwd', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + + var expectedFile = new File({ + base: __dirname, + cwd: __dirname, + path: inputPath, + contents: null + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + done(); + }; + + var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should pass through writes with default cwd', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + + var expectedFile = new File({ + base: __dirname, + cwd: __dirname, + path: inputPath, + contents: null + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + done(); + }; + + var stream = app.dest(path.join(__dirname, 'out-fixtures/')); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should not write null files', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var expectedPath = path.join(__dirname, 'out-fixtures/test.coffee'); + var expectedCwd = __dirname; + var expectedBase = path.join(__dirname, 'out-fixtures'); + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: null + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); + buffered[0].base.should.equal(expectedBase, 'base should have changed'); + buffered[0].path.should.equal(expectedPath, 'path should have changed'); + fs.existsSync(expectedPath).should.equal(false); + done(); + }; + + var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should write buffer files to the right folder with relative cwd', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var expectedPath = path.join(__dirname, 'out-fixtures/test.coffee'); + var expectedCwd = __dirname; + var expectedBase = path.join(__dirname, 'out-fixtures'); + var expectedContents = fs.readFileSync(inputPath); + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); + buffered[0].base.should.equal(expectedBase, 'base should have changed'); + buffered[0].path.should.equal(expectedPath, 'path should have changed'); + fs.existsSync(expectedPath).should.equal(true); + bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); + done(); + }; + + var stream = app.dest('./out-fixtures/', {cwd: path.relative(process.cwd(), __dirname)}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should write buffer files to the right folder with function and relative cwd', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var expectedPath = path.join(__dirname, 'out-fixtures/test.coffee'); + var expectedCwd = __dirname; + var expectedBase = path.join(__dirname, 'out-fixtures'); + var expectedContents = fs.readFileSync(inputPath); + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); + buffered[0].base.should.equal(expectedBase, 'base should have changed'); + buffered[0].path.should.equal(expectedPath, 'path should have changed'); + fs.existsSync(expectedPath).should.equal(true); + bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); + done(); + }; + + var stream = app.dest(function(file){ + should.exist(file); + file.should.equal(expectedFile); + return './out-fixtures'; + }, {cwd: path.relative(process.cwd(), __dirname)}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should write buffer files to the right folder', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var expectedPath = path.join(__dirname, 'out-fixtures/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedCwd = __dirname; + var expectedBase = path.join(__dirname, 'out-fixtures'); + var expectedMode = 0655; + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents, + stat: { + mode: expectedMode + } + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); + buffered[0].base.should.equal(expectedBase, 'base should have changed'); + buffered[0].path.should.equal(expectedPath, 'path should have changed'); + fs.existsSync(expectedPath).should.equal(true); + bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); + realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); + done(); + }; + + var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should write streaming files to the right folder', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var expectedPath = path.join(__dirname, 'out-fixtures/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedCwd = __dirname; + var expectedBase = path.join(__dirname, 'out-fixtures'); + var expectedMode = 0655; + + var contentStream = through.obj(); + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: contentStream, + stat: { + mode: expectedMode + } + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); + buffered[0].base.should.equal(expectedBase, 'base should have changed'); + buffered[0].path.should.equal(expectedPath, 'path should have changed'); + fs.existsSync(expectedPath).should.equal(true); + bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); + realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); + done(); + }; + + var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + setTimeout(function(){ + contentStream.write(expectedContents); + contentStream.end(); + }, 100); + stream.end(); + }); + + it('should write directories to the right folder', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var expectedPath = path.join(__dirname, 'out-fixtures/test'); + var expectedCwd = __dirname; + var expectedBase = path.join(__dirname, 'out-fixtures'); + var expectedMode = 0655; + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: null, + stat: { + isDirectory: function(){ + return true; + }, + mode: expectedMode + } + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); + buffered[0].base.should.equal(expectedBase, 'base should have changed'); + buffered[0].path.should.equal(expectedPath, 'path should have changed'); + fs.existsSync(expectedPath).should.equal(true); + fs.lstatSync(expectedPath).isDirectory().should.equal(true); + realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); + done(); + }; + + var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should allow piping multiple dests in streaming mode', function(done) { + var inputPath1 = path.join(__dirname, 'out-fixtures/multiple-first'); + var inputPath2 = path.join(__dirname, 'out-fixtures/multiple-second'); + var inputBase = path.join(__dirname, 'out-fixtures/'); + var srcPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var stream1 = app.dest('./out-fixtures/', {cwd: __dirname}); + var stream2 = app.dest('./out-fixtures/', {cwd: __dirname}); + var content = fs.readFileSync(srcPath); + var rename = through.obj(function(file, _, next) { + file.path = inputPath2; + this.push(file); + next(); + }); + + stream1.on('data', function(file) { + file.path.should.equal(inputPath1); + }); + + stream1.pipe(rename).pipe(stream2); + stream2.on('data', function(file) { + file.path.should.equal(inputPath2); + }).once('end', function() { + fs.readFileSync(inputPath1, 'utf8').should.equal(content.toString()); + fs.readFileSync(inputPath2, 'utf8').should.equal(content.toString()); + done(); + }); + + var file = new File({ + base: inputBase, + path: inputPath1, + cwd: __dirname, + contents: content + }); + + stream1.write(file); + stream1.end(); + }); + + it('should write new files with the default user mode', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var expectedPath = path.join(__dirname, 'out-fixtures/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedMode = 0666 & (~process.umask()); + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents, + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + fs.existsSync(expectedPath).should.equal(true); + realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); + done(); + }; + + chmodSpy.reset(); + var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should write new files with the specified mode', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var expectedPath = path.join(__dirname, 'out-fixtures/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedMode = 0744; + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents, + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + fs.existsSync(expectedPath).should.equal(true); + realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); + done(); + }; + + chmodSpy.reset(); + var stream = app.dest('./out-fixtures/', {cwd: __dirname, mode:expectedMode}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should update file mode to match the vinyl mode', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var expectedPath = path.join(__dirname, 'out-fixtures/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedBase = path.join(__dirname, 'out-fixtures'); + var startMode = 0655; + var expectedMode = 0722; + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents, + stat: { + mode: expectedMode + } + }); + + var onEnd = function(){ + assert(chmodSpy.called); + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + fs.existsSync(expectedPath).should.equal(true); + realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); + done(); + }; + + fs.mkdirSync(expectedBase); + fs.closeSync(fs.openSync(expectedPath, 'w')); + fs.chmodSync(expectedPath, startMode); + + chmodSpy.reset(); + var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should use different modes for files and directories', function(done) { + var inputBase = path.join(__dirname, 'fixtures/vinyl'); + var inputPath = path.join(__dirname, 'fixtures/vinyl/wow/suchempty'); + var expectedBase = path.join(__dirname, 'out-fixtures/wow'); + var expectedDirMode = 0755; + var expectedFileMode = 0655; + + var firstFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + stat: fs.statSync(inputPath) + }); + + var onEnd = function(){ + realMode(fs.lstatSync(expectedBase).mode).should.equal(expectedDirMode); + realMode(buffered[0].stat.mode).should.equal(expectedFileMode); + done(); + }; + + var stream = app.dest('./out-fixtures/', { + cwd: __dirname, + mode: expectedFileMode, + dirMode: expectedDirMode + }); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + + stream.pipe(bufferStream); + stream.write(firstFile); + stream.end(); + }); + + it('should change to the specified base as string', function(done) { + var inputBase = path.join(__dirname, 'fixtures/vinyl'); + var inputPath = path.join(__dirname, 'fixtures/vinyl/wow/suchempty'); + + var firstFile = new File({ + cwd: __dirname, + path: inputPath, + stat: fs.statSync(inputPath) + }); + + var onEnd = function(){ + buffered[0].base.should.equal(inputBase); + done(); + }; + + var stream = app.dest('./out-fixtures/', { + cwd: __dirname, + base: inputBase + }); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + + stream.pipe(bufferStream); + stream.write(firstFile); + stream.end(); + }); + + it('should change to the specified base as function', function(done) { + var inputBase = path.join(__dirname, 'fixtures/vinyl'); + var inputPath = path.join(__dirname, 'fixtures/vinyl/wow/suchempty'); + + var firstFile = new File({ + cwd: __dirname, + path: inputPath, + stat: fs.statSync(inputPath) + }); + + var onEnd = function() { + buffered[0].base.should.equal(inputBase); + done(); + }; + + var stream = app.dest('./out-fixtures/', { + cwd: __dirname, + base: function(file){ + should.exist(file); + file.path.should.equal(inputPath); + return inputBase; + } + }); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + + stream.pipe(bufferStream); + stream.write(firstFile); + stream.end(); + }); + + it('should report IO errors', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var expectedPath = path.join(__dirname, 'out-fixtures/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedBase = path.join(__dirname, 'out-fixtures'); + var expectedMode = 0722; + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents, + stat: { + mode: expectedMode + } + }); + + fs.mkdirSync(expectedBase); + fs.closeSync(fs.openSync(expectedPath, 'w')); + fs.chmodSync(expectedPath, 0); + + var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + stream.on('error', function(err) { + err.code.should.equal('EACCES'); + done(); + }); + stream.write(expectedFile); + }); + + it('should report stat errors', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var expectedPath = path.join(__dirname, 'out-fixtures/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedBase = path.join(__dirname, 'out-fixtures'); + var expectedMode = 0722; + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents, + stat: { + mode: expectedMode + } + }); + + fs.mkdirSync(expectedBase); + fs.closeSync(fs.openSync(expectedPath, 'w')); + + spies.setError(function(mod, fn) { + if (fn === 'stat' && arguments[2] === expectedPath) { + return new Error('stat error'); + } + }); + + var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + stream.on('error', function(err) { + err.message.should.equal('stat error'); + done(); + }); + stream.write(expectedFile); + }); + + it('should report chmod errors', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var expectedPath = path.join(__dirname, 'out-fixtures/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedBase = path.join(__dirname, 'out-fixtures'); + var expectedMode = 0722; + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents, + stat: { + mode: expectedMode + } + }); + + fs.mkdirSync(expectedBase); + fs.closeSync(fs.openSync(expectedPath, 'w')); + + spies.setError(function(mod, fn) { + if (fn === 'chmod' && arguments[2] === expectedPath) { + return new Error('chmod error'); + } + }); + + var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + stream.on('error', function(err) { + err.message.should.equal('chmod error'); + done(); + }); + stream.write(expectedFile); + }); + + it('should not chmod a matching file', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var expectedPath = path.join(__dirname, 'out-fixtures/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedBase = path.join(__dirname, 'out-fixtures'); + var expectedMode = 0722; + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents, + stat: { + mode: expectedMode + } + }); + + var expectedCount = 0; + spies.setError(function(mod, fn) { + if (fn === 'stat' && arguments[2] === expectedPath) { + expectedCount++; + } + }); + + var onEnd = function(){ + expectedCount.should.equal(1); + assert(!chmodSpy.called); + realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); + done(); + }; + + fs.mkdirSync(expectedBase); + fs.closeSync(fs.openSync(expectedPath, 'w')); + fs.chmodSync(expectedPath, expectedMode); + + statSpy.reset(); + chmodSpy.reset(); + var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should see a file with special chmod (setuid/setgid/sticky) as matching', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var expectedPath = path.join(__dirname, 'out-fixtures/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedBase = path.join(__dirname, 'out-fixtures'); + var expectedMode = 03722; + var normalMode = 0722; + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents, + stat: { + mode: normalMode + } + }); + + var expectedCount = 0; + spies.setError(function(mod, fn) { + if (fn === 'stat' && arguments[2] === expectedPath) { + expectedCount++; + } + }); + + var onEnd = function(){ + expectedCount.should.equal(1); + assert(!chmodSpy.called); + done(); + }; + + fs.mkdirSync(expectedBase); + fs.closeSync(fs.openSync(expectedPath, 'w')); + fs.chmodSync(expectedPath, expectedMode); + + statSpy.reset(); + chmodSpy.reset(); + var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should not overwrite files with overwrite option set to false', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var inputContents = fs.readFileSync(inputPath); + + var expectedPath = path.join(__dirname, 'out-fixtures/test.coffee'); + var expectedBase = path.join(__dirname, 'out-fixtures'); + var existingContents = 'Lorem Ipsum'; + + var inputFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: inputContents + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + bufEqual(fs.readFileSync(expectedPath), new Buffer(existingContents)).should.equal(true); + done(); + }; + + // Write expected file which should not be overwritten + fs.mkdirSync(expectedBase); + fs.writeFileSync(expectedPath, existingContents); + + var stream = app.dest('./out-fixtures/', {cwd: __dirname, overwrite: false}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(inputFile); + stream.end(); + }); + + it('should overwrite files with overwrite option set to true', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var inputContents = fs.readFileSync(inputPath); + + var expectedPath = path.join(__dirname, 'out-fixtures/test.coffee'); + var expectedBase = path.join(__dirname, 'out-fixtures'); + var existingContents = 'Lorem Ipsum'; + + var inputFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: inputContents + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + bufEqual(fs.readFileSync(expectedPath), new Buffer(inputContents)).should.equal(true); + done(); + }; + + // This should be overwritten + fs.mkdirSync(expectedBase); + fs.writeFileSync(expectedPath, existingContents); + + var stream = app.dest('./out-fixtures/', {cwd: __dirname, overwrite: true}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(inputFile); + stream.end(); + }); + + it('should create symlinks when the `symlink` attribute is set on the file', function (done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test-create-dir-symlink'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var inputRelativeSymlinkPath = 'wow'; + + var expectedPath = path.join(__dirname, 'out-fixtures/test-create-dir-symlink'); + + var inputFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: null, //'' + }); + + // `src()` adds this side-effect with `keepSymlinks` option set to false + inputFile.symlink = inputRelativeSymlinkPath; + + var onEnd = function(){ + fs.readlink(buffered[0].path, function () { + buffered[0].symlink.should.equal(inputFile.symlink); + buffered[0].path.should.equal(expectedPath); + done(); + }); + }; + + var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(inputFile); + stream.end(); + }); + + it('should emit finish event', function(done) { + var srcPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + + stream.once('finish', function() { + done(); + }); + + var file = new File({ + path: srcPath, + cwd: __dirname, + contents: new Buffer("1234567890") + }); + + stream.write(file); + stream.end(); + }); +}); + +describe('dest', function() { + beforeEach(function (done) { + rimraf(outpath, done); + app = new App(); + }); + + afterEach(function (done) { + rimraf(outpath, done); + }); + + describe('streams', function () { + it('should return a stream', function (done) { + var stream = app.dest(path.join(__dirname, 'fixtures/')); + should.exist(stream); + should.exist(stream.on); + done(); + }); + + it('should return an output stream that writes files', function (done) { + var instream = app.src(path.join(__dirname, 'fixtures/copy/*.txt')); + var outstream = app.dest(outpath); + instream.pipe(outstream); + + outstream.on('error', done); + outstream.on('data', function (file) { + // data should be re-emitted correctly + should.exist(file); + should.exist(file.path); + should.exist(file.contents); + path.join(file.path, '').should.equal(path.join(outpath, 'example.txt')); + String(file.contents).should.equal('Hello world!'); + }); + outstream.on('end', function () { + fs.readFile(path.join(outpath, 'example.txt'), function (err, contents) { + should.not.exist(err); + should.exist(contents); + String(contents).should.equal('Hello world!'); + done(); + }); + }); + }); + + it('should return an output stream that does not write non-read files', function (done) { + var instream = app.src(path.join(__dirname, 'fixtures/copy/*.txt'), {read: false}); + var outstream = app.dest(outpath); + instream.pipe(outstream); + + outstream.on('error', done); + outstream.on('data', function (file) { + // data should be re-emitted correctly + should.exist(file); + should.exist(file.path); + should.not.exist(file.contents); + path.join(file.path, '').should.equal(path.join(outpath, 'example.txt')); + }); + + outstream.on('end', function () { + fs.readFile(path.join(outpath, 'example.txt'), function (err, contents) { + should.exist(err); + should.not.exist(contents); + done(); + }); + }); + }); + + it('should return an output stream that writes streaming files', function (done) { + var instream = app.src(path.join(__dirname, 'fixtures/copy/*.txt'), {buffer: false}); + var outstream = instream.pipe(app.dest(outpath)); + + outstream.on('error', done); + outstream.on('data', function (file) { + // data should be re-emitted correctly + should.exist(file); + should.exist(file.path); + should.exist(file.contents); + path.join(file.path, '').should.equal(path.join(outpath, 'example.txt')); + }); + outstream.on('end', function () { + fs.readFile(path.join(outpath, 'example.txt'), function (err, contents) { + should.not.exist(err); + should.exist(contents); + String(contents).should.equal('Hello world!'); + done(); + }); + }); + }); + + it('should return an output stream that writes streaming files to new directories', function (done) { + testWriteDir({}, done); + }); + + it('should return an output stream that writes streaming files to new directories (buffer: false)', function (done) { + testWriteDir({buffer: false}, done); + }); + + it('should return an output stream that writes streaming files to new directories (read: false)', function (done) { + testWriteDir({read: false}, done); + }); + + it('should return an output stream that writes streaming files to new directories (read: false, buffer: false)', function (done) { + testWriteDir({buffer: false, read: false}, done); + }); + + }); + + describe('ext', function () { + beforeEach(function () { + app = new App(); + app.set('ext', '.txt'); + }); + + afterEach(function () { + app.set('ext', '.html'); + }); + + it('should return a stream', function (done) { + var stream = app.dest(path.join(__dirname, 'fixtures/')); + should.exist(stream); + should.exist(stream.on); + done(); + }); + + it('should return an output stream that writes files', function (done) { + var instream = app.src(path.join(__dirname, 'fixtures/copy/*.txt')); + var outstream = app.dest(outpath); + instream.pipe(outstream); + + outstream.on('error', done); + outstream.on('data', function (file) { + // data should be re-emitted correctly + should.exist(file); + should.exist(file.path); + should.exist(file.contents); + path.join(file.path, '').should.equal(path.join(outpath, 'example.txt')); + String(file.contents).should.equal('Hello world!'); + }); + outstream.on('end', function () { + fs.readFile(path.join(outpath, 'example.txt'), function (err, contents) { + should.not.exist(err); + should.exist(contents); + String(contents).should.equal('Hello world!'); + done(); + }); + }); + }); + + it('should return an output stream that does not write non-read files', function (done) { + var instream = app.src(path.join(__dirname, 'fixtures/dest/*.txt'), {read: false}); + var outstream = app.dest(outpath); + instream.pipe(outstream); + + outstream.on('error', done); + outstream.on('data', function (file) { + // data should be re-emitted correctly + should.exist(file); + should.exist(file.path); + should.not.exist(file.contents); + path.join(file.path, '').should.equal(path.join(outpath, 'example.txt')); + }); + + outstream.on('end', function () { + fs.readFile(path.join(outpath, 'example.txt'), function (err, contents) { + should.exist(err); + should.not.exist(contents); + done(); + }); + }); + }); + }); + + function testWriteDir(srcOptions, done) { + var instream = app.src(path.join(__dirname, 'fixtures/generic'), srcOptions); + var outstream = instream.pipe(app.dest(outpath)); + + outstream.on('error', done); + outstream.on('data', function(file) { + // data should be re-emitted correctly + should.exist(file); + should.exist(file.path); + path.join(file.path,'').should.equal(path.join(outpath, 'generic')); + }); + + outstream.on('end', function() { + fs.exists(path.join(outpath, 'generic'), function(exists) { + /* jshint expr: true */ + should(exists).be.ok; + /* jshint expr: false */ + done(); + }); + }); + } +}); + diff --git a/test/app.engines.js b/test/app.engines.js new file mode 100644 index 0000000..17c0669 --- /dev/null +++ b/test/app.engines.js @@ -0,0 +1,159 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('engine support', function() { + beforeEach(function() { + app = new App(); + }); + + it('should throw an error when engine name is invalid:', function () { + (function () { + app.engine(null, {}); + }).should.throw('expected engine ext to be a string or array.'); + }); + + it('should register an engine to the given extension', function () { + app.engine('hbs', function () {}); + assert(typeof app.engines['.hbs'] === 'object'); + }); + + it('should set an engine with the given extension', function () { + var hbs = function() {}; + hbs.render = function() {}; + hbs.renderFile = function() {}; + app.engine('hbs', hbs); + assert(app.engines['.hbs']); + assert(app.engines['.hbs'].renderFile); + assert(app.engines['.hbs'].render); + }); + + it('should get an engine:', function () { + app.engine('hbs', function () {}); + var hbs = app.engine('hbs'); + assert(typeof hbs === 'object'); + assert(hbs.hasOwnProperty('render')); + assert(hbs.hasOwnProperty('compile')); + }); + + it('should return undefined if no engine is found:', function () { + var hbs = app.getEngine(); + assert.equal(typeof hbs, 'undefined'); + }); + + it('should register multiple engines to the given extension', function () { + app.engine(['hbs', 'md'], function () {}); + assert(typeof app.engines['.hbs'] === 'object'); + assert(typeof app.engines['.md'] === 'object'); + }); +}); + +describe('engines', function () { + beforeEach(function () { + app = new App(); + app.create('pages'); + app.pages('foo.tmpl', {content: 'A <%= letter %> {{= letter }} C'}); + app.pages('bar.tmpl', {content: 'A <%= letter %> {{ letter }} C'}); + }); + + it('should register an engine:', function () { + app.engine('a', {render: function () {}}); + app.engines.should.have.property('.a'); + }); + + it('should use custom delimiters:', function (done) { + app.engine('tmpl', require('engine-base'), { + delims: ['{{', '}}'] + }); + app.render('foo.tmpl', {letter: 'B'}, function (err, res) { + if (err) return done(err); + res.contents.toString().should.equal('A <%= letter %> B C'); + done(); + }); + }); + + it('should override individual delims values:', function (done) { + app.engine('tmpl', require('engine-base'), { + interpolate: /\{{([^}]+)}}/g, + evaluate: /\{{([^}]+)}}/g, + escape: /\{{-([^}]+)}}/g + }); + app.render('bar.tmpl', {letter: 'B'}, function (err, res) { + if (err) return done(err); + res.contents.toString().should.equal('A <%= letter %> B C'); + done(); + }); + }); + + it('should get an engine:', function () { + app.engine('a', { + render: function () {} + }); + var a = app.engine('a'); + a.should.have.property('render'); + }); +}); + + +describe('engine selection:', function () { + beforeEach(function (done) { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.engine('hbs', require('engine-handlebars')); + app.create('pages'); + done(); + }); + + it('should get the engine from file extension:', function (done) { + app.page('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}) + .render(function (err, view) { + if (err) return done(err); + assert(view.content === 'b'); + done(); + }); + }); + + it('should use the engine defined on the collection:', function (done) { + app.create('posts', {engine: 'hbs'}); + + app.post('a', {content: '{{a}}', locals: {a: 'b'}}) + .render(function (err, view) { + if (err) return done(err); + assert(view.content === 'b'); + done(); + }); + }); + + it('should use the engine defined on the view:', function (done) { + app.create('posts'); + app.post('a', {content: '{{a}}', engine: 'hbs', locals: {a: 'b'}}) + .render(function (err, view) { + if (err) return done(err); + assert(view.content === 'b'); + done(); + }); + }); + + it('should use the engine defined on `view.data`:', function (done) { + app.create('posts'); + app.post('a', {content: '{{a}}', locals: {a: 'b'}, data: {engine: 'hbs'}}) + .render(function (err, view) { + if (err) return done(err); + assert(view.content === 'b'); + done(); + }); + }); + + it('should use the engine defined on render locals:', function (done) { + app.create('posts'); + app.post('a', {content: '{{a}}', locals: {a: 'b'}}) + .render({engine: 'hbs'}, function (err, view) { + if (err) return done(err); + assert(view.content === 'b'); + done(); + }); + }); +}); diff --git a/test/app.events.js b/test/app.events.js new file mode 100644 index 0000000..3505b9c --- /dev/null +++ b/test/app.events.js @@ -0,0 +1,50 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('events', function () { + beforeEach(function () { + app = new App(); + }); + + it('should listen for an event:', function () { + var app = new App(); + app.on('foo', function () { + }); + assert(Array.isArray(app._callbacks['$foo'])); + }); + + it('should emit an event:', function (done) { + var app = new App(); + app.on('foo', function (val) { + assert(val === 'bar'); + done(); + }); + assert(Array.isArray(app._callbacks['$foo'])); + app.emit('foo', 'bar'); + }); + + it('should listen for error events:', function (done) { + var app = new App(); + app.on('foo', function (val) { + assert(val === 'bar'); + done(); + }); + assert(Array.isArray(app._callbacks['$foo'])); + app.emit('foo', 'bar'); + }); + + it('should listen for `view` events:', function () { + var app = new App(); + + app.on('view', function (view) { + view.foo = 'bar'; + }); + + var view = app.view({path: 'a', content: 'b'}); + assert(view.foo === 'bar'); + }); +}); diff --git a/test/app.get-set.js b/test/app.get-set.js new file mode 100644 index 0000000..8bee52c --- /dev/null +++ b/test/app.get-set.js @@ -0,0 +1,72 @@ +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('app.set()', function () { + beforeEach(function() { + app = new App(); + }); + + it('should set a value', function () { + app.set('a', 'b'); + app.get('a').should.equal('b'); + }); + + it('should set properties on the instance.', function () { + app.set('a', 'b'); + app.a.should.equal('b'); + }); + + it('should allow an object to be set directly.', function () { + app.set({x: 'y'}); + app.x.should.equal('y'); + app.get('x').should.equal('y'); + }); + + it('should set nested properties on the instance.', function () { + app.set('c', {d: 'e'}); + app.get('c').d.should.equal('e'); + }); + + it('should use dot notation to `set` values.', function () { + app.set('h.i', 'j'); + app.get('h').should.eql({i: 'j'}); + }); + + it('should use dot notation to `get` values.', function () { + app.set('h', {i: 'j'}); + app.get('h.i').should.equal('j'); + }); + + it('should return `this` for chaining', function () { + app.set('a', 'b').should.equal(app); + app + .set('aa', 'bb') + .set('bb', 'cc') + .set('cc', 'dd'); + app.get('aa').should.equal('bb'); + app.get('bb').should.equal('cc'); + app.get('cc').should.equal('dd'); + }); + + it('should return undefined when not set', function () { + app.set('a', undefined).should.equal(app); + }); +}); + +describe('app.get()', function () { + beforeEach(function() { + app = new App(); + }); + + it('should return undefined when no set', function () { + assert(app.get('a') === undefined); + }); + + it('should otherwise return the value', function () { + app.set('a', 'b'); + app.get('a').should.equal('b'); + }); +}); diff --git a/test/app.handle.js b/test/app.handle.js new file mode 100644 index 0000000..f968d69 --- /dev/null +++ b/test/app.handle.js @@ -0,0 +1,23 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('handler', function () { + beforeEach(function () { + app = new App(); + app.create('pages'); + app.handlers(['foo']); + }); + + it('should support custom handle methods:', function (done) { + var page = app.page('foo', {contents: null}); + + app.handle('foo', page, function (err, view) { + assert(typeof view.path === 'string'); + done(); + }); + }); +}); diff --git a/test/app.handlers.js b/test/app.handlers.js new file mode 100644 index 0000000..e29f184 --- /dev/null +++ b/test/app.handlers.js @@ -0,0 +1,128 @@ +require('should'); +var fs = require('fs'); +var path = require('path'); +var assert = require('assert'); +var resolve = require('resolve-glob'); +var support = require('./support'); +var App = support.resolve(); +var app; + +function read(views) { + return function (view) { + view.read = function () { + if (!this.contents) { + this.contents = fs.readFileSync(this.path); + } + }; + return view; + } +} + +describe('handlers', function () { + describe('custom handlers', function () { + beforeEach(function () { + app = new App(); + app.create('pages') + .use(read) + .option('renameKey', function (key) { + return path.basename(key); + }); + }); + + it('should add custom middleware handlers:', function () { + app.handler('foo'); + app.router.should.have.property('foo'); + assert.equal(typeof app.router.foo, 'function'); + }); + + it('should add custom middleware handlers:', function () { + app.pages.on('view', function (view) { + console.log(view.read) + // view.read(); + }); + + app.handler('foo'); + app.handler('bar'); + + app.foo(/./, function (view, next) { + view.one = 'aaa'; + next(); + }); + + app.bar(/./, function (view, next) { + view.two = 'zzz'; + next(); + }); + + app + .pages('a', {contents: '...'}) + .pages('b', {contents: '...'}) + .pages('c', {contents: '...'}) + .use(function (pages) { + // console.log(pages) + var fn = pages.extendView; + pages.extendView = function (view) { + view = fn(view); + app.handleView('foo', view); + return view; + }; + return pages; + }); + // .pages('test/fixtures/pages/*.hbs') + // .use(function (pages) { + // var fn = pages.extendView; + // pages.extendView = function (view) { + // view = fn(view); + // app.handleView('bar', view); + // return view; + // }; + // return pages; + // }) + + // console.log(pages.getView('a.tmpl').one); + // console.log(app.pages.getView('a.tmpl').one) + // console.log(app.pages.getView('a.hbs').two) + + // app.pages.getView('a.txt').should.have.property('one'); + // app.pages.getView('a.txt').should.have.property('two'); + + // app.pages.getView('a.md').should.not.have.property('one'); + // app.pages.getView('a.md').should.have.property('two'); + }); + + // it('should add custom middleware handlers:', function () { + // app.handler('foo'); + // app.handler('bar'); + + // function handle(method) { + // return function (view) { + // return app.handle(method, view); + // } + // } + + // app.foo(/./, function (view, next) { + // view.one = 'aaa'; + // next(); + // }); + + // app.bar(/./, function (view, next) { + // view.two = 'zzz'; + // next(); + // }); + + // app.pages('test/fixtures/*.txt') + // .use(handle('foo')) + + // .pages('test/fixtures/*.md') + // .use(handle('bar')) + + // .use(utils.rename); + + // app.pages.getView('a.txt').should.have.property('one'); + // app.pages.getView('a.txt').should.have.property('two'); + + // app.pages.getView('a.md').should.not.have.property('one'); + // app.pages.getView('a.md').should.have.property('two'); + // }); + }); +}); diff --git a/test/app.js b/test/app.js new file mode 100644 index 0000000..ef8e463 --- /dev/null +++ b/test/app.js @@ -0,0 +1,130 @@ +/* deps: coveralls istanbul */ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var Base = App.Base; +var app; + +describe('app', function () { + describe('constructor', function () { + it('should create an instance of App:', function () { + app = new App(); + assert(app instanceof App); + }); + + it('should new up without new:', function () { + app = App(); + assert(app instanceof App); + }); + }); + + describe('static methods', function () { + it('should expose `extend`:', function () { + assert(typeof App.extend ==='function'); + }); + }); + + describe('prototype methods', function () { + beforeEach(function() { + app = new App(); + }); + + it('should expose `set`', function () { + assert(typeof app.set ==='function'); + }); + it('should expose `get`', function () { + assert(typeof app.get ==='function'); + }); + it('should expose `visit`', function () { + assert(typeof app.visit ==='function'); + }); + it('should expose `define`', function () { + assert(typeof app.define ==='function'); + }); + it('should expose `views`', function () { + assert(typeof app.views === 'object'); + }); + }); + + describe('instance', function () { + beforeEach(function() { + app = new App(); + }); + + it('should set a value on the instance:', function () { + app.set('a', 'b'); + assert(app.a ==='b'); + }); + + it('should get a value from the instance:', function () { + app.set('a', 'b'); + assert(app.get('a') ==='b'); + }); + }); + + describe('initialization', function () { + it('should listen for errors:', function (done) { + app = new App(); + app.on('error', function (err) { + assert(err.message === 'foo'); + done(); + }); + app.emit('error', new Error('foo')); + }); + + it('should mixin methods after init:', function () { + app = new App(); + app.option({ + mixins: { + foo: function () {} + } + }); + assert(typeof app.foo ==='function'); + }); + + it('should expose constructors from `lib`:', function () { + app = new App(); + app.expose('Collection'); + assert(typeof app.Collection ==='function'); + }); + + it('should update constructors after init:', function () { + var Group = App.Group; + function MyGroup() { + Base.call(this); + } + Base.extend(MyGroup); + + app = new App(); + assert.equal(app.Group, Group); + assert.equal(app.get('Group'), Group); + app.option('Group', MyGroup); + assert.equal(app.Group, MyGroup); + assert.equal(app.get('Group'), MyGroup); + }); + + it('should mixin prototype methods defined on options:', function () { + app = new App({ + mixins: { + foo: function () {} + } + }); + assert(typeof app.foo ==='function'); + delete App.prototype.foo; + }); + + it('should expose `_` on app:', function () { + app = new App(); + assert(typeof app._ ==='object'); + }); + + it('should not re-add `_` in init:', function () { + app = new App(); + app._.foo = 'bar'; + app.defaultConfig(); + assert(app._.foo ==='bar'); + }); + }); +}); diff --git a/test/app.list.compile.js b/test/app.list.compile.js new file mode 100644 index 0000000..a49f81c --- /dev/null +++ b/test/app.list.compile.js @@ -0,0 +1,44 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var List = App.List; +var list; + +describe('app.list.compile', function () { + beforeEach(function () { + list = new List(); + list.engine('tmpl', require('engine-base')); + }); + + it('should compile an item:', function () { + var buffer = new Buffer('a b c'); + var item = list.addItem('a.tmpl', {contents: buffer}) + .compile(); + + assert(typeof item.fn === 'function'); + }); + + it('should use the compiled function to render:', function () { + var buffer = new Buffer('a <%= title %> c'); + var item = list.addItem('a.tmpl', {contents: buffer}) + .compile(); + + assert(item.fn({title: 'z'})); + assert(typeof item.fn({title: 'z'}) === 'string'); + assert(item.fn({title: 'z'}) === 'a z c'); + }); + + it('should compile a view by name:', function () { + var buffer = new Buffer('a <%= title %> c'); + list.addItem('a.tmpl', {contents: buffer}); + + var item = list.compile('a.tmpl'); + + assert(item.fn({title: 'z'})); + assert(typeof item.fn({title: 'z'}) === 'string'); + assert(item.fn({title: 'z'}) === 'a z c'); + }); +}); + diff --git a/test/app.list.render.js b/test/app.list.render.js new file mode 100644 index 0000000..f4a9e0b --- /dev/null +++ b/test/app.list.render.js @@ -0,0 +1,157 @@ +'use strict'; + +require('mocha'); +require('should'); +var async = require('async'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var List = App.List; +var pages, app; + +describe('render', function () { + describe('rendering', function () { + beforeEach(function () { + app = App(); + pages = app.create('pages'); + app.engine('tmpl', require('engine-base')); + pages.engine('tmpl', require('engine-base')); + }); + + it('should throw an error when no callback is given:', function () { + (function() { + app.pages.render({}); + }).should.throw('Views#render is async and expects a callback function'); + }); + + it('should throw an error when an engine is not defined:', function (done) { + pages.addView('foo.bar', {content: '<%= name %>'}); + var page = pages.getView('foo.bar'); + + app.pages.render(page, function(err) { + assert(err.message === 'Views#render cannot find an engine for: .bar'); + done(); + }); + }); + + it('should use helpers defined on app to render a view:', function (done) { + var locals = {name: 'Halle'}; + app.helper('upper', function (str) { + return str.toUpperCase(str) + 'app'; + }); + + pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); + var page = pages.getView('a.tmpl'); + + app.render(page, function (err, res) { + if (err) return done(err); + + assert(res.content === 'a HALLEapp b'); + done(); + }); + }); + + it('should use helpers defined on app to render a view with collection.render:', function (done) { + var locals = {name: 'Halle'}; + app.helper('upper', function (str) { + return str.toUpperCase(str) + 'app'; + }); + + pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); + pages.helper('upper', app._.helpers.sync.upper); + var page = pages.getView('a.tmpl'); + + pages.render(page, function (err, res) { + if (err) return done(err); + + assert(res.content === 'a HALLEapp b'); + done(); + }); + }); + + it('should use helpers when rendering a view:', function (done) { + var locals = {name: 'Halle'}; + pages.helper('upper', function (str) { + return str.toUpperCase(str); + }); + + pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); + var page = pages.getView('a.tmpl'); + + pages.render(page, function (err, res) { + if (err) return done(err); + assert(res.content === 'a HALLE b'); + done(); + }); + }); + + it('should render a template when contents is a buffer:', function (done) { + pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); + var view = pages.getView('a.tmpl'); + + pages.render(view, function (err, view) { + if (err) return done(err); + assert(view.contents.toString() === 'b'); + done(); + }); + }); + + it('should render a template when content is a string:', function (done) { + pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); + var view = pages.getView('a.tmpl'); + + pages.render(view, function (err, view) { + if (err) return done(err); + assert(view.contents.toString() === 'b'); + done(); + }); + }); + + it('should render a view from its path:', function (done) { + pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); + + pages.render('a.tmpl', function (err, view) { + if (err) return done(err); + assert(view.content === 'b'); + done(); + }); + }); + + it('should use a plugin for rendering:', function (done) { + pages.engine('tmpl', require('engine-base')); + pages.option('engine', 'tmpl'); + + pages.addViews({ + 'a': {content: '<%= title %>', locals: {title: 'aaa'}}, + 'b': {content: '<%= title %>', locals: {title: 'bbb'}}, + 'c': {content: '<%= title %>', locals: {title: 'ccc'}}, + 'd': {content: '<%= title %>', locals: {title: 'ddd'}}, + 'e': {content: '<%= title %>', locals: {title: 'eee'}}, + 'f': {content: '<%= title %>', locals: {title: 'fff'}}, + 'g': {content: '<%= title %>', locals: {title: 'ggg'}}, + 'h': {content: '<%= title %>', locals: {title: 'hhh'}}, + 'i': {content: '<%= title %>', locals: {title: 'iii'}}, + 'j': {content: '<%= title %>', locals: {title: 'jjj'}}, + }); + + pages.use(function (collection) { + collection.option('pager', false); + + collection.renderEach = function (cb) { + var list = new List(collection); + async.map(list.items, function (item, next) { + collection.render(item, next); + }, cb); + }; + }); + + pages.renderEach(function (err, items) { + if (err) return done(err); + assert(items[0].content === 'aaa'); + assert(items[9].content === 'jjj'); + assert(items.length === 10); + done(); + }); + }); + }); +}); diff --git a/test/app.lookups.js b/test/app.lookups.js new file mode 100644 index 0000000..27ecfac --- /dev/null +++ b/test/app.lookups.js @@ -0,0 +1,112 @@ +require('mocha'); +require('should'); +var fs = require('fs'); +var path = require('path'); +var assert = require('assert'); +var resolve = require('resolve-glob'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('lookups', function () { + beforeEach(function () { + app = new App(); + app.option('renameKey', function (key) { + return path.basename(key); + }); + app.create('pages') + .use(function (pages) { + pages.on('addViews', function (glob) { + var files = resolve.sync(glob); + files.forEach(function (fp) { + pages.addView(fp, {path: fp}); + }); + pages.loaded = true; + }); + return function (view) { + view.read = function () { + this.contents = fs.readFileSync(this.path); + }; + return view; + }; + }); + + app.pages('test/fixtures/templates/*.tmpl'); + }); + + describe('getView', function () { + it('should find a view', function () { + var view = app.getView('pages', 'a.tmpl'); + assert(typeof view.path === 'string'); + }); + + it('should find a view using the renameKey function', function () { + var view = app.getView('pages', 'test/fixtures/templates/a.tmpl'); + assert(typeof view.path === 'string'); + }); + + it('should return null when nothing is found', function () { + var view = app.getView('pages', 'test/fixtures/templates/foo.tmpl'); + assert(view === null); + }); + + it('should find a view using a glob pattern', function () { + var view = app.getView('pages', 'a', function (key) { + return key + '.tmpl'; + }); + assert(typeof view.path === 'string'); + }); + }); + + describe('getViews', function () { + it('should return the collection object if passed:', function () { + var views = app.getViews(app.views.pages); + assert(Object.keys(views).length > 1); + }); + + it('should return the specified collection with the plural name:', function () { + var views = app.getViews('pages'); + assert(Object.keys(views).length > 1); + }); + + it('should return the specified collection with the singular name:', function () { + var views = app.getViews('page'); + assert(Object.keys(views).length > 1); + }); + + it('should return null when the collection is not found:', function () { + (function () { + app.getViews('nada'); + }).should.throw('getViews cannot find collection: nada'); + }); + }); + + describe('find', function () { + it('should return null when a view is not found:', function () { + (function () { + app.find({}); + }).should.throw('expected name to be a string.'); + }); + + it('should find a view by collection name:', function () { + var view = app.find('a.tmpl', 'pages'); + assert(typeof view.path === 'string'); + }); + + it('should find a view by collection name:', function () { + app = new App(); + app.option('renameKey', function (key) { + return path.basename(key); + }); + app.create('pages'); + app.page('a/b/c.md', {content: '...'}); + var view = app.find('a/b/c.md'); + assert(typeof view.path === 'string'); + }); + + it('should find a view without a collection name:', function () { + var view = app.find('a.tmpl'); + assert(typeof view.path === 'string'); + }); + }); +}); diff --git a/test/app.middleware.js b/test/app.middleware.js new file mode 100644 index 0000000..25843ed --- /dev/null +++ b/test/app.middleware.js @@ -0,0 +1,60 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('middleware', function () { + beforeEach(function () { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.create('pages'); + }); + + it('should call the all method for every middleware method:', function () { + var i = 0; + app.all(/./, function (view, next) { + assert(typeof view.path === 'string'); + i++; + next(); + }); + + assert(i === 0); + app.page('foo.tmpl', {content: 'foo'}); + assert(i === 1); + }); + + it('should call the onLoad method when a view is loaded:', function () { + var i = 0; + app.onLoad(/./, function (view, next) { + assert(typeof view.path === 'string'); + i++; + next(); + }); + + assert(i === 0); + app.page('foo.tmpl', {content: 'foo'}); + assert(i === 1); + }); + + it('should emit an event when a handler is called:', function (done) { + var i = 0; + app.on('onLoad', function () { + i++; + }); + app.on('preRender', function () { + i++; + }); + app.on('preCompile', function () { + i++; + }); + + app.page('foo.tmpl', {content: 'foo'}) + .render(function (err) { + if (err) return done(err); + assert(i === 3); + done(); + }); + }); +}); diff --git a/test/app.option.js b/test/app.option.js new file mode 100644 index 0000000..a13a7aa --- /dev/null +++ b/test/app.option.js @@ -0,0 +1,104 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('app.option', function () { + beforeEach(function () { + app = new App(); + }); + + it('should set a key-value pair on options:', function () { + app.option('a', 'b'); + assert(app.options.a === 'b'); + }); + + it('should set an object on options:', function () { + app.option({c: 'd'}); + assert(app.options.c === 'd'); + }); + + it('should throw on invalid args:', function () { + (function () { + app.option(function () {}); + }).should.throw('expected a string or object.'); + }); + + it('should set an option.', function() { + app.option('a', 'b'); + app.options.should.have.property('a'); + }); + + it('should get an option.', function() { + app.option('a', 'b'); + app.option('a').should.equal('b'); + }); + + it('should extend the `options` object.', function() { + app.option({x: 'xxx', y: 'yyy', z: 'zzz'}); + app.option('x').should.equal('xxx'); + app.option('y').should.equal('yyy'); + app.option('z').should.equal('zzz'); + }); + + it('options should be on the `options` object.', function() { + app.option({x: 'xxx', y: 'yyy', z: 'zzz'}); + app.options.x.should.equal('xxx'); + app.options.y.should.equal('yyy'); + app.options.z.should.equal('zzz'); + }); + + it('should be chainable.', function() { + app.option({x: 'xxx', y: 'yyy', z: 'zzz'}); + app.option({a: 'aaa', b: 'bbb', c: 'ccc'}); + + app.option('x').should.equal('xxx'); + app.option('a').should.equal('aaa'); + }); + + it('should extend the `options` object when the first param is a string.', function() { + app.option('foo', {x: 'xxx', y: 'yyy', z: 'zzz'}); + app.option('bar', {a: 'aaa', b: 'bbb', c: 'ccc'}); + + app.option('foo').should.have.property('x'); + app.option('bar').should.have.property('a'); + + app.options.foo.should.have.property('x'); + app.options.bar.should.have.property('a'); + }); + + it('should set an option.', function() { + app.option('a', 'b'); + app.options.should.have.property('a'); + }); + + it('should get an option.', function() { + app.option('a', 'b'); + app.option('a').should.equal('b'); + }); + + it('should extend the `options` object.', function() { + app.option({x: 'xxx', y: 'yyy', z: 'zzz'}); + app.option('x').should.equal('xxx'); + app.option('y').should.equal('yyy'); + app.option('z').should.equal('zzz'); + }); + + it('options should be on the `options` object.', function() { + app.option({x: 'xxx', y: 'yyy', z: 'zzz'}); + app.options.x.should.equal('xxx'); + app.options.y.should.equal('yyy'); + app.options.z.should.equal('zzz'); + }); + + it('should be chainable.', function() { + app + .option({x: 'xxx', y: 'yyy', z: 'zzz'}) + .option({a: 'aaa', b: 'bbb', c: 'ccc'}); + + app.option('x').should.equal('xxx'); + app.option('a').should.equal('aaa'); + }); +}); diff --git a/test/app.render.js b/test/app.render.js new file mode 100644 index 0000000..e796d95 --- /dev/null +++ b/test/app.render.js @@ -0,0 +1,88 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('render', function () { + describe('rendering', function () { + beforeEach(function () { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.create('page'); + }); + + it('should throw an error when no callback is given:', function () { + (function() { + app.render({}); + }).should.throw('Templates#render is async and expects a callback function'); + }); + + it('should throw an error when an engine is not defined:', function (done) { + app.page('foo.bar', {content: '<%= name %>'}); + var page = app.pages.getView('foo.bar'); + + app.render(page, function(err) { + assert(err.message === 'Templates#render cannot find an engine for: .bar'); + done(); + }); + }); + + it('should use helpers to render a view:', function (done) { + var locals = {name: 'Halle'}; + + app.helper('upper', function (str) { + return str.toUpperCase(str); + }); + + app.page('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); + var page = app.pages.getView('a.tmpl'); + + app.render(page, function (err, res) { + if (err) return done(err); + + assert(res.contents.toString() === 'a HALLE b'); + done(); + }); + }); + + it('should use helpers when rendering a view:', function (done) { + var locals = {name: 'Halle'}; + app.helper('upper', function (str) { + return str.toUpperCase(str); + }); + + app.page('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); + var page = app.pages.getView('a.tmpl'); + + app.render(page, function (err, res) { + if (err) return done(err); + assert(res.contents.toString() === 'a HALLE b'); + done(); + }); + }); + + it('should render a template when contents is a buffer:', function (done) { + app.pages('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); + var view = app.pages.getView('a.tmpl'); + + app.render(view, function (err, view) { + if (err) return done(err); + assert(view.contents.toString() === 'b'); + done(); + }); + }); + + it('should render a template when content is a string:', function (done) { + app.pages('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); + var view = app.pages.getView('a.tmpl'); + + app.render(view, function (err, view) { + if (err) return done(err); + assert(view.contents.toString() === 'b'); + done(); + }); + }); + }); +}); diff --git a/test/app.route.js b/test/app.route.js new file mode 100644 index 0000000..c7ab262 --- /dev/null +++ b/test/app.route.js @@ -0,0 +1,93 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('routes', function () { + beforeEach(function() { + app = new App(); + }); + + describe('routes', function() { + it('should create a route for the given path:', function (done) { + app = new App(); + app.create('posts'); + + app.on('all', function(msg) { + assert(msg === 'done'); + done(); + }); + + app.route('blog/:title') + .all(function(view, next) { + app.emit('all', 'done'); + next(); + }); + + app.post('whatever', {path: 'blog/foo.js', content: 'bar baz'}); + }); + + it('should emit events when a route method is called:', function (done) { + app = new App(); + app.create('posts'); + + app.on('onLoad', function(view) { + assert(view.path === 'blog/foo.js'); + done(); + }); + + app.param('title', function (view, next, title) { + assert(title === 'foo.js'); + next(); + }); + + app.onLoad('blog/:title', function (view, next) { + assert(view.path === 'blog/foo.js'); + next(); + }); + + app.post('whatever', {path: 'blog/foo.js', content: 'bar baz'}); + }); + + it('should emit errors', function (done) { + app = new App(); + app.create('posts'); + + app.on('error', function(err) { + assert(err.message === 'false == true'); + done(); + }); + + // wrong... + app.param('title', function (view, next, title) { + assert(title === 'fo.js'); + next(); + }); + + app.onLoad('/blog/:title', function (view, next) { + assert(view.path === '/blog/foo.js'); + next(); + }); + + app.post('whatever', {path: '/blog/foo.js', content: 'bar baz'}); + }); + + it('should have path property', function () { + var route = new app.Route('/blog/:year/:month/:day/:slug').all([ + function () {} + ]); + route.path.should.equal('/blog/:year/:month/:day/:slug'); + }); + + it('should have stack property', function () { + var route = new app.Route('/blog/:year/:month/:day/:slug').all([ + function () {} + ]); + + route.stack.should.be.instanceof(Array); + route.stack.should.have.length(1); + }); + }); +}); diff --git a/test/app.src.js b/test/app.src.js new file mode 100644 index 0000000..bd2b089 --- /dev/null +++ b/test/app.src.js @@ -0,0 +1,295 @@ +'use strict'; + +var App = require('..'); +var assert = require('assert'); +var should = require('should'); +var join = require('path').join; +var app; + +describe('src()', function() { + beforeEach(function () { + app = new App(); + }); + + it('should return a stream', function (done) { + var stream = app.src(join(__dirname, './fixtures/*.coffee')); + assert(stream); + assert.equal(typeof stream.on, 'function'); + assert.equal(typeof stream.pipe, 'function'); + done(); + }); + + it('should return an input stream from a flat glob', function (done) { + var stream = app.src(join(__dirname, './fixtures/*.coffee')); + stream.on('error', done); + stream.on('data', function (file) { + should.exist(file); + should.exist(file.path); + should.exist(file.contents); + join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); + String(file.contents).should.equal('Hello world!'); + }); + stream.on('end', function () { + done(); + }); + }); + + it('should return an input stream for multiple globs', function (done) { + var globArray = [ + join(__dirname, './fixtures/generic/run.dmc'), + join(__dirname, './fixtures/generic/test.dmc') + ]; + var stream = app.src(globArray); + + var files = []; + stream.on('error', done); + stream.on('data', function (file) { + should.exist(file); + should.exist(file.path); + files.push(file); + }); + stream.on('end', function () { + files.length.should.equal(2); + files[0].path.should.equal(globArray[0]); + files[1].path.should.equal(globArray[1]); + done(); + }); + }); + + it('should return an input stream for multiple globs with negation', function (done) { + var expectedPath = join(__dirname, './fixtures/generic/run.dmc'); + var globArray = [ + join(__dirname, './fixtures/generic/*.dmc'), + '!' + join(__dirname, './fixtures/generic/test.dmc'), + ]; + var stream = app.src(globArray); + + var files = []; + stream.on('error', done); + stream.on('data', function (file) { + should.exist(file); + should.exist(file.path); + files.push(file); + }); + stream.on('end', function () { + files.length.should.equal(1); + files[0].path.should.equal(expectedPath); + done(); + }); + }); + + it('should return an input stream with no contents when read is false', function (done) { + var stream = app.src(join(__dirname, './fixtures/*.coffee'), {read: false}); + stream.on('error', done); + stream.on('data', function (file) { + should.exist(file); + should.exist(file.path); + should.not.exist(file.contents); + join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); + }); + stream.on('end', function () { + done(); + }); + }); + + it('should return an input stream with contents as stream when buffer is false', function (done) { + var stream = app.src(join(__dirname, './fixtures/*.coffee'), {buffer: false}); + stream.on('error', done); + stream.on('data', function (file) { + should.exist(file); + should.exist(file.path); + should.exist(file.contents); + var buf = ''; + file.contents.on('data', function (d) { + buf += d; + }); + file.contents.on('end', function () { + buf.should.equal('Hello world!'); + done(); + }); + join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); + }); + }); + + it('should return an input stream from a deep glob', function (done) { + var stream = app.src(join(__dirname, './fixtures/**/*.jade')); + stream.on('error', done); + stream.on('data', function (file) { + should.exist(file); + should.exist(file.path); + should.exist(file.contents); + join(file.path, '').should.equal(join(__dirname, './fixtures/test/run.jade')); + String(file.contents).should.equal('test template'); + }); + stream.on('end', function () { + done(); + }); + }); + + it('should return an input stream from a deeper glob', function (done) { + var stream = app.src(join(__dirname, './fixtures/**/*.dmc')); + var a = 0; + stream.on('error', done); + stream.on('data', function () { + ++a; + }); + stream.on('end', function () { + a.should.equal(2); + done(); + }); + }); + + it('should return a file stream from a flat path', function (done) { + var a = 0; + var stream = app.src(join(__dirname, './fixtures/test.coffee')); + stream.on('error', done); + stream.on('data', function (file) { + ++a; + should.exist(file); + should.exist(file.path); + should.exist(file.contents); + join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); + String(file.contents).should.equal('Hello world!'); + }); + stream.on('end', function () { + a.should.equal(1); + done(); + }); + }); + + it('should return a stream', function (done) { + var stream = app.src(join(__dirname, './fixtures/*.coffee')); + should.exist(stream); + should.exist(stream.on); + done(); + }); + + it('should return an input stream from a flat glob', function (done) { + var stream = app.src(join(__dirname, './fixtures/*.coffee')); + stream.on('error', done); + stream.on('data', function (file) { + should.exist(file); + should.exist(file.path); + should.exist(file.contents); + join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); + String(file.contents).should.equal('Hello world!'); + }); + stream.on('end', function () { + done(); + }); + }); + + it('should return an input stream for multiple globs', function (done) { + var globArray = [ + join(__dirname, './fixtures/generic/run.dmc'), + join(__dirname, './fixtures/generic/test.dmc') + ]; + var stream = app.src(globArray); + + var files = []; + stream.on('error', done); + stream.on('data', function (file) { + should.exist(file); + should.exist(file.path); + files.push(file); + }); + stream.on('end', function () { + files.length.should.equal(2); + files[0].path.should.equal(globArray[0]); + files[1].path.should.equal(globArray[1]); + done(); + }); + }); + + it('should return an input stream for multiple globs, with negation', function (done) { + var expectedPath = join(__dirname, './fixtures/generic/run.dmc'); + var globArray = [ + join(__dirname, './fixtures/generic/*.dmc'), + '!' + join(__dirname, './fixtures/generic/test.dmc'), + ]; + var stream = app.src(globArray); + + var files = []; + stream.on('error', done); + stream.on('data', function (file) { + should.exist(file); + should.exist(file.path); + files.push(file); + }); + stream.on('end', function () { + files.length.should.equal(1); + files[0].path.should.equal(expectedPath); + done(); + }); + }); + + it('should return an input stream with no contents when read is false', function (done) { + var stream = app.src(join(__dirname, './fixtures/*.coffee'), {read: false}); + stream.on('error', done); + stream.on('data', function (file) { + should.exist(file); + should.exist(file.path); + should.not.exist(file.contents); + join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); + }); + stream.on('end', function () { + done(); + }); + }); + + it.skip('should throw an error when buffer is false', function (done) { + app.src(join(__dirname, './fixtures/*.coffee'), {buffer: false}) + .on('error', function () { + done(); + }) + .on('data', function () { + done(new Error('should have thrown an error')); + }); + }); + + it('should return an input stream from a deep glob', function (done) { + app.src(join(__dirname, './fixtures/**/*.jade')) + .on('error', done) + .on('data', function (file) { + should.exist(file); + should.exist(file.path); + should.exist(file.contents); + join(file.path, '').should.equal(join(__dirname, './fixtures/test/run.jade')); + String(file.contents).should.equal('test template'); + }) + .on('end', function () { + done(); + }); + }); + + it('should return an input stream from a deeper glob', function (done) { + var stream = app.src(join(__dirname, './fixtures/**/*.dmc')); + var a = 0; + stream.on('error', done); + stream.on('data', function () { + ++a; + }); + stream.on('end', function () { + a.should.equal(2); + done(); + }); + }); + + it('should return a file stream from a flat path', function (done) { + var a = 0; + var stream = app.src(join(__dirname, './fixtures/test.coffee')); + stream.on('error', done); + stream.on('data', function (file) { + ++a; + should.exist(file); + should.exist(file.path); + should.exist(file.contents); + join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); + String(file.contents).should.equal('Hello world!'); + }); + stream.on('end', function () { + a.should.equal(1); + done(); + }); + }); +}); diff --git a/test/app.task.js b/test/app.task.js new file mode 100644 index 0000000..c0d38ff --- /dev/null +++ b/test/app.task.js @@ -0,0 +1,156 @@ +var assert = require('assert'); +var App = require('..'); +var app; + +describe('task()', function () { + beforeEach(function () { + app = new App(); + }); + + it('should register a task', function () { + var fn = function (done) { + done(); + }; + app.task('default', fn); + assert.equal(typeof app.tasks.default, 'object'); + assert.equal(app.tasks.default.fn, fn); + }); + + it('should register a task with an array of dependencies', function () { + app.task('default', ['foo', 'bar'], function (done) { + done(); + }); + assert.equal(typeof app.tasks.default, 'object'); + assert.deepEqual(app.tasks.default.deps, ['foo', 'bar']); + }); + + it('should register a task with a list of strings as dependencies', function () { + app.task('default', 'foo', 'bar', function (done) { + done(); + }); + assert.equal(typeof app.tasks.default, 'object'); + assert.deepEqual(app.tasks.default.deps, ['foo', 'bar']); + }); + + it('should run a task', function (done) { + var count = 0; + app.task('default', function (cb) { + count++; + cb(); + }); + + app.build('default', function (err) { + if (err) return done(err); + assert.equal(count, 1); + done(); + }); + }); + + it('should throw an error when a task with unregistered dependencies is run', function (done) { + var count = 0; + app.task('default', ['foo', 'bar'], function (cb) { + count++; + cb(); + }); + + app.build('default', function (err) { + if (!err) return done(new Error('Expected an error to be thrown.')); + assert.equal(count, 0); + done(); + }); + }); + + it('should throw an error when `.build` is called without a callback function.', function () { + try { + app.build('default'); + throw new Error('Expected an error to be thrown.'); + } catch (err) { + } + }); + + it('should emit task events', function (done) { + var events = []; + app.on('starting', function (task) { + events.push('starting.' + task.name); + }); + app.on('finished', function (task) { + events.push('finished.' + task.name); + }); + app.on('error', function (err, task) { + events.push('error.' + task.name); + }); + + app.task('foo', function (cb) { + cb(); + }); + app.task('bar', ['foo'], function (cb) { + cb(); + }); + app.task('default', ['bar']); + app.build('default', function (err) { + if (err) return done(err); + assert.deepEqual(events, [ + 'starting.default', + 'starting.bar', + 'starting.foo', + 'finished.foo', + 'finished.bar', + 'finished.default' + ]); + done(); + }); + }); + + it('should emit an error event when an error is passed back in a task', function (done) { + app.on('error', function (err) { + assert(err); + assert.equal(err.message, 'This is an error'); + }); + app.task('default', function (cb) { + return cb(new Error('This is an error')); + }); + app.build('default', function (err) { + if (err) return done(); + done(new Error('Expected an error')); + }); + }); + + it('should emit an error event when an error is thrown in a task', function (done) { + var errors = 0; + app.on('error', function (err) { + errors++; + assert(err); + assert.equal(err.message, 'This is an error'); + }); + app.task('default', function (cb) { + cb(new Error('This is an error')); + }); + app.build('default', function (err) { + assert.equal(errors, 1); + if (err) return done(); + done(new Error('Expected an error')); + }); + }); + + it('should run dependencies before running the dependent task.', function (done) { + var seq = []; + app.task('foo', function (cb) { + seq.push('foo'); + cb(); + }); + app.task('bar', function (cb) { + seq.push('bar'); + cb(); + }); + app.task('default', ['foo', 'bar'], function (cb) { + seq.push('default'); + cb(); + }); + + app.build('default', function (err) { + if (err) return done(err); + assert.deepEqual(seq, ['foo', 'bar', 'default']); + done(); + }); + }); +}); diff --git a/test/app.use.js b/test/app.use.js new file mode 100644 index 0000000..875e858 --- /dev/null +++ b/test/app.use.js @@ -0,0 +1,281 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var Views = App.Views; +var View = App.View; +var app; + +describe('app.use', function () { + beforeEach(function () { + app = new App(); + }); + + it('should expose the instance to `use`:', function (done) { + app.use(function (inst) { + assert(inst instanceof App); + done(); + }); + }); + + it('should be chainable:', function (done) { + app.use(function (inst) { + assert(inst instanceof App); + }) + .use(function (inst) { + assert(inst instanceof App); + }) + .use(function (inst) { + assert(inst instanceof App); + done(); + }); + }); + + it('should pass to collection `use` if a function is returned:', function () { + app.use(function (inst) { + assert(inst instanceof App); + return function (collection) { + collection.foo = collection.addView; + assert(collection instanceof Views); + return collection; + }; + }); + + app.create('pages') + .foo({path: 'a.md', content: '...'}) + .addView({path: 'b.md', content: '...'}) + .addView({path: 'c.md', content: '...'}); + + assert(app.views.pages.hasOwnProperty('a.md')); + assert(app.views.pages.hasOwnProperty('b.md')); + assert(app.views.pages.hasOwnProperty('c.md')); + }); + + it('should be chainable when a collection function is returned:', function () { + app + .use(function (inst) { + assert(inst instanceof App); + return function (collection) { + collection.foo = collection.addView; + assert(collection instanceof Views); + return collection; + }; + }) + .use(function (inst) { + assert(inst instanceof App); + return function (collection) { + collection.bar = collection.addView; + assert(collection instanceof Views); + return collection; + }; + }) + .use(function (inst) { + assert(inst instanceof App); + return function (collection) { + collection.baz = collection.addView; + assert(collection instanceof Views); + return collection; + }; + }); + + var pages = app.create('pages'); + + pages.foo({path: 'a.md', content: '...'}); + pages.bar({path: 'b.md', content: '...'}); + pages.baz({path: 'c.md', content: '...'}); + + assert(app.views.pages.hasOwnProperty('a.md')); + assert(app.views.pages.hasOwnProperty('b.md')); + assert(app.views.pages.hasOwnProperty('c.md')); + }); + + it('should pass to view `use` if collection.use returns a function:', function () { + app.use(function (inst) { + assert(inst instanceof App); + + return function (collection) { + assert(collection instanceof Views); + collection.foo = collection.addView; + + return function (view) { + assert(view instanceof View); + view.foo = collection.addView.bind(collection); + return view; + }; + }; + }); + + app.create('pages') + .foo({path: 'a.md', content: '...'}) + .foo({path: 'b.md', content: '...'}) + .foo({path: 'c.md', content: '...'}); + + assert(app.views.pages.hasOwnProperty('a.md')); + assert(app.views.pages.hasOwnProperty('b.md')); + assert(app.views.pages.hasOwnProperty('c.md')); + }); + + it('should be chainable when a view function is returned:', function () { + app + .use(function (inst) { + assert(inst instanceof App); + + return function (collection) { + assert(collection instanceof Views); + collection.foo = collection.addView; + + return function (view) { + assert(view instanceof View); + view.a = collection.addView.bind(collection); + return view; + }; + }; + }) + .use(function (inst) { + assert(inst instanceof App); + + return function (collection) { + assert(collection instanceof Views); + collection.bar = collection.addView; + + return function (view) { + assert(view instanceof View); + view.b = collection.addView.bind(collection); + return view; + }; + }; + }) + .use(function (inst) { + assert(inst instanceof App); + + return function (collection) { + assert(collection instanceof Views); + collection.baz = collection.addView; + + return function (view) { + assert(view instanceof View); + view.c = collection.addView.bind(collection); + return view; + }; + }; + }); + + var pages = app.create('pages'); + + pages.foo({path: 'a.md', content: '...'}); + pages.bar({path: 'b.md', content: '...'}); + pages.baz({path: 'c.md', content: '...'}) + .a({path: 'x.md', content: '...'}) + .b({path: 'y.md', content: '...'}) + .c({path: 'z.md', content: '...'}); + + assert(app.views.pages.hasOwnProperty('a.md')); + assert(app.views.pages.hasOwnProperty('b.md')); + assert(app.views.pages.hasOwnProperty('c.md')); + + assert(app.views.pages.hasOwnProperty('x.md')); + assert(app.views.pages.hasOwnProperty('y.md')); + assert(app.views.pages.hasOwnProperty('z.md')); + }); + + it('should work with multiple collections:', function () { + app + .use(function (inst) { + assert(inst instanceof App); + + return function (collection) { + assert(collection instanceof Views); + collection.foo = collection.addView; + + return function (view) { + assert(view instanceof View); + view.a = collection.addView.bind(collection); + return view; + }; + }; + }) + .use(function (inst) { + assert(inst instanceof App); + + return function (collection) { + assert(collection instanceof Views); + collection.bar = collection.addView; + + return function (view) { + assert(view instanceof View); + view.b = collection.addView.bind(collection); + return view; + }; + }; + }) + .use(function (inst) { + assert(inst instanceof App); + assert(this instanceof App); + + return function (collection) { + collection.baz = collection.addView; + assert(collection instanceof Views); + assert(this instanceof Views); + + return function (view) { + assert(this instanceof View); + assert(view instanceof View); + view.c = collection.addView.bind(collection); + return view; + }; + }; + }); + + var pages = app.create('pages'); + + pages.foo({path: 'a.md', content: '...'}); + pages.bar({path: 'b.md', content: '...'}); + pages.baz({path: 'c.md', content: '...'}) + .a({path: 'x.md', content: '...'}) + .b({path: 'y.md', content: '...'}) + .c({path: 'z.md', content: '...'}); + + assert(app.views.pages.hasOwnProperty('a.md')); + assert(app.views.pages.hasOwnProperty('b.md')); + assert(app.views.pages.hasOwnProperty('c.md')); + + assert(app.views.pages.hasOwnProperty('x.md')); + assert(app.views.pages.hasOwnProperty('y.md')); + assert(app.views.pages.hasOwnProperty('z.md')); + + var posts = app.create('posts'); + + posts.foo({path: 'a.md', content: '...'}); + posts.bar({path: 'b.md', content: '...'}); + posts.baz({path: 'c.md', content: '...'}) + .a({path: 'x.md', content: '...'}) + .b({path: 'y.md', content: '...'}) + .c({path: 'z.md', content: '...'}); + + assert(app.views.posts.hasOwnProperty('a.md')); + assert(app.views.posts.hasOwnProperty('b.md')); + assert(app.views.posts.hasOwnProperty('c.md')); + + assert(app.views.posts.hasOwnProperty('x.md')); + assert(app.views.posts.hasOwnProperty('y.md')); + assert(app.views.posts.hasOwnProperty('z.md')); + + var docs = app.create('docs'); + + docs.foo({path: 'a.md', content: '...'}); + docs.bar({path: 'b.md', content: '...'}); + docs.baz({path: 'c.md', content: '...'}) + .a({path: 'x.md', content: '...'}) + .b({path: 'y.md', content: '...'}) + .c({path: 'z.md', content: '...'}); + + assert(app.views.docs.hasOwnProperty('a.md')); + assert(app.views.docs.hasOwnProperty('b.md')); + assert(app.views.docs.hasOwnProperty('c.md')); + + assert(app.views.docs.hasOwnProperty('x.md')); + assert(app.views.docs.hasOwnProperty('y.md')); + assert(app.views.docs.hasOwnProperty('z.md')); + }); +}); diff --git a/test/app.view.compile.js b/test/app.view.compile.js new file mode 100644 index 0000000..e325c92 --- /dev/null +++ b/test/app.view.compile.js @@ -0,0 +1,38 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('app.view.compile', function () { + describe('compile method', function () { + beforeEach(function () { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.create('page'); + }); + + it('should compile a view:', function () { + var buffer = new Buffer('a b c'); + var view = app.page('a.tmpl', {contents: buffer}) + .compile(); + assert(typeof view.fn === 'function'); + }); + + it('should compile a view with settings:', function () { + var buffer = new Buffer('a b c'); + var view = app.page('a.tmpl', {contents: buffer}) + .compile({foo: 'bar'}); + assert(typeof view.fn === 'function'); + }); + + it('should compile a view with isAsync flag:', function () { + var buffer = new Buffer('a b c'); + var view = app.page('a.tmpl', {contents: buffer}) + .compile(true); + assert(typeof view.fn === 'function'); + }); + }); +}); + diff --git a/test/app.view.render.js b/test/app.view.render.js new file mode 100644 index 0000000..fcf3087 --- /dev/null +++ b/test/app.view.render.js @@ -0,0 +1,92 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('helpers', function () { + describe('rendering', function () { + beforeEach(function () { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.create('page'); + }); + + it('should use helpers to render a view:', function (done) { + var locals = {name: 'Halle'}; + + app.helper('upper', function (str) { + return str.toUpperCase(str); + }); + + var buffer = new Buffer('a <%= upper(name) %> b'); + app.page('a.tmpl', {contents: buffer, locals: locals}) + .render(function (err, res) { + if (err) return done(err); + + assert(res.contents.toString() === 'a HALLE b'); + done(); + }); + }); + + it('should support helpers as an array:', function (done) { + var locals = {name: 'Halle'}; + + app.helpers([ + { + lower: function (str) { + return str.toLowerCase(str); + } + } + ]); + + var buffer = new Buffer('a <%= lower(name) %> b'); + app.page('a.tmpl', {contents: buffer, locals: locals}) + .render(function (err, res) { + if (err) return done(err); + + assert(res.contents.toString() === 'a halle b'); + done(); + }); + }); + + it('should support helpers as an object:', function (done) { + var locals = {name: 'Halle'}; + + app.helpers({ + prepend: function (prefix, str) { + return prefix + str; + } + }); + + var buffer = new Buffer('a <%= prepend("foo ", name) %> b'); + app.page('a.tmpl', {contents: buffer, locals: locals}) + .render(function (err, res) { + if (err) return done(err); + assert(res.contents.toString() === 'a foo Halle b'); + done(); + }); + }); + + it('should use the engine defined on view options:', function (done) { + app.engine('hbs', require('engine-handlebars')); + var locals = {name: 'Halle'}; + + app.helpers({ + prepend: function (prefix, str) { + return prefix + str; + } + }); + + var buffer = new Buffer('a {{prepend "foo " name}} b'); + app.page('a.tmpl', {contents: buffer, locals: locals, options: {engine: 'hbs'}}) + .render(function (err, res) { + if (err) return done(err); + assert(res.contents.toString() === 'a foo Halle b'); + done(); + }); + }); + }); +}); + diff --git a/test/app.watch.js b/test/app.watch.js new file mode 100644 index 0000000..698b768 --- /dev/null +++ b/test/app.watch.js @@ -0,0 +1,42 @@ +'use strict'; + +var assert = require('assert'); +var fs = require('fs'); +var App = require('..'); +var app; + +describe.skip('watch()', function () { + beforeEach(function () { + app = new App({runtimes: false}); + }); + + it('should watch files and run a task when files change', function (done) { + this.timeout(750); + + var count = 0, watch; + app.task('default', function (cb) { + count++; + cb(); + }); + + app.task('close', function (cb) { + watch.close(); + app.emit('close'); + cb(); + }); + + app.task('watch', function (cb) { + watch = app.watch('test/fixtures/watch/*.txt', ['default', 'close']); + fs.writeFile('test/fixtures/watch/test.txt', 'test', function (err) { + if (err) return cb(err); + app.on('close', cb); + }); + }); + + app.build(['watch'], function (err) { + if (err) return done(err); + assert.equal(count, 1); + done(); + }); + }); +}); diff --git a/test/collection.engines.js b/test/collection.engines.js new file mode 100644 index 0000000..4c2a619 --- /dev/null +++ b/test/collection.engines.js @@ -0,0 +1,177 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var Views = App.Views; +var collection, pages; + +describe('collection engines', function() { + beforeEach(function() { + pages = new Views(); + }); + + it('should throw an error when engine name is invalid:', function () { + (function () { + pages.engine(null, {}); + }).should.throw('expected engine ext to be a string or array.'); + }); + + it('should register an engine to the given extension', function () { + pages.engine('hbs', function () {}); + assert(typeof pages.engines['.hbs'] === 'object'); + }); + + it('should set an engine with the given extension', function () { + var hbs = function() {}; + hbs.render = function() {}; + hbs.renderFile = function() {}; + pages.engine('hbs', hbs); + assert(pages.engines['.hbs']); + assert(pages.engines['.hbs'].renderFile); + assert(pages.engines['.hbs'].render); + }); + + it('should get an engine:', function () { + pages.engine('hbs', function () {}); + var hbs = pages.engine('hbs'); + assert(typeof hbs === 'object'); + assert(hbs.hasOwnProperty('render')); + assert(hbs.hasOwnProperty('compile')); + }); + + it('should register multiple engines to the given extension', function () { + pages.engine(['hbs', 'md'], function () {}); + assert(typeof pages.engines['.hbs'] === 'object'); + assert(typeof pages.engines['.md'] === 'object'); + }); +}); + +describe('engines', function () { + beforeEach(function () { + pages = new Views(); + pages.addView('foo.tmpl', {content: 'A <%= letter %> {{= letter }} C'}); + pages.addView('bar.tmpl', {content: 'A <%= letter %> {{ letter }} C'}); + }); + + it('should register an engine:', function () { + pages.engine('a', {render: function () {}}); + pages.engines.should.have.property('.a'); + }); + + it('should use custom delimiters:', function (done) { + pages.engine('tmpl', require('engine-base'), { + delims: ['{{', '}}'] + }); + + pages.render('foo.tmpl', {letter: 'B'}, function (err, res) { + if (err) return done(err); + res.content.should.equal('A <%= letter %> B C'); + done(); + }); + }); + + it('should override individual delims values:', function (done) { + pages.engine('tmpl', require('engine-base'), { + interpolate: /\{{([^}]+)}}/g, + evaluate: /\{{([^}]+)}}/g, + escape: /\{{-([^}]+)}}/g + }); + pages.render('bar.tmpl', {letter: 'B'}, function (err, res) { + if (err) return done(err); + res.content.should.equal('A <%= letter %> B C'); + done(); + }); + }); + + it('should get an engine:', function () { + pages.engine('a', { + render: function () {} + }); + var a = pages.engine('a'); + a.should.have.property('render'); + }); +}); + + +describe('engine selection:', function () { + beforeEach(function (done) { + collection = new Views(); + collection.engine('tmpl', require('engine-base')); + collection.engine('hbs', require('engine-handlebars')); + done(); + }); + + it('should get the engine from file extension:', function (done) { + var pages = new Views(); + pages.engine('tmpl', require('engine-base')); + pages.engine('hbs', require('engine-handlebars')); + pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}) + .render(function (err, view) { + if (err) return done(err); + assert(view.content === 'b'); + done(); + }); + }); + + it('should use the engine defined on the collection:', function (done) { + var posts = new Views({engine: 'hbs'}); + posts.engine('tmpl', require('engine-base')); + posts.engine('hbs', require('engine-handlebars')); + + posts.addView('a', {content: '{{a}}', locals: {a: 'b'}}) + .render(function (err, view) { + if (err) return done(err); + assert(view.content === 'b'); + done(); + }); + }); + + it('should use the engine defined on the view:', function (done) { + var posts = new Views(); + posts.engine('tmpl', require('engine-base')); + posts.engine('hbs', require('engine-handlebars')); + posts.addView('a', {content: '{{a}}', engine: 'hbs', locals: {a: 'b'}}) + .render(function (err, view) { + if (err) return done(err); + assert(view.content === 'b'); + done(); + }); + }); + + it('should use the engine defined on view.options:', function (done) { + var posts = new Views(); + posts.engine('tmpl', require('engine-base')); + posts.engine('hbs', require('engine-handlebars')); + posts.addView('a', {content: '{{a}}', data: {a: 'b'}, options: {engine: 'hbs'}}) + .render(function (err, view) { + if (err) return done(err); + assert(view.content === 'b'); + done(); + }); + }); + + it('should use the engine defined on view.data:', function (done) { + var posts = new Views(); + posts.engine('tmpl', require('engine-base')); + posts.engine('hbs', require('engine-handlebars')); + posts.addView('a', {content: '{{a}}', locals: {a: 'b'}, data: {engine: 'hbs'}}) + .render(function (err, view) { + if (err) return done(err); + assert(view.content === 'b'); + done(); + }); + }); + + it('should use the engine defined on render locals:', function (done) { + var posts = new Views(); + posts.engine('tmpl', require('engine-base')); + posts.engine('hbs', require('engine-handlebars')); + posts.addView('a', {content: '{{a}}', locals: {a: 'b'}}) + .render({engine: 'hbs'}, function (err, view) { + if (err) return done(err); + assert(view.content === 'b'); + done(); + }); + }); +}); diff --git a/test/collection.events.js b/test/collection.events.js new file mode 100644 index 0000000..e919835 --- /dev/null +++ b/test/collection.events.js @@ -0,0 +1,27 @@ +require('should'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('collection events', function () { + beforeEach(function () { + app = new App(); + app.create('page'); + }); + + it('should emit events:', function () { + app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}); + var events = []; + + app.pages.on('option', function (key) { + events.push(key); + }); + + app.pages.option('a', 'b'); + app.pages.option('c', 'd'); + app.pages.option('e', 'f'); + app.pages.option({g: 'h'}); + + events.should.eql(['a', 'c', 'e', 'g']); + }); +}); diff --git a/test/collection.js b/test/collection.js new file mode 100644 index 0000000..2ddee92 --- /dev/null +++ b/test/collection.js @@ -0,0 +1,537 @@ +require('mocha'); +require('should'); +var path = require('path'); +var assert = require('assert'); +var typeOf = require('kind-of'); +var isBuffer = require('is-buffer'); + +var support = require('./support'); +var App = support.resolve(); +var List = App.List; +var Item = App.Item; +var Collection = App.Collection; +var collection; + +describe('collection', function () { + describe('constructor', function () { + it('should create an instance of Collection', function () { + var collection = new Collection(); + assert(collection instanceof Collection); + assert(typeof collection === 'object'); + }); + + it('should instantiate without new', function () { + var collection = Collection(); + assert(collection instanceof Collection); + assert(typeof collection === 'object'); + }); + }); + + describe('static methods', function () { + it('should expose `extend`', function () { + assert(typeof Collection.extend ==='function'); + }); + }); + + describe('prototype methods', function () { + beforeEach(function() { + collection = new Collection(); + }); + + var methods = [ + 'use', + 'setItem', + 'addItem', + 'addItems', + 'addList', + 'getItem', + 'constructor', + 'set', + 'get', + 'del', + 'define', + 'visit', + 'on', + 'once', + 'off', + 'emit', + 'listeners', + 'hasListeners' + ]; + + methods.forEach(function (method) { + it('should expose ' + method + ' method', function () { + assert(typeof collection[method] === 'function'); + }); + }); + + it('should expose isCollection property', function () { + assert(typeof collection.isCollection === 'boolean'); + }); + + it('should expose queue property', function () { + assert(Array.isArray(collection.queue)); + }); + + it('should expose items property', function () { + assert(typeOf(collection.items) === 'object'); + }); + + it('should expose options property', function () { + assert(typeOf(collection.options) === 'object'); + }); + }); +}); + +describe('methods', function () { + beforeEach(function() { + collection = new Collection(); + }); + + describe('chaining', function () { + it('should allow collection methods to be chained', function () { + collection + .addItems({'a.hbs': {path: 'a.hbs'}}) + .addItems({'b.hbs': {path: 'b.hbs'}}) + .addItems({'c.hbs': {path: 'c.hbs'}}); + + collection.items.should.have.properties([ + 'a.hbs', + 'b.hbs', + 'c.hbs' + ]); + }); + }); + + describe('use', function () { + it('should expose the instance to plugins', function () { + collection + .use(function (inst) { + inst.foo = 'bar'; + }); + + assert(collection.foo === 'bar'); + }); + + it('should expose `item` when the plugin returns a function', function () { + collection + .use(function () { + return function (item) { + item.foo = 'bar'; + }; + }); + + collection.addItem('aaa'); + collection.addItem('bbb'); + collection.addItem('ccc'); + + assert(collection.items.aaa.foo === 'bar'); + assert(collection.items.bbb.foo === 'bar'); + assert(collection.items.ccc.foo === 'bar'); + }); + }); + + describe('get / set', function () { + it('should set a value on the instance', function () { + collection.set('a', 'b'); + assert(collection.a ==='b'); + }); + + it('should get a value from the instance', function () { + collection.set('a', 'b'); + assert(collection.get('a') ==='b'); + }); + }); + + describe('adding items', function () { + beforeEach(function () { + collection = new Collection(); + }); + + it('should load a item onto the respective collection', function () { + collection.addItem('a.hbs'); + collection.items.should.have.property('a.hbs'); + }); + }); + + describe('item', function() { + beforeEach(function() { + collection = new Collection(); + }); + + it('should return a single collection item from a key-value pair', function () { + var one = collection.item('one', {content: 'foo'}); + var two = collection.item('two', {content: 'bar'}); + + assert(one instanceof Item); + assert(one instanceof collection.Item); + assert(one.path === 'one'); + assert(two instanceof Item); + assert(two instanceof collection.Item); + assert(two.path === 'two'); + }); + + it('should return a single collection item from an object', function () { + var one = collection.item({path: 'one', content: 'foo'}); + var two = collection.item({path: 'two', content: 'bar'}); + + assert(one instanceof Item); + assert(one.path === 'one'); + assert(two instanceof Item); + assert(two.path === 'two'); + }); + }); + + describe('addItem', function() { + beforeEach(function() { + collection = new Collection(); + }); + + it('should throw an error when args are invalid', function () { + (function () { + collection.addItem(function() {}); + }).should.throw('expected value to be an object.'); + }); + + it('should add a item to `items`', function () { + collection.addItem('foo'); + collection.items.should.have.property('foo'); + + collection.addItem('one', {content: '...'}); + assert(typeof collection.items.one === 'object'); + assert(isBuffer(collection.items.one.contents)); + }); + + it('should create an instance of `Item`', function () { + collection.addItem('one', {content: '...'}); + assert(collection.items.one instanceof collection.Item); + }); + + it('should allow an `Item` constructor to be passed', function () { + Item.prototype.foo = function(key, value) { + this[key] = value; + }; + collection = new Collection({Item: Item}); + collection.addItem('one', {content: '...'}); + collection.items.one.foo('bar', 'baz'); + assert(collection.items.one.bar === 'baz'); + }); + + it('should allow an instance of `Item` to be passed', function () { + var collection = new Collection({Item: Item}); + var item = new Item({content: '...'}); + collection.addItem('one', item); + item.set('abc', 'xyz'); + assert(collection.items.one instanceof collection.Item); + assert(isBuffer(collection.items.one.contents)); + assert(collection.items.one.abc === 'xyz'); + }); + }); + + describe('addItems', function () { + it('should add multiple items', function () { + collection.addItems({ + one: {content: 'foo'}, + two: {content: 'bar'} + }); + assert(isBuffer(collection.items.one.contents)); + assert(isBuffer(collection.items.two.contents)); + }); + + it('should create items from an instance of Collection', function () { + collection.addItems({ + one: {content: 'foo'}, + two: {content: 'bar'} + }); + var pages = new Collection(collection); + assert(isBuffer(pages.items.one.contents)); + assert(isBuffer(pages.items.two.contents)); + }); + + it('should add an array of plain-objects', function () { + collection.addItems([ + {path: 'one', content: 'foo'}, + {path: 'two', content: 'bar'} + ]); + assert(isBuffer(collection.items.one.contents)); + assert(isBuffer(collection.items.two.contents)); + }); + + it('should add an array of items', function () { + var list = new List([ + {path: 'one', content: 'foo'}, + {path: 'two', content: 'bar'} + ]); + + collection.addItems(list.items); + assert(isBuffer(collection.items.one.contents)); + assert(isBuffer(collection.items.two.contents)); + }); + }); + + describe('addList', function() { + beforeEach(function() { + collection = new Collection(); + }); + + it('should add a list of items', function () { + collection.addList([ + {path: 'one', content: 'foo'}, + {path: 'two', content: 'bar'} + ]); + assert(isBuffer(collection.items.one.contents)); + assert(isBuffer(collection.items.two.contents)); + }); + + it('should add a list of items from the constructor', function () { + var list = new List([ + {path: 'one', content: 'foo'}, + {path: 'two', content: 'bar'} + ]); + + collection = new Collection(list); + assert(isBuffer(collection.items.one.contents)); + assert(isBuffer(collection.items.two.contents)); + }); + + it('should throw an error when list is not an array', function () { + var items = new Collection(); + (function () { + items.addList(); + }).should.throw('expected list to be an array.'); + + (function () { + items.addList({}); + }).should.throw('expected list to be an array.'); + + (function () { + items.addList('foo'); + }).should.throw('expected list to be an array.'); + }); + + it('should load an array of items from an event', function () { + var collection = new Collection(); + + collection.on('addList', function (list) { + while (list.length) { + collection.addItem({path: list.pop()}); + } + }); + + collection.addList(['a.txt', 'b.txt', 'c.txt']); + assert(collection.items.hasOwnProperty('a.txt')); + assert(collection.items['a.txt'].path === 'a.txt'); + }); + + it('should load an array of items from the addList callback:', function () { + var collection = new Collection(); + + collection.addList(['a.txt', 'b.txt', 'c.txt'], function (fp) { + return {path: fp}; + }); + assert(collection.items.hasOwnProperty('a.txt')); + assert(collection.items['a.txt'].path === 'a.txt'); + }); + + it('should load an object of items from an event', function () { + var collection = new Collection(); + + collection.on('addItems', function (items) { + for (var key in items) { + collection.addItem('foo/' + key, items[key]); + delete items[key]; + } + }); + + collection.addItems({ + a: {path: 'a.txt'}, + b: {path: 'b.txt'}, + c: {path: 'c.txt'} + }); + + assert(collection.items.hasOwnProperty('foo/a')); + assert(collection.items['foo/a'].path === 'a.txt'); + }); + + it('should signal `loaded` when finished (addItems)', function () { + var collection = new Collection(); + + collection.on('addItems', function (items) { + for (var key in items) { + if (key === 'c') { + collection.loaded = true; + break; + } + collection.addItem('foo/' + key, items[key]); + } + }); + + collection.addItems({ + a: {path: 'a.txt'}, + b: {path: 'b.txt'}, + c: {path: 'c.txt'} + }); + + assert(collection.items.hasOwnProperty('foo/a')); + assert(!collection.items.hasOwnProperty('foo/c')); + assert(collection.items['foo/a'].path === 'a.txt'); + }); + + it('should signal `loaded` when finished (addList)', function () { + var collection = new Collection(); + + collection.on('addList', function (items) { + for (var i = 0; i < items.length; i++) { + var item = items[i]; + if (item.key === 'c') { + collection.loaded = true; + break; + } + item.key = 'foo/' + item.key; + collection.addItem(item.key, item); + } + }); + + collection.addList([ + {key: 'a', path: 'a.txt'}, + {key: 'b', path: 'b.txt'}, + {key: 'c', path: 'c.txt'} + ]); + + assert(collection.items.hasOwnProperty('foo/a')); + assert(collection.items['foo/a'].path === 'a.txt'); + assert(!collection.items.hasOwnProperty('foo/c')); + }); + }); + + describe('getItem', function() { + beforeEach(function() { + collection = new Collection(); + }); + it('should get a item from `items`', function () { + collection.addItem('one', {content: 'aaa'}); + collection.addItem('two', {content: 'zzz'}); + assert(isBuffer(collection.items.one.contents)); + assert(isBuffer(collection.getItem('one').contents)); + assert(collection.getItem('one').contents.toString() === 'aaa'); + assert(collection.getItem('two').contents.toString() === 'zzz'); + }); + }); +}); + +describe('queue', function () { + beforeEach(function () { + collection = new Collection(); + }); + + it('should emit arguments on addItem', function (done) { + collection.on('addItem', function (args) { + assert(args[0] === 'a'); + assert(args[1] === 'b'); + assert(args[2] === 'c'); + assert(args[3] === 'd'); + assert(args[4] === 'e'); + done(); + }); + + collection.addItem('a', 'b', 'c', 'd', 'e'); + }); + + it('should expose the `queue` property for loading items', function () { + collection.queue.push(collection.item('b', {path: 'b'})); + + collection.addItem('a', {path: 'a'}); + assert(collection.items.hasOwnProperty('a')); + assert(collection.items.hasOwnProperty('b')); + }); + + it('should load all items on the queue when addItem is called', function () { + collection.on('addItem', function (args) { + var len = args.length; + var last = args[len - 1]; + if (typeof last === 'string') { + args[len - 1] = { content: last }; + } + }); + + collection.addItem('a.html', 'aaa'); + collection.addItem('b.html', 'bbb'); + collection.addItem('c.html', 'ccc'); + + assert(collection.items.hasOwnProperty('a.html')); + assert(collection.getItem('a.html').content === 'aaa'); + assert(collection.items.hasOwnProperty('b.html')); + assert(collection.getItem('b.html').content === 'bbb'); + assert(collection.items.hasOwnProperty('c.html')); + assert(collection.getItem('c.html').content === 'ccc'); + }); +}); + +describe('options', function() { + describe('option', function() { + beforeEach(function() { + collection = new Collection(); + }); + + it('should expose the `option` method', function () { + collection.option('foo', 'bar'); + collection.options.should.have.property('foo', 'bar'); + }); + + it('should be chainable', function () { + collection.option('foo', 'bar') + .addItems('a.hbs') + .addItems('b.hbs') + .addItems('c.hbs'); + + collection.options.should.have.property('foo', 'bar'); + collection.items.should.have.properties([ + 'a.hbs', + 'b.hbs', + 'c.hbs' + ]); + }); + + it('should set a key/value pair on options', function () { + collection.option('a', 'b'); + assert(collection.options.a === 'b'); + }); + + it('should set an object on options', function () { + collection.option({c: 'd'}); + assert(collection.options.c === 'd'); + }); + + it('should get an option', function () { + collection.option({c: 'd'}); + var c = collection.option('c'); + assert(c === 'd'); + }); + }); + + describe('options.renameKey', function() { + beforeEach(function() { + collection = new Collection({ + renameKey: function (key) { + return path.basename(key); + } + }); + }); + + it('should use a custom rename key function on item keys', function() { + collection.addItem('a/b/c/d.hbs', {content: 'foo bar baz'}); + assert(collection.items['d.hbs'].contents.toString() === 'foo bar baz'); + }); + + it('should get a item with the renamed key', function () { + collection.addItem('a/b/c/d.hbs', {content: 'foo bar baz'}); + assert(collection.getItem('d.hbs').contents.toString() === 'foo bar baz'); + }); + + it('should get a item with the original key', function () { + collection.addItem('a/b/c/d.hbs', {content: 'foo bar baz'}); + assert(collection.getItem('a/b/c/d.hbs').contents.toString() === 'foo bar baz'); + }); + }); +}); + diff --git a/test/collection.options.js b/test/collection.options.js new file mode 100644 index 0000000..4994fd4 --- /dev/null +++ b/test/collection.options.js @@ -0,0 +1,25 @@ +require('should'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('collection.option()', function () { + beforeEach(function () { + app = new App(); + app.create('page'); + }); + + it('should set an option:', function () { + app.pages.options.should.not.have.property('foo'); + app.pages.option('foo', 'bar'); + app.pages.options.should.have.property('foo'); + }); + + it('should extend options:', function () { + app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}); + app.pages.option('a', 'b'); + app.pages.option('c', 'd'); + app.pages.option('e', 'f'); + app.pages.options.should.have.properties(['a', 'c', 'e']); + }); +}); diff --git a/test/collection.render.js b/test/collection.render.js new file mode 100644 index 0000000..d7c1579 --- /dev/null +++ b/test/collection.render.js @@ -0,0 +1,138 @@ +require('mocha'); +require('should'); +var async = require('async'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var List = App.List; +var Views = App.Views; +var pages; + +describe('render', function () { + describe('rendering', function () { + beforeEach(function () { + pages = new Views(); + pages.engine('tmpl', require('engine-base')); + }); + + it('should throw an error when no callback is given:', function () { + (function() { + pages.render({}); + }).should.throw('Views#render is async and expects a callback function'); + }); + + it('should throw an error when an engine is not defined:', function (done) { + pages.addView('foo.bar', {content: '<%= name %>'}); + var page = pages.getView('foo.bar'); + + pages.render(page, function(err) { + assert(err.message === 'Views#render cannot find an engine for: .bar'); + done(); + }); + }); + + it('should use helpers to render a view:', function (done) { + var locals = {name: 'Halle'}; + + pages.helper('upper', function (str) { + return str.toUpperCase(str); + }); + + pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); + var page = pages.getView('a.tmpl'); + + pages.render(page, function (err, res) { + if (err) return done(err); + + assert(res.content === 'a HALLE b'); + done(); + }); + }); + + it('should use helpers when rendering a view:', function (done) { + var locals = {name: 'Halle'}; + pages.helper('upper', function (str) { + return str.toUpperCase(str); + }); + + pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); + var page = pages.getView('a.tmpl'); + + pages.render(page, function (err, res) { + if (err) return done(err); + assert(res.content === 'a HALLE b'); + done(); + }); + }); + + it('should render a template when contents is a buffer:', function (done) { + pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); + var view = pages.getView('a.tmpl'); + + pages.render(view, function (err, view) { + if (err) return done(err); + assert(view.contents.toString() === 'b'); + done(); + }); + }); + + it('should render a template when content is a string:', function (done) { + pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); + var view = pages.getView('a.tmpl'); + + pages.render(view, function (err, view) { + if (err) return done(err); + assert(view.contents.toString() === 'b'); + done(); + }); + }); + + it('should render a view from its path:', function (done) { + pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); + + pages.render('a.tmpl', function (err, view) { + if (err) return done(err); + assert(view.content === 'b'); + done(); + }); + }); + + it('should use a plugin for rendering:', function (done) { + pages.engine('tmpl', require('engine-base')); + pages.option('engine', 'tmpl'); + + pages.addViews({ + 'a': {content: '<%= title %>', locals: {title: 'aaa'}}, + 'b': {content: '<%= title %>', locals: {title: 'bbb'}}, + 'c': {content: '<%= title %>', locals: {title: 'ccc'}}, + 'd': {content: '<%= title %>', locals: {title: 'ddd'}}, + 'e': {content: '<%= title %>', locals: {title: 'eee'}}, + 'f': {content: '<%= title %>', locals: {title: 'fff'}}, + 'g': {content: '<%= title %>', locals: {title: 'ggg'}}, + 'h': {content: '<%= title %>', locals: {title: 'hhh'}}, + 'i': {content: '<%= title %>', locals: {title: 'iii'}}, + 'j': {content: '<%= title %>', locals: {title: 'jjj'}}, + }); + + pages.use(function (collection) { + collection.option('pager', false); + + collection.renderEach = function (cb) { + var list = new List(collection); + + async.map(list.items, function (item, next) { + collection.render(item, next); + }, cb); + }; + }); + + pages.renderEach(function (err, items) { + if (err) return done(err); + assert(items[0].content === 'aaa'); + assert(items[9].content === 'jjj'); + assert(items.length === 10); + done(); + }); + }); + }); +}); diff --git a/test/collection.use.js b/test/collection.use.js new file mode 100644 index 0000000..d7f7cbc --- /dev/null +++ b/test/collection.use.js @@ -0,0 +1,156 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var Views = App.Views; +var View = App.View; +var collection; + +describe('collection.use', function () { + beforeEach(function () { + collection = new Views(); + }); + + it('should expose the instance to `use`:', function (done) { + collection.use(function (inst) { + assert(inst instanceof Views); + done(); + }); + }); + + it('should be chainable:', function (done) { + collection.use(function (inst) { + assert(inst instanceof Views); + }) + .use(function (inst) { + assert(inst instanceof Views); + }) + .use(function (inst) { + assert(inst instanceof Views); + done(); + }); + }); + + it('should expose the collection to a plugin:', function () { + collection.use(function (views) { + assert(views instanceof Views); + views.foo = views.addView.bind(views); + }); + + collection.foo('a', {content: '...'}); + assert(collection.views.hasOwnProperty('a')); + }); + + it('should expose collection when chained:', function () { + collection + .use(function (views) { + assert(views instanceof Views); + views.foo = views.addView.bind(views); + }) + .use(function (views) { + assert(views instanceof Views); + views.bar = views.addView.bind(views); + }) + .use(function (views) { + assert(views instanceof Views); + views.baz = views.addView.bind(views); + }); + + var pages = collection; + + pages.foo({path: 'a', content: '...'}); + pages.bar({path: 'b', content: '...'}); + pages.baz({path: 'c', content: '...'}); + + assert(collection.views.hasOwnProperty('a')); + assert(collection.views.hasOwnProperty('b')); + assert(collection.views.hasOwnProperty('c')); + }); + + it('should work when a custom `View` constructor is passed:', function () { + collection = new Views({View: require('vinyl')}); + collection + .use(function (views) { + assert(views instanceof Views); + views.foo = views.addView.bind(views); + }) + .use(function (views) { + assert(views instanceof Views); + views.bar = views.addView.bind(views); + }) + .use(function (views) { + assert(views instanceof Views); + views.baz = views.addView.bind(views); + }); + + var pages = collection; + + pages.foo({path: 'a', content: '...'}); + pages.bar({path: 'b', content: '...'}); + pages.baz({path: 'c', content: '...'}); + + assert(collection.views.hasOwnProperty('a')); + assert(collection.views.hasOwnProperty('b')); + assert(collection.views.hasOwnProperty('c')); + }); + + it('should pass to view `use` if a function is returned:', function () { + collection.use(function (views) { + assert(views instanceof Views); + + return function (view) { + view.foo = views.addView.bind(views); + assert(view instanceof View); + }; + }); + + collection.addView('a', {content: '...'}) + .foo({path: 'b', content: '...'}) + .foo({path: 'c', content: '...'}) + .foo({path: 'd', content: '...'}); + + assert(collection.views.hasOwnProperty('a')); + assert(collection.views.hasOwnProperty('b')); + assert(collection.views.hasOwnProperty('c')); + assert(collection.views.hasOwnProperty('d')); + }); + + it('should be chainable when a view function is returned:', function () { + collection + .use(function (views) { + assert(views instanceof Views); + + return function (view) { + view.foo = views.addView.bind(views); + assert(view instanceof View); + }; + }) + .use(function (views) { + assert(views instanceof Views); + + return function (view) { + view.bar = views.addView.bind(views); + assert(view instanceof View); + }; + }) + .use(function (views) { + assert(views instanceof Views); + + return function (view) { + view.baz = views.addView.bind(views); + assert(view instanceof View); + }; + }); + + collection.addView('a', {content: '...'}) + .foo({path: 'b', content: '...'}) + .bar({path: 'c', content: '...'}) + .baz({path: 'd', content: '...'}); + + assert(collection.views.hasOwnProperty('a')); + assert(collection.views.hasOwnProperty('b')); + assert(collection.views.hasOwnProperty('c')); + assert(collection.views.hasOwnProperty('d')); + }); +}); diff --git a/test/dest.js b/test/dest.js new file mode 100644 index 0000000..3f077c0 --- /dev/null +++ b/test/dest.js @@ -0,0 +1,908 @@ +var spies = require('./support/spy'); +var chmodSpy = spies.chmodSpy; +var statSpy = spies.statSpy; + +var assert = require('assert'); +var App = require('../'); +var app; + +var path = require('path'); +var fs = require('graceful-fs'); +var rimraf = require('rimraf'); + +var bufferStream; +var bufEqual = require('buffer-equal'); +var through = require('through2'); +var File = require('vinyl'); + +var should = require('should'); +require('mocha'); + +var wipeOut = function(cb) { + app = new App(); + rimraf(path.join(__dirname, './out-fixtures/'), cb); + spies.setError('false'); + statSpy.reset(); + chmodSpy.reset(); +}; + +var dataWrap = function(fn) { + return function(data, enc, cb) { + fn(data); + cb(); + }; +}; + +var realMode = function(n) { + return n & 07777; +}; + +describe('dest stream', function() { + beforeEach(wipeOut); + afterEach(wipeOut); + + it('should explode on invalid folder (empty)', function(done) { + var stream; + try { + stream = app.dest(); + } catch (err) { + should.exist(err); + should.not.exist(stream); + done(); + } + }); + + it('should explode on invalid folder (empty string)', function(done) { + var stream; + try { + stream = app.dest(''); + } catch (err) { + should.exist(err); + should.not.exist(stream); + done(); + } + }); + + it('should pass through writes with cwd', function(done) { + var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); + + var expectedFile = new File({ + base: __dirname, + cwd: __dirname, + path: inputPath, + contents: null + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + done(); + }; + + var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should pass through writes with default cwd', function(done) { + var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); + + var expectedFile = new File({ + base: __dirname, + cwd: __dirname, + path: inputPath, + contents: null + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + done(); + }; + + var stream = app.dest(path.join(__dirname, './out-fixtures/')); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should not write null files', function(done) { + var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, './fixtures/vinyl/'); + var expectedPath = path.join(__dirname, './out-fixtures/test.coffee'); + var expectedCwd = __dirname; + var expectedBase = path.join(__dirname, './out-fixtures'); + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: null + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); + buffered[0].base.should.equal(expectedBase, 'base should have changed'); + buffered[0].path.should.equal(expectedPath, 'path should have changed'); + fs.existsSync(expectedPath).should.equal(false); + done(); + }; + + var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should write buffer files to the right folder with relative cwd', function(done) { + var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, './fixtures/vinyl/'); + var expectedPath = path.join(__dirname, './out-fixtures/test.coffee'); + var expectedCwd = __dirname; + var expectedBase = path.join(__dirname, './out-fixtures'); + var expectedContents = fs.readFileSync(inputPath); + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); + buffered[0].base.should.equal(expectedBase, 'base should have changed'); + buffered[0].path.should.equal(expectedPath, 'path should have changed'); + fs.existsSync(expectedPath).should.equal(true); + bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); + done(); + }; + + var stream = app.dest('./out-fixtures/', {cwd: path.relative(process.cwd(), __dirname)}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should write buffer files to the right folder with function and relative cwd', function(done) { + var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, './fixtures/vinyl/'); + var expectedPath = path.join(__dirname, './out-fixtures/test.coffee'); + var expectedCwd = __dirname; + var expectedBase = path.join(__dirname, './out-fixtures'); + var expectedContents = fs.readFileSync(inputPath); + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); + buffered[0].base.should.equal(expectedBase, 'base should have changed'); + buffered[0].path.should.equal(expectedPath, 'path should have changed'); + fs.existsSync(expectedPath).should.equal(true); + bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); + done(); + }; + + var stream = app.dest(function(file){ + should.exist(file); + file.should.equal(expectedFile); + return './out-fixtures'; + }, {cwd: path.relative(process.cwd(), __dirname)}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should write buffer files to the right folder', function(done) { + var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, './fixtures/vinyl/'); + var expectedPath = path.join(__dirname, './out-fixtures/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedCwd = __dirname; + var expectedBase = path.join(__dirname, './out-fixtures'); + var expectedMode = 0655; + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents, + stat: { + mode: expectedMode + } + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); + buffered[0].base.should.equal(expectedBase, 'base should have changed'); + buffered[0].path.should.equal(expectedPath, 'path should have changed'); + fs.existsSync(expectedPath).should.equal(true); + bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); + realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); + done(); + }; + + var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should write streaming files to the right folder', function(done) { + var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, './fixtures/vinyl/'); + var expectedPath = path.join(__dirname, './out-fixtures/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedCwd = __dirname; + var expectedBase = path.join(__dirname, './out-fixtures'); + var expectedMode = 0655; + + var contentStream = through.obj(); + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: contentStream, + stat: { + mode: expectedMode + } + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); + buffered[0].base.should.equal(expectedBase, 'base should have changed'); + buffered[0].path.should.equal(expectedPath, 'path should have changed'); + fs.existsSync(expectedPath).should.equal(true); + bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); + realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); + done(); + }; + + var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + setTimeout(function(){ + contentStream.write(expectedContents); + contentStream.end(); + }, 100); + stream.end(); + }); + + it('should write directories to the right folder', function(done) { + var inputPath = path.join(__dirname, './fixtures/vinyl/test'); + var inputBase = path.join(__dirname, './fixtures/vinyl/'); + var expectedPath = path.join(__dirname, './out-fixtures/test'); + var expectedCwd = __dirname; + var expectedBase = path.join(__dirname, './out-fixtures'); + var expectedMode = 0655; + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: null, + stat: { + isDirectory: function(){ + return true; + }, + mode: expectedMode + } + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); + buffered[0].base.should.equal(expectedBase, 'base should have changed'); + buffered[0].path.should.equal(expectedPath, 'path should have changed'); + fs.existsSync(expectedPath).should.equal(true); + fs.lstatSync(expectedPath).isDirectory().should.equal(true); + realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); + done(); + }; + + var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should allow piping multiple dests in streaming mode', function(done) { + var inputPath1 = path.join(__dirname, './out-fixtures/multiple-first'); + var inputPath2 = path.join(__dirname, './out-fixtures/multiple-second'); + var inputBase = path.join(__dirname, './out-fixtures/'); + var srcPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); + var stream1 = app.dest('./out-fixtures/', {cwd: __dirname}); + var stream2 = app.dest('./out-fixtures/', {cwd: __dirname}); + var content = fs.readFileSync(srcPath); + var rename = through.obj(function(file, _, next) { + file.path = inputPath2; + this.push(file); + next(); + }); + + stream1.on('data', function(file) { + file.path.should.equal(inputPath1); + }); + + stream1.pipe(rename).pipe(stream2); + stream2.on('data', function(file) { + file.path.should.equal(inputPath2); + }).once('end', function() { + fs.readFileSync(inputPath1, 'utf8').should.equal(content.toString()); + fs.readFileSync(inputPath2, 'utf8').should.equal(content.toString()); + done(); + }); + + var file = new File({ + base: inputBase, + path: inputPath1, + cwd: __dirname, + contents: content + }); + + stream1.write(file); + stream1.end(); + }); + + it('should write new files with the default user mode', function(done) { + var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, './fixtures/vinyl/'); + var expectedPath = path.join(__dirname, './out-fixtures/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedMode = 0666 & (~process.umask()); + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents, + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + fs.existsSync(expectedPath).should.equal(true); + realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); + done(); + }; + + chmodSpy.reset(); + var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should write new files with the specified mode', function(done) { + var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, './fixtures/vinyl/'); + var expectedPath = path.join(__dirname, './out-fixtures/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedMode = 0744; + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents, + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + fs.existsSync(expectedPath).should.equal(true); + realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); + done(); + }; + + chmodSpy.reset(); + var stream = app.dest('./out-fixtures/', {cwd: __dirname, mode:expectedMode}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should update file mode to match the vinyl mode', function(done) { + var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, './fixtures/vinyl/'); + var expectedPath = path.join(__dirname, './out-fixtures/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedBase = path.join(__dirname, './out-fixtures'); + var startMode = 0655; + var expectedMode = 0722; + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents, + stat: { + mode: expectedMode + } + }); + + var onEnd = function(){ + assert(chmodSpy.called); + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + fs.existsSync(expectedPath).should.equal(true); + realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); + done(); + }; + + fs.mkdirSync(expectedBase); + fs.closeSync(fs.openSync(expectedPath, 'w')); + fs.chmodSync(expectedPath, startMode); + + chmodSpy.reset(); + var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should use different modes for files and directories', function(done) { + var inputBase = path.join(__dirname, './fixtures/vinyl'); + var inputPath = path.join(__dirname, './fixtures/vinyl/wow/suchempty'); + var expectedBase = path.join(__dirname, './out-fixtures/wow'); + var expectedDirMode = 0755; + var expectedFileMode = 0655; + + var firstFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + stat: fs.statSync(inputPath) + }); + + var onEnd = function(){ + realMode(fs.lstatSync(expectedBase).mode).should.equal(expectedDirMode); + realMode(buffered[0].stat.mode).should.equal(expectedFileMode); + done(); + }; + + var stream = app.dest('./out-fixtures/', { + cwd: __dirname, + mode: expectedFileMode, + dirMode: expectedDirMode + }); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + + stream.pipe(bufferStream); + stream.write(firstFile); + stream.end(); + }); + + it('should change to the specified base as string', function(done) { + var inputBase = path.join(__dirname, './fixtures/vinyl'); + var inputPath = path.join(__dirname, './fixtures/vinyl/wow/suchempty'); + + var firstFile = new File({ + cwd: __dirname, + path: inputPath, + stat: fs.statSync(inputPath) + }); + + var onEnd = function(){ + buffered[0].base.should.equal(inputBase); + done(); + }; + + var stream = app.dest('./out-fixtures/', { + cwd: __dirname, + base: inputBase + }); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + + stream.pipe(bufferStream); + stream.write(firstFile); + stream.end(); + }); + + it('should change to the specified base as function', function(done) { + var inputBase = path.join(__dirname, './fixtures/vinyl'); + var inputPath = path.join(__dirname, './fixtures/vinyl/wow/suchempty'); + + var firstFile = new File({ + cwd: __dirname, + path: inputPath, + stat: fs.statSync(inputPath) + }); + + var onEnd = function() { + buffered[0].base.should.equal(inputBase); + done(); + }; + + var stream = app.dest('./out-fixtures/', { + cwd: __dirname, + base: function(file){ + should.exist(file); + file.path.should.equal(inputPath); + return inputBase; + } + }); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + + stream.pipe(bufferStream); + stream.write(firstFile); + stream.end(); + }); + + it('should report IO errors', function(done) { + var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, './fixtures/vinyl/'); + var expectedPath = path.join(__dirname, './out-fixtures/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedBase = path.join(__dirname, './out-fixtures'); + var expectedMode = 0722; + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents, + stat: { + mode: expectedMode + } + }); + + fs.mkdirSync(expectedBase); + fs.closeSync(fs.openSync(expectedPath, 'w')); + fs.chmodSync(expectedPath, 0); + + var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + stream.on('error', function(err) { + err.code.should.equal('EACCES'); + done(); + }); + stream.write(expectedFile); + }); + + it('should report stat errors', function(done) { + var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, './fixtures/vinyl/'); + var expectedPath = path.join(__dirname, './out-fixtures/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedBase = path.join(__dirname, './out-fixtures'); + var expectedMode = 0722; + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents, + stat: { + mode: expectedMode + } + }); + + fs.mkdirSync(expectedBase); + fs.closeSync(fs.openSync(expectedPath, 'w')); + + spies.setError(function(mod, fn) { + if (fn === 'stat' && arguments[2] === expectedPath) { + return new Error('stat error'); + } + }); + + var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + stream.on('error', function(err) { + err.message.should.equal('stat error'); + done(); + }); + stream.write(expectedFile); + }); + + it('should report chmod errors', function(done) { + var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, './fixtures/vinyl/'); + var expectedPath = path.join(__dirname, './out-fixtures/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedBase = path.join(__dirname, './out-fixtures'); + var expectedMode = 0722; + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents, + stat: { + mode: expectedMode + } + }); + + fs.mkdirSync(expectedBase); + fs.closeSync(fs.openSync(expectedPath, 'w')); + + spies.setError(function(mod, fn) { + if (fn === 'chmod' && arguments[2] === expectedPath) { + return new Error('chmod error'); + } + }); + + var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + stream.on('error', function(err) { + err.message.should.equal('chmod error'); + done(); + }); + stream.write(expectedFile); + }); + + it('should not chmod a matching file', function(done) { + var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, './fixtures/vinyl/'); + var expectedPath = path.join(__dirname, './out-fixtures/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedBase = path.join(__dirname, './out-fixtures'); + var expectedMode = 0722; + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents, + stat: { + mode: expectedMode + } + }); + + var expectedCount = 0; + spies.setError(function(mod, fn) { + if (fn === 'stat' && arguments[2] === expectedPath) { + expectedCount++; + } + }); + + var onEnd = function(){ + expectedCount.should.equal(1); + assert(!chmodSpy.called); + realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); + done(); + }; + + fs.mkdirSync(expectedBase); + fs.closeSync(fs.openSync(expectedPath, 'w')); + fs.chmodSync(expectedPath, expectedMode); + + statSpy.reset(); + chmodSpy.reset(); + var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should see a file with special chmod (setuid/setgid/sticky) as matching', function(done) { + var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, './fixtures/vinyl/'); + var expectedPath = path.join(__dirname, './out-fixtures/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedBase = path.join(__dirname, './out-fixtures'); + var expectedMode = 03722; + var normalMode = 0722; + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents, + stat: { + mode: normalMode + } + }); + + var expectedCount = 0; + spies.setError(function(mod, fn) { + if (fn === 'stat' && arguments[2] === expectedPath) { + expectedCount++; + } + }); + + var onEnd = function(){ + expectedCount.should.equal(1); + assert(!chmodSpy.called); + done(); + }; + + fs.mkdirSync(expectedBase); + fs.closeSync(fs.openSync(expectedPath, 'w')); + fs.chmodSync(expectedPath, expectedMode); + + statSpy.reset(); + chmodSpy.reset(); + var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should not overwrite files with overwrite option set to false', function(done) { + var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, './fixtures/vinyl/'); + var inputContents = fs.readFileSync(inputPath); + + var expectedPath = path.join(__dirname, './out-fixtures/test.coffee'); + var expectedBase = path.join(__dirname, './out-fixtures'); + var existingContents = 'Lorem Ipsum'; + + var inputFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: inputContents + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + bufEqual(fs.readFileSync(expectedPath), new Buffer(existingContents)).should.equal(true); + done(); + }; + + // Write expected file which should not be overwritten + fs.mkdirSync(expectedBase); + fs.writeFileSync(expectedPath, existingContents); + + var stream = app.dest('./out-fixtures/', {cwd: __dirname, overwrite: false}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(inputFile); + stream.end(); + }); + + it('should overwrite files with overwrite option set to true', function(done) { + var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, './fixtures/vinyl/'); + var inputContents = fs.readFileSync(inputPath); + + var expectedPath = path.join(__dirname, './out-fixtures/test.coffee'); + var expectedBase = path.join(__dirname, './out-fixtures'); + var existingContents = 'Lorem Ipsum'; + + var inputFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: inputContents + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + bufEqual(fs.readFileSync(expectedPath), new Buffer(inputContents)).should.equal(true); + done(); + }; + + // This should be overwritten + fs.mkdirSync(expectedBase); + fs.writeFileSync(expectedPath, existingContents); + + var stream = app.dest('./out-fixtures/', {cwd: __dirname, overwrite: true}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(inputFile); + stream.end(); + }); + + it('should create symlinks when the `symlink` attribute is set on the file', function (done) { + var inputPath = path.join(__dirname, './fixtures/vinyl/test-create-dir-symlink'); + var inputBase = path.join(__dirname, './fixtures/vinyl/'); + var inputRelativeSymlinkPath = 'wow'; + + var expectedPath = path.join(__dirname, './out-fixtures/test-create-dir-symlink'); + + var inputFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: null, //'' + }); + + // `src()` adds this side-effect with `keepSymlinks` option set to false + inputFile.symlink = inputRelativeSymlinkPath; + + var onEnd = function(){ + fs.readlink(buffered[0].path, function () { + buffered[0].symlink.should.equal(inputFile.symlink); + buffered[0].path.should.equal(expectedPath); + done(); + }); + }; + + var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(inputFile); + stream.end(); + }); + + it('should emit finish event', function(done) { + var srcPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); + var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + + stream.once('finish', function() { + done(); + }); + + var file = new File({ + path: srcPath, + cwd: __dirname, + contents: new Buffer("1234567890") + }); + + stream.write(file); + stream.end(); + }); +}); diff --git a/test/fixtures/bom-utf16be.txt b/test/fixtures/bom-utf16be.txt new file mode 100644 index 0000000000000000000000000000000000000000..b9dce78a5d31af4803acd1a0f0dfc14f064a5de1 GIT binary patch literal 244 zcmX|+I}XAy5Jacu26Qf=0Eq@sM*@i=qJaY#5&}waq&RRn3Xa4Tc-{cbe#Sc=zn?E@ zuZymVayru+l}y7P<@I1MK)hWXxZY@{g_hJzYt4Dvs;8dRDlmE2!LB37&GahJPDg5G zyEjIUb8?Hu>i*d9+Q4pAn^J>j{bf4+Qmn{O;+32Wrj#?&PC0#o+uan?UxFJmPf0ua E03tFf0ssI2 literal 0 HcmV?d00001 diff --git a/test/fixtures/bom-utf16le.txt b/test/fixtures/bom-utf16le.txt new file mode 100644 index 0000000000000000000000000000000000000000..07cc600c98675d221bb56d10af38e650538734c9 GIT binary patch literal 244 zcmX|+%?`mp6otRFH?W%}3lbZ#mXJt@4G%E1O4?47PI);CkK`4cxy9!GoVn*`-p>~Y zuH1+?F6tGzrhboj9@;Y@-Y$;1UNd3FTy@Kesopkps%IL4CNFld>nNl)y+UZqNwu)u z8>5qRa*M`l|5*Q8iQQ0|QYFpu%XIuwER-RaS8~oYrJPIl?9@kcyPIPAOJL|a#!5Tj E15l \ No newline at end of file diff --git a/test/fixtures/pages/b.hbs b/test/fixtures/pages/b.hbs new file mode 100644 index 0000000..0ec5d8d --- /dev/null +++ b/test/fixtures/pages/b.hbs @@ -0,0 +1 @@ +

\ No newline at end of file diff --git a/test/fixtures/pages/c.hbs b/test/fixtures/pages/c.hbs new file mode 100644 index 0000000..0ec5d8d --- /dev/null +++ b/test/fixtures/pages/c.hbs @@ -0,0 +1 @@ +

\ No newline at end of file diff --git a/test/fixtures/posts/a.txt b/test/fixtures/posts/a.txt new file mode 100644 index 0000000..bca29ee --- /dev/null +++ b/test/fixtures/posts/a.txt @@ -0,0 +1,4 @@ +--- +title: AAA +--- +This is <%= title %> \ No newline at end of file diff --git a/test/fixtures/posts/b.txt b/test/fixtures/posts/b.txt new file mode 100644 index 0000000..1e128c7 --- /dev/null +++ b/test/fixtures/posts/b.txt @@ -0,0 +1,4 @@ +--- +title: BBB +--- +This is <%= title %> \ No newline at end of file diff --git a/test/fixtures/posts/c.txt b/test/fixtures/posts/c.txt new file mode 100644 index 0000000..32f9187 --- /dev/null +++ b/test/fixtures/posts/c.txt @@ -0,0 +1,4 @@ +--- +title: CCC +--- +This is <%= title %> \ No newline at end of file diff --git a/test/fixtures/templates/a.tmpl b/test/fixtures/templates/a.tmpl new file mode 100644 index 0000000..36f1f1b --- /dev/null +++ b/test/fixtures/templates/a.tmpl @@ -0,0 +1 @@ +<%= name %> \ No newline at end of file diff --git a/test/fixtures/templates/b.tmpl b/test/fixtures/templates/b.tmpl new file mode 100644 index 0000000..36f1f1b --- /dev/null +++ b/test/fixtures/templates/b.tmpl @@ -0,0 +1 @@ +<%= name %> \ No newline at end of file diff --git a/test/fixtures/templates/c.tmpl b/test/fixtures/templates/c.tmpl new file mode 100644 index 0000000..36f1f1b --- /dev/null +++ b/test/fixtures/templates/c.tmpl @@ -0,0 +1 @@ +<%= name %> \ No newline at end of file diff --git a/test/fixtures/test-symlink b/test/fixtures/test-symlink new file mode 120000 index 0000000..3fcfe6c --- /dev/null +++ b/test/fixtures/test-symlink @@ -0,0 +1 @@ +test.coffee \ No newline at end of file diff --git a/test/fixtures/test-symlink-dir/suchempty b/test/fixtures/test-symlink-dir/suchempty new file mode 100644 index 0000000..65bbcaa --- /dev/null +++ b/test/fixtures/test-symlink-dir/suchempty @@ -0,0 +1 @@ +suchempty \ No newline at end of file diff --git a/test/fixtures/test.coffee b/test/fixtures/test.coffee new file mode 100644 index 0000000..6769dd6 --- /dev/null +++ b/test/fixtures/test.coffee @@ -0,0 +1 @@ +Hello world! \ No newline at end of file diff --git a/test/fixtures/vinyl/bom-utf16be.txt b/test/fixtures/vinyl/bom-utf16be.txt new file mode 100644 index 0000000000000000000000000000000000000000..b9dce78a5d31af4803acd1a0f0dfc14f064a5de1 GIT binary patch literal 244 zcmX|+I}XAy5Jacu26Qf=0Eq@sM*@i=qJaY#5&}waq&RRn3Xa4Tc-{cbe#Sc=zn?E@ zuZymVayru+l}y7P<@I1MK)hWXxZY@{g_hJzYt4Dvs;8dRDlmE2!LB37&GahJPDg5G zyEjIUb8?Hu>i*d9+Q4pAn^J>j{bf4+Qmn{O;+32Wrj#?&PC0#o+uan?UxFJmPf0ua E03tFf0ssI2 literal 0 HcmV?d00001 diff --git a/test/fixtures/vinyl/bom-utf16le.txt b/test/fixtures/vinyl/bom-utf16le.txt new file mode 100644 index 0000000000000000000000000000000000000000..07cc600c98675d221bb56d10af38e650538734c9 GIT binary patch literal 244 zcmX|+%?`mp6otRFH?W%}3lbZ#mXJt@4G%E1O4?47PI);CkK`4cxy9!GoVn*`-p>~Y zuH1+?F6tGzrhboj9@;Y@-Y$;1UNd3FTy@Kesopkps%IL4CNFld>nNl)y+UZqNwu)u z8>5qRa*M`l|5*Q8iQQ0|QYFpu%XIuwER-RaS8~oYrJPIl?9@kcyPIPAOJL|a#!5Tj E15l', locals: {a: 'bbb'}}); + app.helper('upper', function (str) { + return str.toUpperCase(); + }); + + var page = app.pages.getView('a.tmpl'); + + app.render(page, function (err, view) { + if (err) return done(err); + + assert.equal(typeof view.contents.toString(), 'string'); + assert.equal(view.contents.toString(), 'BBB'); + done(); + }); + }); + + it.skip('should use a namespaced helper:', function (done) { + app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= foo.upper(a) %>', locals: {a: 'bbb'}}); + + app.helperGroup('foo', { + upper: function (str) { + return str.toUpperCase(); + } + }); + + // console.log(app._.helpers) + + var page = app.pages.getView('a.tmpl'); + app.render(page, function (err, view) { + if (err) return done(err); + + assert.equal(typeof view.contents.toString(), 'string'); + assert.equal(view.contents.toString(), 'BBB'); + done(); + }); + }); +}); + +describe('async helpers', function () { + beforeEach(function () { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.create('page'); + }); + + it('should register an async helper:', function () { + app.asyncHelper('a', function () {}); + app.asyncHelper('b', function () {}); + app._.helpers.async.should.have.property('a'); + app._.helpers.async.should.have.property('b'); + }); + + it('should use an async helper:', function (done) { + app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= lower(a) %>', locals: {a: 'BBB'}}); + app.asyncHelper('lower', function (str, next) { + if (typeof next !== 'function') return str; + next(null, str.toLowerCase()); + }); + + var page = app.pages.getView('a.tmpl'); + app.render(page, function (err, view) { + if (err) return done(err); + assert.equal(typeof view.content, 'string'); + assert.equal(view.content, 'bbb'); + done(); + }); + }); +}); + +describe('built-in helpers:', function () { + describe('automatically generated helpers for default view types:', function () { + beforeEach(function () { + app = new App({rethrow: false}); + app.engine('md', require('engine-base')); + app.engine('tmpl', require('engine-base')); + app.create('partials', { viewType: 'partial' }); + app.create('pages'); + + // parse front matter + app.onLoad(/./, function (view, next) { + matter.parse(view, next); + }); + }); + + it('should expose front matter to the `partial` helper.', function (done) { + app.partial('a.md', {content: '---\nname: "AAA"\n---\n<%= name %>', locals: {name: 'BBB'}}); + app.page('b.md', {path: 'b.md', content: 'foo <%= partial("a.md") %> bar'}); + + app.render('b.md', function (err, res) { + if (err) return done(err); + res.content.should.equal('foo AAA bar'); + done(); + }); + }); + + it('should use helper locals.', function (done) { + app.partial('abc.md', {content: '---\nname: "AAA"\n---\n<%= name %>', locals: {name: 'BBB'}}); + app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md", { name: "CCC" }) %> bar'}); + + app.render('xyz.md', {name: 'DDD'}, function (err, res) { + if (err) return done(err); + res.content.should.equal('foo CCC bar'); + done(); + }); + }); + + it('should use front matter data.', function (done) { + app.partial('abc.md', {content: '---\nname: "AAA"\n---\n<%= name %>'}); + app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}); + + app.render('xyz.md', {name: 'DDD'}, function (err, res) { + if (err) return done(err); + res.content.should.equal('foo AAA bar'); + done(); + }); + }); + + it('should use partial locals:', function (done) { + app.partial('abc.md', {content: '<%= name %>', locals: {name: 'EEE'}}); + + app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}) + .render({name: 'DDD'}, function (err, res) { + if (err) return done(err); + res.content.should.equal('foo EEE bar'); + done(); + }); + }); + + it.skip('should use locals from the `view.render` method:', function (done) { + app.partial('abc.md', {content: '<%= name %>', locals: {name: 'EEE'}}); + + app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}) + .render({name: 'DDD'}, function (err, res) { + if (err) return done(err); + + res.content.should.equal('foo EEE bar'); + done(); + }); + }); + + it('should use locals from the `app.render` method:', function (done) { + app.partial('abc.md', {content: '<%= name %>'}); + app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}); + + app.render('xyz.md', {name: 'DDD'}, function (err, res) { + if (err) return done(err); + res.content.should.equal('foo DDD bar'); + done(); + }); + }); + + it('should return an empty string when the partial is missing.', function (done) { + app.partial('abc.md', {content: '---\nname: "AAA"\n---\n<%= name %>', locals: {name: 'BBB'}}); + app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("def.md", { name: "CCC" }) %> bar'}); + app.render('xyz.md', {name: 'DDD'}, function (err, res) { + if (err) return done(err); + res.content.should.equal('foo bar'); + done(); + }); + }); + }); + + describe('helper context:', function () { + beforeEach(function () { + app = new App({rethrow: false}); + app.engine(['tmpl', 'md'], require('engine-base')); + app.create('partial', { viewType: 'partial' }); + app.create('page'); + + // parse front matter + app.onLoad(/./, function (view, next) { + matter.parse(view, next); + }); + }); + + it('should prefer helper locals over view locals.', function (done) { + app.partial('abc.md', {content: '<%= name %>', name: 'BBB'}); + app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md", { name: "CCC" }) %> bar'}); + + app.render('xyz.md', {name: 'DDD'}, function (err, res) { + if (err) return done(err); + res.content.should.equal('foo CCC bar'); + done(); + }); + }); + + it('should give preference to view locals over render locals.', function (done) { + app.partial('abc.md', {content: '<%= name %>', locals: {name: 'BBB'}}); + app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}); + + var page = app.pages.getView('xyz.md'); + + app.render(page, {name: 'DDD'}, function (err, res) { + if (err) return done(err); + res.content.should.equal('foo BBB bar'); + done(); + }); + }); + + it('should use render locals when other locals are not defined.', function (done) { + app.partial('abc.md', {content: '<%= name %>'}); + app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}); + + app.render('xyz.md', {name: 'DDD'}, function (err, res) { + if (err) return done(err); + res.content.should.equal('foo DDD bar'); + done(); + }); + }); + }); + + describe('user-defined engines:', function () { + beforeEach(function () { + app = new App({rethrow: false}); + app.create('partial', { viewType: 'partial' }); + app.create('page'); + + // parse front matter + app.onLoad(/./, function (view, next) { + matter.parse(view, next); + }); + }); + + it('should use the `partial` helper with handlebars.', function (done) { + app.engine(['tmpl', 'md'], require('engine-base')); + app.engine('hbs', handlebars); + + app.partial('title.hbs', {content: '{{name}}', locals: {name: 'BBB'}}); + app.page('a.hbs', {path: 'a.hbs', content: 'foo {{{partial "title.hbs" this}}} bar'}); + + app.render('a.hbs', {name: 'Halle Nicole'}, function (err, res) { + if (err) return done(err); + res.content.should.equal('foo Halle Nicole bar'); + done(); + }); + }); + + it('should use the `partial` helper with any engine.', function (done) { + app.engine('hbs', handlebars); + app.engine('md', handlebars); + app.engine('swig', swig); + app.engine('tmpl', require('engine-base')); + + app.partial('a.hbs', {content: '---\nname: "AAA"\n---\n{{name}}', locals: {name: 'BBB'}}); + app.page('a.hbs', {path: 'a.hbs', content: '{{author}}', locals: {author: 'Halle Nicole'}}); + app.page('b.tmpl', {path: 'b.tmpl', content: '<%= author %>', locals: {author: 'Halle Nicole'}}); + app.page('d.swig', {path: 'd.swig', content: '{{author}}', locals: {author: 'Halle Nicole'}}); + app.page('e.swig', {path: 'e.swig', content: '{{author}}', locals: {author: 'Halle Nicole'}}); + app.page('f.hbs', {content: '{{author}}', locals: {author: 'Halle Nicole'}}); + app.page('g.md', {content: '---\nauthor: Brian Woodward\n---\n{{author}}', locals: {author: 'Halle Nicole'}}); + app.page('with-partial.hbs', {path: 'with-partial.hbs', content: '{{{partial "a.hbs" custom.locals}}}'}); + + var locals = {custom: {locals: {name: 'Halle Nicole' }}}; + app.render('a.hbs', locals, function (err, res) { + if (err) return console.log(err); + res.content.should.equal('Halle Nicole'); + }); + + app.render('with-partial.hbs', locals, function (err, res) { + if (err) return console.log(err); + res.content.should.equal('Halle Nicole'); + }); + + var page = app.pages.getView('g.md'); + locals.author = page.data.author || locals.author; + page.render(locals, function (err, res) { + if (err) return done(err); + res.content.should.equal('Brian Woodward'); + done(null, res.content); + }); + }); + }); +}); + +describe('helpers integration', function () { + beforeEach(function () { + app = new App(); + app.create('pages'); + app.engine('md', require('engine-base')); + }); + + describe('.helpers()', function () { + it('should add helpers and use them in templates.', function (done) { + app.helpers({ + upper: function (str) { + return str.toUpperCase(); + } + }); + + app.page('doc.md', {content: 'a <%= upper(name) %> b'}) + .render({name: 'Halle'}, function (err, res) { + if (err) return done(err); + assert(res.content === 'a HALLE b'); + done(); + }); + }); + }); + + describe('helper options:', function () { + it('should expose `this.options` to helpers:', function (done) { + app.helper('cwd', function (fp) { + return path.join(this.options.cwd, fp); + }); + + app.option('one', 'two'); + app.option('cwd', 'foo/bar'); + app.page('doc.md', {content: 'a <%= cwd("baz") %> b'}) + .render(function (err, res) { + if (err) return done(err); + assert(res.content === 'a foo/bar/baz b'); + done(); + }); + }); + + it('should pass helper options to helpers:', function (done) { + app.helper('cwd', function (fp) { + return path.join(this.options.cwd, fp); + }); + + app.option('helper.cwd', 'foo/bar'); + app.option('helper.whatever', '...'); + + app.page('doc.md', {content: 'a <%= cwd("baz") %> b'}) + .render(function (err, res) { + if (err) return done(err); + assert(res.content === 'a foo/bar/baz b'); + done(); + }); + }); + }); + + describe('options.helpers', function () { + it('should register helpers passed on the options:', function (done) { + app.option({ + helpers: { + upper: function (str) { + return str.toUpperCase(); + }, + foo: function (str) { + return 'foo' + str; + } + } + }); + + app.page('doc.md', {content: 'a <%= upper(name) %> <%= foo("bar") %> b'}) + .render({name: 'Halle'}, function (err, res) { + if (err) return done(err); + assert(res.content === 'a HALLE foobar b'); + done(); + }); + }); + }); + + describe('options.helpers', function () { + it('should add helpers and use them in templates.', function (done) { + app.options.helpers = { + upper: function (str) { + return str.toUpperCase(); + }, + foo: function (str) { + return 'foo' + str; + } + }; + + app.page('doc.md', {content: 'a <%= upper(name) %> b'}) + .render({name: 'Halle'}, function (err, res) { + if (err) return done(err); + assert(res.content === 'a HALLE b'); + done(); + }); + }); + }); +}); + +describe('collection helpers', function () { + beforeEach(function () { + app = new App(); + app.create('posts'); + app.create('pages', {engine: 'hbs'}); + app.create('partials', {viewType: 'partial', engine: 'hbs'}); + app.create('snippet', {viewType: 'partial'}); + app.engine('hbs', require('engine-handlebars')); + app.helper('log', function (ctx) { + console.log(ctx); + }); + }); + + describe('plural', function () { + it('should get the given collection', function (done) { + app.post('a.hbs', {content: 'foo'}); + app.post('b.hbs', {content: 'bar'}); + app.post('c.hbs', {content: 'baz'}); + + app.partial('list.hbs', { + content: '{{#posts}}{{#each items}}{{content}}{{/each}}{{/posts}}' + }); + + app.page('index.hbs', { + content: '{{> list.hbs }}' + }) + .render(function (err, res) { + if (err) return done(err); + assert(res.content === 'foobarbaz'); + done(); + }); + }); + }); + + describe('single', function () { + it('should get a view from an unspecified collection', function (done) { + app.post('a.hbs', {content: 'post-a'}); + app.post('b.hbs', {content: 'post-b'}); + + var one = app.page('one', {content: '{{view "a.hbs"}}'}) + .compile() + .fn(); + + var two = app.page('two', {content: '{{view "b.hbs"}}'}) + .compile() + .fn(); + + assert(one === 'post-a'); + assert(two === 'post-b'); + done(); + }); + + it('should return an empty string if not found', function (done) { + var one = app.page('one', {content: '{{view "foo.hbs"}}'}) + .compile() + .fn(); + assert(one === ''); + done(); + }); + + it('should handle engine errors', function (done) { + app.page('one', {content: '{{posts "foo.hbs"}}'}) + .render(function (err) { + assert(err); + assert(typeof err === 'object'); + assert(typeof err.message === 'string'); + assert(/is not a function/.test(err.message)); + done(); + }); + }); + + it('should handle engine errors', function (done) { + app.engine('tmpl', require('engine-base')); + app.create('foo', {engine: 'tmpl'}); + app.create('bar', {engine: 'tmpl'}); + + app.create('foo', {viewType: 'partial'}); + app.foo('foo.tmpl', {path: 'foo.tmpl', content: '<%= blah.bar %>'}); + app.bar('one.tmpl', {content: '<%= foo("foo.tmpl") %>'}) + .render(function (err) { + assert(err); + assert(typeof err === 'object'); + assert(/blah is not defined/.test(err.message)); + done(); + }); + }); + + it('should work with non-handlebars engine', function (done) { + app.engine('tmpl', require('engine-base')); + app.create('foo', {engine: 'tmpl'}); + app.create('bar', {engine: 'tmpl'}); + + app.foo('a.tmpl', {content: 'foo-a'}); + app.foo('b.tmpl', {content: 'foo-b'}); + + var one = app.bar('one', {content: '<%= view("a.tmpl") %>'}) + .compile() + .fn(); + + var two = app.bar('two', {content: '<%= view("b.tmpl") %>'}) + .compile() + .fn(); + + assert(one === 'foo-a'); + assert(two === 'foo-b'); + done(); + }); + + it('should get a specific view from the given collection', function (done) { + app.post('a.hbs', {content: 'post-a'}); + app.post('b.hbs', {content: 'post-b'}); + app.post('c.hbs', {content: 'post-c'}); + app.page('a.hbs', {content: 'page-a'}); + app.page('b.hbs', {content: 'page-b'}); + app.page('c.hbs', {content: 'page-c'}); + + var one = app.page('one', {content: '{{view "a.hbs" "posts"}}'}) + .compile() + .fn(); + + var two = app.page('two', {content: '{{view "b.hbs" "pages"}}'}) + .compile() + .fn(); + + assert(one === 'post-a'); + assert(two === 'page-b'); + done(); + }); + }); +}); diff --git a/test/item.js b/test/item.js new file mode 100644 index 0000000..2c3becd --- /dev/null +++ b/test/item.js @@ -0,0 +1,1066 @@ +require('mocha'); +var should = require('should'); +var fs = require('fs'); +var path = require('path'); +var util = require('util'); +var assert = require('assert'); +var es = require('event-stream'); +var Stream = require('stream'); +var support = require('./support'); +var App = support.resolve(); +var Item = App.Item; +var item; + +describe('Item', function () { + describe('instance', function () { + it('should create an instance of Item:', function () { + item = new Item(); + assert(item instanceof Item); + }); + + it('should instantiate without new:', function () { + item = Item(); + assert(item instanceof Item); + }); + + it('inspect should not double name `Stream` when ctor is `Stream`', function(done) { + var val = new Stream(); + var item = new Item({contents: val}); + done(); + }); + }); + + describe('static methods', function () { + it('should expose `extend`:', function () { + assert(typeof Item.extend === 'function'); + }); + }); + + describe('prototype methods', function () { + beforeEach(function () { + item = new Item(); + }); + + it('should expose `set`:', function () { + assert(typeof item.set === 'function'); + }); + it('should expose `get`:', function () { + assert(typeof item.get === 'function'); + }); + it('should expose `del`:', function () { + assert(typeof item.del === 'function'); + }); + it('should expose `define`:', function () { + assert(typeof item.define === 'function'); + }); + it('should expose `visit`:', function () { + assert(typeof item.visit === 'function'); + }); + }); + + describe('properties', function () { + it('should expose an `options` property', function () { + item = new Item({}); + assert.deepEqual(item.options, {}); + assert(item.hasOwnProperty('options')); + }); + + it('should add `options` when passed on the constructor', function () { + item = new Item({options: {foo: 'bar'}}); + assert(item.options.foo === 'bar'); + }); + + it('should expose a `data` property', function () { + item = new Item({app: {}}); + assert.deepEqual(item.data, {}); + assert(item.hasOwnProperty('data')); + }); + + it('should add `data` when passed on the constructor', function () { + item = new Item({data: {foo: 'bar'}}); + assert(item.data.foo === 'bar'); + }); + + it('should add `locals` when passed on the constructor', function () { + item = new Item({locals: {foo: 'bar'}}); + assert(item.locals.foo === 'bar'); + }); + }); + + describe('set', function () { + it('should set properties on the object', function () { + item = new Item(); + item.set('foo', 'bar'); + assert.equal(item.foo, 'bar'); + }); + }); + + describe('get', function () { + it('should get properties from the object', function () { + item = new Item(); + item.set('foo', 'bar'); + assert.equal(item.get('foo'), 'bar'); + }); + }); + + describe('cwd', function () { + it('should get properties from the object', function () { + item = new Item({cwd: 'test/fixtures'}); + assert(item.cwd === 'test/fixtures'); + }); + }); + + describe('clone', function () { + it('should clone the item:', function () { + item = new Item({content: 'foo'}); + item.set({path: 'foo/bar'}); + item.set('options.one', 'two'); + var clone = item.clone(); + assert(clone.contents); + clone.set('baz', 'quux'); + clone.set('options.three', 'four'); + assert.equal(clone.get('foo'), item.get('foo')); + assert(clone.get('baz') === 'quux'); + assert(!item.get('baz')); + // not deep cloned + assert(clone.get('options.three') === 'four'); + assert(item.get('options.three') === 'four'); + }); + + it('should deep clone the entire object', function () { + item = new Item({content: 'foo'}); + item.set({path: 'foo/bar'}); + item.set('options.one', 'two'); + var clone = item.clone({deep: true}); + clone.set('options.three', 'four'); + assert(item.get('options.one') === 'two'); + assert(clone.get('options.one') === 'two'); + assert(clone.get('options.three') === 'four'); + assert(!item.get('options.three')); + }); + }); + + describe('visit', function () { + it('should visit all properties on an object and call the specified method', function () { + item = new Item(); + var obj = { + foo: 'bar', + bar: 'baz', + baz: 'bang' + }; + item.visit('set', obj); + assert.equal(item.get('foo'), 'bar'); + assert.equal(item.get('bar'), 'baz'); + assert.equal(item.get('baz'), 'bang'); + }); + + it('should visit all properties on all objects in an array and call the specified method', function () { + item = new Item(); + var arr = [{foo: 'bar', bar: 'baz', baz: 'bang'}]; + item.visit('set', arr); + assert.equal(item.get('foo'), 'bar'); + assert.equal(item.get('bar'), 'baz'); + assert.equal(item.get('baz'), 'bang'); + }); + }); +}); + +/** + * The following unit tests are from Vinyl + * Since we inherit vinyl in Item, we need + * to ensure that these still pass. + */ + +describe('Item', function() { + describe('isVinyl()', function() { + it('should return true on a vinyl object', function(done) { + var item = new Item(); + assert(Item.isVinyl(item) === true); + done(); + }); + it('should return false on a normal object', function(done) { + assert(Item.isVinyl({}) === false); + done(); + }); + it('should return false on a null object', function(done) { + assert(Item.isVinyl({}) === false); + done(); + }); + }); + + describe('constructor()', function() { + it('should default cwd to process.cwd', function(done) { + var item = new Item(); + item.cwd.should.equal(process.cwd()); + done(); + }); + + it('should default base to cwd', function(done) { + var cwd = '/'; + var item = new Item({cwd: cwd}); + item.base.should.equal(cwd); + done(); + }); + + it('should default base to cwd even when none is given', function(done) { + var item = new Item(); + item.base.should.equal(process.cwd()); + done(); + }); + + it('should default path to null', function(done) { + var item = new Item(); + should.not.exist(item.path); + done(); + }); + + it('should default history to []', function(done) { + var item = new Item(); + item.history.should.eql([]); + done(); + }); + + it('should default stat to null', function(done) { + var item = new Item(); + should.not.exist(item.stat); + done(); + }); + + it('should default contents to null', function(done) { + var item = new Item(); + should.not.exist(item.contents); + done(); + }); + + it('should set base to given value', function(done) { + var val = '/'; + var item = new Item({base: val}); + item.base.should.equal(val); + done(); + }); + + it('should set cwd to given value', function(done) { + var val = '/'; + var item = new Item({cwd: val}); + item.cwd.should.equal(val); + done(); + }); + + it('should set path to given value', function(done) { + var val = '/test.coffee'; + var item = new Item({path: val}); + item.path.should.equal(val); + item.history.should.eql([val]); + done(); + }); + + it('should set history to given value', function(done) { + var val = '/test.coffee'; + var item = new Item({history: [val]}); + item.path.should.equal(val); + item.history.should.eql([val]); + done(); + }); + + it('should set stat to given value', function(done) { + var val = {}; + var item = new Item({stat: val}); + item.stat.should.equal(val); + done(); + }); + + it('should set contents to given value', function(done) { + var val = new Buffer('test'); + var item = new Item({contents: val}); + item.contents.should.equal(val); + done(); + }); + }); + + describe('isBuffer()', function() { + it('should return true when the contents are a Buffer', function(done) { + var val = new Buffer('test'); + var item = new Item({contents: val}); + item.isBuffer().should.equal(true); + done(); + }); + + it('should return false when the contents are a Stream', function(done) { + var val = new Stream(); + var item = new Item({contents: val}); + item.isBuffer().should.equal(false); + done(); + }); + + it('should return false when the contents are a null', function(done) { + var item = new Item({contents: null}); + item.isBuffer().should.equal(false); + done(); + }); + }); + + describe('isStream()', function() { + it('should return false when the contents are a Buffer', function(done) { + var val = new Buffer('test'); + var item = new Item({contents: val}); + item.isStream().should.equal(false); + done(); + }); + + it('should return true when the contents are a Stream', function(done) { + var val = new Stream(); + var item = new Item({contents: val}); + item.isStream().should.equal(true); + done(); + }); + + it('should return false when the contents are a null', function(done) { + var item = new Item({contents: null}); + item.isStream().should.equal(false); + done(); + }); + }); + + describe('isNull()', function() { + it('should return false when the contents are a Buffer', function(done) { + var val = new Buffer('test'); + var item = new Item({contents: val}); + item.isNull().should.equal(false); + done(); + }); + + it('should return false when the contents are a Stream', function(done) { + var val = new Stream(); + var item = new Item({contents: val}); + item.isNull().should.equal(false); + done(); + }); + + it('should return true when the contents are a null', function(done) { + var item = new Item({contents: null}); + item.isNull().should.equal(true); + done(); + }); + }); + + describe('isDirectory()', function() { + var fakeStat = { + isDirectory: function() { + return true; + } + }; + + it('should return false when the contents are a Buffer', function(done) { + var val = new Buffer('test'); + var item = new Item({contents: val, stat: fakeStat}); + item.isDirectory().should.equal(false); + done(); + }); + + it('should return false when the contents are a Stream', function(done) { + var val = new Stream(); + var item = new Item({contents: val, stat: fakeStat}); + item.isDirectory().should.equal(false); + done(); + }); + + it('should return true when the contents are a null', function(done) { + var item = new Item({contents: null, stat: fakeStat}); + item.isDirectory().should.equal(true); + done(); + }); + }); + + describe('clone()', function() { + it('should copy all attributes over with Buffer', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: new Buffer('test') + }; + var item = new Item(options); + var item2 = item.clone(); + + item2.should.not.equal(item, 'refs should be different'); + item2.cwd.should.equal(item.cwd); + item2.base.should.equal(item.base); + item2.path.should.equal(item.path); + item2.contents.should.not.equal(item.contents, 'buffer ref should be different'); + item2.contents.toString('utf8').should.equal(item.contents.toString('utf8')); + done(); + }); + + it('should copy buffer\'s reference with option contents: false', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.js', + contents: new Buffer('test') + }; + + var item = new Item(options); + + var copy1 = item.clone({ contents: false }); + copy1.contents.should.equal(item.contents); + + var copy2 = item.clone({}); + copy2.contents.should.not.equal(item.contents); + + var copy3 = item.clone({ contents: 'any string' }); + copy3.contents.should.not.equal(item.contents); + + done(); + }); + + it('should copy all attributes over with Stream', function(done) { + var contents = new Stream.PassThrough(); + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: contents + }; + var item = new Item(options); + var item2 = item.clone(); + + contents.write(new Buffer('wa')); + + process.nextTick(function() { + contents.write(new Buffer('dup')); + contents.end(); + }); + + item2.should.not.equal(item, 'refs should be different'); + item2.cwd.should.equal(item.cwd); + item2.base.should.equal(item.base); + item2.path.should.equal(item.path); + item2.contents.should.not.equal(item.contents, 'stream ref should not be the same'); + item.contents.pipe(es.wait(function(err, data) { + item2.contents.pipe(es.wait(function(err, data2) { + data2.should.not.equal(data, 'stream contents ref should not be the same'); + data2.should.eql(data, 'stream contents should be the same'); + })); + })); + done(); + }); + + it('should copy all attributes over with null', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: null + }; + var item = new Item(options); + var item2 = item.clone(); + + item2.should.not.equal(item, 'refs should be different'); + item2.cwd.should.equal(item.cwd); + item2.base.should.equal(item.base); + item2.path.should.equal(item.path); + should.not.exist(item2.contents); + done(); + }); + + it('should properly clone the `stat` property', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.js', + contents: new Buffer('test'), + stat: fs.statSync(__filename) + }; + + var item = new Item(options); + var copy = item.clone(); + + assert(copy.stat.isFile()); + assert(!copy.stat.isDirectory()); + assert(item.stat instanceof fs.Stats); + assert(copy.stat instanceof fs.Stats); + done(); + }); + + it('should properly clone the `history` property', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.js', + contents: new Buffer('test'), + stat: fs.statSync(__filename) + }; + + var item = new Item(options); + var copy = item.clone(); + + copy.history[0].should.equal(options.path); + copy.path = 'lol'; + item.path.should.not.equal(copy.path); + done(); + }); + + it('should copy custom properties', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: null + }; + + var item = new Item(options); + item.custom = { a: 'custom property' }; + var item2 = item.clone(); + + item2.should.not.equal(item, 'refs should be different'); + item2.cwd.should.equal(item.cwd); + item2.base.should.equal(item.base); + item2.path.should.equal(item.path); + item2.custom.should.equal(item.custom); + item2.custom.a.should.equal(item.custom.a); + + done(); + }); + + it('should copy history', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: null + }; + + var item = new Item(options); + item.path = '/test/test.js'; + item.path = '/test/test-938di2s.js'; + var item2 = item.clone(); + + item2.history.should.eql([ + '/test/test.coffee', + '/test/test.js', + '/test/test-938di2s.js' + ]); + item2.history.should.not.equal([ + '/test/test.coffee', + '/test/test.js', + '/test/test-938di2s.js' + ]); + item2.path.should.eql('/test/test-938di2s.js'); + + done(); + }); + + it('should copy all attributes deeply', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: null + }; + + var item = new Item(options); + item.custom = { a: 'custom property' }; + + var item2 = item.clone(true); + item2.custom.should.eql(item.custom); + item2.custom.should.not.equal(item.custom); + item2.custom.a.should.equal(item.custom.a); + + var item3 = item.clone({ deep: true }); + item3.custom.should.eql(item.custom); + item3.custom.should.not.equal(item.custom); + item3.custom.a.should.equal(item.custom.a); + + var item4 = item.clone(false); + item4.custom.should.eql(item.custom); + item4.custom.should.equal(item.custom); + item4.custom.a.should.equal(item.custom.a); + + var item5 = item.clone({ deep: false }); + item5.custom.should.eql(item.custom); + item5.custom.should.equal(item.custom); + item5.custom.a.should.equal(item.custom.a); + + done(); + }); + }); + + describe('pipe()', function() { + it('should write to stream with Buffer', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: new Buffer('test') + }; + var item = new Item(options); + var stream = new Stream.PassThrough(); + stream.on('data', function(chunk) { + should.exist(chunk); + (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); + chunk.toString('utf8').should.equal(options.contents.toString('utf8')); + }); + stream.on('end', function() { + done(); + }); + var ret = item.pipe(stream); + ret.should.equal(stream, 'should return the stream'); + }); + + it('should pipe to stream with Stream', function(done) { + var testChunk = new Buffer('test'); + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: new Stream.PassThrough() + }; + var item = new Item(options); + var stream = new Stream.PassThrough(); + stream.on('data', function(chunk) { + should.exist(chunk); + (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); + chunk.toString('utf8').should.equal(testChunk.toString('utf8')); + done(); + }); + var ret = item.pipe(stream); + ret.should.equal(stream, 'should return the stream'); + + item.contents.write(testChunk); + }); + + it('should do nothing with null', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: null + }; + var item = new Item(options); + var stream = new Stream.PassThrough(); + stream.on('data', function() { + throw new Error('should not write'); + }); + stream.on('end', function() { + done(); + }); + var ret = item.pipe(stream); + ret.should.equal(stream, 'should return the stream'); + }); + + it('should write to stream with Buffer', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: new Buffer('test') + }; + var item = new Item(options); + var stream = new Stream.PassThrough(); + stream.on('data', function(chunk) { + should.exist(chunk); + (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); + chunk.toString('utf8').should.equal(options.contents.toString('utf8')); + done(); + }); + stream.on('end', function() { + throw new Error('should not end'); + }); + var ret = item.pipe(stream, {end: false}); + ret.should.equal(stream, 'should return the stream'); + }); + + it('should pipe to stream with Stream', function(done) { + var testChunk = new Buffer('test'); + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: new Stream.PassThrough() + }; + var item = new Item(options); + var stream = new Stream.PassThrough(); + stream.on('data', function(chunk) { + should.exist(chunk); + (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); + chunk.toString('utf8').should.equal(testChunk.toString('utf8')); + done(); + }); + stream.on('end', function() { + throw new Error('should not end'); + }); + var ret = item.pipe(stream, {end: false}); + ret.should.equal(stream, 'should return the stream'); + + item.contents.write(testChunk); + }); + + it('should do nothing with null', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: null + }; + var item = new Item(options); + var stream = new Stream.PassThrough(); + stream.on('data', function() { + throw new Error('should not write'); + }); + stream.on('end', function() { + throw new Error('should not end'); + }); + var ret = item.pipe(stream, {end: false}); + ret.should.equal(stream, 'should return the stream'); + process.nextTick(done); + }); + }); + + describe('inspect()', function() { + it('should return correct format when no contents and no path', function(done) { + var item = new Item(); + item.inspect().should.equal(''); + done(); + }); + + it('should return correct format when Buffer and no path', function(done) { + var val = new Buffer('test'); + var item = new Item({ + contents: val + }); + item.inspect().should.equal('>'); + done(); + }); + + it('should return correct format when Buffer and relative path', function(done) { + var val = new Buffer('test'); + var item = new Item({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: val + }); + item.inspect().should.equal('>'); + done(); + }); + + it('should return correct format when Buffer and only path and no base', function(done) { + var val = new Buffer('test'); + var item = new Item({ + cwd: '/', + path: '/test/test.coffee', + contents: val + }); + delete item.base; + item.inspect().should.equal('>'); + done(); + }); + + it('should return correct format when Stream and relative path', function(done) { + var item = new Item({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: new Stream.PassThrough() + }); + item.inspect().should.equal('>'); + done(); + }); + + it('should return correct format when null and relative path', function(done) { + var item = new Item({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: null + }); + item.inspect().should.equal(''); + done(); + }); + }); + + describe('contents get/set', function() { + it('should work with Buffer', function(done) { + var val = new Buffer('test'); + var item = new Item(); + item.contents = val; + item.contents.should.equal(val); + done(); + }); + + it('should work with Stream', function(done) { + var val = new Stream.PassThrough(); + var item = new Item(); + item.contents = val; + item.contents.should.equal(val); + done(); + }); + + it('should work with null', function(done) { + var val = null; + var item = new Item(); + item.contents = val; + (item.contents === null).should.equal(true); + done(); + }); + + it('should work with string', function(done) { + var val = 'test'; + var item = new Item(); + item.contents = val; + item.contents.should.deepEqual(new Buffer(val)); + done(); + }); + }); + + describe('relative get/set', function() { + it('should error on set', function(done) { + var item = new Item(); + try { + item.relative = 'test'; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should error on get when no base', function(done) { + var a; + var item = new Item(); + delete item.base; + try { + a = item.relative; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should error on get when no path', function(done) { + var a; + var item = new Item(); + try { + a = item.relative; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should return a relative path from base', function(done) { + var item = new Item({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee' + }); + item.relative.should.equal('test.coffee'); + done(); + }); + + it('should return a relative path from cwd', function(done) { + var item = new Item({ + cwd: '/', + path: '/test/test.coffee' + }); + item.relative.should.equal(path.join('test','test.coffee')); + done(); + }); + }); + + describe('dirname get/set', function() { + it('should error on get when no path', function(done) { + var a; + var item = new Item(); + try { + a = item.dirname; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should return the dirname of the path', function(done) { + var item = new Item({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee' + }); + item.dirname.should.equal('/test'); + done(); + }); + + it('should error on set when no path', function(done) { + var item = new Item(); + try { + item.dirname = '/test'; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should set the dirname of the path', function(done) { + var item = new Item({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee' + }); + item.dirname = '/test/foo'; + item.path.should.equal('/test/foo/test.coffee'); + done(); + }); + }); + + describe('basename get/set', function() { + it('should error on get when no path', function(done) { + var a; + var item = new Item(); + try { + a = item.basename; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should return the basename of the path', function(done) { + var item = new Item({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee' + }); + item.basename.should.equal('test.coffee'); + done(); + }); + + it('should error on set when no path', function(done) { + var item = new Item(); + try { + item.basename = 'test.coffee'; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should set the basename of the path', function(done) { + var item = new Item({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee' + }); + item.basename = 'foo.png'; + item.path.should.equal('/test/foo.png'); + done(); + }); + }); + + describe('extname get/set', function() { + it('should error on get when no path', function(done) { + var a; + var item = new Item(); + try { + a = item.extname; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should return the extname of the path', function(done) { + var item = new Item({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee' + }); + item.extname.should.equal('.coffee'); + done(); + }); + + it('should error on set when no path', function(done) { + var item = new Item(); + try { + item.extname = '.coffee'; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should set the extname of the path', function(done) { + var item = new Item({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee' + }); + item.extname = '.png'; + item.path.should.equal('/test/test.png'); + done(); + }); + }); + + describe('path get/set', function() { + + it('should record history when instantiation', function() { + var item = new Item({ + cwd: '/', + path: '/test/test.coffee' + }); + + item.path.should.eql('/test/test.coffee'); + item.history.should.eql(['/test/test.coffee']); + }); + + it('should record history when path change', function() { + var item = new Item({ + cwd: '/', + path: '/test/test.coffee' + }); + + item.path = '/test/test.js'; + item.path.should.eql('/test/test.js'); + item.history.should.eql(['/test/test.coffee', '/test/test.js']); + + item.path = '/test/test.coffee'; + item.path.should.eql('/test/test.coffee'); + item.history.should.eql(['/test/test.coffee', '/test/test.js', '/test/test.coffee']); + }); + + it('should not record history when set the same path', function() { + var item = new Item({ + cwd: '/', + path: '/test/test.coffee' + }); + + item.path = '/test/test.coffee'; + item.path = '/test/test.coffee'; + item.path.should.eql('/test/test.coffee'); + item.history.should.eql(['/test/test.coffee']); + + // ignore when set empty string + item.path = ''; + item.path.should.eql('/test/test.coffee'); + item.history.should.eql(['/test/test.coffee']); + }); + + it('should throw when set path null in constructor', function() { + (function() { + new Item({ + cwd: '/', + path: null + }); + }).should.throw('path should be string'); + }); + + it('should throw when set path null', function() { + var item = new Item({ + cwd: '/', + path: 'foo' + }); + + (function() { + item.path = null; + }).should.throw('path should be string'); + }); + }); +}); diff --git a/test/layouts.js b/test/layouts.js new file mode 100644 index 0000000..f079d32 --- /dev/null +++ b/test/layouts.js @@ -0,0 +1,127 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('layouts', function () { + beforeEach(function () { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.create('layout', { viewType: 'layout' }); + app.create('page'); + }); + + it('should apply a layout to a view:', function (done) { + app.layout('base', {path: 'base.tmpl', content: 'a {% body %} c'}); + app.pages('a.tmpl', {path: 'a.tmpl', content: 'b', layout: 'base'}); + var page = app.pages.getView('a.tmpl'); + + app.render(page, function (err, view) { + if (err) return done(err); + assert.equal(typeof view.content, 'string'); + assert.equal(view.content, 'a b c'); + done(); + }); + }); + + it('should not apply a layout when `layoutApplied` is set:', function (done) { + app.layout('base', {path: 'base.tmpl', content: 'a {% body %} c'}); + app.pages('a.tmpl', {path: 'a.tmpl', content: 'b', layout: 'base'}); + var page = app.pages.getView('a.tmpl'); + page.option('layoutApplied', true); + + app.render(page, function (err, view) { + if (err) return done(err); + assert.equal(typeof view.content, 'string'); + assert.equal(view.content, 'b'); + done(); + }); + }); + + it('should not apply a layout to itself:', function (done) { + app.layout('base', {path: 'base.tmpl', content: 'a {% body %} c', layout: 'base'}); + app.pages('a.tmpl', {path: 'a.tmpl', content: 'b', layout: 'base'}); + var page = app.pages.getView('a.tmpl'); + + app.render(page, function (err, view) { + if (err) return done(err); + assert.equal(typeof view.content, 'string'); + assert.equal(view.content, 'a b c'); + done(); + }); + }); + + it('should apply nested layouts to a view:', function (done) { + app.layout('a', {path: 'a.tmpl', content: 'a {% body %} a', layout: 'b'}); + app.layout('b', {path: 'b.tmpl', content: 'b {% body %} b', layout: 'c'}); + app.layout('c', {path: 'c.tmpl', content: 'c {% body %} c', layout: 'base'}); + app.layout('base', {path: 'base.tmpl', content: 'outter {% body %} outter'}); + + app.pages('z.tmpl', {path: 'a.tmpl', content: 'inner', layout: 'a'}); + var page = app.pages.getView('z.tmpl'); + + app.render(page, function (err, view) { + if (err) return done(err); + assert.equal(typeof view.content, 'string'); + assert.equal(view.content, 'outter c b a inner a b c outter'); + done(); + }); + }); + + it('should track layout stack history on `layoutStack`:', function (done) { + app.layout('a', {path: 'a.tmpl', content: 'a {% body %} a', layout: 'b'}); + app.layout('b', {path: 'b.tmpl', content: 'b {% body %} b', layout: 'c'}); + app.layout('c', {path: 'c.tmpl', content: 'c {% body %} c', layout: 'base'}); + app.layout('base', {path: 'base.tmpl', content: 'outter {% body %} outter'}); + + app.pages('z.tmpl', {path: 'a.tmpl', content: 'inner', layout: 'a'}); + var page = app.pages.getView('z.tmpl'); + + app.render(page, function (err, view) { + if (err) return done(err); + assert(view.layoutStack.length === 4); + assert(typeof view.layoutStack[0] === 'object'); + assert(typeof view.layoutStack[0].depth === 'number'); + done(); + }); + }); + + it('should track layout stack history on `layoutStack`:', function (done) { + app.layout('a', {path: 'a.tmpl', content: 'a {% body %} a', layout: 'b'}); + app.layout('b', {path: 'b.tmpl', content: 'b {% body %} b', layout: 'c'}); + app.layout('c', {path: 'c.tmpl', content: 'c {% body %} c', layout: 'base'}); + app.layout('base', {path: 'base.tmpl', content: 'outter {% body %} outter'}); + + app.pages('z.tmpl', {path: 'a.tmpl', content: 'inner', layout: 'a'}); + var page = app.pages.getView('z.tmpl'); + + app.render(page, function (err, view) { + if (err) return done(err); + assert.equal(typeof view.content, 'string'); + assert.equal(view.content, 'outter c b a inner a b c outter'); + done(); + }); + }); + + it('should get layouts from `layout` viewTypes:', function (done) { + app.create('section', { viewType: 'layout' }); + app.create('block', { viewType: 'layout' }); + + app.section('a', {path: 'a.tmpl', content: 'a {% body %} a', layout: 'b'}); + app.block('b', {path: 'b.tmpl', content: 'b {% body %} b', layout: 'c'}); + app.section('c', {path: 'c.tmpl', content: 'c {% body %} c', layout: 'base'}); + app.block('base', {path: 'base.tmpl', content: 'outter {% body %} outter'}); + + app.pages('z.tmpl', {path: 'a.tmpl', content: 'inner', layout: 'a'}); + var page = app.pages.getView('z.tmpl'); + + app.render(page, function (err, view) { + if (err) return done(err); + assert.equal(typeof view.content, 'string'); + assert.equal(view.content, 'outter c b a inner a b c outter'); + done(); + }); + }); +}); diff --git a/test/list.js b/test/list.js new file mode 100644 index 0000000..e4edbca --- /dev/null +++ b/test/list.js @@ -0,0 +1,679 @@ +require('mocha'); +require('should'); +var path = require('path'); +var get = require('get-value'); +var isBuffer = require('is-buffer'); +var assert = require('assert'); +var typeOf = require('kind-of'); +var support = require('./support/'); +assert.containEql = support.containEql; + +var support = require('./support'); +var App = support.resolve(); +var List = App.List; +var Views = App.Views; +var list, views; + +describe('list', function () { + describe('constructor', function () { + it('should create an instance of List', function () { + var list = new List(); + assert(list instanceof List); + }); + + it('should instaniate without `new`', function () { + var list = List(); + assert(list instanceof List); + }); + }); + + describe('static methods', function () { + it('should expose `extend`', function () { + assert(typeof List.extend ==='function'); + }); + }); + + describe('prototype methods', function () { + beforeEach(function() { + list = new List(); + }); + + var methods = [ + 'use', + 'setItem', + 'addItem', + 'addItems', + 'addList', + 'getItem', + 'constructor', + 'set', + 'get', + 'del', + 'define', + 'visit', + 'on', + 'once', + 'off', + 'emit', + 'listeners', + 'hasListeners' + ]; + + methods.forEach(function (method) { + it('should expose the ' + method + ' method', function () { + assert(typeof list[method] === 'function'); + }); + }); + + it('should expose the isList property', function () { + assert(typeof list.isList === 'boolean'); + }); + + it('should expose the keys property', function () { + assert(Array.isArray(list.keys)); + }); + + it('should expose the queue property', function () { + assert(Array.isArray(list.queue)); + }); + + it('should expose the items property', function () { + assert(Array.isArray(list.items)); + }); + + it('should expose the options property', function () { + assert(typeOf(list.options) === 'object'); + }); + }); + + describe('instance', function () { + beforeEach(function() { + list = new List(); + }); + + it('should set a value on the instance', function () { + list.set('a', 'b'); + assert(list.a ==='b'); + }); + + it('should get a value from the instance', function () { + list.set('a', 'b'); + assert(list.get('a') ==='b'); + }); + }); + + describe('use', function () { + beforeEach(function() { + list = new List(); + }); + + it('should expose the instance to plugins', function () { + list + .use(function (inst) { + inst.foo = 'bar'; + }); + + assert(list.foo === 'bar'); + }); + + it('should expose `item` when the plugin returns a function', function () { + list + .use(function () { + return function (item) { + item.foo = 'bar'; + }; + }); + + list.addItem('aaa'); + list.addItem('bbb'); + list.addItem('ccc'); + + assert(list.items[0].foo === 'bar'); + assert(list.items[1].foo === 'bar'); + assert(list.items[2].foo === 'bar'); + }); + }); + + describe('addItem', function() { + }); + + describe('removeItem', function() { + beforeEach(function() { + list = new List(); + }); + + it('should remove an item from `items`', function () { + list.addItem('a', {content: '...'}); + list.addItem('b', {content: '...'}); + list.addItem('c', {content: '...'}); + assert(list.items.length === 3); + var a = list.getItem('a'); + list.removeItem(a); + assert(list.items.length === 2); + var c = list.getItem(c); + list.removeItem(c); + assert(list.items[0].key === 'b'); + }); + + it('should remove an item from `items` by key', function () { + list.addItem('a', {content: '...'}); + list.addItem('b', {content: '...'}); + list.addItem('c', {content: '...'}); + assert(list.items.length === 3); + list.removeItem('c'); + assert(list.items.length === 2); + list.removeItem('b'); + assert(list.items[0].key === 'a'); + }); + }); + + describe('addItems', function() { + beforeEach(function() { + list = new List(); + }); + + it('should add an object with multiple items', function () { + list.addItems({ + one: {content: 'foo'}, + two: {content: 'bar'} + }); + assert(isBuffer(list.items[0].contents)); + assert(isBuffer(list.items[1].contents)); + }); + + it('should signal `loaded` when finished (addItems)', function () { + list.on('addItems', function (items) { + for (var key in items) { + if (key === 'c') { + list.loaded = true; + break; + } + list.addItem('foo/' + key, items[key]); + } + }); + + list.addItems({ + a: {path: 'a.txt'}, + b: {path: 'b.txt'}, + c: {path: 'c.txt'} + }); + + assert.equal(list.items.length, 2); + assert.equal(list.items[0].key, 'foo/a'); + assert.equal(list.items[0].path, 'a.txt'); + }); + }); + + describe('addList', function () { + beforeEach(function() { + list = new List(); + }); + + it('should add an array with multiple items', function () { + list.addList([ + {path: 'one', content: 'foo'}, + {path: 'two', content: 'bar'} + ]); + assert(isBuffer(list.items[0].contents)); + assert(isBuffer(list.items[1].contents)); + }); + + it('should take a callback on `addList`', function () { + function addContents(item) { + item.contents = new Buffer(item.path.charAt(0)); + } + + list.addList([ + { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, + { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, + { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, + ], addContents); + + assert(isBuffer(list.items[0].contents)); + assert(isBuffer(list.items[1].contents)); + assert(isBuffer(list.items[2].contents)); + }); + + it('should throw an error when the list is not an array', function () { + function addContents(item) { + item.contents = new Buffer(item.path.charAt(0)); + } + + (function () { + list.addList({ + 'a.md': {locals: { date: '2014-01-01', foo: 'zzz', bar: 1 }}, + 'f.md': {locals: { date: '2014-01-01', foo: 'mmm', bar: 2 }}, + 'd.md': {locals: { date: '2014-01-01', foo: 'xxx', bar: 3 }}, + }, addContents); + + assert(isBuffer(list.items[0].contents)); + assert(isBuffer(list.items[1].contents)); + assert(isBuffer(list.items[2].contents)); + }).should.throw('expected list to be an array.'); + }); + + it('should signal `loaded` when finished (addList)', function () { + list.on('addList', function (items) { + var len = items.length, i = -1; + while (++i < len) { + if (items[i].path === 'd.md') { + list.loaded = true; + break; + } + list.addItem('foo/' + items[i].path, items[i]); + } + }); + + list.addList([ + { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, + { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, + { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, + ]); + + assert.equal(list.items.length, 2); + assert.equal(list.keys.indexOf('d.md'), -1); + }); + }); + + describe('queue', function () { + beforeEach(function () { + list = new List(); + }); + + it('should emit arguments on addItem', function (done) { + list.on('addItem', function (args) { + assert(args[0] === 'a'); + assert(args[1] === 'b'); + assert(args[2] === 'c'); + assert(args[3] === 'd'); + assert(args[4] === 'e'); + done(); + }); + + list.addItem('a', 'b', 'c', 'd', 'e'); + }); + + it('should expose the `queue` property for loading items', function () { + list.queue.push(list.item('b', {path: 'b'})); + + list.addItem('a', {path: 'a'}); + assert(list.items[0].key === 'a'); + assert(list.items[1].key === 'b'); + }); + + it('should load all items on the queue when addItem is called', function () { + list.on('addItem', function (args) { + var len = args.length; + var last = args[len - 1]; + if (typeof last === 'string') { + args[len - 1] = { content: last }; + } + }); + + list.addItem('a.html', 'aaa'); + list.addItem('b.html', 'bbb'); + list.addItem('c.html', 'ccc'); + + assert(list.items[0].path === 'a.html'); + assert(list.getItem('a.html').content === 'aaa'); + assert(list.items[1].path === 'b.html'); + assert(list.getItem('b.html').content === 'bbb'); + assert(list.items[2].path === 'c.html'); + assert(list.getItem('c.html').content === 'ccc'); + }); + }); + + describe('sortBy', function() { + var items = [ + { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, + { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, + { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, + { path: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, + { path: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, + { path: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, + { path: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, + { path: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, + { path: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, + { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, + { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, + { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, + { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, + ]; + + it('should sort a list', function () { + list = new List(); + list.addList(items); + + var compare = function(prop) { + return function (a, b, fn) { + var valA = get(a, prop); + var valB = get(b, prop); + return fn(valA, valB); + }; + }; + + var res = list.sortBy('locals.date', 'doesnt.exist', [ + compare('locals.foo'), + compare('locals.bar') + ]); + + assert.containEql(res.items, [ + { key: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, + { key: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, + { key: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, + { key: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, + { key: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, + { key: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, + { key: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, + { key: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, + { key: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, + { key: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, + { key: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, + { key: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, + { key: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } } + ]); + }); + + it('should not sort the (original) instance list `items`', function () { + list = new List(); + list.addList(items); + + var compare = function(prop) { + return function (a, b, fn) { + var valA = get(a, prop); + var valB = get(b, prop); + return fn(valA, valB); + }; + }; + + var res = list.sortBy('locals.date', 'doesnt.exist', [ + compare('locals.foo'), + compare('locals.bar') + ]); + + // should not be sorted + assert.containEql(list.items, items); + + // should be sorted + assert.containEql(res.items, [ + { key: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, + { key: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, + { key: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, + { key: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, + { key: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, + { key: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, + { key: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, + { key: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, + { key: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, + { key: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, + { key: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, + { key: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, + { key: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } } + ]); + }); + + it('should pass options to array-sort from the constructor', function () { + list = new List({sort: {reverse: true}}); + list.addList(items); + + var compare = function(prop) { + return function (a, b, fn) { + var valA = get(a, prop); + var valB = get(b, prop); + return fn(valA, valB); + }; + }; + + var res = list.sortBy('locals.date', 'doesnt.exist', [ + compare('locals.foo'), + compare('locals.bar') + ]); + + assert.containEql(res.items, [ + { key: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, + { key: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, + { key: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, + { key: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, + { key: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, + { key: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, + { key: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, + { key: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, + { key: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, + { key: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, + { key: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, + { key: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, + { key: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } } + ]); + }); + + it('should pass options to array-sort from the sortBy method', function () { + list = new List(); + list.addList(items); + + var compare = function(prop) { + return function (a, b, fn) { + var valA = get(a, prop); + var valB = get(b, prop); + return fn(valA, valB); + }; + }; + + var res = list.sortBy('locals.date', 'doesnt.exist', [ + compare('locals.foo'), + compare('locals.bar') + ], {reverse: true}); + + assert.containEql(res.items, [ + { key: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, + { key: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, + { key: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, + { key: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, + { key: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, + { key: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, + { key: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, + { key: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, + { key: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, + { key: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, + { key: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, + { key: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, + { key: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } } + ]); + }); + }); + + describe('groupBy', function() { + var items = [ + { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, + { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, + { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, + { path: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, + { path: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, + { path: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, + { path: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, + { path: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, + { path: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, + { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, + { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, + { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, + { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, + ]; + + it('should group a list by a property', function () { + list = new List(); + list.addList(items); + + var res = list.groupBy('locals.foo'); + var keys = ['zzz', 'mmm', 'xxx', 'aaa', 'ccc', 'rrr', 'ttt', 'yyy']; + assert.deepEqual(Object.keys(res), keys); + }); + }); + + describe('sort and group', function() { + var items = [ + { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, + { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, + { path: 'd.md', locals: { date: '2013-01-01', foo: 'xxx', bar: 3 } }, + { path: 'i.md', locals: { date: '2013-02-01', foo: 'xxx', bar: 5 } }, + { path: 'i.md', locals: { date: '2013-02-01', foo: 'lll', bar: 5 } }, + { path: 'k.md', locals: { date: '2013-03-01', foo: 'xxx', bar: 1 } }, + { path: 'j.md', locals: { date: '2013-02-01', foo: 'xxx', bar: 4 } }, + { path: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, + { path: 'm.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, + { path: 'n.md', locals: { date: '2013-01-01', foo: 'xxx', bar: 7 } }, + { path: 'o.md', locals: { date: '2013-01-01', foo: 'xxx', bar: 7 } }, + { path: 'p.md', locals: { date: '2013-01-01', foo: 'xxx', bar: 7 } }, + { path: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, + { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, + { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, + { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, + { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, + ]; + + it('should group a list by a property', function () { + list = new List(items); + + var context = list + .sortBy('locals.date') + .groupBy(function (view) { + var date = view.locals.date; + view.locals.year = date.slice(0, 4); + view.locals.month = date.slice(5, 7); + view.locals.day = date.slice(8, 10); + return view.locals.year; + }, 'locals.month'); + + var keys = Object.keys(context); + assert(keys[0] === '2012'); + assert(keys[1] === '2013'); + assert(keys[2] === '2014'); + assert(keys[3] === '2015'); + }); + }); + + describe('paginate', function() { + var items = [ + { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, + { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, + { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, + { path: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, + { path: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, + { path: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, + { path: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, + { path: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, + { path: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, + { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, + { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, + { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, + { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, + ]; + + it('should paginate a list', function () { + list = new List(items); + + var res = list.paginate(); + assert.equal(res.length, 2); + assert.containEql(res[0].items, items.slice(0, 10)); + assert.containEql(res[1].items, items.slice(10)); + }); + + it('should add pager properties', function () { + list = new List({pager: true}); + list.addList(items); + list.items.forEach(function (item, i) { + assert.equal(item.data.pager.index, i); + }); + }); + + it('should paginate a list with given options', function () { + list = new List(items); + var res = list.paginate({limit: 5}); + + assert.equal(res.length, 3); + assert.containEql(res[0].items, items.slice(0, 5)); + assert.containEql(res[1].items, items.slice(5, 10)); + assert.containEql(res[2].items, items.slice(10)); + }); + }); + + describe('Views instance', function() { + beforeEach(function() { + views = new Views(); + }); + + it('should add views from an instance of Views', function () { + views.addViews({ + one: {content: 'foo'}, + two: {content: 'bar'} + }); + + list = new List(views); + assert(isBuffer(list.items[0].contents)); + assert(isBuffer(list.items[1].contents)); + }); + }); + + describe('getIndex', function() { + beforeEach(function() { + list = new List(); + }); + it('should get the index of a key when key is not renamed', function () { + list.addItem('a/b/c/ddd.hbs', {content: 'ddd'}); + list.addItem('a/b/c/eee.hbs', {content: 'eee'}); + assert(list.getIndex('a/b/c/ddd.hbs') === 0); + assert(list.getIndex('a/b/c/eee.hbs') === 1); + }); + + it('should get the index of a key when key is renamed', function () { + list = new List({ + renameKey: function (key) { + return path.basename(key); + } + }); + list.addItem('a/b/c/ddd.hbs', {content: 'ddd'}); + list.addItem('a/b/c/eee.hbs', {content: 'eee'}); + assert(list.getIndex('a/b/c/ddd.hbs') === 0); + assert(list.getIndex('ddd.hbs') === 0); + assert(list.getIndex('a/b/c/eee.hbs') === 1); + assert(list.getIndex('eee.hbs') === 1); + }); + }); + + describe('getItem', function() { + beforeEach(function() { + list = new List(); + }); + + it('should get an view from `views`', function () { + list.addItem('one', {content: 'aaa'}); + list.addItem('two', {content: 'zzz'}); + assert(list.items.length === 2); + assert(isBuffer(list.items[0].contents)); + assert(isBuffer(list.getItem('one').contents)); + assert(list.getItem('one').contents.toString() === 'aaa'); + assert(list.getItem('two').contents.toString() === 'zzz'); + }); + }); + + describe('use', function() { + beforeEach(function() { + list = new List(); + }); + + it('should use middleware on a list', function () { + list.addItem('one', {content: 'aaa'}); + list.addItem('two', {content: 'zzz'}); + + list + .use(function () { + this.set('foo', 'bar'); + }) + .use(function () { + this.set('one', 'two'); + }); + + assert(list.one === 'two'); + assert(list.foo === 'bar'); + }); + }); +}); + diff --git a/test/list.render.js b/test/list.render.js new file mode 100644 index 0000000..b660836 --- /dev/null +++ b/test/list.render.js @@ -0,0 +1,137 @@ +require('mocha'); +require('should'); +var async = require('async'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var List = App.List; +var pages; + +describe('render', function () { + describe('rendering', function () { + beforeEach(function () { + pages = new List(); + pages.engine('tmpl', require('engine-base')); + }); + + it('should throw an error when no callback is given:', function () { + (function() { + pages.render({}); + }).should.throw('List#render is async and expects a callback function'); + }); + + it('should throw an error when an engine is not defined:', function (done) { + pages.addItem('foo.bar', {content: '<%= name %>'}); + var page = pages.getItem('foo.bar'); + + pages.render(page, function(err) { + assert(err.message === 'List#render cannot find an engine for: .bar'); + done(); + }); + }); + + it('should use helpers to render a item:', function (done) { + var locals = {name: 'Halle'}; + + pages.helper('upper', function (str) { + return str.toUpperCase(str); + }); + + pages.addItem('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); + var page = pages.getItem('a.tmpl'); + + pages.render(page, function (err, res) { + if (err) return done(err); + + assert(res.content === 'a HALLE b'); + done(); + }); + }); + + it('should use helpers when rendering a item:', function (done) { + var locals = {name: 'Halle'}; + pages.helper('upper', function (str) { + return str.toUpperCase(str); + }); + + pages.addItem('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); + var page = pages.getItem('a.tmpl'); + + pages.render(page, function (err, res) { + if (err) return done(err); + assert(res.content === 'a HALLE b'); + done(); + }); + }); + + it('should render a template when contents is a buffer:', function (done) { + pages.addItem('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); + var item = pages.getItem('a.tmpl'); + + pages.render(item, function (err, item) { + if (err) return done(err); + assert(item.contents.toString() === 'b'); + done(); + }); + }); + + it('should render a template when content is a string:', function (done) { + pages.addItem('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); + var item = pages.getItem('a.tmpl'); + + pages.render(item, function (err, item) { + if (err) return done(err); + assert(item.contents.toString() === 'b'); + done(); + }); + }); + + it('should render a item from its path:', function (done) { + pages.addItem('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); + + pages.render('a.tmpl', function (err, item) { + if (err) return done(err); + assert(item.content === 'b'); + done(); + }); + }); + + it('should use a plugin for rendering:', function (done) { + pages.engine('tmpl', require('engine-base')); + pages.option('engine', 'tmpl'); + + pages.addItems({ + 'a': {content: '<%= title %>', locals: {title: 'aaa'}}, + 'b': {content: '<%= title %>', locals: {title: 'bbb'}}, + 'c': {content: '<%= title %>', locals: {title: 'ccc'}}, + 'd': {content: '<%= title %>', locals: {title: 'ddd'}}, + 'e': {content: '<%= title %>', locals: {title: 'eee'}}, + 'f': {content: '<%= title %>', locals: {title: 'fff'}}, + 'g': {content: '<%= title %>', locals: {title: 'ggg'}}, + 'h': {content: '<%= title %>', locals: {title: 'hhh'}}, + 'i': {content: '<%= title %>', locals: {title: 'iii'}}, + 'j': {content: '<%= title %>', locals: {title: 'jjj'}}, + }); + + pages.use(function (collection) { + collection.option('pager', false); + + collection.renderEach = function (cb) { + var list = new List(collection); + + async.map(list.items, function (item, next) { + collection.render(item, next); + }, cb); + }; + }); + + pages.renderEach(function (err, items) { + if (err) return done(err); + assert(items[0].content === 'aaa'); + assert(items[9].content === 'jjj'); + assert(items.length === 10); + done(); + }); + }); + }); +}); diff --git a/test/mergePartials.js b/test/mergePartials.js new file mode 100644 index 0000000..92e2f77 --- /dev/null +++ b/test/mergePartials.js @@ -0,0 +1,108 @@ +require('should'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('mergePartials', function () { + beforeEach(function () { + app = new App(); + // reset views + app.views = {}; + }); + + it('should merge multiple partials collections onto one collection:', function () { + var opts = { viewType: 'partial' }; + app.create('foo', opts); + app.create('bar', opts); + app.create('baz', opts); + + app.foo('a', {path: 'a', content: 'aaa'}); + app.bar('b', {path: 'b', content: 'bbb'}); + app.baz('c', {path: 'c', content: 'ccc'}); + + var actual = app.mergePartials(); + actual.should.have.property('partials'); + actual.partials.should.have.properties(['a', 'b', 'c']); + }); + + it('should keep partials collections on separate collections:', function () { + var opts = { viewType: 'partial' }; + app.create('foo', opts); + app.create('bar', opts); + app.create('baz', opts); + + app.foo('a', {path: 'a', content: 'aaa'}); + app.bar('b', {path: 'b', content: 'bbb'}); + app.baz('c', {path: 'c', content: 'ccc'}); + + var actual = app.mergePartials({mergePartials: false}); + actual.should.not.have.property('partials'); + actual.should.eql({ foos: { a: 'aaa' }, bars: { b: 'bbb' }, bazs: { c: 'ccc' } }); + }); + + it('should emit `mergePartials`:', function () { + var opts = { viewType: 'partial' }; + app.create('foo', opts); + app.create('bar', opts); + app.create('baz', opts); + var arr = []; + + app.on('onMerge', function (view) { + arr.push(view.content); + }); + + app.foo('a', {path: 'a', content: 'aaa'}); + app.bar('b', {path: 'b', content: 'bbb'}); + app.baz('c', {path: 'c', content: 'ccc'}); + + var actual = app.mergePartials({mergePartials: false}); + actual.should.not.have.property('partials'); + actual.should.eql({ foos: { a: 'aaa' }, bars: { b: 'bbb' }, bazs: { c: 'ccc' } }); + arr.should.eql(['aaa', 'bbb', 'ccc']); + }); + + it('should handle `onMerge` middleware:', function () { + var opts = { viewType: 'partial' }; + app.create('foo', opts); + app.create('bar', opts); + app.create('baz', opts); + + app.onMerge(/./, function (view, next) { + view.content += ' onMerge'; + next(); + }); + + app.foo('a', {path: 'a', content: 'aaa'}); + app.bar('b', {path: 'b', content: 'bbb'}); + app.baz('c', {path: 'c', content: 'ccc'}); + + var actual = app.mergePartials({mergePartials: false}); + actual.should.eql({ + foos: {a: 'aaa onMerge'}, + bars: {b: 'bbb onMerge'}, + bazs: {c: 'ccc onMerge'} + }); + }); + + it('should skip views with `nomerge=true`:', function () { + var opts = { viewType: 'partial' }; + + app.create('foo', opts); + app.create('bar', opts); + app.create('baz', opts); + + app.onMerge(/[ab]/, function (view, next) { + view.options.nomerge = true; + next(); + }); + + app.foo('a', {path: 'a', content: 'aaa'}); + app.bar('b', {path: 'b', content: 'bbb'}); + app.baz('c', {path: 'c', content: 'ccc'}); + + var actual = app.mergePartials({mergePartials: false}); + actual.should.eql({ bazs: { c: 'ccc' } }); + }); +}); + + diff --git a/test/partials.js b/test/partials.js new file mode 100644 index 0000000..575e7b5 --- /dev/null +++ b/test/partials.js @@ -0,0 +1,202 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app, pages; + +describe('partials', function () { + beforeEach(function () { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.engine('hbs', require('engine-handlebars')); + + app.create('partials', { viewType: 'partial' }); + app.create('include', { viewType: 'partial' }); + app.create('layouts', { viewType: 'layout' }); + pages = app.create('page'); + }); + + it('should inject a partial with a helper:', function (done) { + app.include('base', {path: 'base.tmpl', content: 'xyz'}); + app.pages('a.tmpl', {path: 'a.tmpl', content: 'a <%= include("base") %> c'}); + var page = app.pages.getView('a.tmpl'); + + app.render(page, function (err, view) { + if (err) return done(err); + assert.equal(typeof view.content, 'string'); + assert.equal(view.content, 'a xyz c'); + done(); + }); + }); + + it('should inject a partial with a helper on a collection:', function (done) { + app.include('base', {path: 'base.tmpl', content: 'xyz'}); + pages.engine('.tmpl', require('engine-handlebars')); + pages.helpers(app._.helpers.sync); + pages.asyncHelpers(app._.helpers.async); + pages.addView('a.tmpl', {path: 'a.tmpl', content: 'a {{include "base" }} c'}); + var page = pages.getView('a.tmpl'); + + pages.render(page, function (err, view) { + if (err) return done(err); + assert.equal(typeof view.content, 'string'); + assert.equal(view.content, 'a xyz c'); + done(); + }); + }); + + it('should use handlebars partial with a helper on a collection:', function (done) { + app.include('base', {path: 'base.tmpl', content: 'xyz'}); + pages.engine('.tmpl', require('engine-handlebars')); + pages.helpers(app._.helpers.sync); + pages.asyncHelpers(app._.helpers.async); + pages.addView('a.tmpl', {path: 'a.tmpl', content: 'a {{> base }} c'}); + + var page = pages.getView('a.tmpl'); + var locals = app.mergePartials(this.options); + + pages.render(page, locals, function (err, view) { + if (err) return done(err); + assert.equal(typeof view.content, 'string'); + assert.equal(view.content, 'a xyz c'); + done(); + }); + }); + + it('should use layouts with partials:', function (done) { + app.layout('default', {path: 'a.tmpl', content: 'a {% body %} c'}); + app.include('b', {path: 'b.tmpl', content: 'b', layout: 'default'}); + app.pages('a.tmpl', {path: 'c.tmpl', content: '<%= include("b") %>'}); + var page = app.pages.getView('a.tmpl'); + + app.render(page, function (err, view) { + if (err) return done(err); + assert.equal(typeof view.content, 'string'); + assert.equal(view.content, 'a b c'); + done(); + }); + }); + + it('should add `layoutApplied` after layout is applied:', function (done) { + app.layout('default', {path: 'a.tmpl', content: 'a {% body %} c'}); + app.include('b', {path: 'b.tmpl', content: 'b', layout: 'default'}); + app.pages('a.tmpl', {path: 'c.tmpl', content: '<%= include("b") %>'}); + var page = app.pages.getView('a.tmpl'); + + app.render(page, function (err, view) { + if (err) return done(err); + assert.equal(typeof view.content, 'string'); + assert.equal(app.layouts.getView('default').options.layoutApplied); + assert.equal(view.content, 'a b c'); + done(); + }); + }); + + it('should pass partials to handlebars:', function (done) { + app.onMerge(/\.hbs$/, function (view, next) { + app.applyLayout(view); + next(); + }); + + app.layout('default', {path: 'a.hbs', content: 'a {% body %} c'}); + app.include('foo', {path: 'foo.hbs', content: 'foo', layout: 'default'}); + app.pages('a.hbs', {path: 'c.hbs', content: '{{> foo }}'}); + var page = app.pages.getView('a.hbs'); + + app.render(page, function (err, view) { + if (err) return done(err); + assert.equal(typeof view.content, 'string'); + assert.equal(view.content, 'a foo c'); + done(); + }); + }); + + it('should only merge in the specified viewTypes:', function (done) { + app.onMerge(/\.hbs$/, function (view, next) { + app.applyLayout(view); + next(); + }); + + app.layout('default', {path: 'a.hbs', content: 'a {% body %} c'}); + app.option('mergeTypes', ['includes']); + + app.partial('foo', {path: 'bar.hbs', content: 'bar', layout: 'default'}); + app.include('foo', {path: 'foo.hbs', content: 'foo', layout: 'default'}); + + app.pages('a.hbs', {path: 'c.hbs', content: '{{> foo }}'}); + app.pages.getView('a.hbs') + .render(function (err, view) { + if (err) return done(err); + assert.equal(typeof view.content, 'string'); + assert.equal(view.content, 'a foo c'); + done(); + }); + + }); + + it('should merge the specified viewTypes in the order defined:', function (done) { + app.onMerge(/\.hbs$/, function (view, next) { + app.applyLayout(view); + next(); + }); + + app.layout('default', {path: 'a.hbs', content: 'a {% body %} c'}); + app.option('mergeTypes', ['includes', 'partials']); + + app.partial('foo', {path: 'bar.hbs', content: 'bar', layout: 'default'}); + app.include('foo', {path: 'foo.hbs', content: 'foo', layout: 'default'}); + + app.pages('a.hbs', {path: 'c.hbs', content: '{{> foo }}'}); + app.pages.getView('a.hbs') + .render(function (err, view) { + if (err) return done(err); + assert.equal(typeof view.content, 'string'); + assert.equal(view.content, 'a bar c'); + done(); + }); + }); + + it('should not merge in partials with `options.nomerge` defined:', function (done) { + app.onMerge(/\.hbs$/, function (view, next) { + app.applyLayout(view); + next(); + }); + + app.layout('default', {path: 'a.hbs', content: 'a {% body %} c'}); + app.option('mergeTypes', ['includes', 'partials']); + + app.partial('foo', {path: 'bar.hbs', content: 'bar', layout: 'default', options: {nomerge: true}}); + app.include('foo', {path: 'foo.hbs', content: 'foo', layout: 'default'}); + + app.pages('a.hbs', {path: 'c.hbs', content: '{{> foo }}'}); + app.pages.getView('a.hbs') + .render(function (err, view) { + if (err) return done(err); + assert.equal(typeof view.content, 'string'); + assert.equal(view.content, 'a foo c'); + done(); + }); + }); + + it('should emit an `onMerge` event:', function (done) { + app.on('onMerge', function (view) { + app.applyLayout(view); + }); + + app.layout('default', {path: 'a.hbs', content: 'a {% body %} c'}); + app.option('mergeTypes', ['includes', 'partials']); + + app.partial('foo', {path: 'bar.hbs', content: 'bar', layout: 'default'}); + app.include('foo', {path: 'foo.hbs', content: 'foo', layout: 'default'}); + + app.pages('a.hbs', {path: 'c.hbs', content: '{{> foo }}'}); + app.pages.getView('a.hbs') + .render(function (err, view) { + if (err) return done(err); + assert.equal(typeof view.content, 'string'); + assert.equal(view.content, 'a bar c'); + done(); + }); + }); +}); diff --git a/test/questions.js b/test/questions.js new file mode 100644 index 0000000..5ae3bad --- /dev/null +++ b/test/questions.js @@ -0,0 +1,58 @@ +require('mocha'); +require('should'); +var fs = require('fs'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('content', function () { + beforeEach(function () { + app = new App(); + }); + + it('should store a question:', function () { + app.question('a', 'b'); + assert(app.questions); + assert(app.questions.cache); + assert(app.questions.cache.a); + assert(app.questions.cache.a.name === 'a'); + assert(app.questions.cache.a.message === 'b'); + }); + + it('should ask a question and use data value to answer:', function (done) { + app.question('a', 'b'); + app.data('a', 'b'); + + app.ask('a', function (err, answer) { + assert(!err); + assert(answer); + assert(answer === 'b'); + done(); + }) + }); + + it('should ask a question and use store value to answer:', function (done) { + app.question('a', 'b'); + app.store.set('a', 'c'); + + app.ask('a', function (err, answer) { + assert(!err); + assert(answer); + assert(answer === 'c'); + done(); + }) + }); + + it('should ask a question and use config value to answer:', function (done) { + app.question('a', 'b'); + app.store.set('a', 'c'); + + app.ask('a', function (err, answer) { + assert(!err); + assert(answer); + assert(answer === 'c'); + done(); + }) + }); +}); diff --git a/test/renameKey.js b/test/renameKey.js new file mode 100644 index 0000000..9662f03 --- /dev/null +++ b/test/renameKey.js @@ -0,0 +1,350 @@ +var path = require('path'); +var support = require('./support'); +var App = support.resolve(); +var app; + +function renameKey(key) { + return path.basename(key, path.extname(key)); +} + +describe('renameKey', function () { + beforeEach(function () { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.create('pages'); + app.create('posts'); + }); + + describe('global options:', function () { + it('should use `renameKey` function defined on global opts:', function () { + app.option('renameKey', renameKey); + + app.posts('a/b/c/a.txt', {content: '...'}); + app.posts('a/b/c/b.txt', {content: '...'}); + app.posts('a/b/c/c.txt', {content: '...'}); + app.post('a/b/c/d.txt', {content: '...'}); + app.post('a/b/c/e.txt', {content: '...'}); + + app.views.posts.should.have.property('a'); + app.views.posts.should.have.property('b'); + app.views.posts.should.have.property('c'); + app.views.posts.should.have.property('d'); + app.views.posts.should.have.property('e'); + }); + + it('should not have conflicts when view name is the collection name:', function () { + app.option('renameKey', renameKey); + + app.post('a/b/c/post.txt', {content: 'this is contents'}); + app.page('a/b/c/page.txt', {content: 'this is contents'}); + + app.views.posts.should.have.property('post'); + app.views.pages.should.have.property('page'); + }); + }); + + describe('create method:', function () { + it('should use `renameKey` option chained from the `create` method:', function () { + app.create('post') + .option('renameKey', function (key) { + return 'posts/' + path.basename(key); + }); + + app.posts('a/b/c/a.txt', {content: '...'}); + app.posts('a/b/c/b.txt', {content: '...'}); + app.posts('a/b/c/c.txt', {content: '...'}); + app.post('a/b/c/d.txt', {content: '...'}); + app.post('a/b/c/e.txt', {content: '...'}); + + app.views.posts.should.have.property('posts/a.txt'); + app.views.posts.should.have.property('posts/b.txt'); + app.views.posts.should.have.property('posts/c.txt'); + app.views.posts.should.have.property('posts/d.txt'); + app.views.posts.should.have.property('posts/e.txt'); + }); + }); + + describe('create method:', function () { + it('should use `renameKey` defined on the `create` method:', function () { + app.create('post', { + renameKey: function (key) { + return 'posts/' + path.basename(key); + } + }); + + app.posts('a/b/c/a.txt', {content: '...'}); + app.posts('a/b/c/b.txt', {content: '...'}); + app.posts('a/b/c/c.txt', {content: '...'}); + app.post('a/b/c/d.txt', {content: '...'}); + app.post('a/b/c/e.txt', {content: '...'}); + + app.views.posts.should.have.property('posts/a.txt'); + app.views.posts.should.have.property('posts/b.txt'); + app.views.posts.should.have.property('posts/c.txt'); + app.views.posts.should.have.property('posts/d.txt'); + app.views.posts.should.have.property('posts/e.txt'); + }); + }); + + describe('collections:', function () { + describe('setting:', function () { + it('should get a view with the `renameKey` defined on app.options:', function () { + app.option('renameKey', function (key) { + return 'foo/' + path.basename(key); + }); + + app.posts('a/b/c/a.txt', {content: '...'}); + app.posts('a/b/c/b.txt', {content: '...'}); + app.post('a/b/c/c.txt', {content: '...'}); + + app.views.posts.should.have.property('foo/a.txt'); + app.views.posts.should.have.property('foo/b.txt'); + app.views.posts.should.have.property('foo/c.txt'); + }); + + it('should use `renameKey` defined on collection.options:', function () { + app.pages.option('renameKey', function (key) { + return 'page/' + path.basename(key); + }); + + app.posts.option('renameKey', function (key) { + return 'post/' + path.basename(key); + }); + + app.pages('a/b/c/a.txt', {content: '...'}); + app.pages('a/b/c/b.txt', {content: '...'}); + app.pages('a/b/c/c.txt', {content: '...'}); + app.page('a/b/c/d.txt', {content: '...'}); + app.page('a/b/c/e.txt', {content: '...'}); + + app.posts('a/b/c/a.txt', {content: '...'}); + app.posts('a/b/c/b.txt', {content: '...'}); + app.posts('a/b/c/c.txt', {content: '...'}); + app.post('a/b/c/d.txt', {content: '...'}); + app.post('a/b/c/e.txt', {content: '...'}); + + app.views.pages.should.have.property('page/a.txt'); + app.views.pages.should.have.property('page/b.txt'); + app.views.pages.should.have.property('page/c.txt'); + app.views.pages.should.have.property('page/d.txt'); + app.views.pages.should.have.property('page/e.txt'); + + app.views.posts.should.have.property('post/a.txt'); + app.views.posts.should.have.property('post/b.txt'); + app.views.posts.should.have.property('post/c.txt'); + app.views.posts.should.have.property('post/d.txt'); + app.views.posts.should.have.property('post/e.txt'); + }); + + it('should use the `collection.renameKey()` method:', function () { + app.pages.renameKey(function (key) { + return 'baz/' + path.basename(key); + }); + + app.pages('a/b/c/a.txt', {content: '...'}); + app.pages('a/b/c/b.txt', {content: '...'}); + app.pages('a/b/c/c.txt', {content: '...'}); + app.page('a/b/c/d.txt', {content: '...'}); + app.page('a/b/c/e.txt', {content: '...'}); + + app.views.pages.should.have.property('baz/a.txt'); + app.views.pages.should.have.property('baz/b.txt'); + app.views.pages.should.have.property('baz/c.txt'); + app.views.pages.should.have.property('baz/d.txt'); + app.views.pages.should.have.property('baz/e.txt'); + }); + + it('should use the `app.renameKey()` method:', function () { + app.renameKey(function (key) { + return 'app/' + path.basename(key); + }); + + app.pages('a/b/c/a.txt', {content: '...'}); + app.pages('a/b/c/b.txt', {content: '...'}); + app.pages('a/b/c/c.txt', {content: '...'}); + app.page('a/b/c/d.txt', {content: '...'}); + app.page('a/b/c/e.txt', {content: '...'}); + + app.views.pages.should.have.property('app/a.txt'); + app.views.pages.should.have.property('app/b.txt'); + app.views.pages.should.have.property('app/c.txt'); + app.views.pages.should.have.property('app/d.txt'); + app.views.pages.should.have.property('app/e.txt'); + }); + + it('should prefer collection method over app.options:', function () { + // this works when you switch the order around... + app.pages.renameKey(function pagesRenameKey(key) { + return 'aaa/' + path.basename(key); + }); + app.option('renameKey', function optsRenameKey(key) { + return 'foo/' + path.basename(key); + }); + + app.pages('a/b/c/a.txt', {content: '...'}); + app.pages('a/b/c/b.txt', {content: '...'}); + app.pages('a/b/c/c.txt', {content: '...'}); + app.page('a/b/c/d.txt', {content: '...'}); + app.page('a/b/c/e.txt', {content: '...'}); + + app.views.pages.should.have.property('aaa/a.txt'); + app.views.pages.should.have.property('aaa/b.txt'); + app.views.pages.should.have.property('aaa/c.txt'); + app.views.pages.should.have.property('aaa/d.txt'); + app.views.pages.should.have.property('aaa/e.txt'); + }); + + it('should prefer collection method over app method:', function () { + app.pages.renameKey(function (key) { + return 'aaa/' + path.basename(key); + }); + app.renameKey(function (key) { + return 'zzz/' + path.basename(key); + }); + + app.pages('a/b/c/a.txt', {content: '...'}); + app.pages('a/b/c/b.txt', {content: '...'}); + app.pages('a/b/c/c.txt', {content: '...'}); + app.page('a/b/c/d.txt', {content: '...'}); + app.page('a/b/c/e.txt', {content: '...'}); + + app.views.pages.should.have.property('aaa/a.txt'); + app.views.pages.should.have.property('aaa/b.txt'); + app.views.pages.should.have.property('aaa/c.txt'); + app.views.pages.should.have.property('aaa/d.txt'); + app.views.pages.should.have.property('aaa/e.txt'); + }); + + it('should prefer collection options over app.options:', function () { + app.pages.option('renameKey', function (key) { + return 'collection/' + path.basename(key); + }); + app.option('renameKey', function (key) { + return 'app/' + path.basename(key); + }); + + app.pages('a/b/c/a.txt', {content: '...'}); + app.pages('a/b/c/b.txt', {content: '...'}); + app.pages('a/b/c/c.txt', {content: '...'}); + app.page('a/b/c/d.txt', {content: '...'}); + app.page('a/b/c/e.txt', {content: '...'}); + + app.views.pages.should.have.property('collection/a.txt'); + app.views.pages.should.have.property('collection/b.txt'); + app.views.pages.should.have.property('collection/c.txt'); + app.views.pages.should.have.property('collection/d.txt'); + app.views.pages.should.have.property('collection/e.txt'); + }); + + it('should prefer collection options over app method:', function () { + app.pages.option('renameKey', function (key) { + return 'collection/' + path.basename(key); + }); + app.renameKey(function (key) { + return 'app/' + path.basename(key); + }); + + app.pages('a/b/c/a.txt', {content: '...'}); + app.pages('a/b/c/b.txt', {content: '...'}); + app.pages('a/b/c/c.txt', {content: '...'}); + app.page('a/b/c/d.txt', {content: '...'}); + app.page('a/b/c/e.txt', {content: '...'}); + + app.views.pages.should.have.property('collection/a.txt'); + app.views.pages.should.have.property('collection/b.txt'); + app.views.pages.should.have.property('collection/c.txt'); + app.views.pages.should.have.property('collection/d.txt'); + app.views.pages.should.have.property('collection/e.txt'); + }); + + it('should use renameKey on chained methods:', function () { + app.page('test/fixtures/pages/a.txt', { + options: { + renameKey: function foo(key) { + return 'foo/' + path.basename(key); + } + } + }); + + app.page('test/fixtures/pages/a.hbs', { + options: { + renameKey: function bar(key) { + return 'bar/' + path.basename(key); + } + } + }); + + app.views.pages.should.have.properties([ + 'foo/a.txt', + 'bar/a.hbs' + ]); + }); + }); + + describe('getting', function () { + beforeEach(function () { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.create('post'); + app.create('page'); + }); + + it('should get a view with the `renameKey` defined on the `create` method:', function () { + app.create('post', { + renameKey: function createRenameKey(key) { + return 'posts/' + path.basename(key); + } + }); + + app.posts('a/b/c/a.txt', {content: '...'}); + app.posts('a/b/c/b.txt', {content: '...'}); + app.post('a/b/c/c.txt', {content: '...'}); + + app.posts.getView('a.txt').should.have.property('path', 'a/b/c/a.txt'); + app.posts.getView('posts/a.txt').should.have.property('path', 'a/b/c/a.txt'); + }); + + it('should get a view with `renameKey` on collection.options:', function () { + app.pages.option('renameKey', function (key) { + return 'bar/' + path.basename(key); + }); + + app.pages('a/b/c/a.txt', {content: '...'}); + app.pages('a/b/c/b.txt', {content: '...'}); + app.page('a/b/c/c.txt', {content: '...'}); + + app.views.pages.should.have.property('bar/a.txt'); + app.views.pages.should.have.property('bar/b.txt'); + app.views.pages.should.have.property('bar/c.txt'); + }); + + it('should get a view with the the `app.renameKey()` method:', function () { + app.renameKey(function (key) { + return 'baz/' + path.basename(key); + }); + + app.pages('a/b/c/a.txt', {content: '...'}); + app.pages('a/b/c/b.txt', {content: '...'}); + app.page('a/b/c/c.txt', {content: '...'}); + + app.views.pages.should.have.property('baz/a.txt'); + app.views.pages.should.have.property('baz/b.txt'); + app.views.pages.should.have.property('baz/c.txt'); + }); + + it('should get a view with the the `collection.renameKey()` method:', function () { + app.pages.renameKey(function (key) { + return 'baz/' + path.basename(key); + }); + + app.pages('a/b/c/a.txt', {content: '...'}); + app.pages('a/b/c/b.txt', {content: '...'}); + app.page('a/b/c/c.txt', {content: '...'}); + + app.views.pages.should.have.property('baz/a.txt'); + app.views.pages.should.have.property('baz/b.txt'); + app.views.pages.should.have.property('baz/c.txt'); + }); + }); + }); +}); diff --git a/test/render.js b/test/render.js new file mode 100644 index 0000000..1c0b05e --- /dev/null +++ b/test/render.js @@ -0,0 +1,70 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('render', function () { + describe('engine', function () { + var view; + + beforeEach(function () { + app = new App({silent: true}); + app.engine('tmpl', require('engine-base')); + app.create('page'); + view = {contents: new Buffer('a <%= name %> b'), locals: {name: 'Halle'}}; + }); + + it('should render a view from an object:', function (done) { + app.page('a.tmpl', view) + .render(function (err, res) { + if (err) return done(err); + assert(res.contents.toString() === 'a Halle b'); + done(); + }); + }); + + it('should throw an error when a variable is undefined:', function (done) { + delete view.locals.name; + + app.page('a.tmpl', view) + .render(function (err) { + assert(err.message === 'name is not defined'); + done(); + }); + }); + + it('should re-throw an error when rethrow is true:', function (done) { + delete view.locals.name; + + app = new App({rethrow: true, silent: true}); + app.engine('tmpl', require('engine-base')); + app.create('page'); + + app.page('a.tmpl', view) + .render(function (err) { + assert(err.message === 'name is not defined'); + done(); + }); + }); + + it('should emit a re-thrown error when rethrow is true:', function (done) { + delete view.locals.name; + + app = new App({rethrow: true, silent: false}); + app.engine('tmpl', require('engine-base')); + app.create('page'); + + app.on('error', function(err) { + assert(err.message === 'name is not defined'); + done(); + }); + + app.page('a.tmpl', view) + .render(function (err) { + assert(err.message === 'name is not defined'); + }); + }); + }); +}); diff --git a/test/routes.js b/test/routes.js new file mode 100644 index 0000000..fed137f --- /dev/null +++ b/test/routes.js @@ -0,0 +1,98 @@ +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +function append(str) { + return function(view, next) { + var content = view.contents.toString(); + view.contents = new Buffer(content + ' ' + str); + next(); + }; +} +function prepend(str) { + return function(view, next) { + var content = view.contents.toString(); + view.contents = new Buffer(str + ' ' + content); + next(); + }; +} + +describe('routes', function () { + beforeEach(function () { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.create('page'); + }); + + describe('params', function () { + it('should call param function when routing', function(done) { + app.param('id', function(view, next, id) { + assert.equal(id, '123'); + next(); + }); + + app.all('/foo/:id/bar', function(view, next) { + assert.equal(view.options.params.id, '123'); + next(); + }); + + app.router.handle({ path: '/foo/123/bar' }, done); + }); + }); + + describe('onLoad middleware', function () { + it('should run when templates are loaded:', function () { + app.onLoad(/\.tmpl/, prepend('onLoad')); + app.pages('a.tmpl', { path: 'a.tmpl', content: '<%= name %>'}); + + var page = app.pages.getView('a.tmpl'); + page.contents.toString().should.equal('onLoad <%= name %>'); + }); + }); + + describe('preCompile middleware', function () { + it('should run before templates are compiled:', function () { + + }); + }); + + describe('postCompile middleware', function () { + it('should run after templates are compiled:', function () { + + }); + }); + + describe('preRender middleware', function () { + it('should run before templates are rendered:', function (done) { + app.preRender(/\.tmpl/, prepend('preRender')); + app.pages('a.tmpl', { path: 'a.tmpl', content: '<%= name %>', locals: {name: 'aaa'} }); + + var page = app.pages.getView('a.tmpl'); + page.contents.toString().should.equal('<%= name %>'); + + page.render({}, function (err, res) { + if (err) return done(err); + res.contents.toString().should.equal('preRender aaa'); + done(); + }); + }); + }); + + describe('postRender middleware', function () { + it('should run after templates are rendered:', function (done) { + app.postRender(/\.tmpl/, append('postRender')); + app.pages('a.tmpl', { path: 'a.tmpl', content: '<%= name %>', locals: {name: 'aaa' }}); + + var page = app.pages.getView('a.tmpl'); + page.contents.toString().should.equal('<%= name %>'); + + page.render({}, function (err, res) { + if (err) return done(err); + res.contents.toString().should.equal('aaa postRender'); + done(); + }); + }); + }); +}); diff --git a/test/store.js b/test/store.js new file mode 100644 index 0000000..402b082 --- /dev/null +++ b/test/store.js @@ -0,0 +1,233 @@ +'use strict'; + +require('mocha'); +require('should'); +var fs = require('fs'); +var path = require('path'); +var Store = require('data-store'); +var assert = require('assert'); +var App = require('../'); +var app; + +describe('store', function () { + beforeEach(function () { + app = new App(); + }); + + afterEach(function (cb) { + app.store.del({force: true}, function (err) { + if (err) return cb(err); + cb(); + }); + }); + + it('should create an instance of Store', function () { + assert(app.store instanceof Store); + }); + + it('should create a store at the given `cwd`', function () { + app = new App({store: {cwd: __dirname + '/actual'}}); + + app.store.set('foo', 'bar'); + assert(path.basename(app.store.path) === 'update.json'); + assert(app.store.data.hasOwnProperty('foo')); + assert(app.store.data.foo === 'bar'); + assert(fs.existsSync(path.join(__dirname, 'actual', 'update.json'))); + }); + + it('should create a store using the given `indent` value', function () { + app = new App({store: {cwd: __dirname + '/actual', indent: 0}}); + app.store.set('foo', 'bar'); + var contents = fs.readFileSync(path.join(__dirname, 'actual', 'update.json'), 'utf8'); + assert(contents === '{"foo":"bar"}'); + }); + + it('should set a value on the store', function () { + app.store.set('one', 'two'); + app.store.data.one.should.equal('two'); + }); + + it('should set an object', function () { + app.store.set({four: 'five', six: 'seven'}); + app.store.data.four.should.equal('five'); + app.store.data.six.should.equal('seven'); + }); + + it('should set a nested value', function () { + app.store.set('a.b.c.d', {e: 'f'}); + app.store.data.a.b.c.d.e.should.equal('f'); + }); + + it('should union a value onto an array on the store', function () { + app.store.union('one', 'two'); + app.store.data.one.should.eql(['two']); + }); + + it('should not union duplicate values', function () { + app.store.union('one', 'two'); + app.store.data.one.should.eql(['two']); + + app.store.union('one', ['two']); + app.store.data.one.should.eql(['two']); + }); + + it('should concat an existing array:', function () { + app.store.union('one', 'a'); + app.store.data.one.should.eql(['a']); + + app.store.union('one', ['b']); + app.store.data.one.should.eql(['a', 'b']); + + app.store.union('one', ['c', 'd']); + app.store.data.one.should.eql(['a', 'b', 'c', 'd']); + }); + + it('should return true if a key `.has()` on the store', function () { + app.store.set('foo', 'bar'); + app.store.set('baz', null); + app.store.set('qux', undefined); + + app.store.has('foo').should.eql(true); + app.store.has('bar').should.eql(false); + app.store.has('baz').should.eql(false); + app.store.has('qux').should.eql(false); + }); + + it('should return true if a nested key `.has()` on the store', function () { + app.store.set('a.b.c.d', {x: 'zzz'}); + app.store.set('a.b.c.e', {f: null}); + app.store.set('a.b.g.j', {k: undefined}); + + app.store.has('a.b.bar').should.eql(false); + app.store.has('a.b.c.d').should.eql(true); + app.store.has('a.b.c.d.x').should.eql(true); + app.store.has('a.b.c.d.z').should.eql(false); + app.store.has('a.b.c.e').should.eql(true); + app.store.has('a.b.c.e.f').should.eql(false); + app.store.has('a.b.c.e.z').should.eql(false); + app.store.has('a.b.g.j').should.eql(true); + app.store.has('a.b.g.j.k').should.eql(false); + app.store.has('a.b.g.j.z').should.eql(false); + }); + + it('should return true if a key exists `.hasOwn()` on the store', function () { + app.store.set('foo', 'bar'); + app.store.set('baz', null); + app.store.set('qux', undefined); + + app.store.hasOwn('foo').should.eql(true); + app.store.hasOwn('bar').should.eql(false); + app.store.hasOwn('baz').should.eql(true); + app.store.hasOwn('qux').should.eql(true); + }); + + it('should return true if a nested key exists `.hasOwn()` on the store', function () { + app.store.set('a.b.c.d', {x: 'zzz'}); + app.store.set('a.b.c.e', {f: null}); + app.store.set('a.b.g.j', {k: undefined}); + + app.store.hasOwn('a.b.bar').should.eql(false); + app.store.hasOwn('a.b.c.d').should.eql(true); + app.store.hasOwn('a.b.c.d.x').should.eql(true); + app.store.hasOwn('a.b.c.d.z').should.eql(false); + app.store.has('a.b.c.e.f').should.eql(false); + app.store.hasOwn('a.b.c.e.f').should.eql(true); + app.store.hasOwn('a.b.c.e.bar').should.eql(false); + app.store.has('a.b.g.j.k').should.eql(false); + app.store.hasOwn('a.b.g.j.k').should.eql(true); + app.store.hasOwn('a.b.g.j.foo').should.eql(false); + }); + + it('should `.get()` a stored value', function () { + app.store.set('three', 'four'); + app.store.get('three').should.equal('four'); + }); + + it('should `.get()` a nested value', function () { + app.store.set({a: {b: {c: 'd'}}}); + app.store.get('a.b.c').should.equal('d'); + }); + + it('should `.del()` a stored value', function () { + app.store.set('a', 'b'); + app.store.set('c', 'd'); + app.store.del('a'); + app.store.should.not.have.property('a'); + }); + + it('should `.del()` multiple stored values', function () { + app.store.set('a', 'b'); + app.store.set('c', 'd'); + app.store.set('e', 'f'); + app.store.del(['a', 'c', 'e']); + app.store.data.should.eql({}); + }); +}); + +describe('events', function () { + it('should emit `set` when an object is set:', function () { + var keys = []; + app.store.on('set', function (key) { + keys.push(key); + }); + + app.store.set({a: {b: {c: 'd'}}}); + keys.should.eql(['a']); + }); + + it('should emit `set` when a key/value pair is set:', function () { + var keys = []; + app.store.on('set', function (key) { + keys.push(key); + }); + + app.store.set('a', 'b'); + keys.should.eql(['a']); + }); + + it('should emit `set` when an object value is set:', function () { + var keys = []; + app.store.on('set', function (key) { + keys.push(key); + }); + + app.store.set('a', {b: 'c'}); + keys.should.eql(['a']); + }); + + it('should emit `set` when an array of objects is passed:', function () { + var keys = []; + app.store.on('set', function (key) { + keys.push(key); + }); + + app.store.set([{a: 'b'}, {c: 'd'}]); + keys.should.eql(['a', 'c']); + }); + + it('should emit `del` when a value is delted:', function () { + var res; + app.store.on('del', function (keys) { + keys.should.eql('a'); + assert(typeof app.store.get('a') === 'undefined'); + }); + + app.store.set('a', {b: 'c'}); + app.store.get('a').should.eql({b: 'c'}); + app.store.del('a'); + }); + + it('should emit deleted keys on `del`:', function () { + var res; + app.store.on('del', function (keys) { + keys.should.eql(['a', 'c', 'e']); + assert(Object.keys(app.store.data).length === 0); + }); + + app.store.set('a', 'b'); + app.store.set('c', 'd'); + app.store.set('e', 'f'); + app.store.data.should.have.properties(['a', 'c', 'e']); + app.store.del({force: true}); + }); +}); diff --git a/test/support/ignore.js b/test/support/ignore.js new file mode 100644 index 0000000..ef59903 --- /dev/null +++ b/test/support/ignore.js @@ -0,0 +1,6 @@ +module.exports = [ + 'addEventListener', + 'removeEventListener', + 'removeAllListeners', + 'removeListener' +]; \ No newline at end of file diff --git a/test/support/index.js b/test/support/index.js new file mode 100644 index 0000000..43491ce --- /dev/null +++ b/test/support/index.js @@ -0,0 +1,75 @@ +'use strict'; + +var path = require('path'); +var pkg = require('load-pkg'); +var lookup = require('look-up'); +var assert = require('assert'); +var ignore = require('./ignore'); +var cache = {}; + +exports.containEql = function containEql(actual, expected) { + if (Array.isArray(expected)) { + var len = expected.length; + while (len--) { + exports.containEql(actual[len], expected[len]); + } + } else { + for (var key in expected) { + assert.deepEqual(actual[key], expected[key]); + } + } +}; + +exports.keys = function keys(obj) { + var arr = []; + for (var key in obj) { + if (ignore.indexOf(key) === -1) { + arr.push(key); + } + } + return arr; +}; + +exports.resolve = function(filepath) { + filepath = filepath || ''; + var key = 'app:' + filepath; + if (cache.hasOwnProperty(key)) { + return cache[key]; + } + + var prefix = pkg.name !== 'templates' + ? 'templates' + : ''; + + var base = filepath + ? path.join(prefix, filepath) + : process.cwd(); + + var fp = tryResolve(base); + + if (typeof fp === 'undefined') { + throw new Error('cannot resolve: ' + fp); + } + return (cache[key] = require(fp)); +}; + +function tryResolve(name) { + try { + return require.resolve(name); + } catch(err) {} + + try { + return require.resolve(path.resolve(name)); + } catch(err) {} +} + +function tryRequire(name) { + try { + return require(name); + } catch(err) {} + + try { + return require(path.resolve(name)); + } catch(err) {} +} + diff --git a/test/support/spy.js b/test/support/spy.js new file mode 100644 index 0000000..e14512b --- /dev/null +++ b/test/support/spy.js @@ -0,0 +1,27 @@ +var fs = require('fs'); +var sinon = require('sinon'); + +var errorfn = false; + +function maybeCallAsync(module, func) { + var original = module[func]; + return sinon.stub(module, func, function() { + var args = Array.prototype.slice.call(arguments); + args.unshift(module, func); + var err = typeof errorfn === 'function' && + errorfn.apply(this, args); + if (!err) { + original.apply(this, arguments); + } else { + arguments[arguments.length - 1](err); + } + }); +} + +module.exports = { + setError: function(fn) { + errorfn = fn; + }, + chmodSpy: maybeCallAsync(fs, 'chmod'), + statSpy: maybeCallAsync(fs, 'stat') +}; diff --git a/test/view.content.js b/test/view.content.js new file mode 100644 index 0000000..ccd21f7 --- /dev/null +++ b/test/view.content.js @@ -0,0 +1,29 @@ +require('mocha'); +require('should'); +var fs = require('fs'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('content', function () { + beforeEach(function () { + app = new App(); + app.create('page'); + app.engine('tmpl', require('engine-base')); + }); + + it('should normalize the `content` property on a view to a string:', function (done) { + app.page('abc', {path: 'test/fixtures/templates/a.tmpl'}) + .set('read', function () { + this.contents = fs.readFileSync(this.path); + return this; + }); + + app.views.pages.abc.read(); + + assert('content' in app.views.pages.abc); + assert(typeof app.views.pages.abc.content === 'string'); + done(); + }); +}); diff --git a/test/view.events.js b/test/view.events.js new file mode 100644 index 0000000..eef1dcd --- /dev/null +++ b/test/view.events.js @@ -0,0 +1,28 @@ +require('should'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('view.option()', function () { + beforeEach(function () { + app = new App(); + app.create('page'); + }); + + it('should emit events:', function () { + app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}); + var page = app.pages.getView('a.tmpl'); + var events = []; + + page.on('option', function (key) { + events.push(key); + }); + + page.option('a', 'b'); + page.option('c', 'd'); + page.option('e', 'f'); + page.option({g: 'h'}); + + events.should.eql(['a', 'c', 'e', 'g']); + }); +}); diff --git a/test/view.js b/test/view.js new file mode 100644 index 0000000..c16e48d --- /dev/null +++ b/test/view.js @@ -0,0 +1,1151 @@ +require('mocha'); +var should = require('should'); +var fs = require('fs'); +var path = require('path'); +var assert = require('assert'); +var Stream = require('stream'); +var es = require('event-stream'); +var support = require('./support'); +var App = support.resolve(); +var View = App.View; +var view; + +describe('View', function () { + describe('instance', function () { + it('should create an instance of View:', function () { + view = new View(); + assert(view instanceof View); + }); + }); + + describe('static methods', function () { + it('should expose `extend`:', function () { + assert(typeof View.extend === 'function'); + }); + }); + + describe('prototype methods', function () { + beforeEach(function () { + view = new View(); + }); + + it('should expose `set`:', function () { + assert(typeof view.set === 'function'); + }); + it('should expose `get`:', function () { + assert(typeof view.get === 'function'); + }); + it('should expose `del`:', function () { + assert(typeof view.del === 'function'); + }); + it('should expose `define`:', function () { + assert(typeof view.define === 'function'); + }); + it('should expose `visit`:', function () { + assert(typeof view.visit === 'function'); + }); + it('should expose `compile`:', function () { + assert(typeof view.compile === 'function'); + }); + it('should expose `render`:', function () { + assert(typeof view.render === 'function'); + }); + }); + + describe('properties', function () { + it('should expose an `options` property', function () { + view = new View({}); + assert.deepEqual(view.options, {}); + assert(view.hasOwnProperty('options')); + }); + + it('should add `options` when passed on the constructor', function () { + view = new View({options: {foo: 'bar'}}); + assert(view.options.foo === 'bar'); + }); + + it('should expose a `data` property', function () { + view = new View({app: {}}); + assert.deepEqual(view.data, {}); + assert(view.hasOwnProperty('data')); + }); + + it('should add `data` when passed on the constructor', function () { + view = new View({data: {foo: 'bar'}}); + assert(view.data.foo === 'bar'); + }); + + it('should add `locals` when passed on the constructor', function () { + view = new View({locals: {foo: 'bar'}}); + assert(view.locals.foo === 'bar'); + }); + }); + + describe('set', function () { + it('should set properties on the object', function () { + view = new View(); + view.set('foo', 'bar'); + assert.equal(view.foo, 'bar'); + }); + }); + + describe('get', function () { + it('should get properties from the object', function () { + view = new View(); + view.set('foo', 'bar'); + assert.equal(view.get('foo'), 'bar'); + }); + }); + + describe('cwd', function () { + it('should get properties from the object', function () { + view = new View({cwd: 'test/fixtures'}); + assert(view.cwd === 'test/fixtures'); + }); + }); + + describe('clone', function () { + it('should clone the view:', function () { + view = new View({content: 'foo'}); + view.set({path: 'foo/bar'}); + view.set('options.one', 'two'); + var clone = view.clone(); + assert(clone.contents); + clone.set('baz', 'quux'); + clone.set('options.three', 'four'); + assert.equal(clone.get('foo'), view.get('foo')); + assert(clone.get('baz') === 'quux'); + assert(!view.get('baz')); + // not deep cloned + assert(clone.get('options.three') === 'four'); + assert(view.get('options.three') === 'four'); + }); + + it('should deep clone the entire object', function () { + view = new View({content: 'foo'}); + view.set({path: 'foo/bar'}); + view.set('options.one', 'two'); + var clone = view.clone({deep: true}); + clone.set('options.three', 'four'); + assert(view.get('options.one') === 'two'); + assert(clone.get('options.one') === 'two'); + assert(clone.get('options.three') === 'four'); + assert(!view.get('options.three')); + }); + }); + + describe('visit', function () { + it('should visit all properties on an object and call the specified method', function () { + view = new View(); + var obj = { + foo: 'bar', + bar: 'baz', + baz: 'bang' + }; + view.visit('set', obj); + assert.equal(view.get('foo'), 'bar'); + assert.equal(view.get('bar'), 'baz'); + assert.equal(view.get('baz'), 'bang'); + }); + + it('should visit all properties on all objects in an array and call the specified method', function () { + view = new View(); + var arr = [{foo: 'bar', bar: 'baz', baz: 'bang'}]; + view.visit('set', arr); + assert.equal(view.get('foo'), 'bar'); + assert.equal(view.get('bar'), 'baz'); + assert.equal(view.get('baz'), 'bang'); + }); + }); + + describe('compile', function () { + it('should get view.layout from view.data.layout', function () { + view = new View({path: 'foo', contents: 'a b c', data: {layout: 'default'}}); + assert(view.layout === 'default'); + }); + it('should get view.layout from view.options.layout', function () { + view = new View({path: 'foo', contents: 'a b c', options: {layout: 'default'}}); + assert(view.layout === 'default'); + }); + it('should get view.layout from view.locals.layout', function () { + view = new View({path: 'foo', contents: 'a b c', locals: {layout: 'default'}}); + assert(view.layout === 'default'); + }); + it('should get view.layout from the view', function () { + view = new View({path: 'foo', contents: 'a b c', layout: 'default'}); + assert(view.layout === 'default'); + }); + + it('should add a compiled function to `view.fn`', function () { + view = new View({path: 'foo', contents: 'a <%= name %> z'}); + view.compile(); + assert(typeof view.fn === 'function'); + }); + + it('should render a compiled template', function (done) { + view = new View({path: 'foo', contents: 'a <%= name %> z'}); + view.compile(); + view.render({name: 'Halle'}, function (err, res) { + if (err) return done(err); + assert(res.contents.toString() === 'a Halle z'); + done(); + }); + }); + + it('should render `fn` using data passed on the constructor', function (done) { + view = new View({ + path: 'foo', + contents: 'a <%= name %> z', + data: { + name: 'Brooke' + } + }); + + view.compile(); + view.render(function (err, res) { + if (err) return done(err); + assert(res.contents.toString() === 'a Brooke z'); + done(); + }); + }); + }); + + describe('render', function () { + it('should render a template', function (done) { + view = new View({path: 'foo', contents: 'a <%= name %> z'}); + view.render({name: 'Halle'}, function (err, res) { + if (err) return done(err); + assert(res.contents.toString() === 'a Halle z'); + done(); + }); + }); + + it('should render fn using data passed on the constructor', function (done) { + view = new View({ + path: 'foo', + contents: 'a <%= name %> z', + data: { + name: 'Brooke' + } + }); + + view.render(function (err, res) { + if (err) return done(err); + assert(res.contents.toString() === 'a Brooke z'); + done(); + }); + }); + + it('should pass errors in the callback.', function (done) { + view = new View({ + path: 'foo', + contents: 'a <%= name %> z' + }); + + view.render(function (err) { + assert(err.message === 'name is not defined'); + done(); + }); + }); + }); +}); + +/** + * The following unit tests are from Vinyl + * Since we inherit vinyl in View, we need + * to ensure that these still pass. + */ + +describe('View', function() { + describe('isVinyl()', function() { + it('should return true on a vinyl object', function(done) { + var view = new View(); + assert(View.isVinyl(view) === true); + done(); + }); + it('should return false on a normal object', function(done) { + assert(View.isVinyl({}) === false); + done(); + }); + it('should return false on a null object', function(done) { + assert(View.isVinyl({}) === false); + done(); + }); + }); + + describe('constructor()', function() { + it('should default cwd to process.cwd', function(done) { + var view = new View(); + view.cwd.should.equal(process.cwd()); + done(); + }); + + it('should default base to cwd', function(done) { + var cwd = '/'; + var view = new View({cwd: cwd}); + view.base.should.equal(cwd); + done(); + }); + + it('should default base to cwd even when none is given', function(done) { + var view = new View(); + view.base.should.equal(process.cwd()); + done(); + }); + + it('should default path to null', function(done) { + var view = new View(); + should.not.exist(view.path); + done(); + }); + + it('should default history to []', function(done) { + var view = new View(); + view.history.should.eql([]); + done(); + }); + + it('should default stat to null', function(done) { + var view = new View(); + should.not.exist(view.stat); + done(); + }); + + it('should default contents to null', function(done) { + var view = new View(); + should.not.exist(view.contents); + done(); + }); + + it('should set base to given value', function(done) { + var val = '/'; + var view = new View({base: val}); + view.base.should.equal(val); + done(); + }); + + it('should set cwd to given value', function(done) { + var val = '/'; + var view = new View({cwd: val}); + view.cwd.should.equal(val); + done(); + }); + + it('should set path to given value', function(done) { + var val = '/test.coffee'; + var view = new View({path: val}); + view.path.should.equal(val); + view.history.should.eql([val]); + done(); + }); + + it('should set history to given value', function(done) { + var val = '/test.coffee'; + var view = new View({history: [val]}); + view.path.should.equal(val); + view.history.should.eql([val]); + done(); + }); + + it('should set stat to given value', function(done) { + var val = {}; + var view = new View({stat: val}); + view.stat.should.equal(val); + done(); + }); + + it('should set contents to given value', function(done) { + var val = new Buffer('test'); + var view = new View({contents: val}); + view.contents.should.equal(val); + done(); + }); + }); + + describe('isBuffer()', function() { + it('should return true when the contents are a Buffer', function(done) { + var val = new Buffer('test'); + var view = new View({contents: val}); + view.isBuffer().should.equal(true); + done(); + }); + + it('should return false when the contents are a Stream', function(done) { + var val = new Stream(); + var view = new View({contents: val}); + view.isBuffer().should.equal(false); + done(); + }); + + it('should return false when the contents are a null', function(done) { + var view = new View({contents: null}); + view.isBuffer().should.equal(false); + done(); + }); + }); + + describe('isStream()', function() { + it('should return false when the contents are a Buffer', function(done) { + var val = new Buffer('test'); + var view = new View({contents: val}); + view.isStream().should.equal(false); + done(); + }); + + it('should return true when the contents are a Stream', function(done) { + var val = new Stream(); + var view = new View({contents: val}); + view.isStream().should.equal(true); + done(); + }); + + it('should return false when the contents are a null', function(done) { + var view = new View({contents: null}); + view.isStream().should.equal(false); + done(); + }); + }); + + describe('isNull()', function() { + it('should return false when the contents are a Buffer', function(done) { + var val = new Buffer('test'); + var view = new View({contents: val}); + view.isNull().should.equal(false); + done(); + }); + + it('should return false when the contents are a Stream', function(done) { + var val = new Stream(); + var view = new View({contents: val}); + view.isNull().should.equal(false); + done(); + }); + + it('should return true when the contents are a null', function(done) { + var view = new View({contents: null}); + view.isNull().should.equal(true); + done(); + }); + }); + + describe('isDirectory()', function() { + var fakeStat = { + isDirectory: function() { + return true; + } + }; + + it('should return false when the contents are a Buffer', function(done) { + var val = new Buffer('test'); + var view = new View({contents: val, stat: fakeStat}); + view.isDirectory().should.equal(false); + done(); + }); + + it('should return false when the contents are a Stream', function(done) { + var val = new Stream(); + var view = new View({contents: val, stat: fakeStat}); + view.isDirectory().should.equal(false); + done(); + }); + + it('should return true when the contents are a null', function(done) { + var view = new View({contents: null, stat: fakeStat}); + view.isDirectory().should.equal(true); + done(); + }); + }); + + describe('clone()', function() { + it('should copy all attributes over with Buffer', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: new Buffer('test') + }; + var view = new View(options); + var view2 = view.clone(); + + view2.should.not.equal(view, 'refs should be different'); + view2.cwd.should.equal(view.cwd); + view2.base.should.equal(view.base); + view2.path.should.equal(view.path); + view2.contents.should.not.equal(view.contents, 'buffer ref should be different'); + view2.contents.toString('utf8').should.equal(view.contents.toString('utf8')); + done(); + }); + + it('should copy buffer\'s reference with option contents: false', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.js', + contents: new Buffer('test') + }; + + var view = new View(options); + + var copy1 = view.clone({ contents: false }); + copy1.contents.should.equal(view.contents); + + var copy2 = view.clone({}); + copy2.contents.should.not.equal(view.contents); + + var copy3 = view.clone({ contents: 'any string' }); + copy3.contents.should.not.equal(view.contents); + + done(); + }); + + it('should copy all attributes over with Stream', function(done) { + var contents = new Stream.PassThrough(); + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: contents + }; + var view = new View(options); + var view2 = view.clone(); + + contents.write(new Buffer('wa')); + + process.nextTick(function() { + contents.write(new Buffer('dup')); + contents.end(); + }); + + view2.should.not.equal(view, 'refs should be different'); + view2.cwd.should.equal(view.cwd); + view2.base.should.equal(view.base); + view2.path.should.equal(view.path); + view2.contents.should.not.equal(view.contents, 'stream ref should not be the same'); + view.contents.pipe(es.wait(function(err, data) { + view2.contents.pipe(es.wait(function(err, data2) { + data2.should.not.equal(data, 'stream contents ref should not be the same'); + data2.should.eql(data, 'stream contents should be the same'); + })); + })); + done(); + }); + + it('should copy all attributes over with null', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: null + }; + var view = new View(options); + var view2 = view.clone(); + + view2.should.not.equal(view, 'refs should be different'); + view2.cwd.should.equal(view.cwd); + view2.base.should.equal(view.base); + view2.path.should.equal(view.path); + should.not.exist(view2.contents); + done(); + }); + + it('should properly clone the `stat` property', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.js', + contents: new Buffer('test'), + stat: fs.statSync(__filename) + }; + + var view = new View(options); + var copy = view.clone(); + + assert(copy.stat.isFile()); + assert(!copy.stat.isDirectory()); + assert(view.stat instanceof fs.Stats); + assert(copy.stat instanceof fs.Stats); + done(); + }); + + it('should properly clone the `history` property', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.js', + contents: new Buffer('test'), + stat: fs.statSync(__filename) + }; + + var view = new View(options); + var copy = view.clone(); + + copy.history[0].should.equal(options.path); + copy.path = 'lol'; + view.path.should.not.equal(copy.path); + done(); + }); + + it('should copy custom properties', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: null + }; + + var view = new View(options); + view.custom = { a: 'custom property' }; + var view2 = view.clone(); + + view2.should.not.equal(view, 'refs should be different'); + view2.cwd.should.equal(view.cwd); + view2.base.should.equal(view.base); + view2.path.should.equal(view.path); + view2.custom.should.equal(view.custom); + view2.custom.a.should.equal(view.custom.a); + + done(); + }); + + it('should copy history', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: null + }; + + var view = new View(options); + view.path = '/test/test.js'; + view.path = '/test/test-938di2s.js'; + var view2 = view.clone(); + + view2.history.should.eql([ + '/test/test.coffee', + '/test/test.js', + '/test/test-938di2s.js' + ]); + view2.history.should.not.equal([ + '/test/test.coffee', + '/test/test.js', + '/test/test-938di2s.js' + ]); + view2.path.should.eql('/test/test-938di2s.js'); + + done(); + }); + + it('should copy all attributes deeply', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: null + }; + + var view = new View(options); + view.custom = { a: 'custom property' }; + + var view2 = view.clone(true); + view2.custom.should.eql(view.custom); + view2.custom.should.not.equal(view.custom); + view2.custom.a.should.equal(view.custom.a); + + var view3 = view.clone({ deep: true }); + view3.custom.should.eql(view.custom); + view3.custom.should.not.equal(view.custom); + view3.custom.a.should.equal(view.custom.a); + + var view4 = view.clone(false); + view4.custom.should.eql(view.custom); + view4.custom.should.equal(view.custom); + view4.custom.a.should.equal(view.custom.a); + + var view5 = view.clone({ deep: false }); + view5.custom.should.eql(view.custom); + view5.custom.should.equal(view.custom); + view5.custom.a.should.equal(view.custom.a); + + done(); + }); + }); + + describe('pipe()', function() { + it('should write to stream with Buffer', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: new Buffer('test') + }; + var view = new View(options); + var stream = new Stream.PassThrough(); + stream.on('data', function(chunk) { + should.exist(chunk); + (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); + chunk.toString('utf8').should.equal(options.contents.toString('utf8')); + }); + stream.on('end', function() { + done(); + }); + var ret = view.pipe(stream); + ret.should.equal(stream, 'should return the stream'); + }); + + it('should pipe to stream with Stream', function(done) { + var testChunk = new Buffer('test'); + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: new Stream.PassThrough() + }; + var view = new View(options); + var stream = new Stream.PassThrough(); + stream.on('data', function(chunk) { + should.exist(chunk); + (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); + chunk.toString('utf8').should.equal(testChunk.toString('utf8')); + done(); + }); + var ret = view.pipe(stream); + ret.should.equal(stream, 'should return the stream'); + + view.contents.write(testChunk); + }); + + it('should do nothing with null', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: null + }; + var view = new View(options); + var stream = new Stream.PassThrough(); + stream.on('data', function() { + throw new Error('should not write'); + }); + stream.on('end', function() { + done(); + }); + var ret = view.pipe(stream); + ret.should.equal(stream, 'should return the stream'); + }); + + it('should write to stream with Buffer', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: new Buffer('test') + }; + var view = new View(options); + var stream = new Stream.PassThrough(); + stream.on('data', function(chunk) { + should.exist(chunk); + (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); + chunk.toString('utf8').should.equal(options.contents.toString('utf8')); + done(); + }); + stream.on('end', function() { + throw new Error('should not end'); + }); + var ret = view.pipe(stream, {end: false}); + ret.should.equal(stream, 'should return the stream'); + }); + + it('should pipe to stream with Stream', function(done) { + var testChunk = new Buffer('test'); + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: new Stream.PassThrough() + }; + var view = new View(options); + var stream = new Stream.PassThrough(); + stream.on('data', function(chunk) { + should.exist(chunk); + (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); + chunk.toString('utf8').should.equal(testChunk.toString('utf8')); + done(); + }); + stream.on('end', function() { + throw new Error('should not end'); + }); + var ret = view.pipe(stream, {end: false}); + ret.should.equal(stream, 'should return the stream'); + + view.contents.write(testChunk); + }); + + it('should do nothing with null', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: null + }; + var view = new View(options); + var stream = new Stream.PassThrough(); + stream.on('data', function() { + throw new Error('should not write'); + }); + stream.on('end', function() { + throw new Error('should not end'); + }); + var ret = view.pipe(stream, {end: false}); + ret.should.equal(stream, 'should return the stream'); + process.nextTick(done); + }); + }); + + describe('inspect()', function() { + it('should return correct format when no contents and no path', function(done) { + var view = new View(); + view.inspect().should.equal(''); + done(); + }); + + it('should return correct format when Buffer and no path', function(done) { + var val = new Buffer('test'); + var view = new View({ + contents: val + }); + view.inspect().should.equal('>'); + done(); + }); + + it('should return correct format when Buffer and relative path', function(done) { + var val = new Buffer('test'); + var view = new View({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: val + }); + view.inspect().should.equal('>'); + done(); + }); + + it('should return correct format when Buffer and only path and no base', function(done) { + var val = new Buffer('test'); + var view = new View({ + cwd: '/', + path: '/test/test.coffee', + contents: val + }); + delete view.base; + view.inspect().should.equal('>'); + done(); + }); + + it('should return correct format when Stream and relative path', function(done) { + var view = new View({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: new Stream.PassThrough() + }); + view.inspect().should.equal('>'); + done(); + }); + + it('should return correct format when null and relative path', function(done) { + var view = new View({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: null + }); + view.inspect().should.equal(''); + done(); + }); + }); + + describe('contents get/set', function() { + it('should work with Buffer', function(done) { + var val = new Buffer('test'); + var view = new View(); + view.contents = val; + view.contents.should.equal(val); + done(); + }); + + it('should work with Stream', function(done) { + var val = new Stream.PassThrough(); + var view = new View(); + view.contents = val; + view.contents.should.equal(val); + done(); + }); + + it('should work with null', function(done) { + var val = null; + var view = new View(); + view.contents = val; + (view.contents === null).should.equal(true); + done(); + }); + + it('should work with string', function(done) { + var val = 'test'; + var view = new View(); + view.contents = val; + view.contents.should.deepEqual(new Buffer(val)); + done(); + }); + }); + + describe('relative get/set', function() { + it('should error on set', function(done) { + var view = new View(); + try { + view.relative = 'test'; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should error on get when no base', function(done) { + var a; + var view = new View(); + delete view.base; + try { + a = view.relative; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should error on get when no path', function(done) { + var a; + var view = new View(); + try { + a = view.relative; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should return a relative path from base', function(done) { + var view = new View({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee' + }); + view.relative.should.equal('test.coffee'); + done(); + }); + + it('should return a relative path from cwd', function(done) { + var view = new View({ + cwd: '/', + path: '/test/test.coffee' + }); + view.relative.should.equal(path.join('test','test.coffee')); + done(); + }); + }); + + describe('dirname get/set', function() { + it('should error on get when no path', function(done) { + var a; + var view = new View(); + try { + a = view.dirname; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should return the dirname of the path', function(done) { + var view = new View({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee' + }); + view.dirname.should.equal('/test'); + done(); + }); + + it('should error on set when no path', function(done) { + var view = new View(); + try { + view.dirname = '/test'; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should set the dirname of the path', function(done) { + var view = new View({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee' + }); + view.dirname = '/test/foo'; + view.path.should.equal('/test/foo/test.coffee'); + done(); + }); + }); + + describe('basename get/set', function() { + it('should error on get when no path', function(done) { + var a; + var view = new View(); + try { + a = view.basename; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should return the basename of the path', function(done) { + var view = new View({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee' + }); + view.basename.should.equal('test.coffee'); + done(); + }); + + it('should error on set when no path', function(done) { + var view = new View(); + try { + view.basename = 'test.coffee'; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should set the basename of the path', function(done) { + var view = new View({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee' + }); + view.basename = 'foo.png'; + view.path.should.equal('/test/foo.png'); + done(); + }); + }); + + describe('extname get/set', function() { + it('should error on get when no path', function(done) { + var a; + var view = new View(); + try { + a = view.extname; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should return the extname of the path', function(done) { + var view = new View({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee' + }); + view.extname.should.equal('.coffee'); + done(); + }); + + it('should error on set when no path', function(done) { + var view = new View(); + try { + view.extname = '.coffee'; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should set the extname of the path', function(done) { + var view = new View({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee' + }); + view.extname = '.png'; + view.path.should.equal('/test/test.png'); + done(); + }); + }); + + describe('path get/set', function() { + + it('should record history when instantiation', function() { + var view = new View({ + cwd: '/', + path: '/test/test.coffee' + }); + + view.path.should.eql('/test/test.coffee'); + view.history.should.eql(['/test/test.coffee']); + }); + + it('should record history when path change', function() { + var view = new View({ + cwd: '/', + path: '/test/test.coffee' + }); + + view.path = '/test/test.js'; + view.path.should.eql('/test/test.js'); + view.history.should.eql(['/test/test.coffee', '/test/test.js']); + + view.path = '/test/test.coffee'; + view.path.should.eql('/test/test.coffee'); + view.history.should.eql(['/test/test.coffee', '/test/test.js', '/test/test.coffee']); + }); + + it('should not record history when set the same path', function() { + var view = new View({ + cwd: '/', + path: '/test/test.coffee' + }); + + view.path = '/test/test.coffee'; + view.path = '/test/test.coffee'; + view.path.should.eql('/test/test.coffee'); + view.history.should.eql(['/test/test.coffee']); + + // ignore when set empty string + view.path = ''; + view.path.should.eql('/test/test.coffee'); + view.history.should.eql(['/test/test.coffee']); + }); + + it('should throw when set path null in constructor', function() { + (function() { + new View({ + cwd: '/', + path: null + }); + }).should.throw('path should be string'); + }); + + it('should throw when set path null', function() { + var view = new View({ + cwd: '/', + path: 'foo' + }); + + (function() { + view.path = null; + }).should.throw('path should be string'); + }); + }); +}); diff --git a/test/view.methods.js b/test/view.methods.js new file mode 100644 index 0000000..bb120c6 --- /dev/null +++ b/test/view.methods.js @@ -0,0 +1,40 @@ +require('should'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('view.option()', function () { + beforeEach(function () { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.create('page'); + }); + + + describe('.use', function () { + it('should expose `.use` for running plugins on a view:', function () { + app.page('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}) + .use(function () { + this.options.foo = 'bar'; + }) + .use(function () { + this.options.bar = 'baz'; + }); + + var page = app.pages.getView('a.tmpl'); + page.options.should.have.property('foo'); + page.options.should.have.property('bar'); + }); + }); + + describe('.render:', function () { + it('should expose `.render` for rendering a view:', function (done) { + app.page('a.tmpl', {path: 'a.tmpl', content: '<%= a %>', locals: {a: 'bbb'}}) + .render({}, function (err, res) { + if (err) return done(err); + res.contents.toString().should.equal('bbb'); + done(); + }); + }); + }); +}); diff --git a/test/view.option.js b/test/view.option.js new file mode 100644 index 0000000..4190be7 --- /dev/null +++ b/test/view.option.js @@ -0,0 +1,29 @@ +require('should'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('view.option()', function () { + beforeEach(function () { + app = new App(); + app.create('page'); + }); + + it('should set an option:', function () { + app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}); + var page = app.pages.getView('a.tmpl'); + + page.options.should.not.have.property('foo'); + page.option('foo', 'bar'); + page.options.should.have.property('foo'); + }); + + it('should extend options:', function () { + app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}); + var page = app.pages.getView('a.tmpl'); + page.option('a', 'b'); + page.option('c', 'd'); + page.option('e', 'f'); + page.options.should.have.properties(['a', 'c', 'e']); + }); +}); diff --git a/test/view.render.js b/test/view.render.js new file mode 100644 index 0000000..5fe5c7d --- /dev/null +++ b/test/view.render.js @@ -0,0 +1,54 @@ +require('mocha'); +require('should'); +var support = require('./support'); +var App = support.resolve(); +var View = App.View; +var view, app; + +describe('helpers', function () { + describe('rendering', function () { + beforeEach(function () { + app = new App(); + view = new View(); + app.engine('tmpl', require('engine-base')); + app.create('layouts', {viewType: 'layout'}); + app.create('pages'); + }); + + it('should expose `.render` for rendering a view:', function (done) { + app.page('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}) + .render({a: 'bbb'}, function (err, res) { + if (err) return done(err); + res.content.should.equal('bbb'); + done(); + }); + }); + + it('should render a view with a layout', function (done) { + app.layout('default.tmpl', {content: 'a {% body %} b'}); + app.page('a.tmpl', {content: '<%= title %>', layout: 'default.tmpl'}) + .render({title: 'zzz'}, function (err, res) { + if (err) return done(err); + res.content.should.equal('a zzz b'); + done(); + }); + }); + + it('should render a view with a layout', function (done) { + app.layout('foo.tmpl', {content: 'a {% body %} a'}); + app.layout('bar.tmpl', {content: 'b {% body %} b'}); + app.pages('a.tmpl', {content: '<%= title %>'}); + + app.pages.getView('a.tmpl') + .option('resolveLayout', function () { + return 'bar.tmpl'; + }) + .render({title: 'zzz'}, function (err, res) { + if (err) return done(err); + res.content.should.equal('b zzz b'); + done(); + }); + }); + }); +}); + diff --git a/test/view.set.js b/test/view.set.js new file mode 100644 index 0000000..60eb98a --- /dev/null +++ b/test/view.set.js @@ -0,0 +1,34 @@ +require('mocha'); +require('should'); +var fs = require('fs'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('set', function () { + beforeEach(function () { + app = new App(); + app.create('page'); + app.engine('tmpl', require('engine-base')); + }); + + it('should set a property on a view:', function (done) { + app.page('abc', {path: 'test/fixtures/templates/a.tmpl'}) + .set('read', function () { + this.contents = fs.readFileSync(this.path); + return this; + }); + + assert('read' in app.views.pages.abc); + app.views.pages.abc + .read() + .set('data.name', 'Brooke') + .render(function (err, res) { + if (err) return done(err); + + assert(res.content === 'Brooke'); + done(); + }); + }); +}); diff --git a/test/view.use.js b/test/view.use.js new file mode 100644 index 0000000..3706c0b --- /dev/null +++ b/test/view.use.js @@ -0,0 +1,60 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var View = App.View; +var view; + +describe('view.use', function () { + beforeEach(function () { + view = new View(); + }); + + it('should expose the instance to `use`:', function (done) { + view.use(function (inst) { + assert(inst instanceof View); + done(); + }); + }); + + it('should be chainable:', function (done) { + view.use(function (inst) { + assert(inst instanceof View); + }) + .use(function (inst) { + assert(inst instanceof View); + }) + .use(function (inst) { + assert(inst instanceof View); + done(); + }); + }); + + it('should expose the view to a plugin:', function () { + view.use(function (view) { + assert(view instanceof View); + view.foo = function (str) { + return str + ' ' + 'bar'; + }; + }); + assert(view.foo('foo') === 'foo bar'); + }); + + it('should be chainable:', function () { + view + .use(function (view) { + view.a = 'aaa'; + }) + .use(function (view) { + view.b = 'bbb'; + }) + .use(function (view) { + view.c = 'ccc'; + }); + + assert(view.a === 'aaa'); + assert(view.b === 'bbb'); + assert(view.c === 'ccc'); + }); +}); diff --git a/test/viewTypes.js b/test/viewTypes.js new file mode 100644 index 0000000..47da0a9 --- /dev/null +++ b/test/viewTypes.js @@ -0,0 +1,33 @@ +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('viewType', function () { + describe('view types', function () { + beforeEach(function () { + app = new App(); + app.engine('tmpl', require('engine-base')); + }); + + it('should add collection (plural) to the `viewTypes` object', function () { + app.viewTypes = []; // reset + app.create('foo', {viewType: 'layout'}); + app.create('bar', {viewType: 'layout'}); + assert.deepEqual(app.viewTypes.layout, [ 'foos', 'bars' ]); + + app.create('baz', {viewType: 'renderable'}); + assert.deepEqual(app.viewTypes.renderable, [ 'bazs' ]); + }); + + it('should add collection to the given viewType', function () { + app.create('layout', {viewType: 'layout'}); + assert(app.layouts.options.viewType[0] === 'layout'); + }); + + it('should add a collection to multiple viewTypes', function () { + app.create('foo', {viewType: ['layout', 'renderable']}); + assert.deepEqual(app.foos.options.viewType, ['layout', 'renderable']); + }); + }); +}); diff --git a/test/views.js b/test/views.js new file mode 100644 index 0000000..2a88024 --- /dev/null +++ b/test/views.js @@ -0,0 +1,445 @@ +require('mocha'); +require('should'); +var path = require('path'); +var assert = require('assert'); +var typeOf = require('kind-of'); +var isBuffer = require('is-buffer'); +var support = require('./support'); +var App = support.resolve(); +var List = App.List; +var View = App.View; +var Views = App.Views; +var collection; + +describe('views', function () { + describe('constructor', function () { + it('should create an instance of Views:', function () { + var collection = new Views(); + assert(collection instanceof Views); + }); + + it('should instantiate without `new`:', function () { + var collection = Views(); + assert(collection instanceof Views); + }); + }); + + describe('static methods', function () { + it('should expose `extend`:', function () { + assert(typeof Views.extend ==='function'); + }); + }); + + describe('prototype methods', function () { + beforeEach(function() { + collection = new Views(); + }); + + var methods = [ + 'use', + 'setView', + 'addView', + 'addViews', + 'addList', + 'getView', + 'constructor', + 'set', + 'get', + 'del', + 'define', + 'visit', + 'on', + 'once', + 'off', + 'emit', + 'listeners', + 'hasListeners' + ]; + + methods.forEach(function (method) { + it('should expose ' + method + ' method', function () { + assert(typeof collection[method] === 'function'); + }); + }); + + it('should expose isCollection property', function () { + assert(typeof collection.isCollection === 'boolean'); + }); + + it('should expose queue property', function () { + assert(Array.isArray(collection.queue)); + }); + + it('should expose views property', function () { + assert(typeOf(collection.views) === 'object'); + }); + + it('should expose options property', function () { + assert(typeOf(collection.options) === 'object'); + }); + }); + + describe('instance', function () { + beforeEach(function() { + collection = new Views(); + }); + + it('should set a value on the instance:', function () { + collection.set('a', 'b'); + assert(collection.a ==='b'); + }); + + it('should get a value from the instance:', function () { + collection.set('a', 'b'); + assert(collection.get('a') ==='b'); + }); + }); + + describe('option', function() { + beforeEach(function() { + collection = new Views(); + }); + + it('should set a key/value pair on options:', function () { + collection.option('a', 'b'); + assert(collection.options.a === 'b'); + }); + + it('should set an object on options:', function () { + collection.option({c: 'd'}); + assert(collection.options.c === 'd'); + }); + + it('should get an option:', function () { + collection.option({c: 'd'}); + var c = collection.option('c'); + assert(c === 'd'); + }); + }); + + describe('addView', function() { + beforeEach(function() { + collection = new Views(); + }); + + it('should throw an error when args are invalid:', function () { + (function () { + collection.addView(function() {}); + }).should.throw('expected value to be an object.'); + }); + + it('should add a view to `views`:', function () { + collection.addView('foo'); + collection.views.should.have.property('foo'); + + collection.addView('one', {content: '...'}); + assert(typeof collection.views.one === 'object'); + assert(isBuffer(collection.views.one.contents)); + }); + + it('should create an instance of `View`:', function () { + collection.addView('one', {content: '...'}); + assert(collection.views.one instanceof collection.View); + }); + + it('should allow an `View` constructor to be passed:', function () { + View.prototype.foo = function(key, value) { + this[key] = value; + }; + collection = new Views({View: View}); + collection.addView('one', {content: '...'}); + collection.views.one.foo('bar', 'baz'); + assert(collection.views.one.bar === 'baz'); + }); + + it('should allow an instance of `View` to be passed:', function () { + var collection = new Views({View: View}); + var view = new View({content: '...'}); + collection.addView('one', view); + view.set('abc', 'xyz'); + assert(collection.views.one instanceof collection.View); + assert(isBuffer(collection.views.one.contents)); + assert(collection.views.one.abc === 'xyz'); + }); + }); + + describe('addViews', function() { + beforeEach(function() { + collection = new Views(); + }); + + it('should add multiple views:', function () { + collection.addViews({ + one: {content: 'foo'}, + two: {content: 'bar'} + }); + assert(isBuffer(collection.views.one.contents)); + assert(isBuffer(collection.views.two.contents)); + }); + + it('should create views from an instance of Views', function () { + collection.addViews({ + one: {content: 'foo'}, + two: {content: 'bar'} + }); + var pages = new Views(collection); + assert(isBuffer(pages.views.one.contents)); + assert(isBuffer(pages.views.two.contents)); + }); + + it('should add an array of views:', function () { + collection.addViews([ + {path: 'one', content: 'foo'}, + {path: 'two', content: 'bar'} + ]); + assert(isBuffer(collection.views.one.contents)); + assert(isBuffer(collection.views.two.contents)); + }); + }); + + describe('view', function() { + beforeEach(function() { + collection = new Views(); + }); + + it('should return a single collection view from a key-value pair', function () { + var one = collection.view('one', {content: 'foo'}); + var two = collection.view('two', {content: 'bar'}); + + assert(one.isView); + assert(one.path === 'one'); + assert(two.isView); + assert(two.path === 'two'); + }); + + it('should return a single collection view from an object', function () { + var one = collection.view({path: 'one', content: 'foo'}); + var two = collection.view({path: 'two', content: 'bar'}); + + assert(one.isView); + assert(one.path === 'one'); + assert(two.isView); + assert(two.path === 'two'); + }); + }); + + describe('addList', function() { + beforeEach(function() { + collection = new Views(); + }); + + it('should add a list of views:', function () { + collection.addList([ + {path: 'one', content: 'foo'}, + {path: 'two', content: 'bar'} + ]); + assert(isBuffer(collection.views.one.contents)); + assert(isBuffer(collection.views.two.contents)); + }); + + it('should add a list of views from the constructor:', function () { + var list = new List([ + {path: 'one', content: 'foo'}, + {path: 'two', content: 'bar'} + ]); + + collection = new Views(list); + assert(isBuffer(collection.views.one.contents)); + assert(isBuffer(collection.views.two.contents)); + }); + + it('should throw an error when list is not an array:', function () { + var views = new Views(); + (function () { + views.addList(); + }).should.throw('expected list to be an array.'); + + (function () { + views.addList({}); + }).should.throw('expected list to be an array.'); + + (function () { + views.addList('foo'); + }).should.throw('expected list to be an array.'); + }); + + it('should load an array of items from an event:', function () { + var collection = new Views(); + + collection.on('addList', function (list) { + while (list.length) { + collection.addView({path: list.pop()}); + } + this.loaded = true; + }); + + collection.addList(['a.txt', 'b.txt', 'c.txt']); + assert(collection.views.hasOwnProperty('a.txt')); + assert(collection.views['a.txt'].path === 'a.txt'); + }); + + it('should load an array of items from the addList callback:', function () { + var collection = new Views(); + + collection.addList(['a.txt', 'b.txt', 'c.txt'], function (fp) { + return {path: fp}; + }); + assert(collection.views.hasOwnProperty('a.txt')); + assert(collection.views['a.txt'].path === 'a.txt'); + }); + + // it('should not blow up on', function () { + // var collection = new Views(); + // var list = [{path: 'a.txt'}, {path: 'b.txt'}, {path: 'c.txt'}]; + // collection.addList(list, function (item) { + // item.content = path.basename(item.path, path.extname(item.path)); + // }); + // assert(collection.views.hasOwnProperty('a.txt')); + // assert(collection.views['a.txt'].path === 'a.txt'); + // assert(collection.views['a.txt'].content === 'a'); + // }); + + it('should load an object of views from an event:', function () { + var collection = new Views(); + + collection.on('addViews', function (views) { + for (var key in views) { + collection.addView('foo/' + key, views[key]); + delete views[key]; + } + }); + + collection.addViews({ + a: {path: 'a.txt'}, + b: {path: 'b.txt'}, + c: {path: 'c.txt'} + }); + + assert(collection.views.hasOwnProperty('foo/a')); + assert(collection.views['foo/a'].path === 'a.txt'); + }); + + it('should signal `loaded` when finished:', function () { + var collection = new Views(); + + collection.on('addViews', function (views) { + for (var key in views) { + if (key === 'c') break; + collection.addView('foo/' + key, views[key]); + } + }); + + collection.addViews({ + a: {path: 'a.txt'}, + b: {path: 'b.txt'}, + c: {path: 'c.txt'} + }); + + assert(collection.views.hasOwnProperty('foo/a')); + assert(!collection.views.hasOwnProperty('foo/c')); + assert(collection.views['foo/a'].path === 'a.txt'); + }); + }); + + describe('getView', function() { + beforeEach(function() { + collection = new Views(); + }); + it('should get a view from `views`:', function () { + collection.addView('one', {content: 'aaa'}); + collection.addView('two', {content: 'zzz'}); + assert(isBuffer(collection.views.one.contents)); + assert(isBuffer(collection.getView('one').contents)); + assert(collection.getView('one').contents.toString() === 'aaa'); + assert(collection.getView('two').contents.toString() === 'zzz'); + }); + }); + + describe('count', function() { + beforeEach(function() { + collection = new Views(); + }); + + it('should get the number of views:', function () { + collection.addView('one', {content: 'aaa'}); + collection.addView('two', {content: 'zzz'}); + assert(Object.keys(collection.views).length === 2); + }); + }); +}); + +describe('options', function() { + describe('options.renameKey', function() { + beforeEach(function() { + collection = new Views({ + renameKey: function (key) { + return path.basename(key); + } + }); + }); + + it('should use a custom rename key function on view keys', function() { + collection.addView('a/b/c/d.hbs', {content: 'foo bar baz'}); + assert(collection.views['d.hbs'].contents.toString() === 'foo bar baz'); + }); + + it('should get a view with the renamed key:', function () { + collection.addView('a/b/c/d.hbs', {content: 'foo bar baz'}); + assert(collection.getView('d.hbs').contents.toString() === 'foo bar baz'); + }); + + it('should get a view with the original key:', function () { + collection.addView('a/b/c/d.hbs', {content: 'foo bar baz'}); + assert(collection.getView('a/b/c/d.hbs').contents.toString() === 'foo bar baz'); + }); + }); +}); + + +describe('queue', function () { + beforeEach(function () { + collection = new Views(); + }); + + it('should emit arguments on addView', function (done) { + collection.on('addView', function (args) { + assert(args[0] === 'a'); + assert(args[1] === 'b'); + assert(args[2] === 'c'); + assert(args[3] === 'd'); + assert(args[4] === 'e'); + done(); + }); + + collection.addView('a', 'b', 'c', 'd', 'e'); + }); + + it('should expose the `queue` property for loading views', function () { + collection.queue.push(collection.view('b', {path: 'b'})); + + collection.addView('a', {path: 'a'}); + assert(collection.views.hasOwnProperty('a')); + assert(collection.views.hasOwnProperty('b')); + }); + + it('should load all views on the queue when addView is called', function () { + collection.on('addView', function (args) { + var len = args.length; + var last = args[len - 1]; + if (typeof last === 'string') { + args[len - 1] = { content: last }; + } + }); + + collection.addView('a.html', 'aaa'); + collection.addView('b.html', 'bbb'); + collection.addView('c.html', 'ccc'); + + assert(collection.views.hasOwnProperty('a.html')); + assert(collection.getView('a.html').content === 'aaa'); + assert(collection.views.hasOwnProperty('b.html')); + assert(collection.getView('b.html').content === 'bbb'); + assert(collection.views.hasOwnProperty('c.html')); + assert(collection.getView('c.html').content === 'ccc'); + }); +}); \ No newline at end of file diff --git a/updatefile.js b/updatefile.js deleted file mode 100644 index c69569a..0000000 --- a/updatefile.js +++ /dev/null @@ -1,14 +0,0 @@ -var update = require('./'); -var del = require('del'); - -update.task('default', function () { - console.log('default...'); -}); - -update.task('one', function () { - console.log('one...'); -}); - -update.task('two', function () { - console.log('two...'); -}); From 812f542bb6f4f76501d250224150146c1202816f Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 20 Oct 2015 08:02:13 -0400 Subject: [PATCH 099/274] lint, update defaults --- index.js | 12 +++++------- lib/index.js | 27 ++++++++++++++++++++++++++- lib/locals.js | 23 ----------------------- lib/plugins/config.js | 3 +-- lib/plugins/locals.js | 29 ++++++++++++++++++++--------- lib/plugins/questions.js | 34 ---------------------------------- lib/plugins/reloadViews.js | 34 ---------------------------------- lib/plugins/store.js | 15 +++++++-------- lib/updaters/copyright.js | 3 ++- lib/updaters/license.js | 25 +++++++++++-------------- lib/updaters/rename.js | 32 +++++++++++--------------------- lib/utils.js | 24 ------------------------ package.json | 7 +++---- test/app.collection.js | 2 +- test/app.create.js | 2 +- test/fixtures/templates/a.tmpl | 2 +- test/view.set.js | 2 +- 17 files changed, 90 insertions(+), 186 deletions(-) delete mode 100644 lib/locals.js delete mode 100644 lib/plugins/questions.js delete mode 100644 lib/plugins/reloadViews.js delete mode 100644 lib/utils.js diff --git a/index.js b/index.js index 804c788..082d8d2 100644 --- a/index.js +++ b/index.js @@ -10,7 +10,7 @@ var ask = require('assemble-ask'); var Core = require('assemble-core'); var plugin = require('./lib/plugins'); -var utils = require('./lib/utils'); +var utils = require('./lib/'); function Update(options) { if (!(this instanceof Update)) { @@ -33,15 +33,13 @@ Core.extend(Update); Update.prototype.initUpdater = function() { this.use(plugin.locals({name: 'update'})); this.use(plugin.store({name: 'update'})); - // this.use(plugin.config()); + this.use(plugin.config()); // this.use(plugin.reloadViews()); this.use(ask()); - this.engine('md', require('engine-base'), { - delims: ['{%', '%}'] - }); - - this.onLoad(/\.md$/, function (view, next) { + this.engine(['md', 'tmpl'], require('engine-base')); + this.onLoad(/\.(md|tmpl)$/, function (view, next) { + view.content = view.contents.toString(); utils.matter.parse(view, next); }); }; diff --git a/lib/index.js b/lib/index.js index 23b2930..62ad8e6 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1 +1,26 @@ -module.exports = require('export-files')(__dirname); +'use strict'; + +/** + * Lazily required module dependencies + */ + +var utils = require('lazy-cache')(require); +var fn = require; +require = utils; + +// type utils +require('for-own'); +require('mixin-deep', 'merge'); + +// engine/template utiles +require('parser-front-matter', 'matter'); +require('question-cache', 'questions'); +require('resolve-dir', 'resolve'); +require('data-store', 'store'); +require = fn; + +/** + * Expose utils + */ + +module.exports = utils; diff --git a/lib/locals.js b/lib/locals.js deleted file mode 100644 index 6ff09c2..0000000 --- a/lib/locals.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; - -var get = require('get-value'); -var set = require('set-value'); - -function Locals(name, app) { - this.cache = get(app.cache.data, name) || (app.cache.data[name] = {}); -} - -Locals.prototype.get = function(key) { - return get(this.cache, key); -}; - -Locals.prototype.set = function(key, value) { - set(this.cache, key, value); - return this; -}; - -/** - * Expose Locals - */ - -module.exports = Locals; diff --git a/lib/plugins/config.js b/lib/plugins/config.js index 064fde1..b610b4f 100644 --- a/lib/plugins/config.js +++ b/lib/plugins/config.js @@ -1,8 +1,7 @@ 'use strict'; var Config = require('map-config'); -var lib = require('..'); -var utils = lib.utils; +var utils = require('..'); module.exports = function (options) { return function (app) { diff --git a/lib/plugins/locals.js b/lib/plugins/locals.js index d1953fc..35f443f 100644 --- a/lib/plugins/locals.js +++ b/lib/plugins/locals.js @@ -1,19 +1,30 @@ +'use strict'; -var lib = require('..'); -var Locals = lib.locals; -var utils = lib.utils; +var get = require('get-value'); +var set = require('set-value'); +var utils = require('..'); module.exports = function (config) { - config = config || {}; - var name = config.name; - if (!name) { + if (!config || !config.name) { throw new Error('expected config.name to be a string.'); } return function (app) { - var opts = app.option('update') || {}; - opts = utils.merge({}, config, opts); - this.locals = new Locals(name, this); + var opts = utils.merge({}, config, app.option('update')); + this.locals = new Locals(config.name, this); return this; }; }; + +function Locals(name, app) { + this.cache = get(app.cache.data, name) || (app.cache.data[name] = {}); +} + +Locals.prototype.get = function(key) { + return get(this.cache, key); +}; + +Locals.prototype.set = function(key, value) { + set(this.cache, key, value); + return this; +}; diff --git a/lib/plugins/questions.js b/lib/plugins/questions.js deleted file mode 100644 index bd82ad1..0000000 --- a/lib/plugins/questions.js +++ /dev/null @@ -1,34 +0,0 @@ - -var lib = require('../'); -var utils = lib.utils; - -module.exports = function (options) { - return function (app) { - var opts = utils.merge({}, options, app.get('options.questions')); - this.questions = utils.questions(opts); - - /** - * Ask a question, or use a pre-existing value - * to populate the answer. - */ - - this.ask = function (locals) { - var ctx = utils.merge({}, this.cache.data, locals || {}); - return utils.ask({ - questions: this.questions, - store: this.store, - data: ctx - }); - }; - - /** - * Set a question to ask at a later point. - */ - - this.question = function () { - this.questions.set.apply(this.questions, arguments); - return this; - }; - return this; - }; -}; diff --git a/lib/plugins/reloadViews.js b/lib/plugins/reloadViews.js deleted file mode 100644 index 0619488..0000000 --- a/lib/plugins/reloadViews.js +++ /dev/null @@ -1,34 +0,0 @@ -'use strict'; - -/** - * Reload views when a user changes settings. - * - * Initializes event listeners to listen for events - * that indicate if something needs to be re-initialized - * based on user options. - */ - -module.exports = function (key) { - return function (app) { - this.only('reloadViews', 'option', function (key) { - reloadViews(app, key); - }); - - this.only('reloadViews', 'use', function () { - reloadViews(app); - }); - - function reloadViews(key) { - for (var name in app.views) { - if (app.views.hasOwnProperty(name)) { - var views = app.views[name]; - - if (!key || typeof app[name][key] !== 'function') { - app.create(name, app[name].options); - app[name].addViews(views); - } - } - } - } - }; -}; diff --git a/lib/plugins/store.js b/lib/plugins/store.js index b9b6596..9514993 100644 --- a/lib/plugins/store.js +++ b/lib/plugins/store.js @@ -1,15 +1,14 @@ -var lib = require('../'); -var utils = lib.utils; +var utils = require('..'); module.exports = function (config) { - config = config || {}; - var name = config.name; + if (!config || !config.name) { + throw new Error('expected config.name to be a string.'); + } return function (app) { - var opts = app.option('store') || {}; - opts = utils.merge({}, config, opts); - this.store = utils.store(name, opts); + var opts = utils.merge({}, config, app.option('store')); + this.store = utils.store(config.name, opts); return this; - } + }; }; diff --git a/lib/updaters/copyright.js b/lib/updaters/copyright.js index 336c980..5768c9f 100644 --- a/lib/updaters/copyright.js +++ b/lib/updaters/copyright.js @@ -1,8 +1,9 @@ var fs = require('fs'); +var pkg = require('load-pkg')(); var copyright = require('update-copyright'); var str = fs.readFileSync('LICENSE', 'utf8'); var updated = copyright(str, pkg); -console.log(updated) +console.log(updated); diff --git a/lib/updaters/license.js b/lib/updaters/license.js index d4454dd..08b8cff 100644 --- a/lib/updaters/license.js +++ b/lib/updaters/license.js @@ -1,12 +1,9 @@ var fs = require('fs'); var del = require('delete'); -var async = require('async'); +var writeFile = require('write'); var green = require('ansi-green'); var success = require('success-symbol'); -var copyright = require('update-copyright'); -var writeFile = require('write'); -var pkg = require('load-pkg')(); var cwd = require('cwd'); function update(filepath, cb) { @@ -32,13 +29,13 @@ function update(filepath, cb) { }); } -// update('LICENSE', function (err, res) { -// if (err) { -// return console.error(err); -// } -// var msg = ' LICENSE is already up to date.'; -// if (res === 'updated') { -// msg = ' updated LICENSE'; -// } -// console.log(green(success), msg); -// }); +update('LICENSE', function (err, res) { + if (err) { + return console.error(err); + } + var msg = ' LICENSE is already up to date.'; + if (res === 'updated') { + msg = ' updated LICENSE'; + } + console.log(green(success), msg); +}); diff --git a/lib/updaters/rename.js b/lib/updaters/rename.js index 3fdba81..51f5292 100644 --- a/lib/updaters/rename.js +++ b/lib/updaters/rename.js @@ -1,13 +1,8 @@ var fs = require('fs'); -var del = require('delete'); var async = require('async'); var green = require('ansi-green'); var success = require('success-symbol'); -var copyright = require('update-copyright'); -var writeFile = require('write'); -var pkg = require('load-pkg')(); -var cwd = require('cwd'); function renameFiles(files, cb) { async.eachSeries(Object.keys(files), function (key, next) { @@ -21,19 +16,14 @@ var files = { 'README.md': 'readme.md' }; -// renameFiles(files, function(err) { -// if (err) { -// if (err.code !== 'ENOENT') { -// console.error(err); -// } -// return; -// } -// var keys = Object.keys(files); -// var len = keys.length; -// console.log(green(success), ' renamed ' + len, 'files'); -// }); - -var str = fs.readFileSync('LICENSE', 'utf8'); -var updated = copyright(str, pkg); - -console.log(updated) +renameFiles(files, function(err) { + if (err) { + if (err.code !== 'ENOENT') { + console.error(err); + } + return; + } + var keys = Object.keys(files); + var len = keys.length; + console.log(green(success), ' renamed ' + len, 'files'); +}); diff --git a/lib/utils.js b/lib/utils.js deleted file mode 100644 index 63d2c57..0000000 --- a/lib/utils.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict'; - -/** - * Lazily required module dependencies - */ - -var lazy = require('lazy-cache')(require); - -// type utils -lazy('mixin-deep', 'merge'); -lazy('for-own'); - -// engine/template utiles -lazy('ask-once', 'ask'); -lazy('parser-front-matter', 'matter'); -lazy('question-cache', 'questions'); -lazy('data-store', 'store'); -lazy('resolve-dir', 'resolve'); - -/** - * Expose utils - */ - -module.exports = lazy; diff --git a/package.json b/package.json index a0ecb53..b9fb97e 100644 --- a/package.json +++ b/package.json @@ -22,9 +22,9 @@ }, "dependencies": { "ansi-green": "^0.1.1", - "ask-once": "^0.5.1", "assemble-ask": "^0.1.2", "assemble-core": "^0.1.1", + "async": "^1.4.2", "cwd": "^0.8.4", "data-store": "^0.10.1", "delete": "^0.2.1", @@ -34,6 +34,7 @@ "lazy-cache": "^0.2.3", "load-pkg": "^2.0.1", "map-config": "^0.2.0", + "parser-front-matter": "^1.3.0", "question-cache": "^0.3.1", "resolve-dir": "^0.1.0", "set-value": "^0.2.0", @@ -42,7 +43,6 @@ "write": "^0.2.1" }, "devDependencies": { - "async": "^1.4.2", "base-methods": "^0.2.14", "buffer-equal": "0.0.1", "consolidate": "^0.13.1", @@ -50,7 +50,7 @@ "define-property": "^0.2.5", "engine-base": "^0.1.2", "engine-handlebars": "^0.8.0", - "event-stream": "^3.3.1", + "event-stream": "^3.3.2", "graceful-fs": "^4.1.2", "gulp": "^3.9.0", "gulp-istanbul": "^0.10.0", @@ -63,7 +63,6 @@ "look-up": "^0.8.1", "mixin-deep": "^1.1.3", "mocha": "*", - "parser-front-matter": "^1.2.5", "resolve-glob": "^0.1.3", "rimraf": "^2.4.3", "should": "*", diff --git a/test/app.collection.js b/test/app.collection.js index e866028..a2e6b6f 100644 --- a/test/app.collection.js +++ b/test/app.collection.js @@ -137,7 +137,7 @@ describe('collection', function () { .set('data.name', 'Brian') .render(function (err, res) { if (err) return done(err); - assert(res.contents.toString() === 'Brian'); + assert(res.content === 'a Brian b'); done(); }); }); diff --git a/test/app.create.js b/test/app.create.js index 99a15da..9710831 100644 --- a/test/app.create.js +++ b/test/app.create.js @@ -134,7 +134,7 @@ describe('create', function () { collection.addView('test/fixtures/templates/a.tmpl'); collection.read('a.tmpl'); - assert(collection.getView('a.tmpl').contents.toString() === '<%= name %>'); + assert(collection.getView('a.tmpl').contents.toString() === 'a <%= name %> b'); }); }); diff --git a/test/fixtures/templates/a.tmpl b/test/fixtures/templates/a.tmpl index 36f1f1b..e372465 100644 --- a/test/fixtures/templates/a.tmpl +++ b/test/fixtures/templates/a.tmpl @@ -1 +1 @@ -<%= name %> \ No newline at end of file +a <%= name %> b \ No newline at end of file diff --git a/test/view.set.js b/test/view.set.js index 60eb98a..0ed404b 100644 --- a/test/view.set.js +++ b/test/view.set.js @@ -27,7 +27,7 @@ describe('set', function () { .render(function (err, res) { if (err) return done(err); - assert(res.content === 'Brooke'); + assert(res.content === 'a Brooke b'); done(); }); }); From e7ea98d4e04a4fabd3f6730cc47f9530f5bc27ad Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Thu, 22 Oct 2015 01:17:49 -0400 Subject: [PATCH 100/274] start adding updaters --- bin/update.js | 78 +++++++++++++++ examples.js | 18 ++++ index.js | 59 ++++++++++- lib/index.js | 188 ++++++++++++++++++++++++++++++++++- lib/plugins/config.js | 2 +- lib/plugins/locals.js | 2 +- lib/plugins/pkg.js | 47 +++++++++ lib/plugins/schema.js | 120 ++++++++++++++++++++++ lib/plugins/store.js | 2 +- lib/tasks/index.js | 1 + lib/tasks/jshint.js | 8 ++ package.json | 24 ++++- test/fixtures/updaters/a.txt | 1 + test/fixtures/updaters/b.txt | 1 + test/fixtures/updaters/c.txt | 1 + 15 files changed, 541 insertions(+), 11 deletions(-) create mode 100755 bin/update.js create mode 100644 examples.js create mode 100644 lib/plugins/pkg.js create mode 100644 lib/plugins/schema.js create mode 100644 lib/tasks/index.js create mode 100644 lib/tasks/jshint.js create mode 100644 test/fixtures/updaters/a.txt create mode 100644 test/fixtures/updaters/b.txt create mode 100644 test/fixtures/updaters/c.txt diff --git a/bin/update.js b/bin/update.js new file mode 100755 index 0000000..9959b48 --- /dev/null +++ b/bin/update.js @@ -0,0 +1,78 @@ +#!/usr/bin/env node + +var async = require('async'); +var runtimes = require('composer-runtimes'); +var argv = require('minimist')(process.argv.slice(2)); +var builtins = require('../lib/tasks'); +var utils = require('../lib'); +var update = require('..'); +var base = update(argv); + +var updaters = argv._.length ? argv._ : ['base']; + +base.task('default', function (cb) { + console.log(utils.tree(base.updaters)); + cb(); +}); + +base.on('error', function (err) { + if (/Invalid task/.test(err.message)) { + console.error('cannot find task "default"'); + } +}); + +utils.matchGlobal('update-*') + .forEach(function (fp) { + var mod = utils.resolveModule(fp, update); + var name = utils.getName(fp); + var app = mod(base.options) + .set('path', fp) + .use(runtimes({ + displayName: function(key) { + return name + ':' + key; + } + })); + require(utils.updatefile(fp))(app, base); + base.updater(name, app); + }); + + +var proxy = update(); +for (var key in builtins) { + proxy.task(key, builtins[key](proxy)); +} +base.updater('base', proxy); + + +/** + * Updaters to run + */ + +async.eachSeries(updaters, function(arg, next) { + base.emit('base.build'); + + var args = arg.split('.').filter(Boolean); + var tasks = ['default']; + if (args[1]) { + tasks = args[1].split(','); + } + + var app = base.updater(args[0]); + // var name = args[0]; + // var app = name !== 'base' + // ? base.updater(name) + // : base; + + app.build(tasks, function (err) { + if (err) { + next(err); + return; + } + next(); + }); + +}, function (err) { + if (err) { + base.emit('error', err); + } +}); diff --git a/examples.js b/examples.js new file mode 100644 index 0000000..b8ca492 --- /dev/null +++ b/examples.js @@ -0,0 +1,18 @@ +'use strict'; + +var utils = require('./lib/'); +// var Update = require('./'); +// var update = new Update(); + +var updaters = utils.matchGlobal({ + pattern: 'updater-*', + filename: 'updatefile.js', + fn: function() {} +}); + +// updaters.forEach(function (fp) { +// var updater = require(fp); +// updater(update); +// }); + +console.log(updaters) diff --git a/index.js b/index.js index 082d8d2..63c6f2b 100644 --- a/index.js +++ b/index.js @@ -12,12 +12,23 @@ var Core = require('assemble-core'); var plugin = require('./lib/plugins'); var utils = require('./lib/'); +/** + * Create an instance of `Update` with the given `options` + * + * ```js + * var Update = require('update'); + * var update = new Update(); + * ``` + * @param {Object} `options` + * @api public + */ + function Update(options) { if (!(this instanceof Update)) { return new Update(options); } Core.call(this, options); - this.initUpdater(this.options); + this.initUpdate(this); } /** @@ -30,11 +41,13 @@ Core.extend(Update); * Initialize Updater defaults */ -Update.prototype.initUpdater = function() { +Update.prototype.initUpdate = function(base) { + this.set('lookups.tasks', {}); + this.set('updaters', {}); + this.use(plugin.locals({name: 'update'})); this.use(plugin.store({name: 'update'})); this.use(plugin.config()); - // this.use(plugin.reloadViews()); this.use(ask()); this.engine(['md', 'tmpl'], require('engine-base')); @@ -42,8 +55,48 @@ Update.prototype.initUpdater = function() { view.content = view.contents.toString(); utils.matter.parse(view, next); }); + + this.on('base.build', function () { + base.lookup('tasks.base', Object.keys(base.tasks)); + }); +}; + +Update.prototype.lookup = function(key, value) { + if (arguments.length === 1) { + return get(this.lookups, key); + } + if (arguments.length > 1 && Array.isArray(key)) { + key = key.join('.'); + } + utils.set(this.lookups, key, value); + return this; +}; + +Update.prototype.updater = function(name, instance) { + if (arguments.length === 1) { + return this.updaters[name]; + } + + var keys = Object.keys(instance.tasks); + this.lookup(['tasks', name], keys); + utils.union(this, 'taskMap', keys); + this.updaters[name] = instance; + return this; }; +Update.prototype.hasUpdater = function(name) { + return this.updaters.hasOwnProperty(name); +}; + +Update.prototype.hasTask = function(name) { + return this.taskMap.indexOf(name) > -1; +}; + +Update.prototype.opts = function(prop, options) { + var args = [].concat.apply([], [].slice.call(arguments, 1)); + args.unshift(this.option(prop)); + return utils.extend.apply(utils.extend, args); +}; /** * Expose `Update` diff --git a/lib/index.js b/lib/index.js index 62ad8e6..1e5aeb2 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,5 +1,9 @@ 'use strict'; +var fs = require('fs'); +var path = require('path'); +var pkg = require(path.resolve(__dirname, '../package')); + /** * Lazily required module dependencies */ @@ -10,15 +14,195 @@ require = utils; // type utils require('for-own'); -require('mixin-deep', 'merge'); // engine/template utiles +require('data-store', 'store'); +require('global-modules', 'gm'); +require('look-up', 'lookup'); +require('micromatch', 'mm'); require('parser-front-matter', 'matter'); require('question-cache', 'questions'); +require('extend-shallow', 'extend'); require('resolve-dir', 'resolve'); -require('data-store', 'store'); +require('project-name', 'project'); +require('union-value', 'union'); +require('set-value', 'set'); +require('get-value', 'get'); + +/** + * Colors + */ + +require('ansi-yellow', 'yellow'); +require('ansi-green', 'green'); +require('ansi-cyan', 'cyan'); +require('ansi-red', 'red'); require = fn; +/** + * CLI utils + */ + +utils.identity = function(val) { + return val; +}; + +utils.arrayify = function(val) { + return Array.isArray(val) ? val : [val]; +}; + +utils.toArray = function(val) { + if (Array.isArray(val)) return val; + if (val && val.length) { + return [].slice.call(val); + } +}; + +utils.error = function() { + var args = utils.toArray(arguments); + args.unshift(utils.red('[update-cli] Error:')); + console.error.apply(console, args); +}; + +utils.npm = function(name) { + return utils.tryRequire(name) || utils.tryRequire(path.resolve(name)); +}; + +utils.exists = function(fp) { + return fs.existsSync(fp); +}; + +/** + * Create a global path for the given value + */ + +utils.toGlobalPath = function(fp) { + return '@/' + path.basename(fp, path.extname(fp)); +}; + +/** + * Create a global path for the given value + */ + +utils.findGlobal = function(pattern) { + return utils.lookup(pattern, { cwd: utils.gm }); +}; + +/** + * Create a global path for the given value + */ + +utils.listGlobal = function() { + return fs.readdirSync(utils.gm); +}; + +/** + * Get the resolved path to an "updatefile.js" + */ + +utils.updatefile = function(dir) { + return path.join(dir, 'updatefile.js'); +}; + +/** + * Create a global path for the given value + */ + +utils.matchGlobal = function(pattern, filename) { + var isMatch = utils.mm.matcher(pattern); + var files = utils.listGlobal(); + var len = files.length, i = -1; + var res = []; + while (++i < len) { + var name = files[i]; + if (name === 'update-next') continue; + var fp = path.join(utils.gm, name); + if (isMatch(fp) || isMatch(name)) { + res.push(path.join(fp, filename || '')); + } + } + return res; +}; + +/** + * Get the name of an updater + */ + +utils.getName = function(fp) { + return utils.project(fp).split(/[-\W_.]+/).pop(); +}; + +/** + * Resolve the correct updater module to instantiate. + * If `update` exists in `node_modules` of the cwd, + * then that will be used to create the instance, + * otherwise this module will be used. + */ + +utils.resolveModule = function(dir, update) { + dir = path.join(dir, 'node_modules/', pkg.name); + if (utils.exists(dir)) { + return require(path.resolve(dir)); + } + return update; +}; + +/** + * Print a tree of "updaters" and their tasks + * + * ```js + * utils.tree(updaters); + * ``` + */ + +utils.tree = function(updaters) { + var res = ''; + for (var key in updaters) { + res += utils.cyan(key) + '\n'; + for (var task in updaters[key].tasks) { + res += ' - ' + task + '\n'; + } + } + return res; +}; + +/** + * Try to require a file + */ + +utils.tryRequire = function(name) { + try { + return require(name); + } catch(err) { + console.log(err); + } + return null; +}; + +/** + * Try to read a file + */ + +utils.tryRead = function(fp) { + try { + return fs.readFileSync(fp); + } catch(err) {} + return null; +}; + +/** + * Restore `require` + */ + +require = fn; + +/** + * Expose `utils` + */ + +module.exports = utils; + + /** * Expose utils */ diff --git a/lib/plugins/config.js b/lib/plugins/config.js index b610b4f..97fa9be 100644 --- a/lib/plugins/config.js +++ b/lib/plugins/config.js @@ -28,7 +28,7 @@ module.exports = function (options) { var configMap = new Config({ plugins: function (config) { utils.forOwn(config, function (val, key) { - var opts = utils.merge({}, app.options, val); + var opts = utils.extend({}, app.options, val); app.use(require(key)(opts)); }); }, diff --git a/lib/plugins/locals.js b/lib/plugins/locals.js index 35f443f..28a9711 100644 --- a/lib/plugins/locals.js +++ b/lib/plugins/locals.js @@ -10,7 +10,7 @@ module.exports = function (config) { } return function (app) { - var opts = utils.merge({}, config, app.option('update')); + var opts = utils.extend({}, config, app.option('update')); this.locals = new Locals(config.name, this); return this; }; diff --git a/lib/plugins/pkg.js b/lib/plugins/pkg.js new file mode 100644 index 0000000..53d9d2b --- /dev/null +++ b/lib/plugins/pkg.js @@ -0,0 +1,47 @@ + +var Engine = require('engine'); +var typeOf = require('kind-of'); +var pkg = require('load-pkg')(); +var extend = require('extend-shallow'); +var writeJson = require('write-json'); +var schema = require('./defaults'); + +function update(config, options) { + var engine = new Engine(options); + + for (var key in schema) { + if (schema.hasOwnProperty(key)) { + var val = schema[key]; + + if (typeOf(config[key]) !== val.type) { + var value = config[key]; + + if (typeof value === 'undefined' && val.add) { + if (typeof val.value === 'undefined') { + throw new Error('expected "value" to not be undefined for: ' + key); + } + config[key] = val.value; + + } else if (val.value) { + config[key] = val.value; + + } else if (val.template) { + var ctx = extend({}, config); + val.context(config, ctx); + config[key] = engine.render(val.template, ctx); + } + } else if (typeof val.fn === 'function') { + config[key] = val.fn(config[key], config); + } + } + } + return config; +} + +var config = update(pkg); +console.log(config) + +writeJson('package.json', config, function (err) { + if (err) return console.error(err); + console.log('updated package.json'); +}); diff --git a/lib/plugins/schema.js b/lib/plugins/schema.js new file mode 100644 index 0000000..5aa9026 --- /dev/null +++ b/lib/plugins/schema.js @@ -0,0 +1,120 @@ +'use strict'; + +var fs = require('fs'); +var glob = require('matched'); +var get = require('get-value'); +var set = require('set-value'); +var parseAuthor = require('parse-author'); +var parseUrl = require('parse-github-url'); +var url = require('url'); + +function isGithubUrl(str) { + var obj = url.parse(str); + var hosts = ['github.com', 'github.io', 'gist.github.com']; + return hosts.indexOf(obj.host) > -1; +} + +module.exports = { + name: { + type: 'string' + }, + description: { + type: 'string' + }, + version: { + type: 'string' + }, + homepage: { + type: 'string' + }, + author: { + type: 'string', + value: 'Jon Schlinkert (https://github.com/jonschlinkert)', + template: '<%= author.name %> (<%= author.url %>)' + }, + repository: { + type: 'string', + template: '<%= author.username %>/<%= name %>', + context: function (config, ctx) { + var author = get(config, 'author'); + if (typeof author === 'undefined') { + return config; + } + + if (typeof author === 'string') { + author = parseAuthor(author); + set(ctx, 'author', author); + } + + if (typeof author.username === 'undefined') { + if (!author.url || !isGithubUrl(author.url)) { + return config; + } + var parsed = parseUrl(author.url); + var username = parsed.user; + set(ctx, 'author.username', username); + } + return config; + } + }, + bugs: { + type: 'object', + value: { + url: { + type: 'string', + template: 'https://github.com/<%= author.username %>/<%= name %>/issues', + } + } + }, + license: { + type: 'string', + value: 'MIT' + }, + files: { + type: 'array', + fn: function (arr, config) { + var files = glob.sync('**/*.js', { + ignore: ['node_modules/**'] + }); + + var len = files.length, i = -1; + var res = []; + while (++i < len) { + var name = files[i]; + if (/test(\/.*)?\.js$/.test(name)) { + continue; + } + res.push(name); + } + return res; + } + }, + main: { + type: 'string' + }, + engines: { + type: 'object' + }, + scripts: { + type: 'object' + }, + dependencies: { + type: 'object' + }, + devDependencies: { + type: 'object' + }, + keywords: { + type: 'array' + }, + verb: { + type: 'object', + add: true, + value: { + related: { + description: '', + list: [] + } + } + } +}; diff --git a/lib/plugins/store.js b/lib/plugins/store.js index 9514993..91ef068 100644 --- a/lib/plugins/store.js +++ b/lib/plugins/store.js @@ -7,7 +7,7 @@ module.exports = function (config) { } return function (app) { - var opts = utils.merge({}, config, app.option('store')); + var opts = utils.extend({}, config, app.option('store')); this.store = utils.store(config.name, opts); return this; }; diff --git a/lib/tasks/index.js b/lib/tasks/index.js new file mode 100644 index 0000000..23b2930 --- /dev/null +++ b/lib/tasks/index.js @@ -0,0 +1 @@ +module.exports = require('export-files')(__dirname); diff --git a/lib/tasks/jshint.js b/lib/tasks/jshint.js new file mode 100644 index 0000000..ae9435a --- /dev/null +++ b/lib/tasks/jshint.js @@ -0,0 +1,8 @@ + +module.exports = function(app) { + return function (cb) { + app.src('.jshintrc') + .pipe(app.dest('blah')) + .on('end', cb); + } +}; diff --git a/package.json b/package.json index b9fb97e..f6f063f 100644 --- a/package.json +++ b/package.json @@ -17,30 +17,49 @@ "engines": { "node": ">=0.10.0" }, + "preferGlobal": true, + "bin": { + "update": "bin/update.js" + }, "scripts": { "test": "mocha" }, "dependencies": { + "ansi-cyan": "^0.1.1", "ansi-green": "^0.1.1", + "ansi-red": "^0.1.1", + "ansi-yellow": "^0.1.1", "assemble-ask": "^0.1.2", - "assemble-core": "^0.1.1", + "assemble-core": "^0.1.3", "async": "^1.4.2", + "composer-runtimes": "^0.4.1", "cwd": "^0.8.4", "data-store": "^0.10.1", "delete": "^0.2.1", + "engine": "^0.1.8", "export-files": "^2.1.0", + "extend-shallow": "^2.0.1", "for-own": "^0.1.3", "get-value": "^1.2.1", + "global-modules": "^0.2.0", "lazy-cache": "^0.2.3", "load-pkg": "^2.0.1", "map-config": "^0.2.0", + "matched": "^0.3.2", + "micromatch": "jonschlinkert/micromatch#2.3.0", + "minimist": "^1.2.0", + "parse-author": "^0.2.0", + "parse-github-url": "^0.2.1", "parser-front-matter": "^1.3.0", + "project-name": "^0.2.0", "question-cache": "^0.3.1", "resolve-dir": "^0.1.0", "set-value": "^0.2.0", "success-symbol": "^0.1.0", + "union-value": "^0.1.1", "update-copyright": "^0.1.0", - "write": "^0.2.1" + "write": "^0.2.1", + "write-json": "^0.2.1" }, "devDependencies": { "base-methods": "^0.2.14", @@ -61,7 +80,6 @@ "jshint-stylish": "^2.0.1", "kind-of": "^2.0.1", "look-up": "^0.8.1", - "mixin-deep": "^1.1.3", "mocha": "*", "resolve-glob": "^0.1.3", "rimraf": "^2.4.3", diff --git a/test/fixtures/updaters/a.txt b/test/fixtures/updaters/a.txt new file mode 100644 index 0000000..7c4a013 --- /dev/null +++ b/test/fixtures/updaters/a.txt @@ -0,0 +1 @@ +aaa \ No newline at end of file diff --git a/test/fixtures/updaters/b.txt b/test/fixtures/updaters/b.txt new file mode 100644 index 0000000..01f02e3 --- /dev/null +++ b/test/fixtures/updaters/b.txt @@ -0,0 +1 @@ +bbb \ No newline at end of file diff --git a/test/fixtures/updaters/c.txt b/test/fixtures/updaters/c.txt new file mode 100644 index 0000000..2383bd5 --- /dev/null +++ b/test/fixtures/updaters/c.txt @@ -0,0 +1 @@ +ccc \ No newline at end of file From 9762380c5ecaf9db8b97d5d60f4248b1faffdb0d Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Thu, 22 Oct 2015 17:22:32 -0400 Subject: [PATCH 101/274] updaters, omni-multi-mega-super updaters, default tasks --- bin/update.js | 77 ++----------------- examples.js | 32 +++++--- index.js | 59 ++++++++------ lib/config.js | 40 ++++++++++ lib/middleware/index.js | 1 + lib/middleware/readme.js | 11 +++ lib/plugins/config.js | 2 +- lib/plugins/locals.js | 2 +- lib/plugins/schema.js | 1 - lib/plugins/store.js | 2 +- lib/tasks/default.js | 7 ++ lib/tasks/dest.js | 8 ++ lib/tasks/files.js | 12 +++ lib/tasks/jshint.js | 10 +-- lib/tasks/list.js | 12 +++ lib/tasks/noop.js | 9 +++ lib/tasks/tree.js | 10 +++ lib/updaters.js | 152 +++++++++++++++++++++++++++++++++++++ lib/{index.js => utils.js} | 103 +++++++++++++++++++------ package.json | 10 ++- updatefile.js | 5 ++ 21 files changed, 423 insertions(+), 142 deletions(-) create mode 100644 lib/config.js create mode 100644 lib/middleware/index.js create mode 100644 lib/middleware/readme.js create mode 100644 lib/tasks/default.js create mode 100644 lib/tasks/dest.js create mode 100644 lib/tasks/files.js create mode 100644 lib/tasks/list.js create mode 100644 lib/tasks/noop.js create mode 100644 lib/tasks/tree.js create mode 100644 lib/updaters.js rename lib/{index.js => utils.js} (64%) create mode 100644 updatefile.js diff --git a/bin/update.js b/bin/update.js index 9959b48..7728055 100755 --- a/bin/update.js +++ b/bin/update.js @@ -1,78 +1,17 @@ #!/usr/bin/env node -var async = require('async'); -var runtimes = require('composer-runtimes'); var argv = require('minimist')(process.argv.slice(2)); -var builtins = require('../lib/tasks'); -var utils = require('../lib'); -var update = require('..'); -var base = update(argv); +var updaters = require('../lib/updaters'); +var utils = require('../lib/utils'); -var updaters = argv._.length ? argv._ : ['base']; - -base.task('default', function (cb) { - console.log(utils.tree(base.updaters)); - cb(); +updaters(argv).register('update-*', { + cwd: utils.gm }); -base.on('error', function (err) { - if (/Invalid task/.test(err.message)) { - console.error('cannot find task "default"'); - } +updaters.base.task('run', function (cb) { + updaters.run(cb); }); -utils.matchGlobal('update-*') - .forEach(function (fp) { - var mod = utils.resolveModule(fp, update); - var name = utils.getName(fp); - var app = mod(base.options) - .set('path', fp) - .use(runtimes({ - displayName: function(key) { - return name + ':' + key; - } - })); - require(utils.updatefile(fp))(app, base); - base.updater(name, app); - }); - - -var proxy = update(); -for (var key in builtins) { - proxy.task(key, builtins[key](proxy)); -} -base.updater('base', proxy); - - -/** - * Updaters to run - */ - -async.eachSeries(updaters, function(arg, next) { - base.emit('base.build'); - - var args = arg.split('.').filter(Boolean); - var tasks = ['default']; - if (args[1]) { - tasks = args[1].split(','); - } - - var app = base.updater(args[0]); - // var name = args[0]; - // var app = name !== 'base' - // ? base.updater(name) - // : base; - - app.build(tasks, function (err) { - if (err) { - next(err); - return; - } - next(); - }); - -}, function (err) { - if (err) { - base.emit('error', err); - } +updaters.base.build('default', function (err) { + if (err) console.error(err); }); diff --git a/examples.js b/examples.js index b8ca492..4c16a75 100644 --- a/examples.js +++ b/examples.js @@ -1,18 +1,26 @@ 'use strict'; -var utils = require('./lib/'); -// var Update = require('./'); -// var update = new Update(); +var update = require('./'); +var foo = update(); +var bar = update(); -var updaters = utils.matchGlobal({ - pattern: 'updater-*', - filename: 'updatefile.js', - fn: function() {} + +foo.task('files', function (cb) { + console.log('files'); + cb(); +}); + +foo.task('run', function (cb) { + bar.build(, cb); + console.log('run'); + cb(); +}); + +foo.task('dest', function (cb) { + console.log('dest'); + cb(); }); -// updaters.forEach(function (fp) { -// var updater = require(fp); -// updater(update); -// }); +app.task('default', ['files', 'run', 'dest']); -console.log(updaters) +app.build('default', console.log); diff --git a/index.js b/index.js index 63c6f2b..b314543 100644 --- a/index.js +++ b/index.js @@ -9,8 +9,9 @@ var ask = require('assemble-ask'); var Core = require('assemble-core'); +var loader = require('assemble-loader'); var plugin = require('./lib/plugins'); -var utils = require('./lib/'); +var utils = require('./lib/utils'); /** * Create an instance of `Update` with the given `options` @@ -28,6 +29,7 @@ function Update(options) { return new Update(options); } Core.call(this, options); + this.name = this.options.name || 'update'; this.initUpdate(this); } @@ -42,12 +44,13 @@ Core.extend(Update); */ Update.prototype.initUpdate = function(base) { - this.set('lookups.tasks', {}); + this.define('isUpdate', true); this.set('updaters', {}); - this.use(plugin.locals({name: 'update'})); - this.use(plugin.store({name: 'update'})); + this.use(plugin.locals({name: this.name})); + this.use(plugin.store({name: this.name})); this.use(plugin.config()); + this.use(loader()); this.use(ask()); this.engine(['md', 'tmpl'], require('engine-base')); @@ -55,33 +58,33 @@ Update.prototype.initUpdate = function(base) { view.content = view.contents.toString(); utils.matter.parse(view, next); }); - - this.on('base.build', function () { - base.lookup('tasks.base', Object.keys(base.tasks)); - }); }; -Update.prototype.lookup = function(key, value) { - if (arguments.length === 1) { - return get(this.lookups, key); - } - if (arguments.length > 1 && Array.isArray(key)) { - key = key.join('.'); - } - utils.set(this.lookups, key, value); - return this; -}; +/** + * Register updater `name` with the given `update` + * instance. + * + * @param {String} `name` + * @param {Object} `update` Instance of update + * @return {Object} Returns the instance for chaining + */ -Update.prototype.updater = function(name, instance) { +Update.prototype.updater = function(name, update) { if (arguments.length === 1) { return this.updaters[name]; } + update.use(utils.runtimes({ + displayName: function(key) { + return utils.cyan(name + ':' + key); + } + })); + return (this.updaters[name] = update); +}; - var keys = Object.keys(instance.tasks); - this.lookup(['tasks', name], keys); - utils.union(this, 'taskMap', keys); - this.updaters[name] = instance; - return this; +Update.prototype.build = function() { + var fn = Core.prototype.build; + this.emit('build'); + return fn.apply(this, arguments); }; Update.prototype.hasUpdater = function(name) { @@ -103,3 +106,11 @@ Update.prototype.opts = function(prop, options) { */ module.exports = Update; + +/** + * Expose `utils` + */ + +module.exports.utils = utils; +module.exports.meta = require('./package'); +module.exports.dir = __dirname; diff --git a/lib/config.js b/lib/config.js new file mode 100644 index 0000000..538cc01 --- /dev/null +++ b/lib/config.js @@ -0,0 +1,40 @@ +'use strict'; + + +var expand = require('expand-args'); +var config = require('map-config'); + + +module.exports = function(app) { + var res = config(app) + .map('store', store(app.store)) + .map('set') + .map('del') + .map('get', function(key) { + console.log(app.get(key)); + }); + + app.on('argv', function(argv) { + res.process(expand(argv)); + }); +}; + +// function init(app) { +// app.question('') +// } + +function store(app) { + var res = config(app) + .map('set') + .map('del') + .map('has', function(key) { + console.log(!!app.get(key)); + }) + .map('get', function(key) { + console.log(app.get(key)); + }) + + return function(args) { + res.process(expand(args)); + }; +} diff --git a/lib/middleware/index.js b/lib/middleware/index.js new file mode 100644 index 0000000..23b2930 --- /dev/null +++ b/lib/middleware/index.js @@ -0,0 +1 @@ +module.exports = require('export-files')(__dirname); diff --git a/lib/middleware/readme.js b/lib/middleware/readme.js new file mode 100644 index 0000000..539a61b --- /dev/null +++ b/lib/middleware/readme.js @@ -0,0 +1,11 @@ +'use strict'; + +module.exports = function(app, env) { + env.base.onLoad(/\.(?:verb(rc)?|readme)\.md$/i, function(view, next) { + if (view.readme !== false && view.noreadme !== true) { + view.path = 'README.md'; + } + next(); + }); +}; + diff --git a/lib/plugins/config.js b/lib/plugins/config.js index 97fa9be..662ef41 100644 --- a/lib/plugins/config.js +++ b/lib/plugins/config.js @@ -1,7 +1,7 @@ 'use strict'; var Config = require('map-config'); -var utils = require('..'); +var utils = require('../utils'); module.exports = function (options) { return function (app) { diff --git a/lib/plugins/locals.js b/lib/plugins/locals.js index 28a9711..cb191cf 100644 --- a/lib/plugins/locals.js +++ b/lib/plugins/locals.js @@ -2,7 +2,7 @@ var get = require('get-value'); var set = require('set-value'); -var utils = require('..'); +var utils = require('../utils'); module.exports = function (config) { if (!config || !config.name) { diff --git a/lib/plugins/schema.js b/lib/plugins/schema.js index 5aa9026..dc5d5e9 100644 --- a/lib/plugins/schema.js +++ b/lib/plugins/schema.js @@ -1,6 +1,5 @@ 'use strict'; -var fs = require('fs'); var glob = require('matched'); var get = require('get-value'); var set = require('set-value'); diff --git a/lib/plugins/store.js b/lib/plugins/store.js index 91ef068..7e80ba7 100644 --- a/lib/plugins/store.js +++ b/lib/plugins/store.js @@ -1,5 +1,5 @@ -var utils = require('..'); +var utils = require('../utils'); module.exports = function (config) { if (!config || !config.name) { diff --git a/lib/tasks/default.js b/lib/tasks/default.js new file mode 100644 index 0000000..a6960f6 --- /dev/null +++ b/lib/tasks/default.js @@ -0,0 +1,7 @@ +'use strict'; + +var utils = require('../utils'); + +module.exports = function(app, env) { + return ['files', 'run', 'dest']; +}; diff --git a/lib/tasks/dest.js b/lib/tasks/dest.js new file mode 100644 index 0000000..bc0c92f --- /dev/null +++ b/lib/tasks/dest.js @@ -0,0 +1,8 @@ +'use strict'; + +module.exports = function(app, env) { + return function () { + return app.toStream('files') + .pipe(app.dest('.')); + }; +}; diff --git a/lib/tasks/files.js b/lib/tasks/files.js new file mode 100644 index 0000000..a5704bb --- /dev/null +++ b/lib/tasks/files.js @@ -0,0 +1,12 @@ +'use strict'; + +module.exports = function(app, env) { + app.create('files'); + + var glob = env.base.get('argv.files') || '*'; + return function (cb) { + app.files(glob, {dot: true}); + console.log('glob:', app.views.files); + cb(); + } +}; diff --git a/lib/tasks/jshint.js b/lib/tasks/jshint.js index ae9435a..2cae1c2 100644 --- a/lib/tasks/jshint.js +++ b/lib/tasks/jshint.js @@ -1,8 +1,8 @@ +'use strict'; -module.exports = function(app) { - return function (cb) { - app.src('.jshintrc') - .pipe(app.dest('blah')) - .on('end', cb); +module.exports = function(app, env) { + return function () { + return app.src('.jshintrc') + .pipe(app.dest('blah')); } }; diff --git a/lib/tasks/list.js b/lib/tasks/list.js new file mode 100644 index 0000000..80b9da1 --- /dev/null +++ b/lib/tasks/list.js @@ -0,0 +1,12 @@ +'use strict'; + +var utils = require('../utils'); + +module.exports = function(app, env) { + return function (cb) { + env.list(function (err, args) { + if (err) return cb(err); + env.run(args, cb); + }); + }; +}; diff --git a/lib/tasks/noop.js b/lib/tasks/noop.js new file mode 100644 index 0000000..0e1d5d6 --- /dev/null +++ b/lib/tasks/noop.js @@ -0,0 +1,9 @@ +'use strict'; + +var utils = require('../utils'); + +module.exports = function(app, env) { + return function (cb) { + return cb(); + }; +}; diff --git a/lib/tasks/tree.js b/lib/tasks/tree.js new file mode 100644 index 0000000..0234473 --- /dev/null +++ b/lib/tasks/tree.js @@ -0,0 +1,10 @@ +'use strict'; + +var utils = require('../utils'); + +module.exports = function(app, env) { + return function (cb) { + console.log(utils.tree(env.base.updaters)); + cb(); + } +}; diff --git a/lib/updaters.js b/lib/updaters.js new file mode 100644 index 0000000..b8a1a8e --- /dev/null +++ b/lib/updaters.js @@ -0,0 +1,152 @@ +'use strict'; + +var path = require('path'); +var fns = require('./middleware'); +var tasks = require('./tasks'); +var utils = require('./utils'); +var Update = require('..'); + +function Updaters(argv, options) { + if (!(this instanceof Updaters)) { + return new Updaters(argv); + } + + this.options = options || {}; + this.base = new Update() + .on('error', console.error) + .use(utils.runtimes()) + .set('argv', argv); + + for (var fn in fns) { + fns[fn](this.base, this); + } + + for (var key in tasks) { + this.base.task(key, tasks[key](this.base, this)); + } +} + +Updaters.prototype.argv = function(argv, whitelist, updaters) { + var res = {}; + res.whitelist = whitelist; + res.updaters = updaters; + res.argv = argv; + res.commands = []; + res.updaters = {}; + res.flags = utils.expandArgs(utils.omit(argv, '_')); + + var arr = argv._; + var len = arr.length, i = -1; + if (len === 0) { + utils.union(res.updaters, 'base', ['noop']); + } + + while (++i < len) { + var ele = arr[i]; + + if (/\W/.test(ele)) { + var obj = utils.expand(ele); + utils.forOwn(obj, function (val, key) { + utils.union(res.updaters, key, val); + }); + continue; + } + + if (utils.contains(whitelist, ele)) { + res.commands.push(ele); + continue; + } + + if (ele in updaters) { + utils.union(res.updaters, ele, 'default'); + + } else if (ele === 'base') { + utils.union(res.updaters, 'base', 'noop'); + + } else { + utils.union(res.updaters, 'base', ele); + } + } + + return res; +}; + +Updaters.prototype.register = function(pattern, options) { + utils.matchFiles(pattern, options).forEach(function (fp) { + var fullname = utils.projectName(fp); + var name = utils.renameFn(fullname, options); + var mod = utils.resolveModule(fp) || Update; + var app = mod(this.base.options) + .option('name', name) + .set('fullname', fullname) + .set('path', fp); + + var filepath = path.join(fp, 'updatefile.js'); + + require(filepath)(app, this.base); + this.base.updater(name, app); + }.bind(this)); + return this; +}; + +Updaters.prototype.run = function(args, cb) { + if (typeof args === 'function') { + cb = args; + args = null; + } + + if (!args) { + var whitelist = this.options.whitelist || ['set', 'get', 'del', 'store', 'init']; + var argv = this.base.get('argv'); + args = this.argv(argv, whitelist, this.base.updaters); + } + + if (args.commands && args.commands.length > 1) { + var cmd = '"' + args.commands.join(', ') + '"'; + return cb(new Error('Error: only one root level command may be given: ' + cmd)); + } + + var updaters = Object.keys(args.updaters); + + utils.async.eachSeries(updaters, function(name, next) { + var tasks = args.updaters[name]; + var app = name !== 'base' + ? this.base.updater(name) + : this.base; + + app.build(tasks, function (err) { + if (err) return next(err); + next(); + }); + }.bind(this), cb); + + return this; +}; + +Updaters.prototype.list = function(cb) { + var questions = utils.questions(this.base.options); + var question = { + updaters: { + message: 'pick an updater to run', + type: 'checkbox', + choices: utils.list(this.base.updaters) + } + }; + questions.ask(question, function (err, answers) { + if (err) return cb(err); + var args = { + updaters: {} + }; + answers.updaters.forEach(function (answer) { + var segs = answer.split(':'); + utils.union(args.updaters, segs[0], (segs[1] || 'default').split(',')); + }); + return cb(null, args); + }); +}; + +/** + * Expose `Updaters` + */ + +module.exports = Updaters; diff --git a/lib/index.js b/lib/utils.js similarity index 64% rename from lib/index.js rename to lib/utils.js index 1e5aeb2..d532b63 100644 --- a/lib/index.js +++ b/lib/utils.js @@ -12,27 +12,24 @@ var utils = require('lazy-cache')(require); var fn = require; require = utils; -// type utils require('for-own'); - -// engine/template utiles +require('async'); +require('expand-args'); +require('expand-object', 'expand'); require('data-store', 'store'); require('global-modules', 'gm'); require('look-up', 'lookup'); +require('object.omit', 'omit'); require('micromatch', 'mm'); +require('composer-runtimes', 'runtimes'); require('parser-front-matter', 'matter'); require('question-cache', 'questions'); require('extend-shallow', 'extend'); require('resolve-dir', 'resolve'); -require('project-name', 'project'); +require('project-name'); require('union-value', 'union'); require('set-value', 'set'); require('get-value', 'get'); - -/** - * Colors - */ - require('ansi-yellow', 'yellow'); require('ansi-green', 'green'); require('ansi-cyan', 'cyan'); @@ -58,6 +55,10 @@ utils.toArray = function(val) { } }; +utils.contains = function(arr, key) { + return arr.indexOf(key) > -1; +}; + utils.error = function() { var args = utils.toArray(arguments); args.unshift(utils.red('[update-cli] Error:')); @@ -89,47 +90,56 @@ utils.findGlobal = function(pattern) { }; /** - * Create a global path for the given value + * Get the resolved path to an "updatefile.js" */ -utils.listGlobal = function() { - return fs.readdirSync(utils.gm); +utils.updatefile = function(dir) { + return path.join(dir, 'updatefile.js'); }; /** - * Get the resolved path to an "updatefile.js" + * Rename a filepath to the "nickname" of the project. + * + * ```js + * renameFn('updater-foo'); + * //=> 'foo' + * ``` */ -utils.updatefile = function(dir) { - return path.join(dir, 'updatefile.js'); +utils.renameFn = function(filename, options) { + if (options && typeof options.renameFn === 'function') { + return options.renameFn(filename); + } + return filename.split(/[-\W_.]+/).pop(); }; /** - * Create a global path for the given value + * Return a glob of file paths */ -utils.matchGlobal = function(pattern, filename) { +utils.matchFiles = function(pattern, options) { + options = options || {}; var isMatch = utils.mm.matcher(pattern); - var files = utils.listGlobal(); + var files = fs.readdirSync(options.cwd); var len = files.length, i = -1; var res = []; while (++i < len) { var name = files[i]; if (name === 'update-next') continue; - var fp = path.join(utils.gm, name); + var fp = path.join(options.cwd, name); if (isMatch(fp) || isMatch(name)) { - res.push(path.join(fp, filename || '')); + res.push(fp); } } return res; }; /** - * Get the name of an updater + * Create a global path for the given value */ -utils.getName = function(fp) { - return utils.project(fp).split(/[-\W_.]+/).pop(); +utils.matchGlobal = function(pattern, filename) { + return utils.matchFiles(pattern, {cwd: utils.gm}); }; /** @@ -139,12 +149,12 @@ utils.getName = function(fp) { * otherwise this module will be used. */ -utils.resolveModule = function(dir, update) { +utils.resolveModule = function(dir) { dir = path.join(dir, 'node_modules/', pkg.name); if (utils.exists(dir)) { return require(path.resolve(dir)); } - return update; + return null; }; /** @@ -166,6 +176,36 @@ utils.tree = function(updaters) { return res; }; +/** + * Return a list of "updaters" and their tasks + * + * ```js + * utils.list(updaters); + * ``` + */ + +utils.list = function(updaters) { + var list = []; + for (var key in updaters) { + var updater = updaters[key]; + var item = { + name: updater.fullname + ' (default)', + value: key, + short: updater.name + ':default' + }; + list.push(item); + for (var task in updater.tasks) { + if (task === 'default') continue; + list.push({ + name: ' - ' + task, + value: key + ':' + task, + short: key + ':' + task + }); + } + } + return list; +}; + /** * Try to require a file */ @@ -190,6 +230,19 @@ utils.tryRead = function(fp) { return null; }; +utils.register = function(pattern, base, update, options) { + utils.matchFiles(pattern, options).forEach(function (fp) { + var name = utils.projectName(fp); + var mod = utils.resolveModule(fp) || update; + var app = mod(base.options) + .option('name', name) + .set('path', fp); + + require(utils.updatefile(fp))(app, base); + base.updater(name, app); + }); +}; + /** * Restore `require` */ diff --git a/package.json b/package.json index f6f063f..68cc15d 100644 --- a/package.json +++ b/package.json @@ -30,13 +30,16 @@ "ansi-red": "^0.1.1", "ansi-yellow": "^0.1.1", "assemble-ask": "^0.1.2", - "assemble-core": "^0.1.3", + "assemble-core": "^0.1.4", + "assemble-loader": "^0.2.4", "async": "^1.4.2", - "composer-runtimes": "^0.4.1", + "composer-runtimes": "^0.5.1", "cwd": "^0.8.4", "data-store": "^0.10.1", "delete": "^0.2.1", "engine": "^0.1.8", + "expand-args": "^0.2.1", + "expand-object": "^0.3.8", "export-files": "^2.1.0", "extend-shallow": "^2.0.1", "for-own": "^0.1.3", @@ -48,11 +51,12 @@ "matched": "^0.3.2", "micromatch": "jonschlinkert/micromatch#2.3.0", "minimist": "^1.2.0", + "object.omit": "^2.0.0", "parse-author": "^0.2.0", "parse-github-url": "^0.2.1", "parser-front-matter": "^1.3.0", "project-name": "^0.2.0", - "question-cache": "^0.3.1", + "question-cache": "^0.3.3", "resolve-dir": "^0.1.0", "set-value": "^0.2.0", "success-symbol": "^0.1.0", diff --git a/updatefile.js b/updatefile.js new file mode 100644 index 0000000..e3004c0 --- /dev/null +++ b/updatefile.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = function (app) { + app.task('jshint', require('./lib/tasks/jshint')); +}; From 566a8b09c918dce4ec3ad9853984a0adf1e6613b Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Thu, 22 Oct 2015 19:49:16 -0400 Subject: [PATCH 102/274] get everything working --- README.md | 55 ++++++++++++++++++++++++++++++++++++++++ bin/update.js | 10 +++++--- lib/middleware/readme.js | 2 +- lib/tasks/files.js | 15 ++++++++--- lib/updaters.js | 12 ++------- package.json | 18 ++++++++++--- 6 files changed, 91 insertions(+), 21 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..4b69efd --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +# update-next [![NPM version](https://badge.fury.io/js/update-next.svg)](http://badge.fury.io/js/update-next) + +> Update + +## Install + +Install with [npm](https://www.npmjs.com/) + +```sh +$ npm i update-next --save +``` + +## Usage + +```js +var updateNext = require('update-next'); +``` + +## API + + +{%= apidocs("index.js") %} + +## Related projects + + +{%= related([]) %} + +## Running tests + +Install dev dependencies: + +```sh +$ npm i -d && npm test +``` + +## Contributing + +Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](https://github.com/jonschlinkert/update-next/issues/new). + +## Author + +**Jon Schlinkert** + ++ [github/jonschlinkert](https://github.com/jonschlinkert) ++ [twitter/jonschlinkert](http://twitter.com/jonschlinkert) + +## License + +Copyright © 2015 Jon Schlinkert +Released under the MIT license. + +*** + +_This file was generated by [verb-cli](https://github.com/assemble/verb-cli) on October 22, 2015._ \ No newline at end of file diff --git a/bin/update.js b/bin/update.js index 7728055..a59772e 100755 --- a/bin/update.js +++ b/bin/update.js @@ -4,14 +4,16 @@ var argv = require('minimist')(process.argv.slice(2)); var updaters = require('../lib/updaters'); var utils = require('../lib/utils'); -updaters(argv).register('update-*', { +var update = updaters(argv); + +update.register('update-*', { cwd: utils.gm }); -updaters.base.task('run', function (cb) { - updaters.run(cb); +update.base.task('run', function (cb) { + update.run(cb); }); -updaters.base.build('default', function (err) { +update.base.build('default', function (err) { if (err) console.error(err); }); diff --git a/lib/middleware/readme.js b/lib/middleware/readme.js index 539a61b..445162c 100644 --- a/lib/middleware/readme.js +++ b/lib/middleware/readme.js @@ -1,7 +1,7 @@ 'use strict'; module.exports = function(app, env) { - env.base.onLoad(/\.(?:verb(rc)?|readme)\.md$/i, function(view, next) { + app.onLoad(/\.(?:verb(rc)?|readme)\.md$/i, function(view, next) { if (view.readme !== false && view.noreadme !== true) { view.path = 'README.md'; } diff --git a/lib/tasks/files.js b/lib/tasks/files.js index a5704bb..d9112e0 100644 --- a/lib/tasks/files.js +++ b/lib/tasks/files.js @@ -1,12 +1,21 @@ 'use strict'; +var path = require('path'); + module.exports = function(app, env) { - app.create('files'); + app.create('files', { + renameKey: function (key) { + return path.basename(key); + } + }); + + var glob = env.base.get('argv.files'); + if (!glob) { + glob = ['*', 'lib/*', 'bin/*']; + } - var glob = env.base.get('argv.files') || '*'; return function (cb) { app.files(glob, {dot: true}); - console.log('glob:', app.views.files); cb(); } }; diff --git a/lib/updaters.js b/lib/updaters.js index b8a1a8e..c8c8574 100644 --- a/lib/updaters.js +++ b/lib/updaters.js @@ -37,9 +37,6 @@ Updaters.prototype.argv = function(argv, whitelist, updaters) { var arr = argv._; var len = arr.length, i = -1; - if (len === 0) { - utils.union(res.updaters, 'base', ['noop']); - } while (++i < len) { var ele = arr[i]; @@ -60,14 +57,10 @@ Updaters.prototype.argv = function(argv, whitelist, updaters) { if (ele in updaters) { utils.union(res.updaters, ele, 'default'); - } else if (ele === 'base') { - utils.union(res.updaters, 'base', 'noop'); - - } else { + } else if (ele !== 'base') { utils.union(res.updaters, 'base', ele); } } - return res; }; @@ -83,7 +76,7 @@ Updaters.prototype.register = function(pattern, options) { var filepath = path.join(fp, 'updatefile.js'); - require(filepath)(app, this.base); + require(filepath)(app, this); this.base.updater(name, app); }.bind(this)); return this; @@ -119,7 +112,6 @@ Updaters.prototype.run = function(args, cb) { next(); }); }.bind(this), cb); - return this; }; diff --git a/package.json b/package.json index 68cc15d..27a14c5 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "version": "0.1.0", "homepage": "https://github.com/jonschlinkert/update-next", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", - "repository": "jonschlinkert/update-next", + "repository": "jonschlinkert/update", "bugs": { "url": "https://github.com/jonschlinkert/update-next/issues" }, @@ -120,5 +120,17 @@ "plugins": { "@/foo": {} } - } -} + }, + "contributors": [ + { + "name": "Jon Schlinkert", + "email": "github@sellside.com", + "url": "https://github.com/jonschlinkert" + }, + { + "name": "Brian Woodward", + "email": "brian.woodward@gmail.com", + "url": "https://github.com/doowb" + } + ] +} \ No newline at end of file From 80daf6e11988b11a825e9c4ff0cec44d165f3fc1 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 24 Oct 2015 09:00:58 -0400 Subject: [PATCH 103/274] update tests to latest --- test/app.collection.js | 19 +- test/app.collection.render.js | 2 + test/app.copy.js | 6 +- test/app.create.js | 24 +- test/app.data.js | 22 + test/app.dest.js | 173 ++++--- test/app.events.js | 13 +- test/app.handle.js | 10 + test/app.handlers.js | 96 +--- test/app.list.js | 105 ++++ test/app.list.render.js | 157 ------ test/app.option.js | 6 - test/app.renderFile.js | 135 +++++ test/app.symlink.js | 396 ++++++++++++++ test/app.toStream.js | 64 +++ test/collection.use.js | 138 ++--- test/dest.js | 908 --------------------------------- test/fixtures/example.txt | 1 + test/fixtures/pages/a.hbs | 3 +- test/fixtures/pages/b.hbs | 3 +- test/fixtures/pages/c.hbs | 3 +- test/fixtures/templates/a.tmpl | 2 +- test/group.js | 4 + test/helpers.js | 20 +- test/list.js | 10 + test/list.use.js | 156 ++++++ test/mergePartials.js | 4 +- test/store.js | 35 +- test/view.set.js | 2 +- test/views.js | 96 +++- test/views.use.js | 156 ++++++ 31 files changed, 1395 insertions(+), 1374 deletions(-) create mode 100644 test/app.list.js delete mode 100644 test/app.list.render.js create mode 100644 test/app.renderFile.js create mode 100644 test/app.symlink.js create mode 100644 test/app.toStream.js delete mode 100644 test/dest.js create mode 100644 test/fixtures/example.txt create mode 100644 test/list.use.js create mode 100644 test/views.use.js diff --git a/test/app.collection.js b/test/app.collection.js index a2e6b6f..fde0c24 100644 --- a/test/app.collection.js +++ b/test/app.collection.js @@ -1,6 +1,7 @@ require('mocha'); require('should'); var fs = require('fs'); +var path = require('path'); var assert = require('assert'); var define = require('define-property'); var support = require('./support'); @@ -51,7 +52,7 @@ describe('collection', function () { it('should load a view onto the respective collection:', function () { app.pages('test/fixtures/pages/a.hbs'); - app.views.pages.should.have.property('test/fixtures/pages/a.hbs'); + app.views.pages.should.have.property(path.resolve('test/fixtures/pages/a.hbs')); }); it('should allow collection methods to be chained:', function () { @@ -61,9 +62,9 @@ describe('collection', function () { .pages('test/fixtures/pages/c.hbs'); app.views.pages.should.have.properties([ - 'test/fixtures/pages/a.hbs', - 'test/fixtures/pages/b.hbs', - 'test/fixtures/pages/c.hbs' + path.resolve('test/fixtures/pages/a.hbs'), + path.resolve('test/fixtures/pages/b.hbs'), + path.resolve('test/fixtures/pages/c.hbs') ]); }); @@ -75,9 +76,9 @@ describe('collection', function () { app.pages.options.should.have.property('foo', 'bar'); app.views.pages.should.have.properties([ - 'test/fixtures/pages/a.hbs', - 'test/fixtures/pages/b.hbs', - 'test/fixtures/pages/c.hbs' + path.resolve('test/fixtures/pages/a.hbs'), + path.resolve('test/fixtures/pages/b.hbs'), + path.resolve('test/fixtures/pages/c.hbs') ]); }); @@ -137,7 +138,7 @@ describe('collection', function () { .set('data.name', 'Brian') .render(function (err, res) { if (err) return done(err); - assert(res.content === 'a Brian b'); + assert(res.content === 'Brian'); done(); }); }); @@ -165,7 +166,7 @@ describe('collection singular method', function () { it('should add a view to the created collection:', function () { app.page('test/fixtures/pages/a.hbs'); - assert(typeof app.views.pages['test/fixtures/pages/a.hbs'] === 'object'); + assert(typeof app.views.pages[path.resolve('test/fixtures/pages/a.hbs')] === 'object'); }); it('should expose the `option` method:', function () { diff --git a/test/app.collection.render.js b/test/app.collection.render.js index 6cc96a3..135b134 100644 --- a/test/app.collection.render.js +++ b/test/app.collection.render.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var async = require('async'); diff --git a/test/app.copy.js b/test/app.copy.js index 0e979b7..4a0d510 100644 --- a/test/app.copy.js +++ b/test/app.copy.js @@ -6,16 +6,16 @@ var App = require('..'); var app; var fixtures = path.join(__dirname, 'fixtures/copy/*.txt'); -var outpath = path.join(__dirname, 'out-fixtures'); +var actual = path.join(__dirname, 'actual'); describe('copy()', function() { beforeEach(function (done) { - rimraf(outpath, done); + rimraf(actual, done); app = new App(); }); afterEach(function (done) { - rimraf(outpath, done); + rimraf(actual, done); }); describe('streams', function () { diff --git a/test/app.create.js b/test/app.create.js index 9710831..9178245 100644 --- a/test/app.create.js +++ b/test/app.create.js @@ -92,7 +92,7 @@ describe('create', function () { app.page('b.hbs', {content: 'b'}); app.page('c.hbs', {content: 'c'}); app.views.pages.should.have.properties(['a.hbs', 'b.hbs', 'c.hbs']); - assert(app.views.pages['a.hbs'].contents.toString() === 'a'); + assert(app.views.pages['a.hbs'].content === 'a'); }); it('should create views from file paths:', function () { @@ -101,9 +101,9 @@ describe('create', function () { app.page('test/fixtures/pages/c.hbs'); app.views.pages.should.have.properties([ - 'test/fixtures/pages/a.hbs', - 'test/fixtures/pages/b.hbs', - 'test/fixtures/pages/c.hbs' + path.resolve('test/fixtures/pages/a.hbs'), + path.resolve('test/fixtures/pages/b.hbs'), + path.resolve('test/fixtures/pages/c.hbs') ]); }); }); @@ -134,7 +134,7 @@ describe('create', function () { collection.addView('test/fixtures/templates/a.tmpl'); collection.read('a.tmpl'); - assert(collection.getView('a.tmpl').contents.toString() === 'a <%= name %> b'); + assert(collection.getView('a.tmpl').content === '<%= name %>'); }); }); @@ -173,4 +173,18 @@ describe('create', function () { assert(app.layouts.options.foo === 'bar'); }); }); + + describe('collection instantiation', function () { + it('should expose collection instance methods that are created after instantiation on the app collection loader', function () { + app.create('pages'); + app.pages.use(function (collection) { + collection.define('foo', function (msg) { + return 'foo ' + msg; + }); + }); + + assert(app.pages.foo); + assert(typeof app.pages.foo === 'function'); + }); + }); }); diff --git a/test/app.data.js b/test/app.data.js index c2aedc6..9cf2cd3 100644 --- a/test/app.data.js +++ b/test/app.data.js @@ -43,6 +43,28 @@ describe('app.data', function () { assert(app.cache.data.prefix_c.three.c === 'ccc'); }); + it('should use `namespace` defined on data opts:', function () { + app.data('test/fixtures/data/*.json', { + namespace: function (key) { + return 'prefix_' + path.basename(key, path.extname(key)); + } + }); + assert(app.cache.data.prefix_a.one.a === 'aaa'); + assert(app.cache.data.prefix_b.two.b === 'bbb'); + assert(app.cache.data.prefix_c.three.c === 'ccc'); + }); + + it('should use `renameKey` defined on data opts:', function () { + app.data('test/fixtures/data/*.json', { + renameKey: function (key) { + return 'prefix_' + path.basename(key, path.extname(key)); + } + }); + assert(app.cache.data.prefix_a.one.a === 'aaa'); + assert(app.cache.data.prefix_b.two.b === 'bbb'); + assert(app.cache.data.prefix_c.three.c === 'ccc'); + }); + it('should extend `cache.data`', function() { app.data({a: 'aaa', b: 'bbb', c: 'ccc'}); app.data({x: 'xxx', y: 'yyy', z: 'zzz'}); diff --git a/test/app.dest.js b/test/app.dest.js index 52e5584..1e3d67e 100644 --- a/test/app.dest.js +++ b/test/app.dest.js @@ -17,12 +17,11 @@ var bufEqual = require('buffer-equal'); var through = require('through2'); var File = require('vinyl'); -var outpath = path.join(__dirname, 'out-fixtures'); - +var actual = path.join(__dirname, 'actual'); var wipeOut = function(cb) { app = new App(); - rimraf(path.join(__dirname, 'out-fixtures/'), cb); + rimraf(path.join(__dirname, 'actual/'), cb); spies.setError('false'); statSpy.reset(); chmodSpy.reset(); @@ -81,7 +80,7 @@ describe('dest stream', function() { done(); }; - var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + var stream = app.dest('./actual/', {cwd: __dirname}); var buffered = []; bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); @@ -106,7 +105,7 @@ describe('dest stream', function() { done(); }; - var stream = app.dest(path.join(__dirname, 'out-fixtures/')); + var stream = app.dest(path.join(__dirname, 'actual/')); var buffered = []; bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); @@ -118,9 +117,9 @@ describe('dest stream', function() { it('should not write null files', function(done) { var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'out-fixtures/test.coffee'); + var expectedPath = path.join(__dirname, 'actual/test.coffee'); var expectedCwd = __dirname; - var expectedBase = path.join(__dirname, 'out-fixtures'); + var expectedBase = path.join(__dirname, 'actual'); var expectedFile = new File({ base: inputBase, @@ -139,7 +138,7 @@ describe('dest stream', function() { done(); }; - var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + var stream = app.dest('./actual/', {cwd: __dirname}); var buffered = []; bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); @@ -151,9 +150,9 @@ describe('dest stream', function() { it('should write buffer files to the right folder with relative cwd', function(done) { var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'out-fixtures/test.coffee'); + var expectedPath = path.join(__dirname, 'actual/test.coffee'); var expectedCwd = __dirname; - var expectedBase = path.join(__dirname, 'out-fixtures'); + var expectedBase = path.join(__dirname, 'actual'); var expectedContents = fs.readFileSync(inputPath); var expectedFile = new File({ @@ -174,7 +173,7 @@ describe('dest stream', function() { done(); }; - var stream = app.dest('./out-fixtures/', {cwd: path.relative(process.cwd(), __dirname)}); + var stream = app.dest('./actual/', {cwd: path.relative(process.cwd(), __dirname)}); var buffered = []; bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); @@ -186,9 +185,9 @@ describe('dest stream', function() { it('should write buffer files to the right folder with function and relative cwd', function(done) { var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'out-fixtures/test.coffee'); + var expectedPath = path.join(__dirname, 'actual/test.coffee'); var expectedCwd = __dirname; - var expectedBase = path.join(__dirname, 'out-fixtures'); + var expectedBase = path.join(__dirname, 'actual'); var expectedContents = fs.readFileSync(inputPath); var expectedFile = new File({ @@ -212,7 +211,7 @@ describe('dest stream', function() { var stream = app.dest(function(file){ should.exist(file); file.should.equal(expectedFile); - return './out-fixtures'; + return './actual'; }, {cwd: path.relative(process.cwd(), __dirname)}); var buffered = []; @@ -225,10 +224,10 @@ describe('dest stream', function() { it('should write buffer files to the right folder', function(done) { var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'out-fixtures/test.coffee'); + var expectedPath = path.join(__dirname, 'actual/test.coffee'); var expectedContents = fs.readFileSync(inputPath); var expectedCwd = __dirname; - var expectedBase = path.join(__dirname, 'out-fixtures'); + var expectedBase = path.join(__dirname, 'actual'); var expectedMode = 0655; var expectedFile = new File({ @@ -253,7 +252,7 @@ describe('dest stream', function() { done(); }; - var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + var stream = app.dest('./actual/', {cwd: __dirname}); var buffered = []; bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); @@ -265,10 +264,10 @@ describe('dest stream', function() { it('should write streaming files to the right folder', function(done) { var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'out-fixtures/test.coffee'); + var expectedPath = path.join(__dirname, 'actual/test.coffee'); var expectedContents = fs.readFileSync(inputPath); var expectedCwd = __dirname; - var expectedBase = path.join(__dirname, 'out-fixtures'); + var expectedBase = path.join(__dirname, 'actual'); var expectedMode = 0655; var contentStream = through.obj(); @@ -294,7 +293,7 @@ describe('dest stream', function() { done(); }; - var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + var stream = app.dest('./actual/', {cwd: __dirname}); var buffered = []; bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); @@ -310,9 +309,9 @@ describe('dest stream', function() { it('should write directories to the right folder', function(done) { var inputPath = path.join(__dirname, 'fixtures/vinyl/test'); var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'out-fixtures/test'); + var expectedPath = path.join(__dirname, 'actual/test'); var expectedCwd = __dirname; - var expectedBase = path.join(__dirname, 'out-fixtures'); + var expectedBase = path.join(__dirname, 'actual'); var expectedMode = 0655; var expectedFile = new File({ @@ -340,7 +339,7 @@ describe('dest stream', function() { done(); }; - var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + var stream = app.dest('./actual/', {cwd: __dirname}); var buffered = []; bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); @@ -350,12 +349,12 @@ describe('dest stream', function() { }); it('should allow piping multiple dests in streaming mode', function(done) { - var inputPath1 = path.join(__dirname, 'out-fixtures/multiple-first'); - var inputPath2 = path.join(__dirname, 'out-fixtures/multiple-second'); - var inputBase = path.join(__dirname, 'out-fixtures/'); + var inputPath1 = path.join(__dirname, 'actual/multiple-first'); + var inputPath2 = path.join(__dirname, 'actual/multiple-second'); + var inputBase = path.join(__dirname, 'actual/'); var srcPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var stream1 = app.dest('./out-fixtures/', {cwd: __dirname}); - var stream2 = app.dest('./out-fixtures/', {cwd: __dirname}); + var stream1 = app.dest('./actual/', {cwd: __dirname}); + var stream2 = app.dest('./actual/', {cwd: __dirname}); var content = fs.readFileSync(srcPath); var rename = through.obj(function(file, _, next) { file.path = inputPath2; @@ -390,7 +389,7 @@ describe('dest stream', function() { it('should write new files with the default user mode', function(done) { var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'out-fixtures/test.coffee'); + var expectedPath = path.join(__dirname, 'actual/test.coffee'); var expectedContents = fs.readFileSync(inputPath); var expectedMode = 0666 & (~process.umask()); @@ -410,7 +409,7 @@ describe('dest stream', function() { }; chmodSpy.reset(); - var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + var stream = app.dest('./actual/', {cwd: __dirname}); var buffered = []; bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); @@ -423,7 +422,7 @@ describe('dest stream', function() { it('should write new files with the specified mode', function(done) { var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'out-fixtures/test.coffee'); + var expectedPath = path.join(__dirname, 'actual/test.coffee'); var expectedContents = fs.readFileSync(inputPath); var expectedMode = 0744; @@ -443,7 +442,7 @@ describe('dest stream', function() { }; chmodSpy.reset(); - var stream = app.dest('./out-fixtures/', {cwd: __dirname, mode:expectedMode}); + var stream = app.dest('./actual/', {cwd: __dirname, mode:expectedMode}); var buffered = []; bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); @@ -456,9 +455,9 @@ describe('dest stream', function() { it('should update file mode to match the vinyl mode', function(done) { var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'out-fixtures/test.coffee'); + var expectedPath = path.join(__dirname, 'actual/test.coffee'); var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, 'out-fixtures'); + var expectedBase = path.join(__dirname, 'actual'); var startMode = 0655; var expectedMode = 0722; @@ -486,7 +485,7 @@ describe('dest stream', function() { fs.chmodSync(expectedPath, startMode); chmodSpy.reset(); - var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + var stream = app.dest('./actual/', {cwd: __dirname}); var buffered = []; bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); @@ -499,7 +498,7 @@ describe('dest stream', function() { it('should use different modes for files and directories', function(done) { var inputBase = path.join(__dirname, 'fixtures/vinyl'); var inputPath = path.join(__dirname, 'fixtures/vinyl/wow/suchempty'); - var expectedBase = path.join(__dirname, 'out-fixtures/wow'); + var expectedBase = path.join(__dirname, 'actual/wow'); var expectedDirMode = 0755; var expectedFileMode = 0655; @@ -516,7 +515,7 @@ describe('dest stream', function() { done(); }; - var stream = app.dest('./out-fixtures/', { + var stream = app.dest('./actual/', { cwd: __dirname, mode: expectedFileMode, dirMode: expectedDirMode @@ -545,7 +544,7 @@ describe('dest stream', function() { done(); }; - var stream = app.dest('./out-fixtures/', { + var stream = app.dest('./actual/', { cwd: __dirname, base: inputBase }); @@ -573,7 +572,7 @@ describe('dest stream', function() { done(); }; - var stream = app.dest('./out-fixtures/', { + var stream = app.dest('./actual/', { cwd: __dirname, base: function(file){ should.exist(file); @@ -593,9 +592,9 @@ describe('dest stream', function() { it('should report IO errors', function(done) { var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'out-fixtures/test.coffee'); + var expectedPath = path.join(__dirname, 'actual/test.coffee'); var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, 'out-fixtures'); + var expectedBase = path.join(__dirname, 'actual'); var expectedMode = 0722; var expectedFile = new File({ @@ -612,7 +611,7 @@ describe('dest stream', function() { fs.closeSync(fs.openSync(expectedPath, 'w')); fs.chmodSync(expectedPath, 0); - var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + var stream = app.dest('./actual/', {cwd: __dirname}); stream.on('error', function(err) { err.code.should.equal('EACCES'); done(); @@ -623,9 +622,9 @@ describe('dest stream', function() { it('should report stat errors', function(done) { var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'out-fixtures/test.coffee'); + var expectedPath = path.join(__dirname, 'actual/test.coffee'); var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, 'out-fixtures'); + var expectedBase = path.join(__dirname, 'actual'); var expectedMode = 0722; var expectedFile = new File({ @@ -647,7 +646,7 @@ describe('dest stream', function() { } }); - var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + var stream = app.dest('./actual/', {cwd: __dirname}); stream.on('error', function(err) { err.message.should.equal('stat error'); done(); @@ -658,9 +657,9 @@ describe('dest stream', function() { it('should report chmod errors', function(done) { var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'out-fixtures/test.coffee'); + var expectedPath = path.join(__dirname, 'actual/test.coffee'); var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, 'out-fixtures'); + var expectedBase = path.join(__dirname, 'actual'); var expectedMode = 0722; var expectedFile = new File({ @@ -682,7 +681,7 @@ describe('dest stream', function() { } }); - var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + var stream = app.dest('./actual/', {cwd: __dirname}); stream.on('error', function(err) { err.message.should.equal('chmod error'); done(); @@ -693,9 +692,9 @@ describe('dest stream', function() { it('should not chmod a matching file', function(done) { var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'out-fixtures/test.coffee'); + var expectedPath = path.join(__dirname, 'actual/test.coffee'); var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, 'out-fixtures'); + var expectedBase = path.join(__dirname, 'actual'); var expectedMode = 0722; var expectedFile = new File({ @@ -728,7 +727,7 @@ describe('dest stream', function() { statSpy.reset(); chmodSpy.reset(); - var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + var stream = app.dest('./actual/', {cwd: __dirname}); var buffered = []; bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); @@ -741,9 +740,9 @@ describe('dest stream', function() { it('should see a file with special chmod (setuid/setgid/sticky) as matching', function(done) { var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'out-fixtures/test.coffee'); + var expectedPath = path.join(__dirname, 'actual/test.coffee'); var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, 'out-fixtures'); + var expectedBase = path.join(__dirname, 'actual'); var expectedMode = 03722; var normalMode = 0722; @@ -776,7 +775,7 @@ describe('dest stream', function() { statSpy.reset(); chmodSpy.reset(); - var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + var stream = app.dest('./actual/', {cwd: __dirname}); var buffered = []; bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); @@ -791,8 +790,8 @@ describe('dest stream', function() { var inputBase = path.join(__dirname, 'fixtures/vinyl/'); var inputContents = fs.readFileSync(inputPath); - var expectedPath = path.join(__dirname, 'out-fixtures/test.coffee'); - var expectedBase = path.join(__dirname, 'out-fixtures'); + var expectedPath = path.join(__dirname, 'actual/test.coffee'); + var expectedBase = path.join(__dirname, 'actual'); var existingContents = 'Lorem Ipsum'; var inputFile = new File({ @@ -812,7 +811,7 @@ describe('dest stream', function() { fs.mkdirSync(expectedBase); fs.writeFileSync(expectedPath, existingContents); - var stream = app.dest('./out-fixtures/', {cwd: __dirname, overwrite: false}); + var stream = app.dest('./actual/', {cwd: __dirname, overwrite: false}); var buffered = []; bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); @@ -826,8 +825,8 @@ describe('dest stream', function() { var inputBase = path.join(__dirname, 'fixtures/vinyl/'); var inputContents = fs.readFileSync(inputPath); - var expectedPath = path.join(__dirname, 'out-fixtures/test.coffee'); - var expectedBase = path.join(__dirname, 'out-fixtures'); + var expectedPath = path.join(__dirname, 'actual/test.coffee'); + var expectedBase = path.join(__dirname, 'actual'); var existingContents = 'Lorem Ipsum'; var inputFile = new File({ @@ -847,7 +846,7 @@ describe('dest stream', function() { fs.mkdirSync(expectedBase); fs.writeFileSync(expectedPath, existingContents); - var stream = app.dest('./out-fixtures/', {cwd: __dirname, overwrite: true}); + var stream = app.dest('./actual/', {cwd: __dirname, overwrite: true}); var buffered = []; bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); @@ -861,7 +860,7 @@ describe('dest stream', function() { var inputBase = path.join(__dirname, 'fixtures/vinyl/'); var inputRelativeSymlinkPath = 'wow'; - var expectedPath = path.join(__dirname, 'out-fixtures/test-create-dir-symlink'); + var expectedPath = path.join(__dirname, 'actual/test-create-dir-symlink'); var inputFile = new File({ base: inputBase, @@ -881,7 +880,7 @@ describe('dest stream', function() { }); }; - var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + var stream = app.dest('./actual/', {cwd: __dirname}); var buffered = []; bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); @@ -892,7 +891,7 @@ describe('dest stream', function() { it('should emit finish event', function(done) { var srcPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var stream = app.dest('./out-fixtures/', {cwd: __dirname}); + var stream = app.dest('./actual/', {cwd: __dirname}); stream.once('finish', function() { done(); @@ -911,12 +910,12 @@ describe('dest stream', function() { describe('dest', function() { beforeEach(function (done) { - rimraf(outpath, done); + rimraf(actual, done); app = new App(); }); afterEach(function (done) { - rimraf(outpath, done); + rimraf(actual, done); }); describe('streams', function () { @@ -928,8 +927,8 @@ describe('dest', function() { }); it('should return an output stream that writes files', function (done) { - var instream = app.src(path.join(__dirname, 'fixtures/copy/*.txt')); - var outstream = app.dest(outpath); + var instream = app.src(path.join(__dirname, 'fixtures/copy/e*.txt')); + var outstream = app.dest(actual); instream.pipe(outstream); outstream.on('error', done); @@ -938,11 +937,11 @@ describe('dest', function() { should.exist(file); should.exist(file.path); should.exist(file.contents); - path.join(file.path, '').should.equal(path.join(outpath, 'example.txt')); + path.join(file.path, '').should.equal(path.join(actual, 'example.txt')); String(file.contents).should.equal('Hello world!'); }); outstream.on('end', function () { - fs.readFile(path.join(outpath, 'example.txt'), function (err, contents) { + fs.readFile(path.join(actual, 'example.txt'), function (err, contents) { should.not.exist(err); should.exist(contents); String(contents).should.equal('Hello world!'); @@ -952,8 +951,8 @@ describe('dest', function() { }); it('should return an output stream that does not write non-read files', function (done) { - var instream = app.src(path.join(__dirname, 'fixtures/copy/*.txt'), {read: false}); - var outstream = app.dest(outpath); + var instream = app.src(path.join(__dirname, 'fixtures/copy/e*.txt'), {read: false}); + var outstream = app.dest(actual); instream.pipe(outstream); outstream.on('error', done); @@ -962,11 +961,11 @@ describe('dest', function() { should.exist(file); should.exist(file.path); should.not.exist(file.contents); - path.join(file.path, '').should.equal(path.join(outpath, 'example.txt')); + path.join(file.path, '').should.equal(path.join(actual, 'example.txt')); }); outstream.on('end', function () { - fs.readFile(path.join(outpath, 'example.txt'), function (err, contents) { + fs.readFile(path.join(actual, 'example.txt'), function (err, contents) { should.exist(err); should.not.exist(contents); done(); @@ -975,8 +974,8 @@ describe('dest', function() { }); it('should return an output stream that writes streaming files', function (done) { - var instream = app.src(path.join(__dirname, 'fixtures/copy/*.txt'), {buffer: false}); - var outstream = instream.pipe(app.dest(outpath)); + var instream = app.src(path.join(__dirname, 'fixtures/copy/e*.txt'), {buffer: false}); + var outstream = instream.pipe(app.dest(actual)); outstream.on('error', done); outstream.on('data', function (file) { @@ -984,10 +983,10 @@ describe('dest', function() { should.exist(file); should.exist(file.path); should.exist(file.contents); - path.join(file.path, '').should.equal(path.join(outpath, 'example.txt')); + path.join(file.path, '').should.equal(path.join(actual, 'example.txt')); }); outstream.on('end', function () { - fs.readFile(path.join(outpath, 'example.txt'), function (err, contents) { + fs.readFile(path.join(actual, 'example.txt'), function (err, contents) { should.not.exist(err); should.exist(contents); String(contents).should.equal('Hello world!'); @@ -1032,8 +1031,8 @@ describe('dest', function() { }); it('should return an output stream that writes files', function (done) { - var instream = app.src(path.join(__dirname, 'fixtures/copy/*.txt')); - var outstream = app.dest(outpath); + var instream = app.src(path.join(__dirname, 'fixtures/copy/e*.txt')); + var outstream = app.dest(actual); instream.pipe(outstream); outstream.on('error', done); @@ -1042,11 +1041,11 @@ describe('dest', function() { should.exist(file); should.exist(file.path); should.exist(file.contents); - path.join(file.path, '').should.equal(path.join(outpath, 'example.txt')); + path.join(file.path, '').should.equal(path.join(actual, 'example.txt')); String(file.contents).should.equal('Hello world!'); }); outstream.on('end', function () { - fs.readFile(path.join(outpath, 'example.txt'), function (err, contents) { + fs.readFile(path.join(actual, 'example.txt'), function (err, contents) { should.not.exist(err); should.exist(contents); String(contents).should.equal('Hello world!'); @@ -1057,7 +1056,7 @@ describe('dest', function() { it('should return an output stream that does not write non-read files', function (done) { var instream = app.src(path.join(__dirname, 'fixtures/dest/*.txt'), {read: false}); - var outstream = app.dest(outpath); + var outstream = app.dest(actual); instream.pipe(outstream); outstream.on('error', done); @@ -1066,11 +1065,11 @@ describe('dest', function() { should.exist(file); should.exist(file.path); should.not.exist(file.contents); - path.join(file.path, '').should.equal(path.join(outpath, 'example.txt')); + path.join(file.path, '').should.equal(path.join(actual, 'example.txt')); }); outstream.on('end', function () { - fs.readFile(path.join(outpath, 'example.txt'), function (err, contents) { + fs.readFile(path.join(actual, 'example.txt'), function (err, contents) { should.exist(err); should.not.exist(contents); done(); @@ -1081,18 +1080,18 @@ describe('dest', function() { function testWriteDir(srcOptions, done) { var instream = app.src(path.join(__dirname, 'fixtures/generic'), srcOptions); - var outstream = instream.pipe(app.dest(outpath)); + var outstream = instream.pipe(app.dest(actual)); outstream.on('error', done); outstream.on('data', function(file) { // data should be re-emitted correctly should.exist(file); should.exist(file.path); - path.join(file.path,'').should.equal(path.join(outpath, 'generic')); + path.join(file.path,'').should.equal(path.join(actual, 'generic')); }); outstream.on('end', function() { - fs.exists(path.join(outpath, 'generic'), function(exists) { + fs.exists(path.join(actual, 'generic'), function(exists) { /* jshint expr: true */ should(exists).be.ok; /* jshint expr: false */ diff --git a/test/app.events.js b/test/app.events.js index 3505b9c..3bf001e 100644 --- a/test/app.events.js +++ b/test/app.events.js @@ -12,8 +12,7 @@ describe('events', function () { it('should listen for an event:', function () { var app = new App(); - app.on('foo', function () { - }); + app.on('foo', function () {}); assert(Array.isArray(app._callbacks['$foo'])); }); @@ -27,16 +26,6 @@ describe('events', function () { app.emit('foo', 'bar'); }); - it('should listen for error events:', function (done) { - var app = new App(); - app.on('foo', function (val) { - assert(val === 'bar'); - done(); - }); - assert(Array.isArray(app._callbacks['$foo'])); - app.emit('foo', 'bar'); - }); - it('should listen for `view` events:', function () { var app = new App(); diff --git a/test/app.handle.js b/test/app.handle.js index f968d69..8ce9902 100644 --- a/test/app.handle.js +++ b/test/app.handle.js @@ -20,4 +20,14 @@ describe('handler', function () { done(); }); }); + + it('should not blow up if `options.handled` does not exist:', function (done) { + var page = app.page('foo', {contents: null}); + delete page.options.handled; + + app.handle('foo', page, function (err, view) { + assert(typeof view.path === 'string'); + done(); + }); + }); }); diff --git a/test/app.handlers.js b/test/app.handlers.js index e29f184..71712f6 100644 --- a/test/app.handlers.js +++ b/test/app.handlers.js @@ -7,15 +7,25 @@ var support = require('./support'); var App = support.resolve(); var app; -function read(views) { - return function (view) { +function decorateViews(views) { + var fn = views.decorateView; + views.decorateView = function () { + var view = fn.apply(fn, arguments); view.read = function () { if (!this.contents) { this.contents = fs.readFileSync(this.path); } }; return view; - } + }; + views.loader = function (pattern) { + var files = resolve.sync(pattern); + return files.reduce(function (acc, fp) { + acc[fp] = {path: fp}; + return acc; + }, {}); + }; + return views; } describe('handlers', function () { @@ -23,7 +33,7 @@ describe('handlers', function () { beforeEach(function () { app = new App(); app.create('pages') - .use(read) + .use(decorateViews) .option('renameKey', function (key) { return path.basename(key); }); @@ -36,11 +46,6 @@ describe('handlers', function () { }); it('should add custom middleware handlers:', function () { - app.pages.on('view', function (view) { - console.log(view.read) - // view.read(); - }); - app.handler('foo'); app.handler('bar'); @@ -54,75 +59,14 @@ describe('handlers', function () { next(); }); - app - .pages('a', {contents: '...'}) - .pages('b', {contents: '...'}) - .pages('c', {contents: '...'}) - .use(function (pages) { - // console.log(pages) - var fn = pages.extendView; - pages.extendView = function (view) { - view = fn(view); - app.handleView('foo', view); - return view; - }; - return pages; + app.page('abc', {content: '...'}) + .use(function (view) { + app.handleView('foo', view); + app.handleView('bar', view); }); - // .pages('test/fixtures/pages/*.hbs') - // .use(function (pages) { - // var fn = pages.extendView; - // pages.extendView = function (view) { - // view = fn(view); - // app.handleView('bar', view); - // return view; - // }; - // return pages; - // }) - - // console.log(pages.getView('a.tmpl').one); - // console.log(app.pages.getView('a.tmpl').one) - // console.log(app.pages.getView('a.hbs').two) - - // app.pages.getView('a.txt').should.have.property('one'); - // app.pages.getView('a.txt').should.have.property('two'); - // app.pages.getView('a.md').should.not.have.property('one'); - // app.pages.getView('a.md').should.have.property('two'); + app.views.pages.abc.should.have.property('one', 'aaa'); + app.views.pages.abc.should.have.property('two', 'zzz'); }); - - // it('should add custom middleware handlers:', function () { - // app.handler('foo'); - // app.handler('bar'); - - // function handle(method) { - // return function (view) { - // return app.handle(method, view); - // } - // } - - // app.foo(/./, function (view, next) { - // view.one = 'aaa'; - // next(); - // }); - - // app.bar(/./, function (view, next) { - // view.two = 'zzz'; - // next(); - // }); - - // app.pages('test/fixtures/*.txt') - // .use(handle('foo')) - - // .pages('test/fixtures/*.md') - // .use(handle('bar')) - - // .use(utils.rename); - - // app.pages.getView('a.txt').should.have.property('one'); - // app.pages.getView('a.txt').should.have.property('two'); - - // app.pages.getView('a.md').should.not.have.property('one'); - // app.pages.getView('a.md').should.have.property('two'); - // }); }); }); diff --git a/test/app.list.js b/test/app.list.js new file mode 100644 index 0000000..9c9c3ea --- /dev/null +++ b/test/app.list.js @@ -0,0 +1,105 @@ +require('mocha'); +require('should'); +var fs = require('fs'); +var path = require('path'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var List = App.List; +var app; + +describe('list', function () { + describe('method', function () { + beforeEach(function () { + app = new App(); + }); + + it('should expose the list method', function () { + assert(typeof app.list === 'function'); + }); + + it('should return a new list', function () { + var list = app.list(); + assert(typeof list === 'object'); + }); + + it('should have isList property', function () { + var list = app.list(); + assert(list.isList === true); + }); + }); + + describe('adding items', function () { + beforeEach(function () { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.create('pages'); + }); + + it('should add an item to a list:', function () { + app.pages('test/fixtures/pages/a.hbs'); + var list = app.list(); + list.addItem(app.pages.getView('test/fixtures/pages/a.hbs')); + assert(list.hasItem(path.resolve('test/fixtures/pages/a.hbs'))); + }); + + it('should expose the `option` method from a list:', function () { + var list = app.list(); + list.option('a', 'b'); + assert(list.options); + assert(list.options.a === 'b'); + }); + }); + + describe('addItem', function () { + beforeEach(function () { + app = new App(); + }); + + it('should add items to a list', function () { + var pages = app.list({List: List}); + pages.addItem('foo'); + pages.addItem('bar'); + pages.addItem('baz'); + + pages.items.hasOwnProperty('foo'); + pages.items.hasOwnProperty('bar'); + pages.items.hasOwnProperty('baz'); + }); + + it('should create a list from an existing list:', function () { + var pages = app.list({List: List}); + pages.addItem('foo'); + pages.addItem('bar'); + pages.addItem('baz'); + + var posts = app.list(pages); + posts.items.hasOwnProperty('foo'); + posts.items.hasOwnProperty('bar'); + posts.items.hasOwnProperty('baz'); + }); + }); + + describe('rendering items', function () { + beforeEach(function () { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.create('pages'); + }); + + it('should render a item with inherited app.render', function (done) { + app.page('test/fixtures/templates/a.tmpl') + .use(function (item) { + if (!item.contents) { + item.contents = fs.readFileSync(item.path); + } + }) + .set('data.name', 'Brian') + .render(function (err, res) { + if (err) return done(err); + assert(res.content === 'Brian'); + done(); + }); + }); + }); +}); diff --git a/test/app.list.render.js b/test/app.list.render.js deleted file mode 100644 index f4a9e0b..0000000 --- a/test/app.list.render.js +++ /dev/null @@ -1,157 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var async = require('async'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var List = App.List; -var pages, app; - -describe('render', function () { - describe('rendering', function () { - beforeEach(function () { - app = App(); - pages = app.create('pages'); - app.engine('tmpl', require('engine-base')); - pages.engine('tmpl', require('engine-base')); - }); - - it('should throw an error when no callback is given:', function () { - (function() { - app.pages.render({}); - }).should.throw('Views#render is async and expects a callback function'); - }); - - it('should throw an error when an engine is not defined:', function (done) { - pages.addView('foo.bar', {content: '<%= name %>'}); - var page = pages.getView('foo.bar'); - - app.pages.render(page, function(err) { - assert(err.message === 'Views#render cannot find an engine for: .bar'); - done(); - }); - }); - - it('should use helpers defined on app to render a view:', function (done) { - var locals = {name: 'Halle'}; - app.helper('upper', function (str) { - return str.toUpperCase(str) + 'app'; - }); - - pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); - var page = pages.getView('a.tmpl'); - - app.render(page, function (err, res) { - if (err) return done(err); - - assert(res.content === 'a HALLEapp b'); - done(); - }); - }); - - it('should use helpers defined on app to render a view with collection.render:', function (done) { - var locals = {name: 'Halle'}; - app.helper('upper', function (str) { - return str.toUpperCase(str) + 'app'; - }); - - pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); - pages.helper('upper', app._.helpers.sync.upper); - var page = pages.getView('a.tmpl'); - - pages.render(page, function (err, res) { - if (err) return done(err); - - assert(res.content === 'a HALLEapp b'); - done(); - }); - }); - - it('should use helpers when rendering a view:', function (done) { - var locals = {name: 'Halle'}; - pages.helper('upper', function (str) { - return str.toUpperCase(str); - }); - - pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); - var page = pages.getView('a.tmpl'); - - pages.render(page, function (err, res) { - if (err) return done(err); - assert(res.content === 'a HALLE b'); - done(); - }); - }); - - it('should render a template when contents is a buffer:', function (done) { - pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - var view = pages.getView('a.tmpl'); - - pages.render(view, function (err, view) { - if (err) return done(err); - assert(view.contents.toString() === 'b'); - done(); - }); - }); - - it('should render a template when content is a string:', function (done) { - pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - var view = pages.getView('a.tmpl'); - - pages.render(view, function (err, view) { - if (err) return done(err); - assert(view.contents.toString() === 'b'); - done(); - }); - }); - - it('should render a view from its path:', function (done) { - pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - - pages.render('a.tmpl', function (err, view) { - if (err) return done(err); - assert(view.content === 'b'); - done(); - }); - }); - - it('should use a plugin for rendering:', function (done) { - pages.engine('tmpl', require('engine-base')); - pages.option('engine', 'tmpl'); - - pages.addViews({ - 'a': {content: '<%= title %>', locals: {title: 'aaa'}}, - 'b': {content: '<%= title %>', locals: {title: 'bbb'}}, - 'c': {content: '<%= title %>', locals: {title: 'ccc'}}, - 'd': {content: '<%= title %>', locals: {title: 'ddd'}}, - 'e': {content: '<%= title %>', locals: {title: 'eee'}}, - 'f': {content: '<%= title %>', locals: {title: 'fff'}}, - 'g': {content: '<%= title %>', locals: {title: 'ggg'}}, - 'h': {content: '<%= title %>', locals: {title: 'hhh'}}, - 'i': {content: '<%= title %>', locals: {title: 'iii'}}, - 'j': {content: '<%= title %>', locals: {title: 'jjj'}}, - }); - - pages.use(function (collection) { - collection.option('pager', false); - - collection.renderEach = function (cb) { - var list = new List(collection); - async.map(list.items, function (item, next) { - collection.render(item, next); - }, cb); - }; - }); - - pages.renderEach(function (err, items) { - if (err) return done(err); - assert(items[0].content === 'aaa'); - assert(items[9].content === 'jjj'); - assert(items.length === 10); - done(); - }); - }); - }); -}); diff --git a/test/app.option.js b/test/app.option.js index a13a7aa..12bdd6f 100644 --- a/test/app.option.js +++ b/test/app.option.js @@ -20,12 +20,6 @@ describe('app.option', function () { assert(app.options.c === 'd'); }); - it('should throw on invalid args:', function () { - (function () { - app.option(function () {}); - }).should.throw('expected a string or object.'); - }); - it('should set an option.', function() { app.option('a', 'b'); app.options.should.have.property('a'); diff --git a/test/app.renderFile.js b/test/app.renderFile.js new file mode 100644 index 0000000..1b3b478 --- /dev/null +++ b/test/app.renderFile.js @@ -0,0 +1,135 @@ +'use strict'; + +var assemble = require('..'); +var assert = require('assert'); +var should = require('should'); +var path = require('path'); +var app; + +describe('app.renderFile()', function() { + beforeEach(function () { + app = assemble(); + app.engine('hbs', require('engine-handlebars')); + app.engine('*', require('engine-base')); + + app.create('files', {engine: '*'}); + app.file('a', {content: 'this is <%= title() %>'}); + app.file('b', {content: 'this is <%= title() %>'}); + app.file('c', {content: 'this is <%= title() %>'}); + + app.option('renameKey', function (key) { + return path.basename(key, path.extname(key)); + }); + + app.helper('title', function () { + var view = this.context.view; + var key = view.key; + var ctx = this.context[key]; + if (ctx && ctx.title) return ctx.title; + return key; + }); + }); + + it('should render views from src', function (done) { + var stream = app.src(path.join(__dirname, 'fixtures/pages/*.hbs')); + var files = []; + + stream.pipe(app.renderFile()) + .on('error', done) + .on('data', function (file) { + files.push(file); + }) + .on('end', function () { + assert.equal(files[0].basename, 'a.hbs'); + assert.equal(files[1].basename, 'b.hbs'); + assert.equal(files[2].basename, 'c.hbs'); + done(); + }); + }); + + it('should render views with the engine that matches the file extension', function (done) { + var stream = app.src(path.join(__dirname, 'fixtures/pages/*.hbs')); + var files = []; + + stream.pipe(app.renderFile()) + .on('error', done) + .on('data', function (file) { + files.push(file); + }) + .on('end', function () { + assert(/

a<\/h1>/.test(files[0].content)); + assert(/

b<\/h1>/.test(files[1].content)); + assert(/

c<\/h1>/.test(files[2].content)); + done(); + }); + }); + + it('should render views from src with the engine passed on the opts', function (done) { + var stream = app.src(path.join(__dirname, 'fixtures/pages/*.hbs')); + var files = []; + + stream.pipe(app.renderFile({engine: '*'})) + .on('error', done) + .on('data', function (file) { + files.push(file); + }) + .on('end', function () { + assert(/

a<\/h2>/.test(files[0].content)); + assert(/

b<\/h2>/.test(files[1].content)); + assert(/

c<\/h2>/.test(files[2].content)); + done(); + }); + }); + + it('should use the context passed on the opts', function (done) { + var stream = app.src(path.join(__dirname, 'fixtures/pages/*.hbs')); + var files = []; + + stream.pipe(app.renderFile({a: {title: 'foo'}})) + .on('error', done) + .on('data', function (file) { + files.push(file); + }) + .on('end', function () { + assert(/

foo<\/h1>/.test(files[0].content)); + assert(/

b<\/h1>/.test(files[1].content)); + assert(/

c<\/h1>/.test(files[2].content)); + done(); + }); + }); + + it('should render the files in a collection', function (cb) { + var files = []; + app.toStream('files') + .pipe(app.renderFile()) + .on('error', cb) + .on('data', function (file) { + should.exist(file); + should.exist(file.path); + should.exist(file.contents); + files.push(file); + }) + .on('end', function () { + assert(/this is a/.test(files[0].content)); + assert(/this is b/.test(files[1].content)); + assert(/this is c/.test(files[2].content)); + assert.equal(files.length, 3); + cb(); + }); + }); + + it('should handle engine errors', function (cb) { + app.create('notdefined', {engine: '*'}); + app.notdefined('foo', {content: '<%= bar %>'}); + app.toStream('notdefined') + .pipe(app.renderFile()) + .on('error', function (err) { + assert.equal(typeof err, 'object'); + assert.equal(err.message, 'bar is not defined'); + cb(); + }) + .on('end', function () { + cb(new Error('expected renderFile to handle the error.')); + }); + }); +}); diff --git a/test/app.symlink.js b/test/app.symlink.js new file mode 100644 index 0000000..69e8d45 --- /dev/null +++ b/test/app.symlink.js @@ -0,0 +1,396 @@ +require('mocha'); +var should = require('should'); +var fs = require('graceful-fs'); +var path = require('path'); +var rimraf = require('rimraf'); +var bufEqual = require('buffer-equal'); +var through = require('through2'); +var File = require('vinyl'); +var assemble = require('..'); +var spies = require('./support/spy'); +var chmodSpy = spies.chmodSpy; +var statSpy = spies.statSpy; +var app, bufferStream; + +var wipeOut = function(cb) { + rimraf(path.join(__dirname, './actual/'), cb); + spies.setError('false'); + statSpy.reset(); + chmodSpy.reset(); + app = assemble(); +}; + +var dataWrap = function(fn) { + return function(data, enc, cb) { + fn(data); + cb(); + }; +}; + +var realMode = function(n) { + return n & 07777; +}; + +describe('symlink stream', function() { + beforeEach(wipeOut); + afterEach(wipeOut); + + it('should pass through writes with cwd', function(done) { + var inputPath = path.join(__dirname, './fixtures/test.coffee'); + + var expectedFile = new File({ + base: __dirname, + cwd: __dirname, + path: inputPath, + contents: null + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + done(); + }; + + var stream = app.symlink('./actual/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should pass through writes with default cwd', function(done) { + var inputPath = path.join(__dirname, './fixtures/test.coffee'); + + var expectedFile = new File({ + base: __dirname, + cwd: __dirname, + path: inputPath, + contents: null + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + done(); + }; + + var stream = app.symlink(path.join(__dirname, './actual/')); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should make link to the right folder with relative cwd', function(done) { + var inputPath = path.join(__dirname, './fixtures/test.coffee'); + var inputBase = path.join(__dirname, './fixtures/'); + var expectedPath = path.join(__dirname, './actual/test.coffee'); + var expectedBase = path.join(__dirname, './actual'); + var expectedContents = fs.readFileSync(inputPath); + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + buffered[0].cwd.should.equal(__dirname, 'cwd should have changed'); + buffered[0].base.should.equal(expectedBase, 'base should have changed'); + buffered[0].path.should.equal(expectedPath, 'path should have changed'); + fs.existsSync(expectedPath).should.equal(true); + bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); + fs.readlinkSync(expectedPath).should.equal(inputPath); + done(); + }; + + var stream = app.symlink('./actual/', {cwd: path.relative(process.cwd(), __dirname)}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should write buffer files to the right folder with function and relative cwd', function(done) { + var inputPath = path.join(__dirname, './fixtures/test.coffee'); + var inputBase = path.join(__dirname, './fixtures/'); + var expectedPath = path.join(__dirname, './actual/test.coffee'); + var expectedBase = path.join(__dirname, './actual'); + var expectedContents = fs.readFileSync(inputPath); + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + buffered[0].cwd.should.equal(__dirname, 'cwd should have changed'); + buffered[0].base.should.equal(expectedBase, 'base should have changed'); + buffered[0].path.should.equal(expectedPath, 'path should have changed'); + fs.existsSync(expectedPath).should.equal(true); + bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); + fs.readlinkSync(expectedPath).should.equal(inputPath); + done(); + }; + + var stream = app.symlink(function(file){ + should.exist(file); + file.should.equal(expectedFile); + return './actual'; + }, {cwd: path.relative(process.cwd(), __dirname)}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should write buffer files to the right folder', function(done) { + var inputPath = path.join(__dirname, './fixtures/test.coffee'); + var inputBase = path.join(__dirname, './fixtures/'); + var expectedPath = path.join(__dirname, './actual/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedBase = path.join(__dirname, './actual'); + var expectedMode = 0655; + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents, + stat: { + mode: expectedMode + } + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + buffered[0].cwd.should.equal(__dirname, 'cwd should have changed'); + buffered[0].base.should.equal(expectedBase, 'base should have changed'); + buffered[0].path.should.equal(expectedPath, 'path should have changed'); + fs.existsSync(expectedPath).should.equal(true); + bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); + fs.readlinkSync(expectedPath).should.equal(inputPath); + done(); + }; + + var stream = app.symlink('./actual/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should write streaming files to the right folder', function(done) { + var inputPath = path.join(__dirname, './fixtures/test.coffee'); + var inputBase = path.join(__dirname, './fixtures/'); + var expectedPath = path.join(__dirname, './actual/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedBase = path.join(__dirname, './actual'); + var expectedMode = 0655; + + var contentStream = through.obj(); + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: contentStream, + stat: { + mode: expectedMode + } + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + buffered[0].cwd.should.equal(__dirname, 'cwd should have changed'); + buffered[0].base.should.equal(expectedBase, 'base should have changed'); + buffered[0].path.should.equal(expectedPath, 'path should have changed'); + fs.existsSync(expectedPath).should.equal(true); + bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); + fs.readlinkSync(expectedPath).should.equal(inputPath); + done(); + }; + + var stream = app.symlink('./actual/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + setTimeout(function(){ + contentStream.write(expectedContents); + contentStream.end(); + }, 100); + stream.end(); + }); + + it('should write directories to the right folder', function(done) { + var inputPath = path.join(__dirname, './fixtures/wow'); + var inputBase = path.join(__dirname, './fixtures/'); + var expectedPath = path.join(__dirname, './actual/wow'); + var expectedBase = path.join(__dirname, './actual'); + var expectedMode = 0655; + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: null, + stat: { + isDirectory: function(){ + return true; + }, + mode: expectedMode + } + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + buffered[0].cwd.should.equal(__dirname, 'cwd should have changed'); + buffered[0].base.should.equal(expectedBase, 'base should have changed'); + buffered[0].path.should.equal(expectedPath, 'path should have changed'); + fs.readlinkSync(expectedPath).should.equal(inputPath); + fs.lstatSync(expectedPath).isDirectory().should.equal(false); + fs.statSync(expectedPath).isDirectory().should.equal(true); + done(); + }; + + var stream = app.symlink('./actual/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should use different modes for files and directories', function(done) { + var inputBase = path.join(__dirname, './fixtures'); + var inputPath = path.join(__dirname, './fixtures/wow/suchempty'); + var expectedBase = path.join(__dirname, './actual/wow'); + var expectedDirMode = 0755; + var expectedFileMode = 0655; + + var firstFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + stat: fs.statSync(inputPath) + }); + + var onEnd = function(){ + realMode(fs.lstatSync(expectedBase).mode).should.equal(expectedDirMode); + realMode(buffered[0].stat.mode).should.equal(expectedFileMode); + done(); + }; + + var stream = app.symlink('./actual/', { + cwd: __dirname, + mode: expectedFileMode, + dirMode: expectedDirMode + }); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + + stream.pipe(bufferStream); + stream.write(firstFile); + stream.end(); + }); + + it('should change to the specified base', function(done) { + var inputBase = path.join(__dirname, './fixtures'); + var inputPath = path.join(__dirname, './fixtures/wow/suchempty'); + + var firstFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + stat: fs.statSync(inputPath) + }); + + var onEnd = function(){ + buffered[0].base.should.equal(inputBase); + done(); + }; + + var stream = app.symlink('./actual/', { + cwd: __dirname, + base: inputBase + }); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + + stream.pipe(bufferStream); + stream.write(firstFile); + stream.end(); + }); + + it('should report IO errors', function(done) { + var inputPath = path.join(__dirname, './fixtures/test.coffee'); + var inputBase = path.join(__dirname, './fixtures/'); + var expectedContents = fs.readFileSync(inputPath); + var expectedBase = path.join(__dirname, './actual'); + var expectedMode = 0722; + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents, + stat: { + mode: expectedMode + } + }); + + fs.mkdirSync(expectedBase); + fs.chmodSync(expectedBase, 0); + + var stream = app.symlink('./actual/', {cwd: __dirname}); + stream.on('error', function(err) { + err.code.should.equal('EACCES'); + done(); + }); + stream.write(expectedFile); + }); + + ['end', 'finish'].forEach(function(eventName) { + it('should emit ' + eventName + ' event', function(done) { + var srcPath = path.join(__dirname, './fixtures/test.coffee'); + var stream = app.symlink('./actual/', {cwd: __dirname}); + + stream.on(eventName, function() { + done(); + }); + + var file = new File({ + path: srcPath, + cwd: __dirname, + contents: new Buffer("1234567890") + }); + + stream.write(file); + stream.end(); + }); + }); +}); diff --git a/test/app.toStream.js b/test/app.toStream.js new file mode 100644 index 0000000..97bd30e --- /dev/null +++ b/test/app.toStream.js @@ -0,0 +1,64 @@ +'use strict'; + +var assemble = require('..'); +var assert = require('assert'); +var should = require('should'); +var app; + +describe('toStream()', function() { + beforeEach(function () { + app = assemble(); + app.create('pages'); + app.page('a', {content: 'this is A'}); + app.page('b', {content: 'this is B'}); + app.page('c', {content: 'this is C'}); + + app.create('posts'); + app.post('x', {content: 'this is X'}); + app.post('y', {content: 'this is Y'}); + app.post('z', {content: 'this is Z'}); + }); + + it('should return a stream', function (cb) { + var stream = app.toStream(); + should.exist(stream); + should.exist(stream.on); + cb(); + }); + + it('should return a stream for a collection', function (cb) { + var stream = app.toStream('pages'); + should.exist(stream); + should.exist(stream.on); + cb(); + }); + + it('should stack handle multiple collections', function (cb) { + var files = []; + app.toStream('pages') + .pipe(app.toStream('posts')) + .on('data', function(file) { + files.push(file); + }) + .on('end', function () { + assert.equal(files.length, 6); + cb(); + }); + }); + + it('should push each item in the collection into the stream', function (cb) { + var files = []; + app.toStream('pages') + .on('error', cb) + .on('data', function (file) { + should.exist(file); + should.exist(file.path); + should.exist(file.contents); + files.push(file.path); + }) + .on('end', function () { + assert.equal(files.length, 3); + cb(); + }); + }); +}); \ No newline at end of file diff --git a/test/collection.use.js b/test/collection.use.js index d7f7cbc..bc1c045 100644 --- a/test/collection.use.js +++ b/test/collection.use.js @@ -3,58 +3,58 @@ require('should'); var assert = require('assert'); var support = require('./support'); var App = support.resolve(); -var Views = App.Views; -var View = App.View; +var Collection = App.Collection; +var Item = App.Item; var collection; describe('collection.use', function () { beforeEach(function () { - collection = new Views(); + collection = new Collection(); }); it('should expose the instance to `use`:', function (done) { collection.use(function (inst) { - assert(inst instanceof Views); + assert(inst instanceof Collection); done(); }); }); it('should be chainable:', function (done) { collection.use(function (inst) { - assert(inst instanceof Views); + assert(inst instanceof Collection); }) .use(function (inst) { - assert(inst instanceof Views); + assert(inst instanceof Collection); }) .use(function (inst) { - assert(inst instanceof Views); + assert(inst instanceof Collection); done(); }); }); it('should expose the collection to a plugin:', function () { - collection.use(function (views) { - assert(views instanceof Views); - views.foo = views.addView.bind(views); + collection.use(function (items) { + assert(items instanceof Collection); + items.foo = items.addItem.bind(items); }); collection.foo('a', {content: '...'}); - assert(collection.views.hasOwnProperty('a')); + assert(collection.items.hasOwnProperty('a')); }); it('should expose collection when chained:', function () { collection - .use(function (views) { - assert(views instanceof Views); - views.foo = views.addView.bind(views); + .use(function (items) { + assert(items instanceof Collection); + items.foo = items.addItem.bind(items); }) - .use(function (views) { - assert(views instanceof Views); - views.bar = views.addView.bind(views); + .use(function (items) { + assert(items instanceof Collection); + items.bar = items.addItem.bind(items); }) - .use(function (views) { - assert(views instanceof Views); - views.baz = views.addView.bind(views); + .use(function (items) { + assert(items instanceof Collection); + items.baz = items.addItem.bind(items); }); var pages = collection; @@ -63,25 +63,25 @@ describe('collection.use', function () { pages.bar({path: 'b', content: '...'}); pages.baz({path: 'c', content: '...'}); - assert(collection.views.hasOwnProperty('a')); - assert(collection.views.hasOwnProperty('b')); - assert(collection.views.hasOwnProperty('c')); + assert(collection.items.hasOwnProperty('a')); + assert(collection.items.hasOwnProperty('b')); + assert(collection.items.hasOwnProperty('c')); }); - it('should work when a custom `View` constructor is passed:', function () { - collection = new Views({View: require('vinyl')}); + it('should work when a custom `Item` constructor is passed:', function () { + collection = new Collection({Item: require('vinyl')}); collection - .use(function (views) { - assert(views instanceof Views); - views.foo = views.addView.bind(views); + .use(function (items) { + assert(items instanceof Collection); + items.foo = items.addItem.bind(items); }) - .use(function (views) { - assert(views instanceof Views); - views.bar = views.addView.bind(views); + .use(function (items) { + assert(items instanceof Collection); + items.bar = items.addItem.bind(items); }) - .use(function (views) { - assert(views instanceof Views); - views.baz = views.addView.bind(views); + .use(function (items) { + assert(items instanceof Collection); + items.baz = items.addItem.bind(items); }); var pages = collection; @@ -90,67 +90,67 @@ describe('collection.use', function () { pages.bar({path: 'b', content: '...'}); pages.baz({path: 'c', content: '...'}); - assert(collection.views.hasOwnProperty('a')); - assert(collection.views.hasOwnProperty('b')); - assert(collection.views.hasOwnProperty('c')); + assert(collection.items.hasOwnProperty('a')); + assert(collection.items.hasOwnProperty('b')); + assert(collection.items.hasOwnProperty('c')); }); - it('should pass to view `use` if a function is returned:', function () { - collection.use(function (views) { - assert(views instanceof Views); + it('should pass to item `use` if a function is returned:', function () { + collection.use(function (items) { + assert(items instanceof Collection); - return function (view) { - view.foo = views.addView.bind(views); - assert(view instanceof View); + return function (item) { + item.foo = items.addItem.bind(items); + assert(item instanceof Item); }; }); - collection.addView('a', {content: '...'}) + collection.addItem('a', {content: '...'}) .foo({path: 'b', content: '...'}) .foo({path: 'c', content: '...'}) .foo({path: 'd', content: '...'}); - assert(collection.views.hasOwnProperty('a')); - assert(collection.views.hasOwnProperty('b')); - assert(collection.views.hasOwnProperty('c')); - assert(collection.views.hasOwnProperty('d')); + assert(collection.items.hasOwnProperty('a')); + assert(collection.items.hasOwnProperty('b')); + assert(collection.items.hasOwnProperty('c')); + assert(collection.items.hasOwnProperty('d')); }); - it('should be chainable when a view function is returned:', function () { + it('should be chainable when a item function is returned:', function () { collection - .use(function (views) { - assert(views instanceof Views); + .use(function (items) { + assert(items instanceof Collection); - return function (view) { - view.foo = views.addView.bind(views); - assert(view instanceof View); + return function (item) { + item.foo = items.addItem.bind(items); + assert(item instanceof Item); }; }) - .use(function (views) { - assert(views instanceof Views); + .use(function (items) { + assert(items instanceof Collection); - return function (view) { - view.bar = views.addView.bind(views); - assert(view instanceof View); + return function (item) { + item.bar = items.addItem.bind(items); + assert(item instanceof Item); }; }) - .use(function (views) { - assert(views instanceof Views); + .use(function (items) { + assert(items instanceof Collection); - return function (view) { - view.baz = views.addView.bind(views); - assert(view instanceof View); + return function (item) { + item.baz = items.addItem.bind(items); + assert(item instanceof Item); }; }); - collection.addView('a', {content: '...'}) + collection.addItem('a', {content: '...'}) .foo({path: 'b', content: '...'}) .bar({path: 'c', content: '...'}) .baz({path: 'd', content: '...'}); - assert(collection.views.hasOwnProperty('a')); - assert(collection.views.hasOwnProperty('b')); - assert(collection.views.hasOwnProperty('c')); - assert(collection.views.hasOwnProperty('d')); + assert(collection.items.hasOwnProperty('a')); + assert(collection.items.hasOwnProperty('b')); + assert(collection.items.hasOwnProperty('c')); + assert(collection.items.hasOwnProperty('d')); }); }); diff --git a/test/dest.js b/test/dest.js deleted file mode 100644 index 3f077c0..0000000 --- a/test/dest.js +++ /dev/null @@ -1,908 +0,0 @@ -var spies = require('./support/spy'); -var chmodSpy = spies.chmodSpy; -var statSpy = spies.statSpy; - -var assert = require('assert'); -var App = require('../'); -var app; - -var path = require('path'); -var fs = require('graceful-fs'); -var rimraf = require('rimraf'); - -var bufferStream; -var bufEqual = require('buffer-equal'); -var through = require('through2'); -var File = require('vinyl'); - -var should = require('should'); -require('mocha'); - -var wipeOut = function(cb) { - app = new App(); - rimraf(path.join(__dirname, './out-fixtures/'), cb); - spies.setError('false'); - statSpy.reset(); - chmodSpy.reset(); -}; - -var dataWrap = function(fn) { - return function(data, enc, cb) { - fn(data); - cb(); - }; -}; - -var realMode = function(n) { - return n & 07777; -}; - -describe('dest stream', function() { - beforeEach(wipeOut); - afterEach(wipeOut); - - it('should explode on invalid folder (empty)', function(done) { - var stream; - try { - stream = app.dest(); - } catch (err) { - should.exist(err); - should.not.exist(stream); - done(); - } - }); - - it('should explode on invalid folder (empty string)', function(done) { - var stream; - try { - stream = app.dest(''); - } catch (err) { - should.exist(err); - should.not.exist(stream); - done(); - } - }); - - it('should pass through writes with cwd', function(done) { - var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); - - var expectedFile = new File({ - base: __dirname, - cwd: __dirname, - path: inputPath, - contents: null - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - done(); - }; - - var stream = app.dest('./out-fixtures/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should pass through writes with default cwd', function(done) { - var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); - - var expectedFile = new File({ - base: __dirname, - cwd: __dirname, - path: inputPath, - contents: null - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - done(); - }; - - var stream = app.dest(path.join(__dirname, './out-fixtures/')); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should not write null files', function(done) { - var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, './fixtures/vinyl/'); - var expectedPath = path.join(__dirname, './out-fixtures/test.coffee'); - var expectedCwd = __dirname; - var expectedBase = path.join(__dirname, './out-fixtures'); - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: null - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(false); - done(); - }; - - var stream = app.dest('./out-fixtures/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should write buffer files to the right folder with relative cwd', function(done) { - var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, './fixtures/vinyl/'); - var expectedPath = path.join(__dirname, './out-fixtures/test.coffee'); - var expectedCwd = __dirname; - var expectedBase = path.join(__dirname, './out-fixtures'); - var expectedContents = fs.readFileSync(inputPath); - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(true); - bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); - done(); - }; - - var stream = app.dest('./out-fixtures/', {cwd: path.relative(process.cwd(), __dirname)}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should write buffer files to the right folder with function and relative cwd', function(done) { - var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, './fixtures/vinyl/'); - var expectedPath = path.join(__dirname, './out-fixtures/test.coffee'); - var expectedCwd = __dirname; - var expectedBase = path.join(__dirname, './out-fixtures'); - var expectedContents = fs.readFileSync(inputPath); - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(true); - bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); - done(); - }; - - var stream = app.dest(function(file){ - should.exist(file); - file.should.equal(expectedFile); - return './out-fixtures'; - }, {cwd: path.relative(process.cwd(), __dirname)}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should write buffer files to the right folder', function(done) { - var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, './fixtures/vinyl/'); - var expectedPath = path.join(__dirname, './out-fixtures/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedCwd = __dirname; - var expectedBase = path.join(__dirname, './out-fixtures'); - var expectedMode = 0655; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: expectedMode - } - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(true); - bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); - realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); - done(); - }; - - var stream = app.dest('./out-fixtures/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should write streaming files to the right folder', function(done) { - var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, './fixtures/vinyl/'); - var expectedPath = path.join(__dirname, './out-fixtures/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedCwd = __dirname; - var expectedBase = path.join(__dirname, './out-fixtures'); - var expectedMode = 0655; - - var contentStream = through.obj(); - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: contentStream, - stat: { - mode: expectedMode - } - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(true); - bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); - realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); - done(); - }; - - var stream = app.dest('./out-fixtures/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - setTimeout(function(){ - contentStream.write(expectedContents); - contentStream.end(); - }, 100); - stream.end(); - }); - - it('should write directories to the right folder', function(done) { - var inputPath = path.join(__dirname, './fixtures/vinyl/test'); - var inputBase = path.join(__dirname, './fixtures/vinyl/'); - var expectedPath = path.join(__dirname, './out-fixtures/test'); - var expectedCwd = __dirname; - var expectedBase = path.join(__dirname, './out-fixtures'); - var expectedMode = 0655; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: null, - stat: { - isDirectory: function(){ - return true; - }, - mode: expectedMode - } - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(true); - fs.lstatSync(expectedPath).isDirectory().should.equal(true); - realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); - done(); - }; - - var stream = app.dest('./out-fixtures/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should allow piping multiple dests in streaming mode', function(done) { - var inputPath1 = path.join(__dirname, './out-fixtures/multiple-first'); - var inputPath2 = path.join(__dirname, './out-fixtures/multiple-second'); - var inputBase = path.join(__dirname, './out-fixtures/'); - var srcPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); - var stream1 = app.dest('./out-fixtures/', {cwd: __dirname}); - var stream2 = app.dest('./out-fixtures/', {cwd: __dirname}); - var content = fs.readFileSync(srcPath); - var rename = through.obj(function(file, _, next) { - file.path = inputPath2; - this.push(file); - next(); - }); - - stream1.on('data', function(file) { - file.path.should.equal(inputPath1); - }); - - stream1.pipe(rename).pipe(stream2); - stream2.on('data', function(file) { - file.path.should.equal(inputPath2); - }).once('end', function() { - fs.readFileSync(inputPath1, 'utf8').should.equal(content.toString()); - fs.readFileSync(inputPath2, 'utf8').should.equal(content.toString()); - done(); - }); - - var file = new File({ - base: inputBase, - path: inputPath1, - cwd: __dirname, - contents: content - }); - - stream1.write(file); - stream1.end(); - }); - - it('should write new files with the default user mode', function(done) { - var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, './fixtures/vinyl/'); - var expectedPath = path.join(__dirname, './out-fixtures/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedMode = 0666 & (~process.umask()); - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - fs.existsSync(expectedPath).should.equal(true); - realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); - done(); - }; - - chmodSpy.reset(); - var stream = app.dest('./out-fixtures/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should write new files with the specified mode', function(done) { - var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, './fixtures/vinyl/'); - var expectedPath = path.join(__dirname, './out-fixtures/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedMode = 0744; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - fs.existsSync(expectedPath).should.equal(true); - realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); - done(); - }; - - chmodSpy.reset(); - var stream = app.dest('./out-fixtures/', {cwd: __dirname, mode:expectedMode}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should update file mode to match the vinyl mode', function(done) { - var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, './fixtures/vinyl/'); - var expectedPath = path.join(__dirname, './out-fixtures/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, './out-fixtures'); - var startMode = 0655; - var expectedMode = 0722; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: expectedMode - } - }); - - var onEnd = function(){ - assert(chmodSpy.called); - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - fs.existsSync(expectedPath).should.equal(true); - realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); - done(); - }; - - fs.mkdirSync(expectedBase); - fs.closeSync(fs.openSync(expectedPath, 'w')); - fs.chmodSync(expectedPath, startMode); - - chmodSpy.reset(); - var stream = app.dest('./out-fixtures/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should use different modes for files and directories', function(done) { - var inputBase = path.join(__dirname, './fixtures/vinyl'); - var inputPath = path.join(__dirname, './fixtures/vinyl/wow/suchempty'); - var expectedBase = path.join(__dirname, './out-fixtures/wow'); - var expectedDirMode = 0755; - var expectedFileMode = 0655; - - var firstFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - stat: fs.statSync(inputPath) - }); - - var onEnd = function(){ - realMode(fs.lstatSync(expectedBase).mode).should.equal(expectedDirMode); - realMode(buffered[0].stat.mode).should.equal(expectedFileMode); - done(); - }; - - var stream = app.dest('./out-fixtures/', { - cwd: __dirname, - mode: expectedFileMode, - dirMode: expectedDirMode - }); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(firstFile); - stream.end(); - }); - - it('should change to the specified base as string', function(done) { - var inputBase = path.join(__dirname, './fixtures/vinyl'); - var inputPath = path.join(__dirname, './fixtures/vinyl/wow/suchempty'); - - var firstFile = new File({ - cwd: __dirname, - path: inputPath, - stat: fs.statSync(inputPath) - }); - - var onEnd = function(){ - buffered[0].base.should.equal(inputBase); - done(); - }; - - var stream = app.dest('./out-fixtures/', { - cwd: __dirname, - base: inputBase - }); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(firstFile); - stream.end(); - }); - - it('should change to the specified base as function', function(done) { - var inputBase = path.join(__dirname, './fixtures/vinyl'); - var inputPath = path.join(__dirname, './fixtures/vinyl/wow/suchempty'); - - var firstFile = new File({ - cwd: __dirname, - path: inputPath, - stat: fs.statSync(inputPath) - }); - - var onEnd = function() { - buffered[0].base.should.equal(inputBase); - done(); - }; - - var stream = app.dest('./out-fixtures/', { - cwd: __dirname, - base: function(file){ - should.exist(file); - file.path.should.equal(inputPath); - return inputBase; - } - }); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(firstFile); - stream.end(); - }); - - it('should report IO errors', function(done) { - var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, './fixtures/vinyl/'); - var expectedPath = path.join(__dirname, './out-fixtures/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, './out-fixtures'); - var expectedMode = 0722; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: expectedMode - } - }); - - fs.mkdirSync(expectedBase); - fs.closeSync(fs.openSync(expectedPath, 'w')); - fs.chmodSync(expectedPath, 0); - - var stream = app.dest('./out-fixtures/', {cwd: __dirname}); - stream.on('error', function(err) { - err.code.should.equal('EACCES'); - done(); - }); - stream.write(expectedFile); - }); - - it('should report stat errors', function(done) { - var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, './fixtures/vinyl/'); - var expectedPath = path.join(__dirname, './out-fixtures/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, './out-fixtures'); - var expectedMode = 0722; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: expectedMode - } - }); - - fs.mkdirSync(expectedBase); - fs.closeSync(fs.openSync(expectedPath, 'w')); - - spies.setError(function(mod, fn) { - if (fn === 'stat' && arguments[2] === expectedPath) { - return new Error('stat error'); - } - }); - - var stream = app.dest('./out-fixtures/', {cwd: __dirname}); - stream.on('error', function(err) { - err.message.should.equal('stat error'); - done(); - }); - stream.write(expectedFile); - }); - - it('should report chmod errors', function(done) { - var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, './fixtures/vinyl/'); - var expectedPath = path.join(__dirname, './out-fixtures/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, './out-fixtures'); - var expectedMode = 0722; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: expectedMode - } - }); - - fs.mkdirSync(expectedBase); - fs.closeSync(fs.openSync(expectedPath, 'w')); - - spies.setError(function(mod, fn) { - if (fn === 'chmod' && arguments[2] === expectedPath) { - return new Error('chmod error'); - } - }); - - var stream = app.dest('./out-fixtures/', {cwd: __dirname}); - stream.on('error', function(err) { - err.message.should.equal('chmod error'); - done(); - }); - stream.write(expectedFile); - }); - - it('should not chmod a matching file', function(done) { - var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, './fixtures/vinyl/'); - var expectedPath = path.join(__dirname, './out-fixtures/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, './out-fixtures'); - var expectedMode = 0722; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: expectedMode - } - }); - - var expectedCount = 0; - spies.setError(function(mod, fn) { - if (fn === 'stat' && arguments[2] === expectedPath) { - expectedCount++; - } - }); - - var onEnd = function(){ - expectedCount.should.equal(1); - assert(!chmodSpy.called); - realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); - done(); - }; - - fs.mkdirSync(expectedBase); - fs.closeSync(fs.openSync(expectedPath, 'w')); - fs.chmodSync(expectedPath, expectedMode); - - statSpy.reset(); - chmodSpy.reset(); - var stream = app.dest('./out-fixtures/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should see a file with special chmod (setuid/setgid/sticky) as matching', function(done) { - var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, './fixtures/vinyl/'); - var expectedPath = path.join(__dirname, './out-fixtures/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, './out-fixtures'); - var expectedMode = 03722; - var normalMode = 0722; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: normalMode - } - }); - - var expectedCount = 0; - spies.setError(function(mod, fn) { - if (fn === 'stat' && arguments[2] === expectedPath) { - expectedCount++; - } - }); - - var onEnd = function(){ - expectedCount.should.equal(1); - assert(!chmodSpy.called); - done(); - }; - - fs.mkdirSync(expectedBase); - fs.closeSync(fs.openSync(expectedPath, 'w')); - fs.chmodSync(expectedPath, expectedMode); - - statSpy.reset(); - chmodSpy.reset(); - var stream = app.dest('./out-fixtures/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should not overwrite files with overwrite option set to false', function(done) { - var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, './fixtures/vinyl/'); - var inputContents = fs.readFileSync(inputPath); - - var expectedPath = path.join(__dirname, './out-fixtures/test.coffee'); - var expectedBase = path.join(__dirname, './out-fixtures'); - var existingContents = 'Lorem Ipsum'; - - var inputFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: inputContents - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - bufEqual(fs.readFileSync(expectedPath), new Buffer(existingContents)).should.equal(true); - done(); - }; - - // Write expected file which should not be overwritten - fs.mkdirSync(expectedBase); - fs.writeFileSync(expectedPath, existingContents); - - var stream = app.dest('./out-fixtures/', {cwd: __dirname, overwrite: false}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(inputFile); - stream.end(); - }); - - it('should overwrite files with overwrite option set to true', function(done) { - var inputPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, './fixtures/vinyl/'); - var inputContents = fs.readFileSync(inputPath); - - var expectedPath = path.join(__dirname, './out-fixtures/test.coffee'); - var expectedBase = path.join(__dirname, './out-fixtures'); - var existingContents = 'Lorem Ipsum'; - - var inputFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: inputContents - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - bufEqual(fs.readFileSync(expectedPath), new Buffer(inputContents)).should.equal(true); - done(); - }; - - // This should be overwritten - fs.mkdirSync(expectedBase); - fs.writeFileSync(expectedPath, existingContents); - - var stream = app.dest('./out-fixtures/', {cwd: __dirname, overwrite: true}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(inputFile); - stream.end(); - }); - - it('should create symlinks when the `symlink` attribute is set on the file', function (done) { - var inputPath = path.join(__dirname, './fixtures/vinyl/test-create-dir-symlink'); - var inputBase = path.join(__dirname, './fixtures/vinyl/'); - var inputRelativeSymlinkPath = 'wow'; - - var expectedPath = path.join(__dirname, './out-fixtures/test-create-dir-symlink'); - - var inputFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: null, //'' - }); - - // `src()` adds this side-effect with `keepSymlinks` option set to false - inputFile.symlink = inputRelativeSymlinkPath; - - var onEnd = function(){ - fs.readlink(buffered[0].path, function () { - buffered[0].symlink.should.equal(inputFile.symlink); - buffered[0].path.should.equal(expectedPath); - done(); - }); - }; - - var stream = app.dest('./out-fixtures/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(inputFile); - stream.end(); - }); - - it('should emit finish event', function(done) { - var srcPath = path.join(__dirname, './fixtures/vinyl/test.coffee'); - var stream = app.dest('./out-fixtures/', {cwd: __dirname}); - - stream.once('finish', function() { - done(); - }); - - var file = new File({ - path: srcPath, - cwd: __dirname, - contents: new Buffer("1234567890") - }); - - stream.write(file); - stream.end(); - }); -}); diff --git a/test/fixtures/example.txt b/test/fixtures/example.txt new file mode 100644 index 0000000..a8a9406 --- /dev/null +++ b/test/fixtures/example.txt @@ -0,0 +1 @@ +this is a test \ No newline at end of file diff --git a/test/fixtures/pages/a.hbs b/test/fixtures/pages/a.hbs index 0ec5d8d..51320bd 100644 --- a/test/fixtures/pages/a.hbs +++ b/test/fixtures/pages/a.hbs @@ -1 +1,2 @@ -

\ No newline at end of file +

{{title}}

+

<%= title() %>

\ No newline at end of file diff --git a/test/fixtures/pages/b.hbs b/test/fixtures/pages/b.hbs index 0ec5d8d..51320bd 100644 --- a/test/fixtures/pages/b.hbs +++ b/test/fixtures/pages/b.hbs @@ -1 +1,2 @@ -

\ No newline at end of file +

{{title}}

+

<%= title() %>

\ No newline at end of file diff --git a/test/fixtures/pages/c.hbs b/test/fixtures/pages/c.hbs index 0ec5d8d..51320bd 100644 --- a/test/fixtures/pages/c.hbs +++ b/test/fixtures/pages/c.hbs @@ -1 +1,2 @@ -

\ No newline at end of file +

{{title}}

+

<%= title() %>

\ No newline at end of file diff --git a/test/fixtures/templates/a.tmpl b/test/fixtures/templates/a.tmpl index e372465..36f1f1b 100644 --- a/test/fixtures/templates/a.tmpl +++ b/test/fixtures/templates/a.tmpl @@ -1 +1 @@ -a <%= name %> b \ No newline at end of file +<%= name %> \ No newline at end of file diff --git a/test/group.js b/test/group.js index e846a4f..601e91e 100644 --- a/test/group.js +++ b/test/group.js @@ -66,6 +66,10 @@ describe('group', function () { group = new Group(); }); + it('should expose options:', function () { + assert(typeof group.options === 'object'); + }); + it('should set a value on the instance:', function () { group.set('a', 'b'); assert(group.a ==='b'); diff --git a/test/helpers.js b/test/helpers.js index 4f5be2a..13981f0 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -6,12 +6,13 @@ var assert = require('assert'); var consolidate = require('consolidate'); var handlebars = require('engine-handlebars'); var matter = require('parser-front-matter'); -var support = require('./support'); var swig = consolidate.swig; require('swig'); var support = require('./support'); var App = support.resolve(); +var helpers = App._.proto.helpers; +var init = App._.proto.init; var app; describe('helpers', function () { @@ -34,6 +35,19 @@ describe('helpers', function () { }); }); + describe('instance', function () { + it('should prime _', function () { + function Foo() { + Base.call(this); + init(this); + } + Base.extend(Foo); + var foo = new Foo(); + helpers(foo); + assert(typeof foo._ ==='object'); + }); + }); + describe('helpers', function() { beforeEach(function() { app = new App(); @@ -203,7 +217,7 @@ describe('sync helpers', function () { }); }); - it.skip('should use a namespaced helper:', function (done) { + it('should use a namespaced helper:', function (done) { app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= foo.upper(a) %>', locals: {a: 'bbb'}}); app.helperGroup('foo', { @@ -315,7 +329,7 @@ describe('built-in helpers:', function () { }); }); - it.skip('should use locals from the `view.render` method:', function (done) { + it('should use locals from the `view.render` method:', function (done) { app.partial('abc.md', {content: '<%= name %>', locals: {name: 'EEE'}}); app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}) diff --git a/test/list.js b/test/list.js index e4edbca..0dc23ca 100644 --- a/test/list.js +++ b/test/list.js @@ -135,6 +135,16 @@ describe('list', function () { }); describe('addItem', function() { + beforeEach(function() { + list = new List(); + }); + + it('should add items to a list', function () { + list.addItem('a', {content: '...'}); + list.addItem('b', {content: '...'}); + list.addItem('c', {content: '...'}); + assert(list.items.length === 3); + }); }); describe('removeItem', function() { diff --git a/test/list.use.js b/test/list.use.js new file mode 100644 index 0000000..c192a00 --- /dev/null +++ b/test/list.use.js @@ -0,0 +1,156 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var List = App.List; +var Item = App.Item; +var list; + +describe('list.use', function () { + beforeEach(function () { + list = new List(); + }); + + it('should expose the instance to `use`:', function (done) { + list.use(function (inst) { + assert(inst instanceof List); + done(); + }); + }); + + it('should be chainable:', function (done) { + list.use(function (inst) { + assert(inst instanceof List); + }) + .use(function (inst) { + assert(inst instanceof List); + }) + .use(function (inst) { + assert(inst instanceof List); + done(); + }); + }); + + it('should expose the list to a plugin:', function () { + list.use(function (items) { + assert(items instanceof List); + items.foo = items.addItem.bind(items); + }); + + list.foo('a', {content: '...'}); + assert(list.hasItem('a')); + }); + + it('should expose list when chained:', function () { + list + .use(function (items) { + assert(items instanceof List); + items.foo = items.addItem.bind(items); + }) + .use(function (items) { + assert(items instanceof List); + items.bar = items.addItem.bind(items); + }) + .use(function (items) { + assert(items instanceof List); + items.baz = items.addItem.bind(items); + }); + + var pages = list; + + pages.foo({path: 'a', content: '...'}); + pages.bar({path: 'b', content: '...'}); + pages.baz({path: 'c', content: '...'}); + + assert(list.hasItem('a')); + assert(list.hasItem('b')); + assert(list.hasItem('c')); + }); + + it('should work when a custom `Item` constructor is passed:', function () { + list = new List({Item: require('vinyl')}); + list + .use(function (items) { + assert(items instanceof List); + items.foo = items.addItem.bind(items); + }) + .use(function (items) { + assert(items instanceof List); + items.bar = items.addItem.bind(items); + }) + .use(function (items) { + assert(items instanceof List); + items.baz = items.addItem.bind(items); + }); + + var pages = list; + + pages.foo({path: 'a', content: '...'}); + pages.bar({path: 'b', content: '...'}); + pages.baz({path: 'c', content: '...'}); + + assert(list.hasItem('a')); + assert(list.hasItem('b')); + assert(list.hasItem('c')); + }); + + it('should pass to item `use` if a function is returned:', function () { + list.use(function (items) { + assert(items instanceof List); + + return function (item) { + item.foo = items.addItem.bind(items); + assert(item.isItem || item.isView); + }; + }); + + list.addItem('a', {content: '...'}) + .foo({path: 'b', content: '...'}) + .foo({path: 'c', content: '...'}) + .foo({path: 'd', content: '...'}); + + assert(list.hasItem('a')); + assert(list.hasItem('b')); + assert(list.hasItem('c')); + assert(list.hasItem('d')); + }); + + it('should be chainable when a item function is returned:', function () { + list + .use(function (items) { + assert(items instanceof List); + + return function (item) { + item.foo = items.addItem.bind(items); + assert(item instanceof Item); + }; + }) + .use(function (items) { + assert(items instanceof List); + + return function (item) { + item.bar = items.addItem.bind(items); + assert(item instanceof Item); + }; + }) + .use(function (items) { + assert(items instanceof List); + + return function (item) { + item.baz = items.addItem.bind(items); + assert(item instanceof Item); + }; + }); + + list.addItem('a', {content: '...'}) + .foo({path: 'b', content: '...'}) + .bar({path: 'c', content: '...'}) + .baz({path: 'd', content: '...'}); + + assert(list.hasItem('a')); + assert(list.hasItem('b')); + assert(list.hasItem('c')); + assert(list.hasItem('d')); + }); +}); diff --git a/test/mergePartials.js b/test/mergePartials.js index 92e2f77..0c4e481 100644 --- a/test/mergePartials.js +++ b/test/mergePartials.js @@ -6,8 +6,6 @@ var app; describe('mergePartials', function () { beforeEach(function () { app = new App(); - // reset views - app.views = {}; }); it('should merge multiple partials collections onto one collection:', function () { @@ -25,7 +23,7 @@ describe('mergePartials', function () { actual.partials.should.have.properties(['a', 'b', 'c']); }); - it('should keep partials collections on separate collections:', function () { + it('should keep partials collections on separaet collections:', function () { var opts = { viewType: 'partial' }; app.create('foo', opts); app.create('bar', opts); diff --git a/test/store.js b/test/store.js index 402b082..5f714c3 100644 --- a/test/store.js +++ b/test/store.js @@ -4,7 +4,7 @@ require('mocha'); require('should'); var fs = require('fs'); var path = require('path'); -var Store = require('data-store'); +var store = require('data-store'); var assert = require('assert'); var App = require('../'); var app; @@ -12,17 +12,16 @@ var app; describe('store', function () { beforeEach(function () { app = new App(); + app.store = store('update-next-tests'); }); afterEach(function (cb) { - app.store.del({force: true}, function (err) { - if (err) return cb(err); - cb(); - }); + app.store.del({force: true}); + cb(); }); - it('should create an instance of Store', function () { - assert(app.store instanceof Store); + it('should create an instance of store', function () { + assert(app.store instanceof store); }); it('should create a store at the given `cwd`', function () { @@ -165,6 +164,16 @@ describe('store', function () { }); describe('events', function () { + beforeEach(function () { + app = new App(); + app.store = store('update-next-tests'); + }); + + afterEach(function (cb) { + app.store.del({force: true}); + cb(); + }); + it('should emit `set` when an object is set:', function () { var keys = []; app.store.on('set', function (key) { @@ -217,17 +226,17 @@ describe('events', function () { app.store.del('a'); }); - it('should emit deleted keys on `del`:', function () { - var res; - app.store.on('del', function (keys) { - keys.should.eql(['a', 'c', 'e']); - assert(Object.keys(app.store.data).length === 0); + it('should emit deleted keys on `del`:', function (done) { + var keys = []; + app.store.on('del', function (key) { + keys.push(key); }); app.store.set('a', 'b'); app.store.set('c', 'd'); app.store.set('e', 'f'); - app.store.data.should.have.properties(['a', 'c', 'e']); app.store.del({force: true}); + keys.should.eql(['a', 'c', 'e']); + done(); }); }); diff --git a/test/view.set.js b/test/view.set.js index 0ed404b..60eb98a 100644 --- a/test/view.set.js +++ b/test/view.set.js @@ -27,7 +27,7 @@ describe('set', function () { .render(function (err, res) { if (err) return done(err); - assert(res.content === 'a Brooke b'); + assert(res.content === 'Brooke'); done(); }); }); diff --git a/test/views.js b/test/views.js index 2a88024..8f99b7d 100644 --- a/test/views.js +++ b/test/views.js @@ -168,6 +168,30 @@ describe('views', function () { collection = new Views(); }); + it('should emit an error if a string glob pattern is passed', function (done) { + try { + collection.addViews('*.js'); + done(new Error('expected an error')); + } catch(err) { + assert(err); + assert(err.message); + assert(/glob/.test(err.message)); + done(); + } + }); + + it('should emit an error if an array glob pattern is passed', function (done) { + try { + collection.addViews(['*.js']); + done(new Error('expected an error')); + } catch(err) { + assert(err); + assert(err.message); + assert(/glob/.test(err.message)); + done(); + } + }); + it('should add multiple views:', function () { collection.addViews({ one: {content: 'foo'}, @@ -177,6 +201,18 @@ describe('views', function () { assert(isBuffer(collection.views.two.contents)); }); + it('should return the collection instance for chaining:', function () { + var views = collection.addViews({ + one: {content: 'foo'}, + two: {content: 'bar'} + }); + + var view = views.getView('one'); + assert(view); + assert(view.content); + assert(view.content === 'foo'); + }); + it('should create views from an instance of Views', function () { collection.addViews({ one: {content: 'foo'}, @@ -228,6 +264,30 @@ describe('views', function () { collection = new Views(); }); + it('should emit an error if a string glob pattern is passed', function (done) { + try { + collection.addList('*.js'); + done(new Error('expected an error')); + } catch(err) { + assert(err); + assert(err.message); + assert(/glob/.test(err.message)); + done(); + } + }); + + it('should emit an error if an array glob pattern is passed', function (done) { + try { + collection.addList(['*.js']); + done(new Error('expected an error')); + } catch(err) { + assert(err); + assert(err.message); + assert(/glob/.test(err.message)); + done(); + } + }); + it('should add a list of views:', function () { collection.addList([ {path: 'one', content: 'foo'}, @@ -237,7 +297,7 @@ describe('views', function () { assert(isBuffer(collection.views.two.contents)); }); - it('should add a list of views from the constructor:', function () { + it('should add a list from the constructor:', function () { var list = new List([ {path: 'one', content: 'foo'}, {path: 'two', content: 'bar'} @@ -248,6 +308,17 @@ describe('views', function () { assert(isBuffer(collection.views.two.contents)); }); + it('should add list items from the constructor:', function () { + var list = new List([ + {path: 'one', content: 'foo'}, + {path: 'two', content: 'bar'} + ]); + + collection = new Views(list.items); + assert(isBuffer(collection.views.one.contents)); + assert(isBuffer(collection.views.two.contents)); + }); + it('should throw an error when list is not an array:', function () { var views = new Views(); (function () { @@ -264,18 +335,18 @@ describe('views', function () { }); it('should load an array of items from an event:', function () { - var collection = new Views(); + var pages = new Views(); - collection.on('addList', function (list) { + pages.on('addList', function (list) { while (list.length) { - collection.addView({path: list.pop()}); + pages.addView({path: list.pop()}); } this.loaded = true; }); - collection.addList(['a.txt', 'b.txt', 'c.txt']); - assert(collection.views.hasOwnProperty('a.txt')); - assert(collection.views['a.txt'].path === 'a.txt'); + pages.addList(['a.txt', 'b.txt', 'c.txt']); + assert(pages.views.hasOwnProperty('a.txt')); + assert(pages.views['a.txt'].path === 'a.txt'); }); it('should load an array of items from the addList callback:', function () { @@ -288,17 +359,6 @@ describe('views', function () { assert(collection.views['a.txt'].path === 'a.txt'); }); - // it('should not blow up on', function () { - // var collection = new Views(); - // var list = [{path: 'a.txt'}, {path: 'b.txt'}, {path: 'c.txt'}]; - // collection.addList(list, function (item) { - // item.content = path.basename(item.path, path.extname(item.path)); - // }); - // assert(collection.views.hasOwnProperty('a.txt')); - // assert(collection.views['a.txt'].path === 'a.txt'); - // assert(collection.views['a.txt'].content === 'a'); - // }); - it('should load an object of views from an event:', function () { var collection = new Views(); diff --git a/test/views.use.js b/test/views.use.js new file mode 100644 index 0000000..09d47df --- /dev/null +++ b/test/views.use.js @@ -0,0 +1,156 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var Views = App.Views; +var View = App.View; +var collection; + +describe('views.use', function () { + beforeEach(function () { + collection = new Views(); + }); + + it('should expose the instance to `use`:', function (done) { + collection.use(function (inst) { + assert(inst instanceof Views); + done(); + }); + }); + + it('should be chainable:', function (done) { + collection.use(function (inst) { + assert(inst instanceof Views); + }) + .use(function (inst) { + assert(inst instanceof Views); + }) + .use(function (inst) { + assert(inst instanceof Views); + done(); + }); + }); + + it('should expose the collection to a plugin:', function () { + collection.use(function (views) { + assert(views instanceof Views); + views.foo = views.addView.bind(views); + }); + + collection.foo('a', {content: '...'}); + assert(collection.views.hasOwnProperty('a')); + }); + + it('should expose collection when chained:', function () { + collection + .use(function (views) { + assert(views instanceof Views); + views.foo = views.addView.bind(views); + }) + .use(function (views) { + assert(views instanceof Views); + views.bar = views.addView.bind(views); + }) + .use(function (views) { + assert(views instanceof Views); + views.baz = views.addView.bind(views); + }); + + var pages = collection; + + pages.foo({path: 'a', content: '...'}); + pages.bar({path: 'b', content: '...'}); + pages.baz({path: 'c', content: '...'}); + + assert(collection.views.hasOwnProperty('a')); + assert(collection.views.hasOwnProperty('b')); + assert(collection.views.hasOwnProperty('c')); + }); + + it('should work when a custom `View` constructor is passed:', function () { + collection = new Views({View: require('vinyl')}); + collection + .use(function (views) { + assert(views instanceof Views); + views.foo = views.addView.bind(views); + }) + .use(function (views) { + assert(views instanceof Views); + views.bar = views.addView.bind(views); + }) + .use(function (views) { + assert(views instanceof Views); + views.baz = views.addView.bind(views); + }); + + var pages = collection; + + pages.foo({path: 'a', content: '...'}); + pages.bar({path: 'b', content: '...'}); + pages.baz({path: 'c', content: '...'}); + + assert(collection.views.hasOwnProperty('a')); + assert(collection.views.hasOwnProperty('b')); + assert(collection.views.hasOwnProperty('c')); + }); + + it('should pass to view `use` if a function is returned:', function () { + collection.use(function (views) { + assert(views instanceof Views); + + return function (view) { + view.foo = views.addView.bind(views); + assert(view instanceof View); + }; + }); + + collection.addView('a', {content: '...'}) + .foo({path: 'b', content: '...'}) + .foo({path: 'c', content: '...'}) + .foo({path: 'd', content: '...'}); + + assert(collection.views.hasOwnProperty('a')); + assert(collection.views.hasOwnProperty('b')); + assert(collection.views.hasOwnProperty('c')); + assert(collection.views.hasOwnProperty('d')); + }); + + it('should be chainable when a view function is returned:', function () { + collection + .use(function (views) { + assert(views instanceof Views); + + return function (view) { + view.foo = views.addView.bind(views); + assert(view instanceof View); + }; + }) + .use(function (views) { + assert(views instanceof Views); + + return function (view) { + view.bar = views.addView.bind(views); + assert(view instanceof View); + }; + }) + .use(function (views) { + assert(views instanceof Views); + + return function (view) { + view.baz = views.addView.bind(views); + assert(view instanceof View); + }; + }); + + collection.addView('a', {content: '...'}) + .foo({path: 'b', content: '...'}) + .bar({path: 'c', content: '...'}) + .baz({path: 'd', content: '...'}); + + assert(collection.views.hasOwnProperty('a')); + assert(collection.views.hasOwnProperty('b')); + assert(collection.views.hasOwnProperty('c')); + assert(collection.views.hasOwnProperty('d')); + }); +}); From 8932b5f72a6b2b28e8cf7f10c69389bc72a6d3cb Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 24 Oct 2015 09:01:19 -0400 Subject: [PATCH 104/274] build out cli, plugins/pipeline --- .verb.md | 35 ++++++++ README.md | 35 +++++++- bin/update.js | 23 ++++-- examples.js | 38 +++++---- gulpfile.js | 31 ++++--- index.js | 39 ++++++--- lib/config.js | 44 +++++----- lib/multi.js | 187 ++++++++++++++++++++++++++++++++++++++++++ lib/pipeline/a.js | 16 ++++ lib/pipeline/b.js | 14 ++++ lib/pipeline/c.js | 14 ++++ lib/pipeline/d.js | 14 ++++ lib/plugins/config.js | 2 +- lib/plugins/pkg.md | 19 +++++ lib/tasks/files.js | 5 +- lib/tasks/lint.js | 19 +++++ lib/updaters.js | 144 -------------------------------- lib/utils.js | 50 +++++++++-- package.json | 16 ++-- pipeline.js | 79 ++++++++++++++++++ 20 files changed, 588 insertions(+), 236 deletions(-) create mode 100644 lib/multi.js create mode 100644 lib/pipeline/a.js create mode 100644 lib/pipeline/b.js create mode 100644 lib/pipeline/c.js create mode 100644 lib/pipeline/d.js create mode 100644 lib/plugins/pkg.md create mode 100644 lib/tasks/lint.js delete mode 100644 lib/updaters.js create mode 100644 pipeline.js diff --git a/.verb.md b/.verb.md index b97aa7a..2622f45 100644 --- a/.verb.md +++ b/.verb.md @@ -19,6 +19,41 @@ var updateNext = require('{%= name %}'); {%%= related([]) %} +## Authoring + +### Create your own updater + + +#### tasks + +#### tasks + +#### plugins + +> Updater plugins follow the same signature as gulp plugins + +**Example** + +```js +function foo(options) { + return through.obj(function (file, enc, cb) { + var str = file.contents.toString(); + // do stuff + file.contents = new Buffer(file.contents); + this.push(file); + cb(); + }); +} +``` + +### Publish + +1. Name your project following the convention: `updater-*` +2. Don't use dots in the name (e.g `.js`) +3. Make sure you add `updater` to the keywords in package.json +4. Tweet about your updater! + + ## Running tests {%= include("tests") %} diff --git a/README.md b/README.md index 4b69efd..cf2b1d5 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,39 @@ var updateNext = require('update-next'); {%= related([]) %} +## Authoring + +### Create your own updater + +#### tasks + +#### tasks + +#### plugins + +> Updater plugins follow the same signature as gulp plugins + +**Example** + +```js +function foo(options) { + return through.obj(function (file, enc, cb) { + var str = file.contents.toString(); + // do stuff + file.contents = new Buffer(file.contents); + this.push(file); + cb(); + }); +} +``` + +### Publish + +1. Name your project following the convention: `updater-*` +2. Don't use dots in the name (e.g `.js`) +3. Make sure you add `updater` to the keywords in package.json +4. Tweet about your updater! + ## Running tests Install dev dependencies: @@ -52,4 +85,4 @@ Released under the MIT license. *** -_This file was generated by [verb-cli](https://github.com/assemble/verb-cli) on October 22, 2015._ \ No newline at end of file +_This file was generated by [verb-cli](https://github.com/assemble/verb-cli) on October 24, 2015._ \ No newline at end of file diff --git a/bin/update.js b/bin/update.js index a59772e..c9dfdba 100755 --- a/bin/update.js +++ b/bin/update.js @@ -1,19 +1,28 @@ #!/usr/bin/env node var argv = require('minimist')(process.argv.slice(2)); -var updaters = require('../lib/updaters'); +var multi = require('../lib/multi')(); var utils = require('../lib/utils'); -var update = updaters(argv); +var cmd = utils.commands(argv); +var cli = multi(argv); -update.register('update-*', { - cwd: utils.gm +var task = cmd.list ? 'list' : 'default'; + +cli.on('*', function (method, key, val) { + console.log(method + ':', key, val); +}); +cli.on('register', function(key) { + console.log('registered ', utils.yellow(key)); }); -update.base.task('run', function (cb) { - update.run(cb); +cli.register('update-*', {cwd: utils.gm}); + +cli.base.task('run', function (cb) { + cli.run(cb); }); -update.base.build('default', function (err) { +cli.base.build(task, function (err) { if (err) console.error(err); + utils.ok('Finished (see '); }); diff --git a/examples.js b/examples.js index 4c16a75..4749480 100644 --- a/examples.js +++ b/examples.js @@ -1,26 +1,30 @@ 'use strict'; +var through = require('through2'); var update = require('./'); -var foo = update(); -var bar = update(); +var del = require('del'); +var app = update() + .use(require('./pipeline')) +app.plugin('a', require('./lib/pipeline/a')()); +app.plugin('b', require('./lib/pipeline/b')); +app.plugin('c', require('./lib/pipeline/c')()); +// app.plugin(/foo/, require('./lib/pipeline/d')()); -foo.task('files', function (cb) { - console.log('files'); - cb(); -}); +app.disable('plugin.c'); -foo.task('run', function (cb) { - bar.build(, cb); - console.log('run'); - cb(); +app.task('default', function (cb) { + app.src('*') + .on('error', console.log) + .pipe(through.obj(function (file, enc, next) { + if (file.isNull()) return next(); + next(null, file); + })) + .pipe(app.pipeline()) + .pipe(app.dest('actual')) + .on('finish', cb); }); -foo.task('dest', function (cb) { - console.log('dest'); - cb(); +app.build('default', function(err) { + if (err) return console.log(err); }); - -app.task('default', ['files', 'run', 'dest']); - -app.build('default', console.log); diff --git a/gulpfile.js b/gulpfile.js index 9ae53e9..82538fa 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,10 +1,12 @@ +'use strict'; + var gulp = require('gulp'); -var mocha = require('gulp-mocha'); +var stylish = require('jshint-stylish'); var istanbul = require('gulp-istanbul'); var jshint = require('gulp-jshint'); -require('jshint-stylish'); +var mocha = require('gulp-mocha'); -var lint = ['index.js', 'lib/utils.js', 'test/*.js']; +var lint = ['index.js', 'lib/**/*.js']; gulp.task('coverage', function () { return gulp.src(lint) @@ -12,23 +14,20 @@ gulp.task('coverage', function () { .pipe(istanbul.hookRequire()); }); -gulp.task('mocha', ['coverage'], function () { +gulp.task('test', ['coverage'], function () { return gulp.src('test/*.js') .pipe(mocha({reporter: 'spec'})) - .pipe(istanbul.writeReports()); + .pipe(istanbul.writeReports()) + .pipe(istanbul.writeReports({ + reporters: [ 'text' ], + reportOpts: {dir: 'coverage', file: 'summary.txt'} + })); }); -gulp.task('jshint', function () { - return gulp.src(lint) +gulp.task('lint', function () { + return gulp.src(lint.concat('test/*.js')) .pipe(jshint()) - .pipe(jshint.reporter('jshint-stylish')) + .pipe(jshint.reporter(stylish)); }); -gulp.task('default', ['mocha', 'jshint'], function (cb) { - console.log('Finished "default"'); - - // force the process to end since `verb.watch` - // holds it open in the tests - process.exit(); - cb(); -}); +gulp.task('default', ['test', 'lint']); diff --git a/index.js b/index.js index b314543..cf1e2b1 100644 --- a/index.js +++ b/index.js @@ -7,9 +7,12 @@ 'use strict'; +var argv = require('minimist'); +var cli = require('base-cli'); var ask = require('assemble-ask'); var Core = require('assemble-core'); var loader = require('assemble-loader'); +var config = require('./lib/config'); var plugin = require('./lib/plugins'); var utils = require('./lib/utils'); @@ -45,13 +48,17 @@ Core.extend(Update); Update.prototype.initUpdate = function(base) { this.define('isUpdate', true); + this.set('argv', this.argv || argv(process.argv.slice(2))); this.set('updaters', {}); - this.use(plugin.locals({name: this.name})); - this.use(plugin.store({name: this.name})); - this.use(plugin.config()); - this.use(loader()); - this.use(ask()); + this + .use(utils.runtimes()) + .use(plugin.locals({name: this.name})) + .use(plugin.store({name: this.name})) + .use(config()) + .use(loader()) + .use(ask()) + .use(cli); this.engine(['md', 'tmpl'], require('engine-base')); this.onLoad(/\.(md|tmpl)$/, function (view, next) { @@ -60,6 +67,14 @@ Update.prototype.initUpdate = function(base) { }); }; +Update.prototype.flag = function(key) { + return utils.expandArgs(this.argv)[key]; +}; + +Update.prototype.cmd = function(key) { + return utils.commands(utils.expandArgs(this.argv))[key] || false; +}; + /** * Register updater `name` with the given `update` * instance. @@ -81,12 +96,6 @@ Update.prototype.updater = function(name, update) { return (this.updaters[name] = update); }; -Update.prototype.build = function() { - var fn = Core.prototype.build; - this.emit('build'); - return fn.apply(this, arguments); -}; - Update.prototype.hasUpdater = function(name) { return this.updaters.hasOwnProperty(name); }; @@ -96,8 +105,12 @@ Update.prototype.hasTask = function(name) { }; Update.prototype.opts = function(prop, options) { - var args = [].concat.apply([], [].slice.call(arguments, 1)); - args.unshift(this.option(prop)); + var args = [].concat.apply([], [].slice.call(arguments)); + if (typeof opts === 'string') { + args.unshift(this.option(args.shift())); + } else { + args.unshift(this.options); + } return utils.extend.apply(utils.extend, args); }; diff --git a/lib/config.js b/lib/config.js index 538cc01..c2acc17 100644 --- a/lib/config.js +++ b/lib/config.js @@ -1,30 +1,26 @@ 'use strict'; - -var expand = require('expand-args'); -var config = require('map-config'); - - -module.exports = function(app) { - var res = config(app) - .map('store', store(app.store)) - .map('set') - .map('del') - .map('get', function(key) { - console.log(app.get(key)); - }); - - app.on('argv', function(argv) { - res.process(expand(argv)); - }); -}; - -// function init(app) { -// app.question('') -// } +var utils = require('./utils'); + +module.exports = function(options) { + return function(app) { + var res = utils.config(app) + .map('store', store(app.store)) + .map('option') + .map('set') + .map('del') + .map('get', function(key) { + console.log(app.get(key)); + }); + + // app.on('argv', function(argv) { + // res.process(utils.expand(argv)); + // }); + }; +} function store(app) { - var res = config(app) + var res = utils.config(app) .map('set') .map('del') .map('has', function(key) { @@ -35,6 +31,6 @@ function store(app) { }) return function(args) { - res.process(expand(args)); + // res.process(utils.expand(args)); }; } diff --git a/lib/multi.js b/lib/multi.js new file mode 100644 index 0000000..c5ab47d --- /dev/null +++ b/lib/multi.js @@ -0,0 +1,187 @@ +'use strict'; + +var path = require('path'); +var Emitter = require('component-emitter'); +var fns = require('./middleware'); +var tasks = require('./tasks'); +var utils = require('./utils'); +var Update = require('..'); + +module.exports = function(namespace, config) { + function Multi(argv, options) { + if (!(this instanceof Multi)) { + return new Multi(argv, options); + } + + this.options = options || {}; + this.commands = ['set', 'get', 'del', 'store', 'init', 'option', 'data']; + this.base = new Update() + .on('error', console.error) + .set('argv', argv); + + for (var fn in fns) { + fns[fn](this.base, this); + } + + for (var key in tasks) { + this.base.task(key, tasks[key](this.base, this)); + } + + if (this.base.disabled('verbose')) { + this.emit = function () {} + this.on = function () {} + } + this._listen(); + } + + Emitter(Multi.prototype); + + Multi.prototype._listen = function() { + var store = ['store.set', 'store.has', 'store.get', 'store.del']; + var methods = ['set', 'has', 'get', 'del', 'option', 'data']; + + var names = store.concat(methods); + var len = names.length, i = -1; + var multi = this; + + while (++i < len) { + var method = names[i]; + var prop = method.split('.'); + + if (prop.length === 2) { + this.base[prop[0]].on(prop[1], multi.emit.bind(multi, method)); + this.base[prop[0]].on(prop[1], multi.emit.bind(multi, '*', method)); + } else { + this.base.on(method, function(key, val) { + multi.emit(method, key, val); + multi.emit('*', method, key, val); + }); + } + } + }; + + Multi.prototype.argv = function(argv, commands, updaters) { + var res = {}; + res.updaters = updaters; + res.argv = argv; + res.commands = []; + res.updaters = {}; + + var files = argv.files ? utils.pick(argv, 'files') : null; + res.flags = utils.expandArgs(utils.omit(argv, ['_', 'files'])); + if (files) res.flags.files = files; + res.flagskeys = Object.keys(res.flags); + + var arr = argv._; + var len = arr.length, i = -1; + + while (++i < len) { + var ele = arr[i]; + + if (/\W/.test(ele)) { + var obj = utils.expand(ele); + utils.forOwn(obj, function (val, key) { + utils.union(res.updaters, key, val); + }); + continue; + } + + if (utils.contains(commands, ele)) { + res.commands.push(ele); + continue; + } + + if (ele in updaters) { + utils.union(res.updaters, ele, 'default'); + + } else if (ele !== 'base') { + utils.union(res.updaters, 'base', ele); + } + } + return res; + }; + + Multi.prototype.register = function(pattern, options) { + utils.matchFiles(pattern, options).forEach(function (fp) { + var fullname = utils.projectName(fp); + var name = utils.renameFn(fullname, options); + var mod = utils.resolveModule(fp) || Update; + var app = mod(this.base.options) + .option('name', name) + .set('fullname', fullname) + .set('path', fp); + + var filepath = path.join(fp, 'updatefile.js'); + + require(filepath)(app, this); + this.base.updater(name, app); + + this.emit('register', name, app); + }.bind(this)); + return this; + }; + + Multi.prototype.run = function(args, cb) { + if (typeof args === 'function') { + cb = args; + args = null; + } + + if (!args) { + var argv = this.base.get('argv'); + var commands = this.options.commands || this.commands; + args = this.argv(argv, commands, this.base.updaters); + } + + if (args.commands && args.commands.length > 1) { + var cmd = '"' + args.commands.join(', ') + '"'; + return cb(new Error('Error: only one root level command may be given: ' + cmd)); + } + + this.base.cli.process(args.flags); + var updaters = Object.keys(args.updaters); + + utils.async.eachSeries(updaters, function(name, next) { + var tasks = args.updaters[name]; + var app = name !== 'base' + ? this.base.updater(name) + : this.base; + + this.emit('task', name, tasks); + app.build(tasks, function (err) { + if (err) return next(err); + next(); + }); + }.bind(this), cb); + return this; + }; + + Multi.prototype.list = function(cb) { + var questions = utils.questions(this.base.options); + var question = { + updaters: { + message: 'pick an updater to run', + type: 'checkbox', + choices: utils.list(this.base.updaters) + } + }; + + questions.ask(question, function (err, answers) { + if (err) return cb(err); + var args = { + updaters: {} + }; + answers.updaters.forEach(function (answer) { + var segs = answer.split(':'); + utils.union(args.updaters, segs[0], (segs[1] || 'default').split(',')); + }); + return cb(null, args); + }); + }; + + /** + * Expose `Multi` + */ + + return Multi; +}; diff --git a/lib/pipeline/a.js b/lib/pipeline/a.js new file mode 100644 index 0000000..07e63ec --- /dev/null +++ b/lib/pipeline/a.js @@ -0,0 +1,16 @@ +'use strict'; + +var through = require('through2'); + +module.exports = function(options) { + console.log(this) + return through.obj(function (file, enc, cb) { + console.log(file) + var str = file.contents.toString(); + str += 'aaa\n'; + + file.contents = new Buffer(str); + this.push(file); + cb(); + }); +}; diff --git a/lib/pipeline/b.js b/lib/pipeline/b.js new file mode 100644 index 0000000..5f69bb6 --- /dev/null +++ b/lib/pipeline/b.js @@ -0,0 +1,14 @@ +'use strict'; + +var through = require('through2'); + +module.exports = function(options) { + return through.obj(function (file, enc, cb) { + var str = file.contents.toString(); + str += 'bbb\n'; + + file.contents = new Buffer(str); + this.push(file); + cb(); + }); +}; diff --git a/lib/pipeline/c.js b/lib/pipeline/c.js new file mode 100644 index 0000000..84a1706 --- /dev/null +++ b/lib/pipeline/c.js @@ -0,0 +1,14 @@ +'use strict'; + +var through = require('through2'); + +module.exports = function(options) { + return through.obj(function (file, enc, cb) { + var str = file.contents.toString(); + str += 'ccc\n'; + + file.contents = new Buffer(str); + this.push(file); + cb(); + }); +}; diff --git a/lib/pipeline/d.js b/lib/pipeline/d.js new file mode 100644 index 0000000..79a5dfb --- /dev/null +++ b/lib/pipeline/d.js @@ -0,0 +1,14 @@ +'use strict'; + +var through = require('through2'); + +module.exports = function(options) { + return through.obj(function (file, enc, cb) { + var str = file.contents.toString(); + str += 'ddd\n'; + + file.contents = new Buffer(str); + this.push(file); + cb(); + }); +}; diff --git a/lib/plugins/config.js b/lib/plugins/config.js index 662ef41..a336d82 100644 --- a/lib/plugins/config.js +++ b/lib/plugins/config.js @@ -26,7 +26,7 @@ module.exports = function (options) { var collections = new Config(views); var configMap = new Config({ - plugins: function (config) { + fns: function (config) { utils.forOwn(config, function (val, key) { var opts = utils.extend({}, app.options, val); app.use(require(key)(opts)); diff --git a/lib/plugins/pkg.md b/lib/plugins/pkg.md new file mode 100644 index 0000000..adae3dd --- /dev/null +++ b/lib/plugins/pkg.md @@ -0,0 +1,19 @@ +# update-pkg + +## update deps + +Remove devDeps/deps: + +- verb (if `verbfile.js` does not exist) +- verb-tag-jscomments + +Add devDeps/deps: + +- verb (if `verbfile.js` exists) + +## keywords + + +## Miscellaneous + +- **sort**: sort by given keys or based on stored config \ No newline at end of file diff --git a/lib/tasks/files.js b/lib/tasks/files.js index d9112e0..bcd6339 100644 --- a/lib/tasks/files.js +++ b/lib/tasks/files.js @@ -1,6 +1,7 @@ 'use strict'; var path = require('path'); +var utils = require('../utils'); module.exports = function(app, env) { app.create('files', { @@ -10,7 +11,9 @@ module.exports = function(app, env) { }); var glob = env.base.get('argv.files'); - if (!glob) { + if (glob) { + glob = glob.split(','); + } else { glob = ['*', 'lib/*', 'bin/*']; } diff --git a/lib/tasks/lint.js b/lib/tasks/lint.js new file mode 100644 index 0000000..b36b0ea --- /dev/null +++ b/lib/tasks/lint.js @@ -0,0 +1,19 @@ +'use strict'; + +var through = require('through2'); +var jshint = require('gulp-jshint'); +var stylish = require('jshint-stylish'); + +module.exports = function(app, env) { + return function () { + return env.base.toStream('files') + .pipe(through.obj(function (file, enc, cb) { + if (/\.js$/.test(file.path)) { + this.push(file); + } + cb(); + })) + .pipe(jshint()) + .pipe(jshint.reporter(stylish)); + }; +}; diff --git a/lib/updaters.js b/lib/updaters.js deleted file mode 100644 index c8c8574..0000000 --- a/lib/updaters.js +++ /dev/null @@ -1,144 +0,0 @@ -'use strict'; - -var path = require('path'); -var fns = require('./middleware'); -var tasks = require('./tasks'); -var utils = require('./utils'); -var Update = require('..'); - -function Updaters(argv, options) { - if (!(this instanceof Updaters)) { - return new Updaters(argv); - } - - this.options = options || {}; - this.base = new Update() - .on('error', console.error) - .use(utils.runtimes()) - .set('argv', argv); - - for (var fn in fns) { - fns[fn](this.base, this); - } - - for (var key in tasks) { - this.base.task(key, tasks[key](this.base, this)); - } -} - -Updaters.prototype.argv = function(argv, whitelist, updaters) { - var res = {}; - res.whitelist = whitelist; - res.updaters = updaters; - res.argv = argv; - res.commands = []; - res.updaters = {}; - res.flags = utils.expandArgs(utils.omit(argv, '_')); - - var arr = argv._; - var len = arr.length, i = -1; - - while (++i < len) { - var ele = arr[i]; - - if (/\W/.test(ele)) { - var obj = utils.expand(ele); - utils.forOwn(obj, function (val, key) { - utils.union(res.updaters, key, val); - }); - continue; - } - - if (utils.contains(whitelist, ele)) { - res.commands.push(ele); - continue; - } - - if (ele in updaters) { - utils.union(res.updaters, ele, 'default'); - - } else if (ele !== 'base') { - utils.union(res.updaters, 'base', ele); - } - } - return res; -}; - -Updaters.prototype.register = function(pattern, options) { - utils.matchFiles(pattern, options).forEach(function (fp) { - var fullname = utils.projectName(fp); - var name = utils.renameFn(fullname, options); - var mod = utils.resolveModule(fp) || Update; - var app = mod(this.base.options) - .option('name', name) - .set('fullname', fullname) - .set('path', fp); - - var filepath = path.join(fp, 'updatefile.js'); - - require(filepath)(app, this); - this.base.updater(name, app); - }.bind(this)); - return this; -}; - -Updaters.prototype.run = function(args, cb) { - if (typeof args === 'function') { - cb = args; - args = null; - } - - if (!args) { - var whitelist = this.options.whitelist || ['set', 'get', 'del', 'store', 'init']; - var argv = this.base.get('argv'); - args = this.argv(argv, whitelist, this.base.updaters); - } - - if (args.commands && args.commands.length > 1) { - var cmd = '"' + args.commands.join(', ') + '"'; - return cb(new Error('Error: only one root level command may be given: ' + cmd)); - } - - var updaters = Object.keys(args.updaters); - - utils.async.eachSeries(updaters, function(name, next) { - var tasks = args.updaters[name]; - var app = name !== 'base' - ? this.base.updater(name) - : this.base; - - app.build(tasks, function (err) { - if (err) return next(err); - next(); - }); - }.bind(this), cb); - return this; -}; - -Updaters.prototype.list = function(cb) { - var questions = utils.questions(this.base.options); - var question = { - updaters: { - message: 'pick an updater to run', - type: 'checkbox', - choices: utils.list(this.base.updaters) - } - }; - questions.ask(question, function (err, answers) { - if (err) return cb(err); - var args = { - updaters: {} - }; - answers.updaters.forEach(function (answer) { - var segs = answer.split(':'); - utils.union(args.updaters, segs[0], (segs[1] || 'default').split(',')); - }); - return cb(null, args); - }); -}; - -/** - * Expose `Updaters` - */ - -module.exports = Updaters; diff --git a/lib/utils.js b/lib/utils.js index d532b63..fb85e13 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -20,7 +20,9 @@ require('data-store', 'store'); require('global-modules', 'gm'); require('look-up', 'lookup'); require('object.omit', 'omit'); +require('object.pick', 'pick'); require('micromatch', 'mm'); +require('map-config', 'config'); require('composer-runtimes', 'runtimes'); require('parser-front-matter', 'matter'); require('question-cache', 'questions'); @@ -30,16 +32,52 @@ require('project-name'); require('union-value', 'union'); require('set-value', 'set'); require('get-value', 'get'); + +require('success-symbol'); require('ansi-yellow', 'yellow'); require('ansi-green', 'green'); require('ansi-cyan', 'cyan'); require('ansi-red', 'red'); require = fn; +function Status(status) { + status = status || {}; + this.err = status.err || null; + this.code = status.code || null; + this.name = status.name || ''; + this.msg = status.msg || ''; +} + +utils.ok = function() { + var args = utils.toArray(arguments) || []; + args.unshift(' ' + utils.green(utils.successSymbol)); + console.log.apply(console, args); +}; +utils.success = function() { + var args = utils.toArray(arguments) || []; + args[0] = utils.green(args[0] || ''); + console.log.apply(console, args); +}; +utils.error = function() { + var args = utils.toArray(arguments); + args.unshift(utils.red('Error:')); + console.error.apply(console, args); +}; + /** * CLI utils */ +utils.commands = function(argv) { + argv._ = argv._ || []; + var commands = {}; + + argv._.forEach(function (key) { + commands[key] = true; + }); + return commands; +}; + utils.identity = function(val) { return val; }; @@ -48,6 +86,12 @@ utils.arrayify = function(val) { return Array.isArray(val) ? val : [val]; }; +utils.toArgv = function(args) { + var argv = args.flags; + argv._ = args.commands; + return argv; +}; + utils.toArray = function(val) { if (Array.isArray(val)) return val; if (val && val.length) { @@ -59,12 +103,6 @@ utils.contains = function(arr, key) { return arr.indexOf(key) > -1; }; -utils.error = function() { - var args = utils.toArray(arguments); - args.unshift(utils.red('[update-cli] Error:')); - console.error.apply(console, args); -}; - utils.npm = function(name) { return utils.tryRequire(name) || utils.tryRequire(path.resolve(name)); }; diff --git a/package.json b/package.json index 27a14c5..e27e197 100644 --- a/package.json +++ b/package.json @@ -29,13 +29,14 @@ "ansi-green": "^0.1.1", "ansi-red": "^0.1.1", "ansi-yellow": "^0.1.1", - "assemble-ask": "^0.1.2", - "assemble-core": "^0.1.4", + "assemble-ask": "^0.1.4", + "assemble-core": "^0.1.6", "assemble-loader": "^0.2.4", "async": "^1.4.2", + "component-emitter": "^1.2.0", "composer-runtimes": "^0.5.1", "cwd": "^0.8.4", - "data-store": "^0.10.1", + "data-store": "^0.11.1", "delete": "^0.2.1", "engine": "^0.1.8", "expand-args": "^0.2.1", @@ -45,20 +46,23 @@ "for-own": "^0.1.3", "get-value": "^1.2.1", "global-modules": "^0.2.0", - "lazy-cache": "^0.2.3", + "lazy-cache": "^0.2.4", "load-pkg": "^2.0.1", - "map-config": "^0.2.0", + "map-config": "^0.2.1", "matched": "^0.3.2", "micromatch": "jonschlinkert/micromatch#2.3.0", "minimist": "^1.2.0", "object.omit": "^2.0.0", + "object.pick": "^1.1.1", "parse-author": "^0.2.0", "parse-github-url": "^0.2.1", "parser-front-matter": "^1.3.0", + "pretty-time": "^0.2.0", "project-name": "^0.2.0", "question-cache": "^0.3.3", "resolve-dir": "^0.1.0", "set-value": "^0.2.0", + "stream-combiner": "^0.2.2", "success-symbol": "^0.1.0", "union-value": "^0.1.1", "update-copyright": "^0.1.0", @@ -133,4 +137,4 @@ "url": "https://github.com/doowb" } ] -} \ No newline at end of file +} diff --git a/pipeline.js b/pipeline.js new file mode 100644 index 0000000..42be1e0 --- /dev/null +++ b/pipeline.js @@ -0,0 +1,79 @@ + +var path = require('path'); +var pipeline = require('stream-combiner'); +var through = require('through2'); +var typeOf = require('kind-of'); +var utils = require('./lib/utils'); + +module.exports = function(app) { + this.fns = this.fns || {}; + this.define('stack', []); + + /** + * Register a plugin by `name` + * + * @param {String} `name` + * @param {Function} `fn` + * @api public + */ + + this.define('plugin', function(name, fn) { + if (!typeof name === 'string') { + throw new TypeError('expected plugin name to be a string'); + } + if (arguments.length === 1) { + return this.fns[name]; + } + this.stack.push(fn); + this.fns[name] = fn; + return this; + }); + + /** + * Create a plugin pipeline from an array of fns. + * + * @param {Array} `fns` Each plugin is a function that returns a stream, + * or the name of a registered plugin. + * @param {Object} `options` + * @return {Stream} + * @api public + */ + + this.define('pipeline', function(plugins, options) { + if (isStream(plugins)) return plugins; + + if (!Array.isArray(plugins)) { + options = typeOf(plugins) === 'object' ? plugins : {}; + plugins = this.stack; + } + + var len = plugins.length, i = -1; + var fns = []; + + while (++i < len) { + var plugin = normalize(app, plugins[i], options); + if (!plugin) continue; + fns.push(plugin); + } + return pipeline.apply(pipeline, fns); + }); +}; + +function normalize(app, val, options) { + if (typeof val === 'string' && app.fns.hasOwnProperty(val)) { + if (app.isFalse('plugin.' + val)) return null; + return normalize(app, app.fns[val]); + } + if (typeof val === 'function') { + return val.call(app, app.opts(options)); + } + if (isStream(val)) { + val.on('error', app.emit.bind(app, 'error')); + return val; + } +} + +function isStream(val) { + return val && typeof val === 'object' + && typeof val.pipe === 'function'; +} From fd4fe5869bd78471ddb1a3f1a8f08ae3fc5955ab Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 2 Nov 2015 03:05:52 -0500 Subject: [PATCH 105/274] more refactoring --- lib/middleware/json.js | 13 +++++ lib/middleware/readme.js | 11 ---- lib/pipeline/a.js | 7 ++- lib/plugins/config.js | 44 -------------- lib/plugins/index.js | 1 - lib/plugins/locals.js | 30 ---------- lib/plugins/pkg.js | 47 --------------- lib/plugins/pkg.md | 19 ------ lib/plugins/schema.js | 119 -------------------------------------- lib/plugins/store.js | 14 ----- lib/tasks/default.js | 4 +- lib/tasks/del.js | 17 ++++++ lib/tasks/dest.js | 28 +++++++-- lib/tasks/files.js | 9 +-- lib/tasks/jshint.js | 8 --- lib/tasks/lint.js | 4 +- lib/tasks/list.js | 6 +- lib/tasks/noop.js | 2 +- lib/tasks/rename.js | 65 +++++++++++++++++++++ lib/tasks/tree.js | 4 +- lib/updaters/copyright.js | 9 --- lib/updaters/license.js | 41 ------------- lib/updaters/rename.js | 29 ---------- test/render.js | 14 ++--- test/store.js | 102 +++++++++++++++++++++----------- 25 files changed, 214 insertions(+), 433 deletions(-) create mode 100644 lib/middleware/json.js delete mode 100644 lib/middleware/readme.js delete mode 100644 lib/plugins/config.js delete mode 100644 lib/plugins/index.js delete mode 100644 lib/plugins/locals.js delete mode 100644 lib/plugins/pkg.js delete mode 100644 lib/plugins/pkg.md delete mode 100644 lib/plugins/schema.js delete mode 100644 lib/plugins/store.js create mode 100644 lib/tasks/del.js delete mode 100644 lib/tasks/jshint.js create mode 100644 lib/tasks/rename.js delete mode 100644 lib/updaters/copyright.js delete mode 100644 lib/updaters/license.js delete mode 100644 lib/updaters/rename.js diff --git a/lib/middleware/json.js b/lib/middleware/json.js new file mode 100644 index 0000000..61a92b8 --- /dev/null +++ b/lib/middleware/json.js @@ -0,0 +1,13 @@ +'use strict'; + +module.exports = function (app, base, env) { + base.onLoad(/\.js(on|hintrc)$/, function(file, next) { + file.json = JSON.parse(file.content); + next(); + }); + + base.preWrite(/\.js(on|hintrc)$/, function(file, next) { + file.content = JSON.stringify(file.json, null, 2); + next(); + }); +}; diff --git a/lib/middleware/readme.js b/lib/middleware/readme.js deleted file mode 100644 index 445162c..0000000 --- a/lib/middleware/readme.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -module.exports = function(app, env) { - app.onLoad(/\.(?:verb(rc)?|readme)\.md$/i, function(view, next) { - if (view.readme !== false && view.noreadme !== true) { - view.path = 'README.md'; - } - next(); - }); -}; - diff --git a/lib/pipeline/a.js b/lib/pipeline/a.js index 07e63ec..3361998 100644 --- a/lib/pipeline/a.js +++ b/lib/pipeline/a.js @@ -3,12 +3,15 @@ var through = require('through2'); module.exports = function(options) { - console.log(this) return through.obj(function (file, enc, cb) { - console.log(file) var str = file.contents.toString(); str += 'aaa\n'; + // var err = new Error('foo'); + // err.plugin = 'a'; + // this.emit('error', err); + // return cb(err); + file.contents = new Buffer(str); this.push(file); cb(); diff --git a/lib/plugins/config.js b/lib/plugins/config.js deleted file mode 100644 index a336d82..0000000 --- a/lib/plugins/config.js +++ /dev/null @@ -1,44 +0,0 @@ -'use strict'; - -var Config = require('map-config'); -var utils = require('../utils'); - -module.exports = function (options) { - return function (app) { - var config = app.locals.cache; - var keys = Object.keys(app.views); - var views = {}; - - keys.forEach(function (key) { - var instance = app[key]; - - views[key] = function (config) { - var mapper = new Config({ - options: 'option', - set: 'set', - addViews: 'addViews' - }, instance); - - console.log('loading templates from config: "' + key + '"'); - return mapper.process(config); - }; - }); - - var collections = new Config(views); - var configMap = new Config({ - fns: function (config) { - utils.forOwn(config, function (val, key) { - var opts = utils.extend({}, app.options, val); - app.use(require(key)(opts)); - }); - }, - collections: function (config) { - return collections.process(config); - }, - helpers: 'helpers', - asyncHelpers: 'asyncHelpers' - }, app); - - configMap.process(config); - }; -}; diff --git a/lib/plugins/index.js b/lib/plugins/index.js deleted file mode 100644 index 23b2930..0000000 --- a/lib/plugins/index.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('export-files')(__dirname); diff --git a/lib/plugins/locals.js b/lib/plugins/locals.js deleted file mode 100644 index cb191cf..0000000 --- a/lib/plugins/locals.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict'; - -var get = require('get-value'); -var set = require('set-value'); -var utils = require('../utils'); - -module.exports = function (config) { - if (!config || !config.name) { - throw new Error('expected config.name to be a string.'); - } - - return function (app) { - var opts = utils.extend({}, config, app.option('update')); - this.locals = new Locals(config.name, this); - return this; - }; -}; - -function Locals(name, app) { - this.cache = get(app.cache.data, name) || (app.cache.data[name] = {}); -} - -Locals.prototype.get = function(key) { - return get(this.cache, key); -}; - -Locals.prototype.set = function(key, value) { - set(this.cache, key, value); - return this; -}; diff --git a/lib/plugins/pkg.js b/lib/plugins/pkg.js deleted file mode 100644 index 53d9d2b..0000000 --- a/lib/plugins/pkg.js +++ /dev/null @@ -1,47 +0,0 @@ - -var Engine = require('engine'); -var typeOf = require('kind-of'); -var pkg = require('load-pkg')(); -var extend = require('extend-shallow'); -var writeJson = require('write-json'); -var schema = require('./defaults'); - -function update(config, options) { - var engine = new Engine(options); - - for (var key in schema) { - if (schema.hasOwnProperty(key)) { - var val = schema[key]; - - if (typeOf(config[key]) !== val.type) { - var value = config[key]; - - if (typeof value === 'undefined' && val.add) { - if (typeof val.value === 'undefined') { - throw new Error('expected "value" to not be undefined for: ' + key); - } - config[key] = val.value; - - } else if (val.value) { - config[key] = val.value; - - } else if (val.template) { - var ctx = extend({}, config); - val.context(config, ctx); - config[key] = engine.render(val.template, ctx); - } - } else if (typeof val.fn === 'function') { - config[key] = val.fn(config[key], config); - } - } - } - return config; -} - -var config = update(pkg); -console.log(config) - -writeJson('package.json', config, function (err) { - if (err) return console.error(err); - console.log('updated package.json'); -}); diff --git a/lib/plugins/pkg.md b/lib/plugins/pkg.md deleted file mode 100644 index adae3dd..0000000 --- a/lib/plugins/pkg.md +++ /dev/null @@ -1,19 +0,0 @@ -# update-pkg - -## update deps - -Remove devDeps/deps: - -- verb (if `verbfile.js` does not exist) -- verb-tag-jscomments - -Add devDeps/deps: - -- verb (if `verbfile.js` exists) - -## keywords - - -## Miscellaneous - -- **sort**: sort by given keys or based on stored config \ No newline at end of file diff --git a/lib/plugins/schema.js b/lib/plugins/schema.js deleted file mode 100644 index dc5d5e9..0000000 --- a/lib/plugins/schema.js +++ /dev/null @@ -1,119 +0,0 @@ -'use strict'; - -var glob = require('matched'); -var get = require('get-value'); -var set = require('set-value'); -var parseAuthor = require('parse-author'); -var parseUrl = require('parse-github-url'); -var url = require('url'); - -function isGithubUrl(str) { - var obj = url.parse(str); - var hosts = ['github.com', 'github.io', 'gist.github.com']; - return hosts.indexOf(obj.host) > -1; -} - -module.exports = { - name: { - type: 'string' - }, - description: { - type: 'string' - }, - version: { - type: 'string' - }, - homepage: { - type: 'string' - }, - author: { - type: 'string', - value: 'Jon Schlinkert (https://github.com/jonschlinkert)', - template: '<%= author.name %> (<%= author.url %>)' - }, - repository: { - type: 'string', - template: '<%= author.username %>/<%= name %>', - context: function (config, ctx) { - var author = get(config, 'author'); - if (typeof author === 'undefined') { - return config; - } - - if (typeof author === 'string') { - author = parseAuthor(author); - set(ctx, 'author', author); - } - - if (typeof author.username === 'undefined') { - if (!author.url || !isGithubUrl(author.url)) { - return config; - } - var parsed = parseUrl(author.url); - var username = parsed.user; - set(ctx, 'author.username', username); - } - return config; - } - }, - bugs: { - type: 'object', - value: { - url: { - type: 'string', - template: 'https://github.com/<%= author.username %>/<%= name %>/issues', - } - } - }, - license: { - type: 'string', - value: 'MIT' - }, - files: { - type: 'array', - fn: function (arr, config) { - var files = glob.sync('**/*.js', { - ignore: ['node_modules/**'] - }); - - var len = files.length, i = -1; - var res = []; - while (++i < len) { - var name = files[i]; - if (/test(\/.*)?\.js$/.test(name)) { - continue; - } - res.push(name); - } - return res; - } - }, - main: { - type: 'string' - }, - engines: { - type: 'object' - }, - scripts: { - type: 'object' - }, - dependencies: { - type: 'object' - }, - devDependencies: { - type: 'object' - }, - keywords: { - type: 'array' - }, - verb: { - type: 'object', - add: true, - value: { - related: { - description: '', - list: [] - } - } - } -}; diff --git a/lib/plugins/store.js b/lib/plugins/store.js deleted file mode 100644 index 7e80ba7..0000000 --- a/lib/plugins/store.js +++ /dev/null @@ -1,14 +0,0 @@ - -var utils = require('../utils'); - -module.exports = function (config) { - if (!config || !config.name) { - throw new Error('expected config.name to be a string.'); - } - - return function (app) { - var opts = utils.extend({}, config, app.option('store')); - this.store = utils.store(config.name, opts); - return this; - }; -}; diff --git a/lib/tasks/default.js b/lib/tasks/default.js index a6960f6..cca37f1 100644 --- a/lib/tasks/default.js +++ b/lib/tasks/default.js @@ -2,6 +2,6 @@ var utils = require('../utils'); -module.exports = function(app, env) { - return ['files', 'run', 'dest']; +module.exports = function(app, base, env) { + return ['del', 'files', 'run', 'dest']; }; diff --git a/lib/tasks/del.js b/lib/tasks/del.js new file mode 100644 index 0000000..13eac44 --- /dev/null +++ b/lib/tasks/del.js @@ -0,0 +1,17 @@ +'use strict'; + +var path = require('path'); +var async = require('async'); +var rimraf = require('rimraf'); + +var list = ['.npmignore', '.jshintrc']; + +module.exports = function(app, base, env) { + var files = base.option('delete') || list; + + return function (cb) { + async.each(files, function(fp, next) { + rimraf(path.resolve(process.cwd(), fp), next); + }, cb); + }; +}; diff --git a/lib/tasks/dest.js b/lib/tasks/dest.js index bc0c92f..de93e05 100644 --- a/lib/tasks/dest.js +++ b/lib/tasks/dest.js @@ -1,8 +1,28 @@ 'use strict'; -module.exports = function(app, env) { - return function () { - return app.toStream('files') - .pipe(app.dest('.')); + +module.exports = function(app, base, env) { + var through = require('through2'); + var plugins = base.get('argv.plugins'); + + return function (cb) { + app.toStream('files') + .on('error', cb) + .pipe(through.obj(function (file, enc, next) { + if (file.isNull()) return next(); + app.handle('onStream', file, next); + })) + .pipe(app.pipeline(plugins)) + .pipe(through.obj(function (file, enc, next) { + if (file.isNull()) return next(); + app.handle('preWrite', file, next); + })) + .pipe(app.dest('.')) + .pipe(through.obj(function (file, enc, next) { + if (file.isNull()) return next(); + app.handle('postWrite', file, next); + })) + .on('error', cb) + .on('end', cb); }; }; diff --git a/lib/tasks/files.js b/lib/tasks/files.js index bcd6339..6a82714 100644 --- a/lib/tasks/files.js +++ b/lib/tasks/files.js @@ -3,14 +3,14 @@ var path = require('path'); var utils = require('../utils'); -module.exports = function(app, env) { - app.create('files', { +module.exports = function(app, base, env) { + base.create('files', { renameKey: function (key) { return path.basename(key); } }); - var glob = env.base.get('argv.files'); + var glob = base.get('argv.files'); if (glob) { glob = glob.split(','); } else { @@ -18,7 +18,8 @@ module.exports = function(app, env) { } return function (cb) { - app.files(glob, {dot: true}); + base.files(glob, {dot: true, ignore: ['.DS_Store']}); + base.emit('loaded', base.files); cb(); } }; diff --git a/lib/tasks/jshint.js b/lib/tasks/jshint.js deleted file mode 100644 index 2cae1c2..0000000 --- a/lib/tasks/jshint.js +++ /dev/null @@ -1,8 +0,0 @@ -'use strict'; - -module.exports = function(app, env) { - return function () { - return app.src('.jshintrc') - .pipe(app.dest('blah')); - } -}; diff --git a/lib/tasks/lint.js b/lib/tasks/lint.js index b36b0ea..75e7d19 100644 --- a/lib/tasks/lint.js +++ b/lib/tasks/lint.js @@ -4,9 +4,9 @@ var through = require('through2'); var jshint = require('gulp-jshint'); var stylish = require('jshint-stylish'); -module.exports = function(app, env) { +module.exports = function(app, base, env) { return function () { - return env.base.toStream('files') + return base.toStream('files') .pipe(through.obj(function (file, enc, cb) { if (/\.js$/.test(file.path)) { this.push(file); diff --git a/lib/tasks/list.js b/lib/tasks/list.js index 80b9da1..6943d13 100644 --- a/lib/tasks/list.js +++ b/lib/tasks/list.js @@ -2,11 +2,11 @@ var utils = require('../utils'); -module.exports = function(app, env) { +module.exports = function(app, base, env) { return function (cb) { - env.list(function (err, args) { + base.list(function (err, args) { if (err) return cb(err); - env.run(args, cb); + base.run(args, cb); }); }; }; diff --git a/lib/tasks/noop.js b/lib/tasks/noop.js index 0e1d5d6..b0f679c 100644 --- a/lib/tasks/noop.js +++ b/lib/tasks/noop.js @@ -2,7 +2,7 @@ var utils = require('../utils'); -module.exports = function(app, env) { +module.exports = function(app, base, env) { return function (cb) { return cb(); }; diff --git a/lib/tasks/rename.js b/lib/tasks/rename.js new file mode 100644 index 0000000..852ac34 --- /dev/null +++ b/lib/tasks/rename.js @@ -0,0 +1,65 @@ +'use strict'; + +var path = require('path'); +var rimraf = require('rimraf'); +var through = require('through2'); + +var mapping = { + 'LICENSE': 'LICENSE-MIT', + 'readme.md': 'README.md' +}; + +module.exports = function(app, base, env) { + var config = base.option('rename') || mapping; + + app.task('undo', function () { + return base.toStream('files') + .pipe(rename(config, {invert: true})); + }); + + return function () { + return base.toStream('files') + .pipe(rename(config)); + }; +}; + +function rename(mapping, options) { + options = options || {}; + if (options.invert === true) { + mapping = invert(mapping); + } + return through.obj(function (file, enc, next) { + if (file.isNull()) return next(null, file); + var fp = file.path; + function del(err) { + if (err) return next(err); + next(null, file); + } + for (var key in mapping) { + if (isMatch(file, mapping[key])) { + file.path = path.resolve(file.base, key); + rimraf(fp, del); + return; + } + } + next(null, file); + }); +} + +function isMatch(file, src) { + file.basename = path.basename(file.path); + if (src instanceof RegExp) { + return src.test(file.basename) || src.test(file.path); + } + if (typeof src === 'string') { + return src === file.path || src === file.basename; + } +} + +function invert(obj) { + var res = {}; + for (var key in obj) { + res[obj[key]] = key; + } + return res; +} diff --git a/lib/tasks/tree.js b/lib/tasks/tree.js index 0234473..efc1f18 100644 --- a/lib/tasks/tree.js +++ b/lib/tasks/tree.js @@ -2,9 +2,9 @@ var utils = require('../utils'); -module.exports = function(app, env) { +module.exports = function(app, base, env) { return function (cb) { - console.log(utils.tree(env.base.updaters)); + console.log(utils.tree(base.updaters)); cb(); } }; diff --git a/lib/updaters/copyright.js b/lib/updaters/copyright.js deleted file mode 100644 index 5768c9f..0000000 --- a/lib/updaters/copyright.js +++ /dev/null @@ -1,9 +0,0 @@ - -var fs = require('fs'); -var pkg = require('load-pkg')(); -var copyright = require('update-copyright'); - -var str = fs.readFileSync('LICENSE', 'utf8'); -var updated = copyright(str, pkg); - -console.log(updated); diff --git a/lib/updaters/license.js b/lib/updaters/license.js deleted file mode 100644 index 08b8cff..0000000 --- a/lib/updaters/license.js +++ /dev/null @@ -1,41 +0,0 @@ - -var fs = require('fs'); -var del = require('delete'); -var writeFile = require('write'); -var green = require('ansi-green'); -var success = require('success-symbol'); -var cwd = require('cwd'); - -function update(filepath, cb) { - var banner = 'The MIT License (MIT)\n\n'; - var fp = cwd(filepath); - - fs.readFile(fp, 'utf8', function (err, str) { - if (err) return cb(err); - - if (!/^The MIT License \(MIT\)/i.test(str)) { - str = banner + str; - } - - del(filepath, function (err) { - if (err) return cb(err); - - writeFile('LICENSE', str, function (err) { - if (err) return cb(err); - - return cb(null, 'updated'); - }); - }); - }); -} - -update('LICENSE', function (err, res) { - if (err) { - return console.error(err); - } - var msg = ' LICENSE is already up to date.'; - if (res === 'updated') { - msg = ' updated LICENSE'; - } - console.log(green(success), msg); -}); diff --git a/lib/updaters/rename.js b/lib/updaters/rename.js deleted file mode 100644 index 51f5292..0000000 --- a/lib/updaters/rename.js +++ /dev/null @@ -1,29 +0,0 @@ - -var fs = require('fs'); -var async = require('async'); -var green = require('ansi-green'); -var success = require('success-symbol'); - -function renameFiles(files, cb) { - async.eachSeries(Object.keys(files), function (key, next) { - fs.rename(key, files[key], next); - }, cb); -} - -var files = { - 'LICENSE-MIT': 'LICENSE', - '.verbrc.md': '.verb.md', - 'README.md': 'readme.md' -}; - -renameFiles(files, function(err) { - if (err) { - if (err.code !== 'ENOENT') { - console.error(err); - } - return; - } - var keys = Object.keys(files); - var len = keys.length; - console.log(green(success), ' renamed ' + len, 'files'); -}); diff --git a/test/render.js b/test/render.js index 1c0b05e..e097fcd 100644 --- a/test/render.js +++ b/test/render.js @@ -26,17 +26,17 @@ describe('render', function () { }); it('should throw an error when a variable is undefined:', function (done) { - delete view.locals.name; + view = {contents: new Buffer('a <%= foo %> b')}; app.page('a.tmpl', view) .render(function (err) { - assert(err.message === 'name is not defined'); + assert(err.message === 'foo is not defined'); done(); }); }); it('should re-throw an error when rethrow is true:', function (done) { - delete view.locals.name; + view = {contents: new Buffer('a <%= foo %> b')}; app = new App({rethrow: true, silent: true}); app.engine('tmpl', require('engine-base')); @@ -44,26 +44,26 @@ describe('render', function () { app.page('a.tmpl', view) .render(function (err) { - assert(err.message === 'name is not defined'); + assert(err.message === 'foo is not defined'); done(); }); }); it('should emit a re-thrown error when rethrow is true:', function (done) { - delete view.locals.name; + view = {contents: new Buffer('a <%= foo %> b')}; app = new App({rethrow: true, silent: false}); app.engine('tmpl', require('engine-base')); app.create('page'); app.on('error', function(err) { - assert(err.message === 'name is not defined'); + assert(err.message === 'foo is not defined'); done(); }); app.page('a.tmpl', view) .render(function (err) { - assert(err.message === 'name is not defined'); + assert(err.message === 'foo is not defined'); }); }); }); diff --git a/test/store.js b/test/store.js index 5f714c3..f830180 100644 --- a/test/store.js +++ b/test/store.js @@ -4,60 +4,61 @@ require('mocha'); require('should'); var fs = require('fs'); var path = require('path'); -var store = require('data-store'); var assert = require('assert'); -var App = require('../'); +var store = require('base-store'); +var update = require('..'); var app; describe('store', function () { beforeEach(function () { - app = new App(); - app.store = store('update-next-tests'); + app = update(); + app.use(store('update-tests')); }); - afterEach(function (cb) { + afterEach(function () { + app.store.data = {}; app.store.del({force: true}); - cb(); }); - it('should create an instance of store', function () { - assert(app.store instanceof store); + it('should create a store with the given `name`', function () { + app.use(store('foo-bar-baz')); + assert(app.store.name === 'foo-bar-baz'); }); it('should create a store at the given `cwd`', function () { - app = new App({store: {cwd: __dirname + '/actual'}}); - + var cwd = path.resolve(__dirname, 'actual'); + app.use(store('abc', {cwd: cwd})); app.store.set('foo', 'bar'); - assert(path.basename(app.store.path) === 'update.json'); - assert(app.store.data.hasOwnProperty('foo')); - assert(app.store.data.foo === 'bar'); - assert(fs.existsSync(path.join(__dirname, 'actual', 'update.json'))); + path.basename(app.store.path).should.equal('abc.json'); + app.store.data.should.have.property('foo', 'bar'); + assert.equal(fs.existsSync(path.join(cwd, 'abc.json')), true); }); it('should create a store using the given `indent` value', function () { - app = new App({store: {cwd: __dirname + '/actual', indent: 0}}); + var cwd = path.resolve(__dirname, 'actual'); + app.use(store('abc', {cwd: cwd, indent: 0})); app.store.set('foo', 'bar'); - var contents = fs.readFileSync(path.join(__dirname, 'actual', 'update.json'), 'utf8'); - assert(contents === '{"foo":"bar"}'); + var contents = fs.readFileSync(path.resolve(cwd, 'abc.json'), 'utf8'); + assert.equal(contents, '{"foo":"bar"}'); }); - it('should set a value on the store', function () { + it('should `.set()` a value on the store', function () { app.store.set('one', 'two'); app.store.data.one.should.equal('two'); }); - it('should set an object', function () { + it('should `.set()` an object', function () { app.store.set({four: 'five', six: 'seven'}); app.store.data.four.should.equal('five'); app.store.data.six.should.equal('seven'); }); - it('should set a nested value', function () { + it('should `.set()` a nested value', function () { app.store.set('a.b.c.d', {e: 'f'}); app.store.data.a.b.c.d.e.should.equal('f'); }); - it('should union a value onto an array on the store', function () { + it('should `.union()` a value on the store', function () { app.store.union('one', 'two'); app.store.data.one.should.eql(['two']); }); @@ -150,8 +151,13 @@ describe('store', function () { it('should `.del()` a stored value', function () { app.store.set('a', 'b'); app.store.set('c', 'd'); + app.store.data.should.have.property('a'); + app.store.data.should.have.property('c'); + app.store.del('a'); - app.store.should.not.have.property('a'); + app.store.del('c'); + app.store.data.should.not.have.property('a'); + app.store.data.should.not.have.property('c'); }); it('should `.del()` multiple stored values', function () { @@ -165,13 +171,12 @@ describe('store', function () { describe('events', function () { beforeEach(function () { - app = new App(); - app.store = store('update-next-tests'); + app.use(store('abc')); }); - afterEach(function (cb) { + afterEach(function () { + app.store.data = {}; app.store.del({force: true}); - cb(); }); it('should emit `set` when an object is set:', function () { @@ -186,6 +191,7 @@ describe('events', function () { it('should emit `set` when a key/value pair is set:', function () { var keys = []; + app.store.on('set', function (key) { keys.push(key); }); @@ -196,6 +202,7 @@ describe('events', function () { it('should emit `set` when an object value is set:', function () { var keys = []; + app.store.on('set', function (key) { keys.push(key); }); @@ -204,21 +211,35 @@ describe('events', function () { keys.should.eql(['a']); }); - it('should emit `set` when an array of objects is passed:', function () { + it('should emit `set` when an array of objects is passed:', function (cb) { var keys = []; + app.store.on('set', function (key) { keys.push(key); }); app.store.set([{a: 'b'}, {c: 'd'}]); keys.should.eql(['a', 'c']); + cb(); + }); + + it('should emit `has`:', function (cb) { + var keys = []; + + app.store.on('has', function (val) { + assert(val); + cb(); + }); + + app.store.set('a', 'b'); + app.store.has('a'); }); - it('should emit `del` when a value is delted:', function () { - var res; + it('should emit `del` when a value is delted:', function (cb) { app.store.on('del', function (keys) { keys.should.eql('a'); assert(typeof app.store.get('a') === 'undefined'); + cb(); }); app.store.set('a', {b: 'c'}); @@ -226,17 +247,30 @@ describe('events', function () { app.store.del('a'); }); - it('should emit deleted keys on `del`:', function (done) { - var keys = []; + it('should emit deleted keys on `del`:', function (cb) { + var arr = []; + app.store.on('del', function (key) { - keys.push(key); + arr.push(key); + assert(Object.keys(app.store.data).length === 0); }); app.store.set('a', 'b'); app.store.set('c', 'd'); app.store.set('e', 'f'); + app.store.del({force: true}); - keys.should.eql(['a', 'c', 'e']); - done(); + arr.should.eql(['a', 'c', 'e']); + cb(); + }); + + it('should throw an error if force is not passed', function () { + app.store.set('a', 'b'); + app.store.set('c', 'd'); + app.store.set('e', 'f'); + + (function () { + app.store.del(); + }).should.throw('options.force is required to delete the entire cache.'); }); }); From b6df6588ed7938d8bc064f26d07e817484f84531 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 2 Nov 2015 03:06:30 -0500 Subject: [PATCH 106/274] adds locals class to handle user-defined values on package.json `update` object --- lib/locals.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 lib/locals.js diff --git a/lib/locals.js b/lib/locals.js new file mode 100644 index 0000000..2ff5b47 --- /dev/null +++ b/lib/locals.js @@ -0,0 +1,26 @@ +'use strict'; + +var get = require('get-value'); +var set = require('set-value'); +var utils = require('./utils'); + +module.exports = function (name) { + name = name || utils.project(process.cwd()); + + return function (app) { + app.define('locals', new Locals(name, this)); + }; +}; + +function Locals(name, app) { + this.cache = get(app, ['cache.data', name]) || {}; +} + +Locals.prototype.get = function(key) { + return get(this.cache, key); +}; + +Locals.prototype.set = function(key, value) { + set(this.cache, key, value); + return this; +}; From f29126a62a2857a3eacf9d91bd51fe466052da06 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 2 Nov 2015 03:06:47 -0500 Subject: [PATCH 107/274] update cli, multi-updater handling --- bin/update.js | 20 ++++++--- index.js | 122 ++++++++++++++++++++++++++++++++++---------------- lib/config.js | 52 ++++++++++----------- lib/multi.js | 81 +++++++++++++++++++++++++-------- lib/utils.js | 46 +++++++++++++++++-- 5 files changed, 224 insertions(+), 97 deletions(-) diff --git a/bin/update.js b/bin/update.js index c9dfdba..f9464c8 100755 --- a/bin/update.js +++ b/bin/update.js @@ -1,22 +1,28 @@ #!/usr/bin/env node -var argv = require('minimist')(process.argv.slice(2)); +var path = require('path'); var multi = require('../lib/multi')(); var utils = require('../lib/utils'); +var argv = require('minimist')(process.argv.slice(2), { + alias: {verbose: 'v'} +}); var cmd = utils.commands(argv); var cli = multi(argv); -var task = cmd.list ? 'list' : 'default'; +var task = cmd.list ? ['list', 'default'] : 'default'; cli.on('*', function (method, key, val) { console.log(method + ':', key, val); }); -cli.on('register', function(key) { - console.log('registered ', utils.yellow(key)); -}); -cli.register('update-*', {cwd: utils.gm}); +if (argv.verbose) { + cli.on('register', function(key) { + utils.ok(utils.gray('registered'), 'updater', utils.cyan(key)); + }); +} + +cli.registerEach('update-*', {cwd: utils.gm}); cli.base.task('run', function (cb) { cli.run(cb); @@ -24,5 +30,5 @@ cli.base.task('run', function (cb) { cli.base.build(task, function (err) { if (err) console.error(err); - utils.ok('Finished (see '); + utils.ok('Finished.'); }); diff --git a/index.js b/index.js index cf1e2b1..5676e73 100644 --- a/index.js +++ b/index.js @@ -7,17 +7,27 @@ 'use strict'; -var argv = require('minimist'); -var cli = require('base-cli'); -var ask = require('assemble-ask'); -var Core = require('assemble-core'); +/** + * module dependencies + */ + +var path = require('path'); +var pipeline = require('base-pipeline'); +var minimist = require('minimist'); var loader = require('assemble-loader'); +var Core = require('assemble-core'); +var ask = require('assemble-ask'); +var store = require('base-store'); +var cli = require('base-cli'); + +var expand = require('expand-args'); var config = require('./lib/config'); -var plugin = require('./lib/plugins'); +var locals = require('./lib/locals'); var utils = require('./lib/utils'); /** - * Create an instance of `Update` with the given `options` + * Create an `update` application. This is the main function exported + * by the update module. * * ```js * var Update = require('update'); @@ -32,7 +42,7 @@ function Update(options) { return new Update(options); } Core.call(this, options); - this.name = this.options.name || 'update'; + this.set('name', 'update'); this.initUpdate(this); } @@ -48,17 +58,38 @@ Core.extend(Update); Update.prototype.initUpdate = function(base) { this.define('isUpdate', true); - this.set('argv', this.argv || argv(process.argv.slice(2))); this.set('updaters', {}); - this - .use(utils.runtimes()) - .use(plugin.locals({name: this.name})) - .use(plugin.store({name: this.name})) - .use(config()) + // custom middleware handlers + this.handler('onStream'); + this.handler('preWrite'); + this.handler('postWrite'); + + // parse command line arguments + var argv = minimist(process.argv.slice(2)); + if (process.argv.length > 3) { + argv = expand(argv); + } + + // expose `argv` on the instance + this.set('argv', this.argv || argv); + + // expose `package.json` on `cache.data` + this.data(utils.pkg()); + config(this); + + this.use(utils.runtimes()) + .use(locals('update')) + .use(store()) + .use(pipeline()) .use(loader()) .use(ask()) - .use(cli); + .use(cli()) + + .use(utils.defaults()) + .use(utils.opts()) + + this.config.process(this.cache.data); this.engine(['md', 'tmpl'], require('engine-base')); this.onLoad(/\.(md|tmpl)$/, function (view, next) { @@ -67,12 +98,39 @@ Update.prototype.initUpdate = function(base) { }); }; +Update.prototype.cwd = function(dir) { + var cwd = dir || process.cwd(); + return function() { + var args = [].slice.call(arguments); + args.unshift(cwd); + return path.resolve.apply(null, args); + }; +}; + Update.prototype.flag = function(key) { - return utils.expandArgs(this.argv)[key]; + return this.get('argv.' + key); }; Update.prototype.cmd = function(key) { - return utils.commands(utils.expandArgs(this.argv))[key] || false; + return utils.commands(this.argv)[key] || false; +}; + +Update.prototype.extendFile = function(file, config, opts) { + var parsed = utils.tryParse(file.content); + var obj = utils.extend({}, parsed, config); + var res = {}; + if (opts && opts.sort === true) { + var keys = Object.keys(obj).sort(); + var len = keys.length, i = -1; + while (++i < len) { + var key = keys[i]; + res[key] = obj[key]; + } + } else { + res = obj; + } + file.content = JSON.stringify(res, null, 2); + if (opts.newline) file.content += '\n'; }; /** @@ -84,34 +142,16 @@ Update.prototype.cmd = function(key) { * @return {Object} Returns the instance for chaining */ -Update.prototype.updater = function(name, update) { +Update.prototype.updater = function(name, app) { if (arguments.length === 1) { return this.updaters[name]; } - update.use(utils.runtimes({ + app.use(utils.runtimes({ displayName: function(key) { return utils.cyan(name + ':' + key); } })); - return (this.updaters[name] = update); -}; - -Update.prototype.hasUpdater = function(name) { - return this.updaters.hasOwnProperty(name); -}; - -Update.prototype.hasTask = function(name) { - return this.taskMap.indexOf(name) > -1; -}; - -Update.prototype.opts = function(prop, options) { - var args = [].concat.apply([], [].slice.call(arguments)); - if (typeof opts === 'string') { - args.unshift(this.option(args.shift())); - } else { - args.unshift(this.options); - } - return utils.extend.apply(utils.extend, args); + return (this.updaters[name] = app); }; /** @@ -125,5 +165,9 @@ module.exports = Update; */ module.exports.utils = utils; -module.exports.meta = require('./package'); -module.exports.dir = __dirname; + +/** + * Expose package.json metadata + */ + +module.exports.pkg = require('./package'); diff --git a/lib/config.js b/lib/config.js index c2acc17..1a4eadd 100644 --- a/lib/config.js +++ b/lib/config.js @@ -1,36 +1,30 @@ 'use strict'; -var utils = require('./utils'); -module.exports = function(options) { - return function(app) { - var res = utils.config(app) - .map('store', store(app.store)) - .map('option') - .map('set') - .map('del') - .map('get', function(key) { - console.log(app.get(key)); - }); +module.exports = function (app) { + if (!app.isUpdate) return; - // app.on('argv', function(argv) { - // res.process(utils.expand(argv)); - // }); - }; -} + var config = require('base-config'); + app.use(config()); -function store(app) { - var res = utils.config(app) - .map('set') - .map('del') - .map('has', function(key) { - console.log(!!app.get(key)); + app.config + .map('addViews') + .map('addView') + .map('helpers') + .map('asyncHelpers') + .map('plugins', function(val) { + app.visit('plugin', val); }) - .map('get', function(key) { - console.log(app.get(key)); + .map('data', function(val) { + app.visit('data', val); }) - - return function(args) { - // res.process(utils.expand(args)); - }; -} + .map('collections', function(val) { + app.visit('create', val); + }) + .map('reflinks', function(val) { + app.data({reflinks: val}); + }) + .map('related', function(val) { + app.data({related: val}); + }); +}; diff --git a/lib/multi.js b/lib/multi.js index c5ab47d..d27d3cc 100644 --- a/lib/multi.js +++ b/lib/multi.js @@ -15,28 +15,31 @@ module.exports = function(namespace, config) { this.options = options || {}; this.commands = ['set', 'get', 'del', 'store', 'init', 'option', 'data']; + this.base = new Update() .on('error', console.error) .set('argv', argv); + // register middleware for (var fn in fns) { - fns[fn](this.base, this); + fns[fn](this.base, this.base, this); } + // register tasks for (var key in tasks) { - this.base.task(key, tasks[key](this.base, this)); - } - - if (this.base.disabled('verbose')) { - this.emit = function () {} - this.on = function () {} + this.base.task(key, tasks[key](this.base, this.base, this)); } this._listen(); } Emitter(Multi.prototype); + Multi.prototype.updater = function(name) { + return this.base.updater(name); + }; + Multi.prototype._listen = function() { + if (this.base.disabled('verbose')) return; var store = ['store.set', 'store.has', 'store.get', 'store.del']; var methods = ['set', 'has', 'get', 'del', 'option', 'data']; @@ -101,23 +104,55 @@ module.exports = function(namespace, config) { return res; }; - Multi.prototype.register = function(pattern, options) { + Multi.prototype.registerEach = function(pattern, options) { utils.matchFiles(pattern, options).forEach(function (fp) { - var fullname = utils.projectName(fp); + var filepath = path.resolve(fp, 'updatefile.js'); + var updater = require(filepath); + + // get the full project name ('updater-foo') + var fullname = utils.project(fp); + // get the updater name ('foo') var name = utils.renameFn(fullname, options); - var mod = utils.resolveModule(fp) || Update; - var app = mod(this.base.options) - .option('name', name) - .set('fullname', fullname) - .set('path', fp); + var opts = {}; + // get the constructor to use (node_modules or our 'Update') + opts.Update = utils.resolveModule(fp); + opts.fullname = fullname; + opts.path = fp; - var filepath = path.join(fp, 'updatefile.js'); + this.register(name, opts, updater); + }.bind(this)); + return this; + }; - require(filepath)(app, this); - this.base.updater(name, app); + Multi.prototype.register = function(name, options, updater) { + if (arguments.length === 2) { + updater = options; + options = {}; + } - this.emit('register', name, app); - }.bind(this)); + var Ctor = options.Update || Update; + var app = new Ctor(this.base.options) + .option('name', name) + .option('fullname', options.fullname || name) + .option('path', options.path || ''); + + app.create('templates', { + cwd: path.resolve(options.path, 'templates'), + renameKey: function (key) { + return path.basename(key); + } + }); + + app.define('getTemplate', function(name) { + var view = app.templates.getView.apply(app.templates, arguments); + view.basename = view.basename.replace(/^_/, '.'); + return view; + }); + + updater(app, this.base, this); + this.base.updater(name, app); + + this.emit('register', name, app); return this; }; @@ -156,6 +191,14 @@ module.exports = function(namespace, config) { return this; }; + Multi.prototype.hasUpdater = function(name) { + return this.updaters.hasOwnProperty(name); + }; + + Multi.prototype.hasTask = function(name) { + return this.taskMap.indexOf(name) > -1; + }; + Multi.prototype.list = function(cb) { var questions = utils.questions(this.base.options); var question = { diff --git a/lib/utils.js b/lib/utils.js index fb85e13..0b9fb51 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -15,8 +15,8 @@ require = utils; require('for-own'); require('async'); require('expand-args'); +require('load-pkg', 'pkg'); require('expand-object', 'expand'); -require('data-store', 'store'); require('global-modules', 'gm'); require('look-up', 'lookup'); require('object.omit', 'omit'); @@ -28,7 +28,7 @@ require('parser-front-matter', 'matter'); require('question-cache', 'questions'); require('extend-shallow', 'extend'); require('resolve-dir', 'resolve'); -require('project-name'); +require('project-name', 'project'); require('union-value', 'union'); require('set-value', 'set'); require('get-value', 'get'); @@ -36,6 +36,7 @@ require('get-value', 'get'); require('success-symbol'); require('ansi-yellow', 'yellow'); require('ansi-green', 'green'); +require('ansi-gray', 'gray'); require('ansi-cyan', 'cyan'); require('ansi-red', 'red'); require = fn; @@ -268,9 +269,16 @@ utils.tryRead = function(fp) { return null; }; +utils.tryParse = function(str) { + try { + return JSON.parse(str); + } catch(err) {} + return {}; +}; + utils.register = function(pattern, base, update, options) { utils.matchFiles(pattern, options).forEach(function (fp) { - var name = utils.projectName(fp); + var name = utils.project(fp); var mod = utils.resolveModule(fp) || update; var app = mod(base.options) .option('name', name) @@ -281,6 +289,38 @@ utils.register = function(pattern, base, update, options) { }); }; +utils.opts = function(key) { + key = key || 'opts'; + return function () { + this.define(key, function () { + var config = this.defaults.apply(this, arguments); + + return function (key, opts) { + var args = [].concat.apply([], [].slice.call(arguments)); + var prop = typeof key === 'string' ? args.shift() : null; + + if (prop && !args.length) { + return utils.get(config, prop); + } + var options = utils.extend.apply(utils.extend, [config].concat(args)); + return prop ? utils.get(options, prop) : options; + }; + }); + }; +}; + +utils.defaults = function(key) { + key = key || 'defaults'; + + return function() { + this.define(key, function() { + var args = [].concat.apply([], [].slice.call(arguments)); + args.unshift({}, this.options); + return utils.extend.apply(utils.extend, args); + }); + }; +}; + /** * Restore `require` */ From 5e6fbb0fd908b0e889baeca11ee34a65a3f89543 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 2 Nov 2015 03:07:15 -0500 Subject: [PATCH 108/274] lint --- .editorconfig | 2 +- .jshintrc | 19 ------------ .travis.yml | 10 ++----- .verb.md | 60 +++++++++++++++++++++++++++---------- README.md | 77 +++++++++++++++++++++++++++++++++++++++++------ examples.js | 75 ++++++++++++++++++++++++++++++++-------------- package.json | 83 ++++++++++++++++++++++++++------------------------- pipeline.js | 79 ------------------------------------------------ 8 files changed, 211 insertions(+), 194 deletions(-) delete mode 100644 .jshintrc delete mode 100644 pipeline.js diff --git a/.editorconfig b/.editorconfig index 1ff40e6..991900b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,7 +13,7 @@ insert_final_newline = true trim_trailing_whitespace = false insert_final_newline = false -[{,test/}{actual,fixtures}/**] +[test/**] trim_trailing_whitespace = false insert_final_newline = false diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 1d9d592..0000000 --- a/.jshintrc +++ /dev/null @@ -1,19 +0,0 @@ -{ - "asi": false, - "boss": true, - "curly": false, - "eqeqeq": true, - "eqnull": true, - "esnext": true, - "immed": true, - "latedef": false, - "laxbreak": true, - "laxcomma": false, - "mocha": true, - "newcap": true, - "noarg": true, - "node": true, - "sub": true, - "undef": true, - "unused": true -} diff --git a/.travis.yml b/.travis.yml index 0fc9381..d7a0715 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,5 @@ -sudo: false language: node_js node_js: - - "0.10" + - "stable" - "0.12" - - "0.13" - - "iojs" -matrix: - fast_finish: true - allow_failures: - - node_js: "0.13" + - "0.10" diff --git a/.verb.md b/.verb.md index 2622f45..601496c 100644 --- a/.verb.md +++ b/.verb.md @@ -2,33 +2,63 @@ > {%= description %} -## Install -{%= include("install-npm", {save: true}) %} +## CLI + +### Install + +{%= include("install-global") %} + +```sh +$ update +``` + +### tasks +### plugins +#### pipeline plugins +#### instance plugins +### middleware -## Usage +A middleware function takes `file` and `next`. ```js -var updateNext = require('{%= name %}'); +function rename(file, next) { + file.path = 'foo/' + path.basename(file.path); + next(); +} ``` -## API - -{%%= apidocs("index.js") %} -## Related projects - -{%%= related([]) %} +**Example** -## Authoring +The `onStream` method is a custom [middleware](docs/middleware.md) handler that the `update` i + +```js +app.onStream(/lib\//, rename); +``` + + +## API + +### Install + +{%= include("install-npm", {save: true}) %} -### Create your own updater +```js +var update = require('{%= name %}'); +``` +## API +{%= apidocs("index.js") %} -#### tasks +## Related projects +{%= related(verb.related.list) %} -#### tasks +## Authoring -#### plugins +### Updaters +#### Tasks +#### Middleware +#### Plugins > Updater plugins follow the same signature as gulp plugins diff --git a/README.md b/README.md index cf2b1d5..bf2d6b0 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,50 @@ > Update -## Install +## CLI + +### Install + +Install globally with [npm](https://www.npmjs.com/) + +```sh +$ npm i -g update-next +``` + +```sh +$ update +``` + +### tasks + +### plugins + +#### pipeline plugins + +#### instance plugins + +### middleware + +A middleware function takes `file` and `next`. + +```js +function rename(file, next) { + file.path = 'foo/' + path.basename(file.path); + next(); +} +``` + +**Example** + +The `onStream` method is a custom [middleware](docs/middleware.md) handler that the `update` i + +```js +app.onStream(/lib\//, rename); +``` + +## API + +### Install Install with [npm](https://www.npmjs.com/) @@ -10,21 +53,37 @@ Install with [npm](https://www.npmjs.com/) $ npm i update-next --save ``` -## Usage - ```js -var updateNext = require('update-next'); +var update = require('update-next'); ``` ## API - -{%= apidocs("index.js") %} +### [Update](index.js#L40) + +Create an `update` application. This is the main function exported by the update module. + +**Params** + +* `options` **{Object}** + +**Example** + +```js +var Update = require('update'); +var update = new Update(); +``` ## Related projects - -{%= related([]) %} +* [assemble](https://www.npmjs.com/package/assemble): Static site generator for Grunt.js, Yeoman and Node.js. Used by Zurb Foundation, Zurb Ink, H5BP/Effeckt,… [more](https://www.npmjs.com/package/assemble) | [homepage](http://assemble.io) +* [boilerplate](https://www.npmjs.com/package/boilerplate): Tools and conventions for authoring and publishing boilerplates that can be generated by any build… [more](https://www.npmjs.com/package/boilerplate) | [homepage](http://boilerplates.io) +* [composer](https://www.npmjs.com/package/composer): API-first task runner with three methods: task, run and watch. | [homepage](https://github.com/jonschlinkert/composer) +* [generate](https://www.npmjs.com/package/generate): Project generator, for node.js. | [homepage](https://github.com/generate/generate) +* [scaffold](https://www.npmjs.com/package/scaffold): Conventions and API for creating scaffolds that can by used by any build system or… [more](https://www.npmjs.com/package/scaffold) | [homepage](https://github.com/jonschlinkert/scaffold) +* [templates](https://www.npmjs.com/package/templates): System for creating and managing template collections, and rendering templates with any node.js template engine.… [more](https://www.npmjs.com/package/templates) | [homepage](https://github.com/jonschlinkert/templates) +* [update](https://www.npmjs.com/package/update): Update the year in all files in a project using glob patterns. | [homepage](https://github.com/jonschlinkert/update) +* [verb](https://www.npmjs.com/package/verb): Documentation generator for GitHub projects. Verb is extremely powerful, easy to use, and is used… [more](https://www.npmjs.com/package/verb) | [homepage](https://github.com/verbose/verb) ## Authoring @@ -85,4 +144,4 @@ Released under the MIT license. *** -_This file was generated by [verb-cli](https://github.com/assemble/verb-cli) on October 24, 2015._ \ No newline at end of file +_This file was generated by [verb-cli](https://github.com/assemble/verb-cli) on November 02, 2015._ \ No newline at end of file diff --git a/examples.js b/examples.js index 4749480..8c5de7b 100644 --- a/examples.js +++ b/examples.js @@ -1,30 +1,59 @@ 'use strict'; +var path = require('path'); +var del = require('rimraf'); var through = require('through2'); var update = require('./'); -var del = require('del'); -var app = update() - .use(require('./pipeline')) +var app = update(); -app.plugin('a', require('./lib/pipeline/a')()); -app.plugin('b', require('./lib/pipeline/b')); -app.plugin('c', require('./lib/pipeline/c')()); +// app.config.get = function () { + +// }; +// console.log(app) + + +// app.config({ +// plugins: function (obj) { +// for (var key in obj) { +// var name = path.basename(key, path.extname(key)); +// var fn = require(path.resolve(key)); +// app.plugin(name, obj[key], fn); +// } +// } +// }); + + +// app.config('plugins', function (obj) { +// for (var key in obj) { +// var name = path.basename(key, path.extname(key)); +// var fn = require(path.resolve(key)); +// app.plugin(name, obj[key], fn); +// } +// }); + +// app.config.process(); + +// app.plugin('a', require('./lib/pipeline/a')()); +// app.plugin('b', require('./lib/pipeline/b')); +// app.plugin('c', require('./lib/pipeline/c')()); // app.plugin(/foo/, require('./lib/pipeline/d')()); -app.disable('plugin.c'); - -app.task('default', function (cb) { - app.src('*') - .on('error', console.log) - .pipe(through.obj(function (file, enc, next) { - if (file.isNull()) return next(); - next(null, file); - })) - .pipe(app.pipeline()) - .pipe(app.dest('actual')) - .on('finish', cb); -}); - -app.build('default', function(err) { - if (err) return console.log(err); -}); + +// app.on('error', function(err) { +// console.log('Error in plugin:', err.plugin, err.message) +// }); + +// app.task('default', function (cb) { +// app.src('LICENSE') +// .pipe(through.obj(function (file, enc, next) { +// if (file.isNull()) return next(); +// next(null, file); +// })) +// .pipe(app.pipeline()) +// .pipe(app.dest('actual')) +// .on('finish', cb); +// }); + +// app.build('default', function(err) { +// if (err) return console.log(err); +// }); diff --git a/package.json b/package.json index e27e197..ded2cf6 100644 --- a/package.json +++ b/package.json @@ -4,73 +4,76 @@ "version": "0.1.0", "homepage": "https://github.com/jonschlinkert/update-next", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", + "contributors": [ + "Jon Schlinkert (https://github.com/jonschlinkert)", + "Brian Woodward (https://github.com/doowb)" + ], "repository": "jonschlinkert/update", "bugs": { "url": "https://github.com/jonschlinkert/update-next/issues" }, "license": "MIT", "files": [ - "lib/", - "index.js" + "index.js", + "bin", + "lib" ], "main": "index.js", "engines": { "node": ">=0.10.0" }, + "scripts": { + "test": "mocha" + }, "preferGlobal": true, "bin": { "update": "bin/update.js" }, - "scripts": { - "test": "mocha" - }, "dependencies": { "ansi-cyan": "^0.1.1", + "ansi-gray": "^0.1.1", "ansi-green": "^0.1.1", "ansi-red": "^0.1.1", "ansi-yellow": "^0.1.1", "assemble-ask": "^0.1.4", - "assemble-core": "^0.1.6", + "assemble-core": "^0.1.8", "assemble-loader": "^0.2.4", - "async": "^1.4.2", + "async": "^1.5.0", + "base-cli": "^0.2.1", + "base-config": "^0.2.0", + "base-pipeline": "^0.1.2", + "base-store": "^0.1.2", "component-emitter": "^1.2.0", "composer-runtimes": "^0.5.1", "cwd": "^0.8.4", - "data-store": "^0.11.1", "delete": "^0.2.1", - "engine": "^0.1.8", "expand-args": "^0.2.1", "expand-object": "^0.3.8", "export-files": "^2.1.0", "extend-shallow": "^2.0.1", "for-own": "^0.1.3", - "get-value": "^1.2.1", + "get-value": "^2.0.0", "global-modules": "^0.2.0", "lazy-cache": "^0.2.4", "load-pkg": "^2.0.1", "map-config": "^0.2.1", - "matched": "^0.3.2", "micromatch": "jonschlinkert/micromatch#2.3.0", "minimist": "^1.2.0", "object.omit": "^2.0.0", "object.pick": "^1.1.1", - "parse-author": "^0.2.0", - "parse-github-url": "^0.2.1", "parser-front-matter": "^1.3.0", - "pretty-time": "^0.2.0", "project-name": "^0.2.0", "question-cache": "^0.3.3", "resolve-dir": "^0.1.0", - "set-value": "^0.2.0", - "stream-combiner": "^0.2.2", + "set-value": "^0.3.0", + "sort-object": "^3.0.0", "success-symbol": "^0.1.0", "union-value": "^0.1.1", "update-copyright": "^0.1.0", - "write": "^0.2.1", - "write-json": "^0.2.1" + "write": "^0.2.1" }, "devDependencies": { - "base-methods": "^0.2.14", + "base-methods": "^0.4.0", "buffer-equal": "0.0.1", "consolidate": "^0.13.1", "coveralls": "^2.11.4", @@ -80,33 +83,39 @@ "event-stream": "^3.3.2", "graceful-fs": "^4.1.2", "gulp": "^3.9.0", - "gulp-istanbul": "^0.10.0", - "gulp-jshint": "^1.11.2", + "gulp-istanbul": "^0.10.2", + "gulp-jshint": "^1.12.0", "gulp-mocha": "^2.1.3", "is-buffer": "^1.1.0", "istanbul": "^0.4.0", "jshint-stylish": "^2.0.1", "kind-of": "^2.0.1", "look-up": "^0.8.1", - "mocha": "*", + "mocha": "^2.3.3", "resolve-glob": "^0.1.3", "rimraf": "^2.4.3", - "should": "*", - "sinon": "^1.17.0", + "should": "^7.1.1", + "sinon": "^1.17.2", "swig": "^1.4.2", "through2": "^2.0.0", - "vinyl": "^0.5.3" + "vinyl": "^1.1.0" }, - "keywords": [], + "keywords": [ + "next", + "update" + ], "verb": { "related": { + "description": "", "list": [ "composer", "generate", "boilerplate", "scaffold", "templates", - "verb" + "verb", + "assemble", + "update" ] }, "reflinks": [ @@ -118,23 +127,17 @@ ] }, "update": { + "cwd": "lib/pipeline", "helpers": { "related": "@/helper-related" }, "plugins": { - "@/foo": {} - } - }, - "contributors": [ - { - "name": "Jon Schlinkert", - "email": "github@sellside.com", - "url": "https://github.com/jonschlinkert" + "./lib/pipeline/a": {}, + "./lib/pipeline/b": {}, + "./lib/pipeline/c": {} }, - { - "name": "Brian Woodward", - "email": "brian.woodward@gmail.com", - "url": "https://github.com/doowb" + "set": { + "aaa": "yyy" } - ] + } } diff --git a/pipeline.js b/pipeline.js deleted file mode 100644 index 42be1e0..0000000 --- a/pipeline.js +++ /dev/null @@ -1,79 +0,0 @@ - -var path = require('path'); -var pipeline = require('stream-combiner'); -var through = require('through2'); -var typeOf = require('kind-of'); -var utils = require('./lib/utils'); - -module.exports = function(app) { - this.fns = this.fns || {}; - this.define('stack', []); - - /** - * Register a plugin by `name` - * - * @param {String} `name` - * @param {Function} `fn` - * @api public - */ - - this.define('plugin', function(name, fn) { - if (!typeof name === 'string') { - throw new TypeError('expected plugin name to be a string'); - } - if (arguments.length === 1) { - return this.fns[name]; - } - this.stack.push(fn); - this.fns[name] = fn; - return this; - }); - - /** - * Create a plugin pipeline from an array of fns. - * - * @param {Array} `fns` Each plugin is a function that returns a stream, - * or the name of a registered plugin. - * @param {Object} `options` - * @return {Stream} - * @api public - */ - - this.define('pipeline', function(plugins, options) { - if (isStream(plugins)) return plugins; - - if (!Array.isArray(plugins)) { - options = typeOf(plugins) === 'object' ? plugins : {}; - plugins = this.stack; - } - - var len = plugins.length, i = -1; - var fns = []; - - while (++i < len) { - var plugin = normalize(app, plugins[i], options); - if (!plugin) continue; - fns.push(plugin); - } - return pipeline.apply(pipeline, fns); - }); -}; - -function normalize(app, val, options) { - if (typeof val === 'string' && app.fns.hasOwnProperty(val)) { - if (app.isFalse('plugin.' + val)) return null; - return normalize(app, app.fns[val]); - } - if (typeof val === 'function') { - return val.call(app, app.opts(options)); - } - if (isStream(val)) { - val.on('error', app.emit.bind(app, 'error')); - return val; - } -} - -function isStream(val) { - return val && typeof val === 'object' - && typeof val.pipe === 'function'; -} From e9bd3d182abc7d134c419af83babee6f5e6276a6 Mon Sep 17 00:00:00 2001 From: Brian Woodward Date: Tue, 3 Nov 2015 17:53:59 -0500 Subject: [PATCH 109/274] cleaning up list command --- lib/multi.js | 11 +++++++++-- lib/tasks/list.js | 4 ++-- lib/utils.js | 9 +++++++-- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/multi.js b/lib/multi.js index d27d3cc..3eb4855 100644 --- a/lib/multi.js +++ b/lib/multi.js @@ -14,7 +14,7 @@ module.exports = function(namespace, config) { } this.options = options || {}; - this.commands = ['set', 'get', 'del', 'store', 'init', 'option', 'data']; + this.commands = ['set', 'get', 'del', 'store', 'init', 'option', 'data', 'list']; this.base = new Update() .on('error', console.error) @@ -201,11 +201,17 @@ module.exports = function(namespace, config) { Multi.prototype.list = function(cb) { var questions = utils.questions(this.base.options); + var choices = utils.list(this.base.updaters); + if (!choices.length) { + console.log(utils.cyan(' No updater tasks found.')); + return cb(null, {updaters: {}}); + } + var question = { updaters: { message: 'pick an updater to run', type: 'checkbox', - choices: utils.list(this.base.updaters) + choices: choices } }; @@ -216,6 +222,7 @@ module.exports = function(namespace, config) { }; answers.updaters.forEach(function (answer) { var segs = answer.split(':'); + if (segs.length === 1) return; utils.union(args.updaters, segs[0], (segs[1] || 'default').split(',')); }); return cb(null, args); diff --git a/lib/tasks/list.js b/lib/tasks/list.js index 6943d13..9995608 100644 --- a/lib/tasks/list.js +++ b/lib/tasks/list.js @@ -4,9 +4,9 @@ var utils = require('../utils'); module.exports = function(app, base, env) { return function (cb) { - base.list(function (err, args) { + env.list(function (err, args) { if (err) return cb(err); - base.run(args, cb); + env.run(args, cb); }); }; }; diff --git a/lib/utils.js b/lib/utils.js index 0b9fb51..63c2fb7 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -227,10 +227,15 @@ utils.list = function(updaters) { var list = []; for (var key in updaters) { var updater = updaters[key]; + if (!Object.keys(updater.tasks).length) { + continue; + } + + var name = updater.option('name'); var item = { - name: updater.fullname + ' (default)', + name: name + (updater.tasks['default'] ? ' (default)' : ''), value: key, - short: updater.name + ':default' + short: name + (updater.tasks['default'] ? ':default' : '') }; list.push(item); for (var task in updater.tasks) { From 184ef73979ecc1ab0b3b0e619b14ff0eca39bf05 Mon Sep 17 00:00:00 2001 From: Brian Woodward Date: Tue, 3 Nov 2015 17:54:59 -0500 Subject: [PATCH 110/274] moving dev deps to deps --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ded2cf6..e63d69e 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "composer-runtimes": "^0.5.1", "cwd": "^0.8.4", "delete": "^0.2.1", + "engine-base": "^0.1.2", "expand-args": "^0.2.1", "expand-object": "^0.3.8", "export-files": "^2.1.0", @@ -65,6 +66,7 @@ "project-name": "^0.2.0", "question-cache": "^0.3.3", "resolve-dir": "^0.1.0", + "rimraf": "^2.4.3", "set-value": "^0.3.0", "sort-object": "^3.0.0", "success-symbol": "^0.1.0", @@ -78,7 +80,6 @@ "consolidate": "^0.13.1", "coveralls": "^2.11.4", "define-property": "^0.2.5", - "engine-base": "^0.1.2", "engine-handlebars": "^0.8.0", "event-stream": "^3.3.2", "graceful-fs": "^4.1.2", @@ -93,7 +94,6 @@ "look-up": "^0.8.1", "mocha": "^2.3.3", "resolve-glob": "^0.1.3", - "rimraf": "^2.4.3", "should": "^7.1.1", "sinon": "^1.17.2", "swig": "^1.4.2", From ebba38f7abf17a67cb3ff31acfe6bef39fb68468 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 3 Nov 2015 18:52:53 -0500 Subject: [PATCH 111/274] adds eslint --- .eslintrc | 125 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 .eslintrc diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..7b5d047 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,125 @@ +{ + "ecmaFeatures": { + "modules": true, + "experimentalObjectRestSpread": true + }, + + "env": { + "browser": false, + "es6": true, + "node": true, + "mocha": true + }, + + "globals": { + "document": false, + "navigator": false, + "window": false + }, + + "rules": { + "accessor-pairs": 2, + "arrow-spacing": [2, { "before": true, "after": true }], + "block-spacing": [2, "always"], + "brace-style": [2, "1tbs", { "allowSingleLine": true }], + "comma-dangle": [2, "never"], + "comma-spacing": [2, { "before": false, "after": true }], + "comma-style": [2, "last"], + "constructor-super": 2, + "curly": [2, "multi-line"], + "dot-location": [2, "property"], + "eol-last": 2, + "eqeqeq": [2, "allow-null"], + "generator-star-spacing": [2, { "before": true, "after": true }], + "handle-callback-err": [2, "^(err|error)$" ], + "indent": [2, 2, { "SwitchCase": 1 }], + "key-spacing": [2, { "beforeColon": false, "afterColon": true }], + "new-cap": [2, { "newIsCap": true, "capIsNew": false }], + "new-parens": 2, + "no-array-constructor": 2, + "no-caller": 2, + "no-class-assign": 2, + "no-cond-assign": 2, + "no-const-assign": 2, + "no-control-regex": 2, + "no-debugger": 2, + "no-delete-var": 2, + "no-dupe-args": 2, + "no-dupe-class-members": 2, + "no-dupe-keys": 2, + "no-duplicate-case": 2, + "no-empty-character-class": 2, + "no-empty-label": 2, + "no-eval": 2, + "no-ex-assign": 2, + "no-extend-native": 2, + "no-extra-bind": 2, + "no-extra-boolean-cast": 2, + "no-extra-parens": [2, "functions"], + "no-fallthrough": 2, + "no-floating-decimal": 2, + "no-func-assign": 2, + "no-implied-eval": 2, + "no-inner-declarations": [2, "functions"], + "no-invalid-regexp": 2, + "no-irregular-whitespace": 2, + "no-iterator": 2, + "no-label-var": 2, + "no-labels": 2, + "no-lone-blocks": 2, + "no-mixed-spaces-and-tabs": 2, + "no-multi-spaces": 2, + "no-multi-str": 2, + "no-multiple-empty-lines": [2, { "max": 1 }], + "no-native-reassign": 2, + "no-negated-in-lhs": 2, + "no-new": 2, + "no-new-func": 2, + "no-new-object": 2, + "no-new-require": 2, + "no-new-wrappers": 2, + "no-obj-calls": 2, + "no-octal": 2, + "no-octal-escape": 2, + "no-proto": 0, + "no-redeclare": 2, + "no-regex-spaces": 2, + "no-return-assign": 2, + "no-self-compare": 2, + "no-sequences": 2, + "no-shadow-restricted-names": 2, + "no-spaced-func": 2, + "no-sparse-arrays": 2, + "no-this-before-super": 2, + "no-throw-literal": 2, + "no-trailing-spaces": 0, + "no-undef": 2, + "no-undef-init": 2, + "no-unexpected-multiline": 2, + "no-unneeded-ternary": [2, { "defaultAssignment": false }], + "no-unreachable": 2, + "no-unused-vars": [2, { "vars": "all", "args": "none" }], + "no-useless-call": 0, + "no-with": 2, + "one-var": [0, { "initialized": "never" }], + "operator-linebreak": [0, "after", { "overrides": { "?": "before", ":": "before" } }], + "padded-blocks": [0, "never"], + "quotes": [2, "single", "avoid-escape"], + "radix": 2, + "semi": [2, "always"], + "semi-spacing": [2, { "before": false, "after": true }], + "space-after-keywords": [2, "always"], + "space-before-blocks": [2, "always"], + "space-before-function-paren": [2, "never"], + "space-before-keywords": [2, "always"], + "space-in-parens": [2, "never"], + "space-infix-ops": 2, + "space-return-throw-case": 2, + "space-unary-ops": [2, { "words": true, "nonwords": false }], + "spaced-comment": [0, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!", ","] }], + "use-isnan": 2, + "valid-typeof": 2, + "wrap-iife": [2, "any"], + "yoda": [2, "never"] + } +} From 7b3b0d85a92e405923643421385ae1098f68f968 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 3 Nov 2015 18:56:34 -0500 Subject: [PATCH 112/274] metadata --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index e63d69e..23bf4cd 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,9 @@ "vinyl": "^1.1.0" }, "keywords": [ + "lint", "next", + "repo", "update" ], "verb": { @@ -127,6 +129,7 @@ ] }, "update": { + "note": "this is just pseudo data for tests. I'll update with real stuff soon.", "cwd": "lib/pipeline", "helpers": { "related": "@/helper-related" From 641b2f2420af7c05ef663137bab893de4b931e8d Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 3 Nov 2015 18:56:48 -0500 Subject: [PATCH 113/274] argv/options handling --- index.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/index.js b/index.js index 5676e73..9e0d6a8 100644 --- a/index.js +++ b/index.js @@ -66,16 +66,17 @@ Update.prototype.initUpdate = function(base) { this.handler('postWrite'); // parse command line arguments - var argv = minimist(process.argv.slice(2)); - if (process.argv.length > 3) { - argv = expand(argv); - } + var argv = expand(minimist(process.argv.slice(2))); // expose `argv` on the instance - this.set('argv', this.argv || argv); + this.mixin('argv', function(prop) { + var args = [].slice.call(arguments); + args.unshift(argv); + return utils.get.apply(null, args); + }); - // expose `package.json` on `cache.data` - this.data(utils.pkg()); + // load the package.json for the updater + this.data(utils.pkg(this.options.path)); config(this); this.use(utils.runtimes()) @@ -89,7 +90,10 @@ Update.prototype.initUpdate = function(base) { .use(utils.defaults()) .use(utils.opts()) - this.config.process(this.cache.data); + var data = utils.get(this.cache.data, 'update'); + data = utils.extend({}, data, argv); + + this.config.process(data); this.engine(['md', 'tmpl'], require('engine-base')); this.onLoad(/\.(md|tmpl)$/, function (view, next) { From 026ec6d774f59f76abe952945c721ce9397ca1e9 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 3 Nov 2015 18:56:59 -0500 Subject: [PATCH 114/274] update travis opts --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index d7a0715..ed8aaac 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,3 +3,7 @@ node_js: - "stable" - "0.12" - "0.10" +matrix: + fast_finish: true + allow_failures: + - node_js: "0.10" From 78bb4b3421ae955dc616c04554cfb326dea9ad50 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 3 Nov 2015 18:57:31 -0500 Subject: [PATCH 115/274] use getFile --- lib/multi.js | 16 ++++++++++++---- lib/utils.js | 23 ++++++++++++++--------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/lib/multi.js b/lib/multi.js index 3eb4855..255d13c 100644 --- a/lib/multi.js +++ b/lib/multi.js @@ -131,7 +131,9 @@ module.exports = function(namespace, config) { } var Ctor = options.Update || Update; - var app = new Ctor(this.base.options) + var base = this.base; + + var app = new Ctor(base.options) .option('name', name) .option('fullname', options.fullname || name) .option('path', options.path || ''); @@ -143,13 +145,19 @@ module.exports = function(namespace, config) { } }); - app.define('getTemplate', function(name) { - var view = app.templates.getView.apply(app.templates, arguments); + app.define('getFile', function(name) { + var view = base.files.getView.apply(base.files, arguments); + if (!view) { + view = app.templates.getView.apply(app.templates, arguments); + } view.basename = view.basename.replace(/^_/, '.'); return view; }); - updater(app, this.base, this); + base.define('getFile', app.getFile); + base.files.getFile = base.files.getView.bind(base.files); + + updater(app, base, this); this.base.updater(name, app); this.emit('register', name, app); diff --git a/lib/utils.js b/lib/utils.js index 63c2fb7..da97e19 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -73,7 +73,7 @@ utils.commands = function(argv) { argv._ = argv._ || []; var commands = {}; - argv._.forEach(function (key) { + argv._.forEach(function(key) { commands[key] = true; }); return commands; @@ -282,7 +282,7 @@ utils.tryParse = function(str) { }; utils.register = function(pattern, base, update, options) { - utils.matchFiles(pattern, options).forEach(function (fp) { + utils.matchFiles(pattern, options).forEach(function(fp) { var name = utils.project(fp); var mod = utils.resolveModule(fp) || update; var app = mod(base.options) @@ -296,17 +296,23 @@ utils.register = function(pattern, base, update, options) { utils.opts = function(key) { key = key || 'opts'; - return function () { - this.define(key, function () { - var config = this.defaults.apply(this, arguments); + return function(app) { + var name = this.options.name || 'base'; + + console.log(this.options) - return function (key, opts) { + this.define(key, function() { + var config = this.defaults.apply(this, arguments); + return function(key, opts) { var args = [].concat.apply([], [].slice.call(arguments)); var prop = typeof key === 'string' ? args.shift() : null; + var val; if (prop && !args.length) { - return utils.get(config, prop); + val = utils.get(config, prop); + if (val) return val; } + var options = utils.extend.apply(utils.extend, [config].concat(args)); return prop ? utils.get(options, prop) : options; }; @@ -316,8 +322,7 @@ utils.opts = function(key) { utils.defaults = function(key) { key = key || 'defaults'; - - return function() { + return function(app) { this.define(key, function() { var args = [].concat.apply([], [].slice.call(arguments)); args.unshift({}, this.options); From 535920fc2524d62b81280c1194e927351350c959 Mon Sep 17 00:00:00 2001 From: Brian Woodward Date: Tue, 3 Nov 2015 19:26:19 -0500 Subject: [PATCH 116/274] adding stream-exhaust to ensure all files are pulled through the stream --- lib/tasks/dest.js | 23 +++++++++++------------ package.json | 1 + 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/tasks/dest.js b/lib/tasks/dest.js index de93e05..29e71ef 100644 --- a/lib/tasks/dest.js +++ b/lib/tasks/dest.js @@ -3,25 +3,24 @@ module.exports = function(app, base, env) { var through = require('through2'); + var exhaust = require('stream-exhaust'); var plugins = base.get('argv.plugins'); + function handle(stage) { + return through.obj(function (file, enc, next) { + if (file.isNull()) return next(); + app.handle(stage, file, next); + }); + } + return function (cb) { app.toStream('files') .on('error', cb) - .pipe(through.obj(function (file, enc, next) { - if (file.isNull()) return next(); - app.handle('onStream', file, next); - })) + .pipe(handle('onStream')) .pipe(app.pipeline(plugins)) - .pipe(through.obj(function (file, enc, next) { - if (file.isNull()) return next(); - app.handle('preWrite', file, next); - })) + .pipe(handle('preWrite')) .pipe(app.dest('.')) - .pipe(through.obj(function (file, enc, next) { - if (file.isNull()) return next(); - app.handle('postWrite', file, next); - })) + .pipe(exhaust(handle('postWrite'))) .on('error', cb) .on('end', cb); }; diff --git a/package.json b/package.json index e63d69e..7f58dbd 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "rimraf": "^2.4.3", "set-value": "^0.3.0", "sort-object": "^3.0.0", + "stream-exhaust": "^1.0.1", "success-symbol": "^0.1.0", "union-value": "^0.1.1", "update-copyright": "^0.1.0", From 6b2fe0220ae262bc201b70d9f3da17c134374df8 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Thu, 5 Nov 2015 10:32:23 -0500 Subject: [PATCH 117/274] minor edits, remove unused code --- index.js | 1 - lib/utils.js | 8 +++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index 9e0d6a8..2c46bbc 100644 --- a/index.js +++ b/index.js @@ -97,7 +97,6 @@ Update.prototype.initUpdate = function(base) { this.engine(['md', 'tmpl'], require('engine-base')); this.onLoad(/\.(md|tmpl)$/, function (view, next) { - view.content = view.contents.toString(); utils.matter.parse(view, next); }); }; diff --git a/lib/utils.js b/lib/utils.js index da97e19..2bedb76 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -231,11 +231,12 @@ utils.list = function(updaters) { continue; } + var hasDefault = updater.tasks['default']; var name = updater.option('name'); var item = { - name: name + (updater.tasks['default'] ? ' (default)' : ''), + name: name + (hasDefault ? ' (default)' : ''), value: key, - short: name + (updater.tasks['default'] ? ':default' : '') + short: name + (hasDefault ? ':default' : '') }; list.push(item); for (var task in updater.tasks) { @@ -298,9 +299,6 @@ utils.opts = function(key) { key = key || 'opts'; return function(app) { var name = this.options.name || 'base'; - - console.log(this.options) - this.define(key, function() { var config = this.defaults.apply(this, arguments); return function(key, opts) { From 3530950b2d3b541158a656c61aa58f4c4a4bf577 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 6 Nov 2015 02:27:32 -0500 Subject: [PATCH 118/274] move `pipeline` files to test/fixtures --- {lib => test/fixtures}/pipeline/a.js | 0 {lib => test/fixtures}/pipeline/b.js | 0 {lib => test/fixtures}/pipeline/c.js | 0 {lib => test/fixtures}/pipeline/d.js | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename {lib => test/fixtures}/pipeline/a.js (100%) rename {lib => test/fixtures}/pipeline/b.js (100%) rename {lib => test/fixtures}/pipeline/c.js (100%) rename {lib => test/fixtures}/pipeline/d.js (100%) diff --git a/lib/pipeline/a.js b/test/fixtures/pipeline/a.js similarity index 100% rename from lib/pipeline/a.js rename to test/fixtures/pipeline/a.js diff --git a/lib/pipeline/b.js b/test/fixtures/pipeline/b.js similarity index 100% rename from lib/pipeline/b.js rename to test/fixtures/pipeline/b.js diff --git a/lib/pipeline/c.js b/test/fixtures/pipeline/c.js similarity index 100% rename from lib/pipeline/c.js rename to test/fixtures/pipeline/c.js diff --git a/lib/pipeline/d.js b/test/fixtures/pipeline/d.js similarity index 100% rename from lib/pipeline/d.js rename to test/fixtures/pipeline/d.js From 928792f63e096da09d01dcdc08e8f3b684477497 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 6 Nov 2015 03:11:39 -0500 Subject: [PATCH 119/274] update deps --- package.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 486997d..5ee2300 100644 --- a/package.json +++ b/package.json @@ -36,20 +36,20 @@ "ansi-red": "^0.1.1", "ansi-yellow": "^0.1.1", "assemble-ask": "^0.1.4", - "assemble-core": "^0.1.8", + "assemble-core": "^0.1.9", "assemble-loader": "^0.2.4", "async": "^1.5.0", "base-cli": "^0.2.1", "base-config": "^0.2.0", - "base-pipeline": "^0.1.2", + "base-pipeline": "^0.1.4", "base-store": "^0.1.2", "component-emitter": "^1.2.0", "composer-runtimes": "^0.5.1", "cwd": "^0.8.4", "delete": "^0.2.1", "engine-base": "^0.1.2", - "expand-args": "^0.2.1", - "expand-object": "^0.3.8", + "expand-args": "^0.3.0", + "expand-object": "^0.4.0", "export-files": "^2.1.0", "extend-shallow": "^2.0.1", "for-own": "^0.1.3", @@ -63,7 +63,7 @@ "object.omit": "^2.0.0", "object.pick": "^1.1.1", "parser-front-matter": "^1.3.0", - "project-name": "^0.2.0", + "project-name": "^0.2.1", "question-cache": "^0.3.3", "resolve-dir": "^0.1.0", "rimraf": "^2.4.3", @@ -71,7 +71,7 @@ "sort-object": "^3.0.0", "stream-exhaust": "^1.0.1", "success-symbol": "^0.1.0", - "union-value": "^0.1.1", + "union-value": "^0.2.0", "update-copyright": "^0.1.0", "write": "^0.2.1" }, @@ -93,9 +93,9 @@ "jshint-stylish": "^2.0.1", "kind-of": "^2.0.1", "look-up": "^0.8.1", - "mocha": "^2.3.3", + "mocha": "*", "resolve-glob": "^0.1.3", - "should": "^7.1.1", + "should": "*", "sinon": "^1.17.2", "swig": "^1.4.2", "through2": "^2.0.0", @@ -109,7 +109,6 @@ ], "verb": { "related": { - "description": "", "list": [ "composer", "generate", @@ -119,7 +118,8 @@ "verb", "assemble", "update" - ] + ], + "description": "" }, "reflinks": [ "scaffold", From f8246a7d34a03b3d77afb70b8b77cd16808e834f Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sun, 15 Nov 2015 22:00:57 -0500 Subject: [PATCH 120/274] adds stream-exhaust --- .travis.yml | 1 + bin/update.js | 9 ++- gulpfile.js | 24 +++---- index.js | 10 +-- lib/multi.js | 159 +++++++++++++++++++++++++++------------------- lib/tasks/dest.js | 7 +- lib/utils.js | 2 + package.json | 5 +- 8 files changed, 125 insertions(+), 92 deletions(-) diff --git a/.travis.yml b/.travis.yml index ed8aaac..d6e658e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,4 @@ +sudo: false language: node_js node_js: - "stable" diff --git a/bin/update.js b/bin/update.js index f9464c8..9ee6725 100755 --- a/bin/update.js +++ b/bin/update.js @@ -1,6 +1,8 @@ #!/usr/bin/env node var path = require('path'); +var stamp = require('time-stamp'); +var gray = require('ansi-gray'); var multi = require('../lib/multi')(); var utils = require('../lib/utils'); var argv = require('minimist')(process.argv.slice(2), { @@ -30,5 +32,10 @@ cli.base.task('run', function (cb) { cli.base.build(task, function (err) { if (err) console.error(err); - utils.ok('Finished.'); + timestamp('finished'); }); + +function timestamp(msg) { + var time = ' ' + gray(stamp('HH:mm:ss.ms', new Date())); + return console.log(time, msg, utils.green(utils.successSymbol)); +} diff --git a/gulpfile.js b/gulpfile.js index 82538fa..7d0c54a 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,12 +1,11 @@ 'use strict'; var gulp = require('gulp'); -var stylish = require('jshint-stylish'); -var istanbul = require('gulp-istanbul'); -var jshint = require('gulp-jshint'); var mocha = require('gulp-mocha'); +var istanbul = require('gulp-istanbul'); +var eslint = require('gulp-eslint'); -var lint = ['index.js', 'lib/**/*.js']; +var lint = ['index.js', 'lib/*.js', 'test/*.js']; gulp.task('coverage', function () { return gulp.src(lint) @@ -14,20 +13,15 @@ gulp.task('coverage', function () { .pipe(istanbul.hookRequire()); }); -gulp.task('test', ['coverage'], function () { +gulp.task('mocha', ['coverage'], function () { return gulp.src('test/*.js') .pipe(mocha({reporter: 'spec'})) - .pipe(istanbul.writeReports()) - .pipe(istanbul.writeReports({ - reporters: [ 'text' ], - reportOpts: {dir: 'coverage', file: 'summary.txt'} - })); + .pipe(istanbul.writeReports()); }); -gulp.task('lint', function () { - return gulp.src(lint.concat('test/*.js')) - .pipe(jshint()) - .pipe(jshint.reporter(stylish)); +gulp.task('eslint', function () { + return gulp.src(lint) + .pipe(eslint()) }); -gulp.task('default', ['test', 'lint']); +gulp.task('default', ['mocha', 'eslint']); diff --git a/index.js b/index.js index 2c46bbc..a014df2 100644 --- a/index.js +++ b/index.js @@ -12,15 +12,15 @@ */ var path = require('path'); -var pipeline = require('base-pipeline'); var minimist = require('minimist'); +var expand = require('expand-args'); +var cli = require('base-cli'); +var store = require('base-store'); +var pipeline = require('base-pipeline'); var loader = require('assemble-loader'); var Core = require('assemble-core'); var ask = require('assemble-ask'); -var store = require('base-store'); -var cli = require('base-cli'); -var expand = require('expand-args'); var config = require('./lib/config'); var locals = require('./lib/locals'); var utils = require('./lib/utils'); @@ -43,6 +43,7 @@ function Update(options) { } Core.call(this, options); this.set('name', 'update'); + this.isUpdate = true; this.initUpdate(this); } @@ -57,7 +58,6 @@ Core.extend(Update); */ Update.prototype.initUpdate = function(base) { - this.define('isUpdate', true); this.set('updaters', {}); // custom middleware handlers diff --git a/lib/multi.js b/lib/multi.js index 255d13c..c9f3369 100644 --- a/lib/multi.js +++ b/lib/multi.js @@ -1,7 +1,9 @@ 'use strict'; var path = require('path'); -var Emitter = require('component-emitter'); +var Base = require('base-methods'); +var option = require('base-options'); +var store = require('base-store'); var fns = require('./middleware'); var tasks = require('./tasks'); var utils = require('./utils'); @@ -13,6 +15,10 @@ module.exports = function(namespace, config) { return new Multi(argv, options); } + Base.call(this); + this.use(option()); + this.use(store()); + this.options = options || {}; this.commands = ['set', 'get', 'del', 'store', 'init', 'option', 'data', 'list']; @@ -20,6 +26,10 @@ module.exports = function(namespace, config) { .on('error', console.error) .set('argv', argv); + // console.log(this.base.cli.keys) + + this.base.use(utils.runtimes()); + // register middleware for (var fn in fns) { fns[fn](this.base, this.base, this); @@ -29,15 +39,25 @@ module.exports = function(namespace, config) { for (var key in tasks) { this.base.task(key, tasks[key](this.base, this.base, this)); } + + this.on('task', function() { + console.log(arguments); + }); + this._listen(); } - Emitter(Multi.prototype); + Base.extend(Multi); Multi.prototype.updater = function(name) { return this.base.updater(name); }; + Multi.prototype.build = function() { + this.base.build.apply(this.base, arguments); + return this; + }; + Multi.prototype._listen = function() { if (this.base.disabled('verbose')) return; var store = ['store.set', 'store.has', 'store.get', 'store.del']; @@ -63,67 +83,6 @@ module.exports = function(namespace, config) { } }; - Multi.prototype.argv = function(argv, commands, updaters) { - var res = {}; - res.updaters = updaters; - res.argv = argv; - res.commands = []; - res.updaters = {}; - - var files = argv.files ? utils.pick(argv, 'files') : null; - res.flags = utils.expandArgs(utils.omit(argv, ['_', 'files'])); - if (files) res.flags.files = files; - res.flagskeys = Object.keys(res.flags); - - var arr = argv._; - var len = arr.length, i = -1; - - while (++i < len) { - var ele = arr[i]; - - if (/\W/.test(ele)) { - var obj = utils.expand(ele); - utils.forOwn(obj, function (val, key) { - utils.union(res.updaters, key, val); - }); - continue; - } - - if (utils.contains(commands, ele)) { - res.commands.push(ele); - continue; - } - - if (ele in updaters) { - utils.union(res.updaters, ele, 'default'); - - } else if (ele !== 'base') { - utils.union(res.updaters, 'base', ele); - } - } - return res; - }; - - Multi.prototype.registerEach = function(pattern, options) { - utils.matchFiles(pattern, options).forEach(function (fp) { - var filepath = path.resolve(fp, 'updatefile.js'); - var updater = require(filepath); - - // get the full project name ('updater-foo') - var fullname = utils.project(fp); - // get the updater name ('foo') - var name = utils.renameFn(fullname, options); - var opts = {}; - // get the constructor to use (node_modules or our 'Update') - opts.Update = utils.resolveModule(fp); - opts.fullname = fullname; - opts.path = fp; - - this.register(name, opts, updater); - }.bind(this)); - return this; - }; - Multi.prototype.register = function(name, options, updater) { if (arguments.length === 2) { updater = options; @@ -157,13 +116,74 @@ module.exports = function(namespace, config) { base.define('getFile', app.getFile); base.files.getFile = base.files.getView.bind(base.files); - updater(app, base, this); + updater.call(app, app, base, this); this.base.updater(name, app); this.emit('register', name, app); return this; }; + Multi.prototype.registerEach = function(pattern, options) { + utils.matchFiles(pattern, options).forEach(function (fp) { + var filepath = path.resolve(fp, 'updatefile.js'); + var updater = require(filepath); + + // get the full project name ('updater-foo') + var fullname = utils.project(fp); + // get the updater name ('foo') + var name = utils.renameFn(fullname, options); + var opts = {}; + // get the constructor to use (node_modules or our 'Update') + opts.Update = utils.resolveModule(fp); + opts.fullname = fullname; + opts.path = fp; + + this.register(name, opts, updater); + }.bind(this)); + + this.base.emit('registered'); + return this; + }; + + Multi.prototype.argv = function(argv, commands, fn) { + var args = {}; + args.argv = argv; + args.commands = []; + args.updaters = {}; + + args.flags = utils.expandArgs(utils.omit(argv, ['_', 'files'])); + args.flagskeys = Object.keys(args.flags); + + var files = argv.files ? utils.pick(argv, 'files') : null; + if (files) args.flags.files = files; + + var arr = argv._; + var len = arr.length, i = -1; + + while (++i < len) { + var key = arr[i]; + + if (/\W/.test(key)) { + var obj = utils.expand(key); + + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + var val = obj[key]; + utils.union(args, 'updaters.' + key, val); + } + } + continue; + } + + if (utils.contains(commands, key)) { + args.commands.push(key); + continue; + } + fn(key, args); + } + return args; + }; + Multi.prototype.run = function(args, cb) { if (typeof args === 'function') { cb = args; @@ -173,7 +193,16 @@ module.exports = function(namespace, config) { if (!args) { var argv = this.base.get('argv'); var commands = this.options.commands || this.commands; - args = this.argv(argv, commands, this.base.updaters); + args = this.argv(argv, commands, function(key, args) { + var updaters = this.base.updaters; + if (key in updaters) { + utils.union(args, 'updaters.' + key, 'default'); + + } else if (key !== 'base') { + utils.union(args, 'updaters.base', key); + } + return args; + }.bind(this)); } if (args.commands && args.commands.length > 1) { diff --git a/lib/tasks/dest.js b/lib/tasks/dest.js index 29e71ef..bd4c216 100644 --- a/lib/tasks/dest.js +++ b/lib/tasks/dest.js @@ -1,13 +1,12 @@ 'use strict'; +var utils = require('../utils'); module.exports = function(app, base, env) { - var through = require('through2'); - var exhaust = require('stream-exhaust'); var plugins = base.get('argv.plugins'); function handle(stage) { - return through.obj(function (file, enc, next) { + return utils.through.obj(function (file, enc, next) { if (file.isNull()) return next(); app.handle(stage, file, next); }); @@ -20,7 +19,7 @@ module.exports = function(app, base, env) { .pipe(app.pipeline(plugins)) .pipe(handle('preWrite')) .pipe(app.dest('.')) - .pipe(exhaust(handle('postWrite'))) + .pipe(utils.exhaust(handle('postWrite'))) .on('error', cb) .on('end', cb); }; diff --git a/lib/utils.js b/lib/utils.js index 2bedb76..7ff492b 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -26,12 +26,14 @@ require('map-config', 'config'); require('composer-runtimes', 'runtimes'); require('parser-front-matter', 'matter'); require('question-cache', 'questions'); +require('stream-exhaust', 'exhaust'); require('extend-shallow', 'extend'); require('resolve-dir', 'resolve'); require('project-name', 'project'); require('union-value', 'union'); require('set-value', 'set'); require('get-value', 'get'); +require('through2', 'through'); require('success-symbol'); require('ansi-yellow', 'yellow'); diff --git a/package.json b/package.json index 5ee2300..b45f067 100644 --- a/package.json +++ b/package.json @@ -10,13 +10,13 @@ ], "repository": "jonschlinkert/update", "bugs": { - "url": "https://github.com/jonschlinkert/update-next/issues" + "url": "https://github.com/jonschlinkert/update/issues" }, "license": "MIT", "files": [ "index.js", "bin", - "lib" + "lib/" ], "main": "index.js", "engines": { @@ -71,6 +71,7 @@ "sort-object": "^3.0.0", "stream-exhaust": "^1.0.1", "success-symbol": "^0.1.0", + "time-stamp": "^0.1.1", "union-value": "^0.2.0", "update-copyright": "^0.1.0", "write": "^0.2.1" From 9212fa13a2532c27ce5cc1dc9d7971c8d09a30db Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sun, 15 Nov 2015 23:24:39 -0500 Subject: [PATCH 121/274] organize runner files --- .verb.md | 2 +- bin/update.js | 18 +-- index.js | 8 +- lib/_updaters/copyright.js | 9 ++ lib/_updaters/license.js | 46 +++++++ lib/config.js | 2 +- lib/context.js | 74 ++++++++++ lib/locals.js | 4 +- lib/middleware/json.js | 2 +- lib/multi.js | 274 ------------------------------------- lib/runner/argv.js | 55 ++++++++ lib/runner/decorate.js | 37 +++++ lib/runner/env.js | 53 +++++++ lib/runner/list.js | 40 ++++++ lib/runner/listen.js | 33 +++++ lib/runner/run.js | 40 ++++++ lib/runner/runner.js | 113 +++++++++++++++ lib/runner/updater.js | 127 +++++++++++++++++ lib/tasks/default.js | 2 - lib/tasks/del.js | 2 +- lib/tasks/dest.js | 4 +- lib/tasks/files.js | 5 +- lib/tasks/lint.js | 4 +- lib/tasks/list.js | 6 +- lib/tasks/noop.js | 4 +- lib/tasks/rename.js | 6 +- lib/tasks/tree.js | 4 +- lib/utils.js | 7 +- package.json | 7 +- 29 files changed, 670 insertions(+), 318 deletions(-) create mode 100644 lib/_updaters/copyright.js create mode 100644 lib/_updaters/license.js create mode 100644 lib/context.js delete mode 100644 lib/multi.js create mode 100644 lib/runner/argv.js create mode 100644 lib/runner/decorate.js create mode 100644 lib/runner/env.js create mode 100644 lib/runner/list.js create mode 100644 lib/runner/listen.js create mode 100644 lib/runner/run.js create mode 100644 lib/runner/runner.js create mode 100644 lib/runner/updater.js diff --git a/.verb.md b/.verb.md index 601496c..9796e9a 100644 --- a/.verb.md +++ b/.verb.md @@ -1,4 +1,4 @@ -# {%= name %} {%= badge("fury") %} +# {%= name %} {%= badge("fury") %} {%= badge("travis") %} > {%= description %} diff --git a/bin/update.js b/bin/update.js index 9ee6725..7ed74dc 100755 --- a/bin/update.js +++ b/bin/update.js @@ -3,34 +3,36 @@ var path = require('path'); var stamp = require('time-stamp'); var gray = require('ansi-gray'); -var multi = require('../lib/multi')(); +var Runner = require('../lib/runner/runner')(); var utils = require('../lib/utils'); var argv = require('minimist')(process.argv.slice(2), { alias: {verbose: 'v'} }); var cmd = utils.commands(argv); -var cli = multi(argv); +var runner = new Runner(argv); +console.log(runner) var task = cmd.list ? ['list', 'default'] : 'default'; -cli.on('*', function (method, key, val) { +runner.on('*', function (method, key, val) { console.log(method + ':', key, val); }); + if (argv.verbose) { - cli.on('register', function(key) { + runner.on('register', function(key) { utils.ok(utils.gray('registered'), 'updater', utils.cyan(key)); }); } -cli.registerEach('update-*', {cwd: utils.gm}); +runner.registerEach('update-*', {cwd: utils.gm}); -cli.base.task('run', function (cb) { - cli.run(cb); +runner.base.task('run', function (cb) { + runner.run(cb); }); -cli.base.build(task, function (err) { +runner.base.build(task, function (err) { if (err) console.error(err); timestamp('finished'); }); diff --git a/index.js b/index.js index a014df2..e4ee75d 100644 --- a/index.js +++ b/index.js @@ -146,15 +146,19 @@ Update.prototype.extendFile = function(file, config, opts) { */ Update.prototype.updater = function(name, app) { - if (arguments.length === 1) { + if (arguments.length === 1 && typeof name === 'string') { return this.updaters[name]; } + app.use(utils.runtimes({ displayName: function(key) { return utils.cyan(name + ':' + key); } })); - return (this.updaters[name] = app); + + this.emit('updater', name, app); + this.updaters[name] = app; + return app; }; /** diff --git a/lib/_updaters/copyright.js b/lib/_updaters/copyright.js new file mode 100644 index 0000000..5768c9f --- /dev/null +++ b/lib/_updaters/copyright.js @@ -0,0 +1,9 @@ + +var fs = require('fs'); +var pkg = require('load-pkg')(); +var copyright = require('update-copyright'); + +var str = fs.readFileSync('LICENSE', 'utf8'); +var updated = copyright(str, pkg); + +console.log(updated); diff --git a/lib/_updaters/license.js b/lib/_updaters/license.js new file mode 100644 index 0000000..56ce211 --- /dev/null +++ b/lib/_updaters/license.js @@ -0,0 +1,46 @@ + +var fs = require('fs'); +var del = require('delete'); +var writeFile = require('write'); +var green = require('ansi-green'); +var success = require('success-symbol'); +var cwd = require('cwd'); + +var banner = 'The MIT License (MIT)\n\n'; + +function addBanner(str) { + if (!/^The MIT License \(MIT\)/i.test(str)) { + str = banner + str; + } + return str; +} + +function update(filepath, cb) { + var fp = cwd(filepath); + + fs.readFile(fp, 'utf8', function(err, str) { + if (err) return cb(err); + + + del(filepath, function(err) { + if (err) return cb(err); + + writeFile('LICENSE', str, function(err) { + if (err) return cb(err); + + return cb(null, 'updated'); + }); + }); + }); +} + +update('LICENSE', function(err, res) { + if (err) { + return console.error(err); + } + var msg = ' LICENSE is already up to date.'; + if (res === 'updated') { + msg = ' updated LICENSE'; + } + console.log(green(success), msg); +}); diff --git a/lib/config.js b/lib/config.js index 1a4eadd..c829c9b 100644 --- a/lib/config.js +++ b/lib/config.js @@ -1,7 +1,7 @@ 'use strict'; -module.exports = function (app) { +module.exports = function(app) { if (!app.isUpdate) return; var config = require('base-config'); diff --git a/lib/context.js b/lib/context.js new file mode 100644 index 0000000..f50064c --- /dev/null +++ b/lib/context.js @@ -0,0 +1,74 @@ +'use strict'; + +var utils = require('./utils'); + +/** + * Create an instance of `Settings`. + * + * @class `Settings` + * @api public + */ + +function Settings() { + this.settings = []; +} + +/** + * Add a setting to merge. + * + * ```js + * setting + * .addSettings({a: 'b', c: 'd'}) + * .addSettings({a: 'z'}) + * .merge(); + * + * //=> {a: 'z', c: 'd'} + * ``` + * + * @param {Object} `object` The object to merge into the setting + * @api public + */ + +Settings.prototype.addSettings = function(obj) { + if (Array.isArray(obj) || arguments.length > 1) { + var args = [].concat.apply([], arguments); + args.forEach(function(arg) { + this.settings.push(arg); + }.bind(this)); + } else { + this.settings.push(obj); + } + return this; +}; + +/** + * Calculate the setting, optionally passing a callback `fn` for sorting. + * _(Note that sorting must be done on levels, not on the setting names)_. + * + * ```js + * app.calculate(['a', 'b'], function(a, b) { + * return app.lvl[a] - app.lvl[a]; + * }); + * ``` + * + * @param {String|Array} `keys` Key, or array of keys for setting levels to include. + * @param {Function} `fn` Sort function for determining the order of merging. + * @api public + */ + +Settings.prototype.merge = function(obj) { + if (obj) this.addSettings.apply(this, arguments); + var setting = {}; + this.settings.forEach(function(ctx) { + utils.merge(setting, ctx); + }); + return setting; +}; + +/** + * Export `Settings` + * + * @type {Object} + */ + +module.exports = Settings; diff --git a/lib/locals.js b/lib/locals.js index 2ff5b47..4d0097b 100644 --- a/lib/locals.js +++ b/lib/locals.js @@ -4,10 +4,10 @@ var get = require('get-value'); var set = require('set-value'); var utils = require('./utils'); -module.exports = function (name) { +module.exports = function(name) { name = name || utils.project(process.cwd()); - return function (app) { + return function(app) { app.define('locals', new Locals(name, this)); }; }; diff --git a/lib/middleware/json.js b/lib/middleware/json.js index 61a92b8..dd1ae54 100644 --- a/lib/middleware/json.js +++ b/lib/middleware/json.js @@ -1,6 +1,6 @@ 'use strict'; -module.exports = function (app, base, env) { +module.exports = function(app, base, env) { base.onLoad(/\.js(on|hintrc)$/, function(file, next) { file.json = JSON.parse(file.content); next(); diff --git a/lib/multi.js b/lib/multi.js deleted file mode 100644 index c9f3369..0000000 --- a/lib/multi.js +++ /dev/null @@ -1,274 +0,0 @@ -'use strict'; - -var path = require('path'); -var Base = require('base-methods'); -var option = require('base-options'); -var store = require('base-store'); -var fns = require('./middleware'); -var tasks = require('./tasks'); -var utils = require('./utils'); -var Update = require('..'); - -module.exports = function(namespace, config) { - function Multi(argv, options) { - if (!(this instanceof Multi)) { - return new Multi(argv, options); - } - - Base.call(this); - this.use(option()); - this.use(store()); - - this.options = options || {}; - this.commands = ['set', 'get', 'del', 'store', 'init', 'option', 'data', 'list']; - - this.base = new Update() - .on('error', console.error) - .set('argv', argv); - - // console.log(this.base.cli.keys) - - this.base.use(utils.runtimes()); - - // register middleware - for (var fn in fns) { - fns[fn](this.base, this.base, this); - } - - // register tasks - for (var key in tasks) { - this.base.task(key, tasks[key](this.base, this.base, this)); - } - - this.on('task', function() { - console.log(arguments); - }); - - this._listen(); - } - - Base.extend(Multi); - - Multi.prototype.updater = function(name) { - return this.base.updater(name); - }; - - Multi.prototype.build = function() { - this.base.build.apply(this.base, arguments); - return this; - }; - - Multi.prototype._listen = function() { - if (this.base.disabled('verbose')) return; - var store = ['store.set', 'store.has', 'store.get', 'store.del']; - var methods = ['set', 'has', 'get', 'del', 'option', 'data']; - - var names = store.concat(methods); - var len = names.length, i = -1; - var multi = this; - - while (++i < len) { - var method = names[i]; - var prop = method.split('.'); - - if (prop.length === 2) { - this.base[prop[0]].on(prop[1], multi.emit.bind(multi, method)); - this.base[prop[0]].on(prop[1], multi.emit.bind(multi, '*', method)); - } else { - this.base.on(method, function(key, val) { - multi.emit(method, key, val); - multi.emit('*', method, key, val); - }); - } - } - }; - - Multi.prototype.register = function(name, options, updater) { - if (arguments.length === 2) { - updater = options; - options = {}; - } - - var Ctor = options.Update || Update; - var base = this.base; - - var app = new Ctor(base.options) - .option('name', name) - .option('fullname', options.fullname || name) - .option('path', options.path || ''); - - app.create('templates', { - cwd: path.resolve(options.path, 'templates'), - renameKey: function (key) { - return path.basename(key); - } - }); - - app.define('getFile', function(name) { - var view = base.files.getView.apply(base.files, arguments); - if (!view) { - view = app.templates.getView.apply(app.templates, arguments); - } - view.basename = view.basename.replace(/^_/, '.'); - return view; - }); - - base.define('getFile', app.getFile); - base.files.getFile = base.files.getView.bind(base.files); - - updater.call(app, app, base, this); - this.base.updater(name, app); - - this.emit('register', name, app); - return this; - }; - - Multi.prototype.registerEach = function(pattern, options) { - utils.matchFiles(pattern, options).forEach(function (fp) { - var filepath = path.resolve(fp, 'updatefile.js'); - var updater = require(filepath); - - // get the full project name ('updater-foo') - var fullname = utils.project(fp); - // get the updater name ('foo') - var name = utils.renameFn(fullname, options); - var opts = {}; - // get the constructor to use (node_modules or our 'Update') - opts.Update = utils.resolveModule(fp); - opts.fullname = fullname; - opts.path = fp; - - this.register(name, opts, updater); - }.bind(this)); - - this.base.emit('registered'); - return this; - }; - - Multi.prototype.argv = function(argv, commands, fn) { - var args = {}; - args.argv = argv; - args.commands = []; - args.updaters = {}; - - args.flags = utils.expandArgs(utils.omit(argv, ['_', 'files'])); - args.flagskeys = Object.keys(args.flags); - - var files = argv.files ? utils.pick(argv, 'files') : null; - if (files) args.flags.files = files; - - var arr = argv._; - var len = arr.length, i = -1; - - while (++i < len) { - var key = arr[i]; - - if (/\W/.test(key)) { - var obj = utils.expand(key); - - for (var key in obj) { - if (obj.hasOwnProperty(key)) { - var val = obj[key]; - utils.union(args, 'updaters.' + key, val); - } - } - continue; - } - - if (utils.contains(commands, key)) { - args.commands.push(key); - continue; - } - fn(key, args); - } - return args; - }; - - Multi.prototype.run = function(args, cb) { - if (typeof args === 'function') { - cb = args; - args = null; - } - - if (!args) { - var argv = this.base.get('argv'); - var commands = this.options.commands || this.commands; - args = this.argv(argv, commands, function(key, args) { - var updaters = this.base.updaters; - if (key in updaters) { - utils.union(args, 'updaters.' + key, 'default'); - - } else if (key !== 'base') { - utils.union(args, 'updaters.base', key); - } - return args; - }.bind(this)); - } - - if (args.commands && args.commands.length > 1) { - var cmd = '"' + args.commands.join(', ') + '"'; - return cb(new Error('Error: only one root level command may be given: ' + cmd)); - } - - this.base.cli.process(args.flags); - var updaters = Object.keys(args.updaters); - - utils.async.eachSeries(updaters, function(name, next) { - var tasks = args.updaters[name]; - var app = name !== 'base' - ? this.base.updater(name) - : this.base; - - this.emit('task', name, tasks); - app.build(tasks, function (err) { - if (err) return next(err); - next(); - }); - }.bind(this), cb); - return this; - }; - - Multi.prototype.hasUpdater = function(name) { - return this.updaters.hasOwnProperty(name); - }; - - Multi.prototype.hasTask = function(name) { - return this.taskMap.indexOf(name) > -1; - }; - - Multi.prototype.list = function(cb) { - var questions = utils.questions(this.base.options); - var choices = utils.list(this.base.updaters); - if (!choices.length) { - console.log(utils.cyan(' No updater tasks found.')); - return cb(null, {updaters: {}}); - } - - var question = { - updaters: { - message: 'pick an updater to run', - type: 'checkbox', - choices: choices - } - }; - - questions.ask(question, function (err, answers) { - if (err) return cb(err); - var args = { - updaters: {} - }; - answers.updaters.forEach(function (answer) { - var segs = answer.split(':'); - if (segs.length === 1) return; - utils.union(args.updaters, segs[0], (segs[1] || 'default').split(',')); - }); - return cb(null, args); - }); - }; - - /** - * Expose `Multi` - */ - - return Multi; -}; diff --git a/lib/runner/argv.js b/lib/runner/argv.js new file mode 100644 index 0000000..95dd750 --- /dev/null +++ b/lib/runner/argv.js @@ -0,0 +1,55 @@ +'use strict'; + +var utils = require('../utils'); + +module.exports = function(options) { + return function(app) { + + this.define('argv', function(argv, commands, fn) { + var args = {}; + args.argv = argv; + args.commands = []; + args.updaters = {}; + + args.flags = utils.expandArgs(utils.omit(argv, ['_', 'files'])); + args.flagskeys = Object.keys(args.flags); + + var files = argv.files ? utils.pick(argv, 'files') : null; + if (files) args.flags.files = files; + + var arr = argv._; + var len = arr.length, i = -1; + + while (++i < len) { + var key = arr[i]; + + if (/\W/.test(key)) { + var obj = utils.expand(key); + + for (var prop in obj) { + if (obj.hasOwnProperty(prop)) { + var val = obj[prop]; + utils.union(args, 'updaters.' + prop, val); + } + } + continue; + } + + if (utils.contains(commands, key)) { + args.commands.push(key); + continue; + } + // fn(key, args); + var updaters = this.base.updaters; + if (key in updaters) { + utils.union(args, 'updaters.' + key, 'default'); + + } else if (key !== 'base') { + utils.union(args, 'updaters.base', key); + } + } + return args; + }); + }; +}; + diff --git a/lib/runner/decorate.js b/lib/runner/decorate.js new file mode 100644 index 0000000..c61fe95 --- /dev/null +++ b/lib/runner/decorate.js @@ -0,0 +1,37 @@ +'use strict'; + +var path = require('path'); +var utils = require('../utils'); + +module.exports = function(options) { + return function(app) { + + this.define('decorate', function(name, app, options) { + app.option('name', name) + .option('fullname', options.fullname || name) + .option('path', options.path || ''); + + app.create('templates', { + cwd: path.resolve(options.path, 'templates'), + renameKey: function(key) { + return path.basename(key); + } + }); + + var base = this.base; + + app.define('getFile', function(name) { + var view = base.files.getView.apply(base.files, arguments); + if (!view) { + view = app.templates.getView.apply(app.templates, arguments); + } + view.basename = view.basename.replace(/^_/, '.'); + return view; + }); + + base.define('getFile', app.getFile); + base.files.getFile = base.files.getView.bind(base.files); + return this; + }); + }; +}; diff --git a/lib/runner/env.js b/lib/runner/env.js new file mode 100644 index 0000000..4d431b7 --- /dev/null +++ b/lib/runner/env.js @@ -0,0 +1,53 @@ +'use strict'; + +var path = require('path'); +var define = require('define-property'); +var extend = require('extend-shallow'); +var get = require('get-value'); +var set = require('set-value'); + +function Env(options) { + this.options = options || {}; + define(this, 'cache', {}); +} + +Env.prototype.set = function(key, value) { + set(this, key, value); + return this; +}; + +Env.prototype.get = function(key) { + return get(this, key); +}; + +Object.defineProperty(Env.prototype, 'cwd', { + set: function(dir) { + this.cache.cwd = dir; + }, + get: function() { + return this.cache.cwd || process.cwd(); + } +}); + +Object.defineProperty(Env.prototype, 'pkg', { + set: function() { + throw new Error('env.pkg is a getter and cannot be set directly.'); + }, + get: function() { + if (!this.cache.pkg) { + this.cache.pkg = require(path.resolve(this.cwd, 'package.json')); + } + return this.cache.pkg; + } +}); + +/** + * Expose `Env` + */ + +module.exports = function(options) { + return function(app) { + var opts = extend({}, this.options, options); + app.define('env', new Env(opts)); + }; +}; diff --git a/lib/runner/list.js b/lib/runner/list.js new file mode 100644 index 0000000..15122ee --- /dev/null +++ b/lib/runner/list.js @@ -0,0 +1,40 @@ +'use strict'; + +var utils = require('../utils'); + +module.exports = function(options) { + return function(app) { + + this.define('list', function(cb) { + var questions = utils.questions(this.base.options); + var choices = utils.list(this.base.updaters); + if (!choices.length) { + console.log(utils.cyan(' No updater tasks found.')); + return cb(null, { + updaters: {} + }); + } + + var question = { + updaters: { + message: 'pick an updater to run', + type: 'checkbox', + choices: choices + } + }; + + questions.ask(question, function(err, answers) { + if (err) return cb(err); + var args = { + updaters: {} + }; + answers.updaters.forEach(function(answer) { + var segs = answer.split(':'); + if (segs.length === 1) return; + utils.union(args.updaters, segs[0], (segs[1] || 'default').split(',')); + }); + return cb(null, args); + }); + }); + }; +}; diff --git a/lib/runner/listen.js b/lib/runner/listen.js new file mode 100644 index 0000000..844c9df --- /dev/null +++ b/lib/runner/listen.js @@ -0,0 +1,33 @@ +'use strict'; + +module.exports = function(options) { + return function(app) { + + this.define('_listen', function() { + if (this.base.disabled('verbose')) return; + var store = ['store.set', 'store.has', 'store.get', 'store.del']; + var methods = ['set', 'has', 'get', 'del', 'option', 'data']; + + var names = store.concat(methods); + var len = names.length, + i = -1; + var multi = this; + + while (++i < len) { + var method = names[i]; + var prop = method.split('.'); + + if (prop.length === 2) { + this.base[prop[0]].on(prop[1], multi.emit.bind(multi, method)); + this.base[prop[0]].on(prop[1], multi.emit.bind(multi, '*', method)); + } else { + this.base.on(method, function(key, val) { + multi.emit(method, key, val); + multi.emit('*', method, key, val); + }); + } + } + }); + + }; +}; diff --git a/lib/runner/run.js b/lib/runner/run.js new file mode 100644 index 0000000..d9ce57c --- /dev/null +++ b/lib/runner/run.js @@ -0,0 +1,40 @@ +'use strict'; + +var utils = require('../utils'); + +module.exports = function(options) { + return function(app) { + + this.define('run', function(args, cb) { + if (typeof args === 'function') { + cb = args; + args = null; + } + + if (!args) { + var commands = this.options.commands || this.commands; + args = this.argv(this.base.get('argv'), commands); + } + + if (args.commands && args.commands.length > 1) { + var cmd = '"' + args.commands.join(', ') + '"'; + return cb(new Error('Error: only one root level command may be given: ' + cmd)); + } + + this.base.cli.process(args.flags); + var updaters = Object.keys(args.updaters); + + utils.async.eachSeries(updaters, function(name, next) { + var tasks = args.updaters[name]; + var app = name !== 'base' ? this.base.updater(name) : this.base; + + this.emit('task', name, tasks); + app.build(tasks, function(err) { + if (err) return next(err); + next(); + }); + }.bind(this), cb); + return this; + }); + }; +}; diff --git a/lib/runner/runner.js b/lib/runner/runner.js new file mode 100644 index 0000000..3b468a4 --- /dev/null +++ b/lib/runner/runner.js @@ -0,0 +1,113 @@ +'use strict'; + +var path = require('path'); +var option = require('base-options'); +var store = require('base-store'); +var Base = require('base-methods'); +var fns = require('../middleware'); +var Updater = require('./updater'); +var tasks = require('../tasks'); +var utils = require('../utils'); +var decorate = require('./decorate'); +var listen = require('./listen'); +var args = require('./argv'); +var list = require('./list'); +var run = require('./run'); +var Update = require('../..'); + +module.exports = function(namespace, config) { + function Runner(argv, options) { + if (!(this instanceof Runner)) { + return new Runner(argv, options); + } + + Base.call(this); + this.use(option()); + this.use(store()); + this.use(decorate()); + this.use(listen()); + this.use(args()); + this.use(list()); + this.use(run()); + + this.options = options || {}; + this.commands = ['set', 'get', 'del', 'store', 'init', 'option', 'data', 'list']; + + this.base = new Update() + .on('error', console.error) + .set('argv', argv) + .use(utils.runtimes()); + + // console.log(this.base.cli.keys) + + // register middleware + for (var fn in fns) { + fns[fn](this.base, this.base, this); + } + + // register tasks + for (var key in tasks) { + this.base.task(key, tasks[key](this.base, this.base, this)); + } + + this._listen(); + } + + Base.extend(Runner); + + Runner.prototype.updater = function(name) { + var foo = new Updater(name); + console.log(foo); + return this.base.updater(name); + }; + + Runner.prototype.build = function() { + this.base.build.apply(this.base, arguments); + return this; + }; + + Runner.prototype.register = function(name, options, updater) { + if (arguments.length === 2) { + updater = options; + options = {}; + } + + var Ctor = options.Update || Update; + var app = new Ctor(this.base.options); + this.decorate(name, app, options); + + updater.call(app, app, this.base, this); + this.base.updater(name, app); + + this.emit('register', name, app); + return this; + }; + + Runner.prototype.registerEach = function(pattern, options) { + utils.matchFiles(pattern, options).forEach(function(fp) { + var filepath = path.resolve(fp, 'updatefile.js'); + var updater = require(filepath); + + // get the full project name ('updater-foo') + var fullname = utils.project(fp); + // get the updater name ('foo') + var name = utils.renameFn(fullname, options); + var opts = {}; + // get the constructor to use (node_modules or our 'Update') + opts.Update = utils.resolveModule(fp); + opts.fullname = fullname; + opts.path = fp; + + this.register(name, opts, updater); + }.bind(this)); + + this.base.emit('registered'); + return this; + }; + + /** + * Expose `Runner` + */ + + return Runner; +}; diff --git a/lib/runner/updater.js b/lib/runner/updater.js new file mode 100644 index 0000000..222dcfa --- /dev/null +++ b/lib/runner/updater.js @@ -0,0 +1,127 @@ +'use strict'; + +var path = require('path'); +var set = require('set-value'); +var define = require('define-property'); +var use = require('use'); + +/** + * Create an instance of `Updater`, optionally passing + * a default object to initialize with. + * + * ```js + * var app = new Updater({ + * path: 'foo.html' + * }); + * ``` + * @param {Object} `app` + * @api public + */ + +function Updater(name, config, fn) { + if (!(this instanceof Updater)) { + return new Updater(config); + } + + if (typeof config === 'function') { + fn = config; + config = {}; + } + + this.isUpdater = true; + define(this, 'cache', {}); + config = config || {}; + config.fn = fn; + + for (var key in config) { + if (!(key in this)) { + this.set(key, config[key]); + } + } + use(this); +} + +/** + * Set `key` on the instance with the given `value`. + * + * @param {String} `key` + * @param {Object} `value` + * @return {Object} Returns the instance for chaining + */ + +Updater.prototype.set = function(key, value) { + set(this, key, value); + return this; +}; + +/** + * Custom `inspect` method. + */ + +// Updater.prototype.inspect = function() { +// var name = this.name || 'Updater'; +// var inspect = []; + +// if (this.alias) { +// inspect.push('"' + this.alias + '"'); +// } +// return '<' + name + ' ' + inspect.join(' ') + '>'; +// }; + +/** + * Get the `cwd` (current working directory) for the updater. + */ + +define(Updater.prototype, 'cwd', { + set: function(dir) { + this.cache.cwd = dir; + }, + get: function() { + return this.cache.cwd || (this.cache.cwd = process.cwd()); + } +}); + +/** + * Get the `dirname` for the updater. + */ + +define(Updater.prototype, 'dirname', { + set: function(dir) { + this.path = path.join(dir, path.basename(this.path)); + }, + get: function() { + return path.dirname(this.path); + } +}); + +/** + * Get the `basename` for the updater. + */ + +define(Updater.prototype, 'basename', { + set: function(basename) { + this.path = path.join(path.dirname(this.path), basename); + }, + get: function() { + return path.basename(this.path); + } +}); + +/** + * Get the `filename` for the updater. + */ + +define(Updater.prototype, 'filename', { + set: function(filename) { + this.path = path.join(path.dirname(this.path), filename + this.extname); + }, + get: function() { + return path.basename(this.path, this.extname); + } +}); + +/** + * Expose `Updater` + */ + +module.exports = Updater; diff --git a/lib/tasks/default.js b/lib/tasks/default.js index cca37f1..54bcf12 100644 --- a/lib/tasks/default.js +++ b/lib/tasks/default.js @@ -1,7 +1,5 @@ 'use strict'; -var utils = require('../utils'); - module.exports = function(app, base, env) { return ['del', 'files', 'run', 'dest']; }; diff --git a/lib/tasks/del.js b/lib/tasks/del.js index 13eac44..acd270d 100644 --- a/lib/tasks/del.js +++ b/lib/tasks/del.js @@ -9,7 +9,7 @@ var list = ['.npmignore', '.jshintrc']; module.exports = function(app, base, env) { var files = base.option('delete') || list; - return function (cb) { + return function(cb) { async.each(files, function(fp, next) { rimraf(path.resolve(process.cwd(), fp), next); }, cb); diff --git a/lib/tasks/dest.js b/lib/tasks/dest.js index bd4c216..7422459 100644 --- a/lib/tasks/dest.js +++ b/lib/tasks/dest.js @@ -6,13 +6,13 @@ module.exports = function(app, base, env) { var plugins = base.get('argv.plugins'); function handle(stage) { - return utils.through.obj(function (file, enc, next) { + return utils.through.obj(function(file, enc, next) { if (file.isNull()) return next(); app.handle(stage, file, next); }); } - return function (cb) { + return function(cb) { app.toStream('files') .on('error', cb) .pipe(handle('onStream')) diff --git a/lib/tasks/files.js b/lib/tasks/files.js index 6a82714..4c958ac 100644 --- a/lib/tasks/files.js +++ b/lib/tasks/files.js @@ -1,11 +1,10 @@ 'use strict'; var path = require('path'); -var utils = require('../utils'); module.exports = function(app, base, env) { base.create('files', { - renameKey: function (key) { + renameKey: function(key) { return path.basename(key); } }); @@ -17,7 +16,7 @@ module.exports = function(app, base, env) { glob = ['*', 'lib/*', 'bin/*']; } - return function (cb) { + return function(cb) { base.files(glob, {dot: true, ignore: ['.DS_Store']}); base.emit('loaded', base.files); cb(); diff --git a/lib/tasks/lint.js b/lib/tasks/lint.js index 75e7d19..8dafe52 100644 --- a/lib/tasks/lint.js +++ b/lib/tasks/lint.js @@ -5,9 +5,9 @@ var jshint = require('gulp-jshint'); var stylish = require('jshint-stylish'); module.exports = function(app, base, env) { - return function () { + return function() { return base.toStream('files') - .pipe(through.obj(function (file, enc, cb) { + .pipe(through.obj(function(file, enc, cb) { if (/\.js$/.test(file.path)) { this.push(file); } diff --git a/lib/tasks/list.js b/lib/tasks/list.js index 9995608..92ffe1d 100644 --- a/lib/tasks/list.js +++ b/lib/tasks/list.js @@ -1,10 +1,8 @@ 'use strict'; -var utils = require('../utils'); - module.exports = function(app, base, env) { - return function (cb) { - env.list(function (err, args) { + return function(cb) { + env.list(function(err, args) { if (err) return cb(err); env.run(args, cb); }); diff --git a/lib/tasks/noop.js b/lib/tasks/noop.js index b0f679c..0386d7e 100644 --- a/lib/tasks/noop.js +++ b/lib/tasks/noop.js @@ -1,9 +1,7 @@ 'use strict'; -var utils = require('../utils'); - module.exports = function(app, base, env) { - return function (cb) { + return function(cb) { return cb(); }; }; diff --git a/lib/tasks/rename.js b/lib/tasks/rename.js index 852ac34..ad419c1 100644 --- a/lib/tasks/rename.js +++ b/lib/tasks/rename.js @@ -12,12 +12,12 @@ var mapping = { module.exports = function(app, base, env) { var config = base.option('rename') || mapping; - app.task('undo', function () { + app.task('undo', function() { return base.toStream('files') .pipe(rename(config, {invert: true})); }); - return function () { + return function() { return base.toStream('files') .pipe(rename(config)); }; @@ -28,7 +28,7 @@ function rename(mapping, options) { if (options.invert === true) { mapping = invert(mapping); } - return through.obj(function (file, enc, next) { + return through.obj(function(file, enc, next) { if (file.isNull()) return next(null, file); var fp = file.path; function del(err) { diff --git a/lib/tasks/tree.js b/lib/tasks/tree.js index efc1f18..06e6cc8 100644 --- a/lib/tasks/tree.js +++ b/lib/tasks/tree.js @@ -3,8 +3,8 @@ var utils = require('../utils'); module.exports = function(app, base, env) { - return function (cb) { + return function(cb) { console.log(utils.tree(base.updaters)); cb(); - } + }; }; diff --git a/lib/utils.js b/lib/utils.js index 7ff492b..7358caa 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -260,7 +260,7 @@ utils.list = function(updaters) { utils.tryRequire = function(name) { try { return require(name); - } catch(err) { + } catch (err) { console.log(err); } return null; @@ -273,14 +273,14 @@ utils.tryRequire = function(name) { utils.tryRead = function(fp) { try { return fs.readFileSync(fp); - } catch(err) {} + } catch (err) {} return null; }; utils.tryParse = function(str) { try { return JSON.parse(str); - } catch(err) {} + } catch (err) {} return {}; }; @@ -343,7 +343,6 @@ require = fn; module.exports = utils; - /** * Expose utils */ diff --git a/package.json b/package.json index b45f067..20ddd0b 100644 --- a/package.json +++ b/package.json @@ -41,9 +41,9 @@ "async": "^1.5.0", "base-cli": "^0.2.1", "base-config": "^0.2.0", + "base-options": "^0.5.4", "base-pipeline": "^0.1.4", "base-store": "^0.1.2", - "component-emitter": "^1.2.0", "composer-runtimes": "^0.5.1", "cwd": "^0.8.4", "delete": "^0.2.1", @@ -68,12 +68,12 @@ "resolve-dir": "^0.1.0", "rimraf": "^2.4.3", "set-value": "^0.3.0", - "sort-object": "^3.0.0", "stream-exhaust": "^1.0.1", "success-symbol": "^0.1.0", "time-stamp": "^0.1.1", "union-value": "^0.2.0", "update-copyright": "^0.1.0", + "use": "^1.1.2", "write": "^0.2.1" }, "devDependencies": { @@ -86,6 +86,7 @@ "event-stream": "^3.3.2", "graceful-fs": "^4.1.2", "gulp": "^3.9.0", + "gulp-eslint": "^1.1.0", "gulp-istanbul": "^0.10.2", "gulp-jshint": "^1.12.0", "gulp-mocha": "^2.1.3", @@ -145,4 +146,4 @@ "aaa": "yyy" } } -} +} \ No newline at end of file From 41a24d26cc4631ae778d68fd98e4036e2b54c36c Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 17 Nov 2015 05:14:46 -0500 Subject: [PATCH 122/274] extra checking around json files --- lib/middleware/json.js | 4 ++-- lib/runner/decorate.js | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/middleware/json.js b/lib/middleware/json.js index dd1ae54..eb586e7 100644 --- a/lib/middleware/json.js +++ b/lib/middleware/json.js @@ -2,12 +2,12 @@ module.exports = function(app, base, env) { base.onLoad(/\.js(on|hintrc)$/, function(file, next) { - file.json = JSON.parse(file.content); + if (file.content) file.json = JSON.parse(file.content); next(); }); base.preWrite(/\.js(on|hintrc)$/, function(file, next) { - file.content = JSON.stringify(file.json, null, 2); + if (file.json) file.content = JSON.stringify(file.json, null, 2); next(); }); }; diff --git a/lib/runner/decorate.js b/lib/runner/decorate.js index c61fe95..068e068 100644 --- a/lib/runner/decorate.js +++ b/lib/runner/decorate.js @@ -25,6 +25,7 @@ module.exports = function(options) { if (!view) { view = app.templates.getView.apply(app.templates, arguments); } + if (!view) return null; view.basename = view.basename.replace(/^_/, '.'); return view; }); From 95114d1c903e122e45e667554b5d3caca92018df Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 17 Nov 2015 05:15:30 -0500 Subject: [PATCH 123/274] adds `getApp` lookup method --- lib/runner/run.js | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/lib/runner/run.js b/lib/runner/run.js index d9ce57c..e86cd94 100644 --- a/lib/runner/run.js +++ b/lib/runner/run.js @@ -5,6 +5,12 @@ var utils = require('../utils'); module.exports = function(options) { return function(app) { + this.define('getApp', function(name) { + return name !== 'base' + ? this.base.updater(name) + : this.base; + }); + this.define('run', function(args, cb) { if (typeof args === 'function') { cb = args; @@ -22,18 +28,15 @@ module.exports = function(options) { } this.base.cli.process(args.flags); - var updaters = Object.keys(args.updaters); - - utils.async.eachSeries(updaters, function(name, next) { - var tasks = args.updaters[name]; - var app = name !== 'base' ? this.base.updater(name) : this.base; - - this.emit('task', name, tasks); - app.build(tasks, function(err) { - if (err) return next(err); - next(); - }); - }.bind(this), cb); + var self = this; + + utils.async.eachOf(args.updaters, function(tasks, name, next) { + var app = self.getApp(name); + + self.emit('task', name, tasks); + app.build(tasks, next); + }, cb); + return this; }); }; From e82fbad6bd48d3917eed1f69639bd0139342baa5 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 17 Nov 2015 05:15:38 -0500 Subject: [PATCH 124/274] clean up --- lib/runner/runner.js | 16 +++++++++++----- lib/utils.js | 1 + package.json | 13 ++++++++++--- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/lib/runner/runner.js b/lib/runner/runner.js index 3b468a4..2c6cefe 100644 --- a/lib/runner/runner.js +++ b/lib/runner/runner.js @@ -56,8 +56,6 @@ module.exports = function(namespace, config) { Base.extend(Runner); Runner.prototype.updater = function(name) { - var foo = new Updater(name); - console.log(foo); return this.base.updater(name); }; @@ -83,8 +81,10 @@ module.exports = function(namespace, config) { return this; }; - Runner.prototype.registerEach = function(pattern, options) { - utils.matchFiles(pattern, options).forEach(function(fp) { + Runner.prototype.registerEach = function(patterns, options) { + // var res = resolve(this, patterns, options); + + utils.matchFiles(patterns, options).forEach(function(fp) { var filepath = path.resolve(fp, 'updatefile.js'); var updater = require(filepath); @@ -93,6 +93,7 @@ module.exports = function(namespace, config) { // get the updater name ('foo') var name = utils.renameFn(fullname, options); var opts = {}; + // get the constructor to use (node_modules or our 'Update') opts.Update = utils.resolveModule(fp); opts.fullname = fullname; @@ -101,7 +102,12 @@ module.exports = function(namespace, config) { this.register(name, opts, updater); }.bind(this)); - this.base.emit('registered'); + function resolve(patterns, options) { + var opts = utils.extend({}, options); + return opts.resolveGlobal !== true + ? utils.glob.sync(patterns, opts) + : utils.resolve(patterns, opts); + } return this; }; diff --git a/lib/utils.js b/lib/utils.js index 7358caa..527c81a 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -23,6 +23,7 @@ require('object.omit', 'omit'); require('object.pick', 'pick'); require('micromatch', 'mm'); require('map-config', 'config'); +require('matched', 'glob'); require('composer-runtimes', 'runtimes'); require('parser-front-matter', 'matter'); require('question-cache', 'questions'); diff --git a/package.json b/package.json index 20ddd0b..58c625d 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "homepage": "https://github.com/jonschlinkert/update-next", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", "contributors": [ - "Jon Schlinkert (https://github.com/jonschlinkert)", + "Jon Schlinkert (https://github.com/jonschlinkert)", "Brian Woodward (https://github.com/doowb)" ], "repository": "jonschlinkert/update", @@ -18,7 +18,9 @@ "bin", "lib/" ], - "main": "index.js", + "main": [ + "index.js" + ], "engines": { "node": ">=0.10.0" }, @@ -58,6 +60,7 @@ "lazy-cache": "^0.2.4", "load-pkg": "^2.0.1", "map-config": "^0.2.1", + "matched": "^0.3.2", "micromatch": "jonschlinkert/micromatch#2.3.0", "minimist": "^1.2.0", "object.omit": "^2.0.0", @@ -145,5 +148,9 @@ "set": { "aaa": "yyy" } - } + }, + "authors": [ + "Jon Schlinkert (https://github.com/jonschlinkert)", + "Brian Woodward (https://github.com/doowb)" + ] } \ No newline at end of file From ddf748c240e83c2c128295207424ee82eb17ac53 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 17 Nov 2015 05:32:15 -0500 Subject: [PATCH 125/274] make tests consistent --- package.json | 34 +++++++++++++++++----------------- test/app.copy.js | 16 +++++++++------- test/app.dest.js | 3 ++- test/app.renderFile.js | 5 +++-- test/app.src.js | 3 ++- test/app.symlink.js | 5 +++-- test/app.task.js | 5 ++++- test/app.toStream.js | 5 +++-- test/app.watch.js | 5 +++-- test/store.js | 3 ++- test/support/index.js | 6 +++++- 11 files changed, 53 insertions(+), 37 deletions(-) diff --git a/package.json b/package.json index 58c625d..40bfeae 100644 --- a/package.json +++ b/package.json @@ -38,16 +38,18 @@ "ansi-red": "^0.1.1", "ansi-yellow": "^0.1.1", "assemble-ask": "^0.1.4", - "assemble-core": "^0.1.9", + "assemble-core": "^0.2.0", "assemble-loader": "^0.2.4", "async": "^1.5.0", "base-cli": "^0.2.1", "base-config": "^0.2.0", + "base-methods": "^0.4.1", "base-options": "^0.5.4", "base-pipeline": "^0.1.4", - "base-store": "^0.1.2", + "base-store": "^0.2.0", "composer-runtimes": "^0.5.1", "cwd": "^0.8.4", + "define-property": "^0.2.5", "delete": "^0.2.1", "engine-base": "^0.1.2", "expand-args": "^0.3.0", @@ -57,11 +59,14 @@ "for-own": "^0.1.3", "get-value": "^2.0.0", "global-modules": "^0.2.0", + "gulp-jshint": "^1.12.0", + "jshint-stylish": "^2.1.0", "lazy-cache": "^0.2.4", "load-pkg": "^2.0.1", - "map-config": "^0.2.1", + "look-up": "^0.8.1", + "map-config": "^0.3.0", "matched": "^0.3.2", - "micromatch": "jonschlinkert/micromatch#2.3.0", + "micromatch": "^2.3.2", "minimist": "^1.2.0", "object.omit": "^2.0.0", "object.pick": "^1.1.1", @@ -70,40 +75,35 @@ "question-cache": "^0.3.3", "resolve-dir": "^0.1.0", "rimraf": "^2.4.3", - "set-value": "^0.3.0", + "set-value": "^0.3.1", "stream-exhaust": "^1.0.1", "success-symbol": "^0.1.0", + "through2": "^2.0.0", "time-stamp": "^0.1.1", - "union-value": "^0.2.0", + "union-value": "^0.2.1", "update-copyright": "^0.1.0", "use": "^1.1.2", "write": "^0.2.1" }, "devDependencies": { - "base-methods": "^0.4.0", "buffer-equal": "0.0.1", "consolidate": "^0.13.1", "coveralls": "^2.11.4", - "define-property": "^0.2.5", "engine-handlebars": "^0.8.0", "event-stream": "^3.3.2", "graceful-fs": "^4.1.2", "gulp": "^3.9.0", "gulp-eslint": "^1.1.0", "gulp-istanbul": "^0.10.2", - "gulp-jshint": "^1.12.0", - "gulp-mocha": "^2.1.3", + "gulp-mocha": "^2.2.0", "is-buffer": "^1.1.0", "istanbul": "^0.4.0", - "jshint-stylish": "^2.0.1", "kind-of": "^2.0.1", - "look-up": "^0.8.1", - "mocha": "*", - "resolve-glob": "^0.1.3", - "should": "*", + "mocha": "^2.3.4", + "resolve-glob": "^0.1.7", + "should": "^7.1.1", "sinon": "^1.17.2", "swig": "^1.4.2", - "through2": "^2.0.0", "vinyl": "^1.1.0" }, "keywords": [ @@ -153,4 +153,4 @@ "Jon Schlinkert (https://github.com/jonschlinkert)", "Brian Woodward (https://github.com/doowb)" ] -} \ No newline at end of file +} diff --git a/test/app.copy.js b/test/app.copy.js index 4a0d510..99eb812 100644 --- a/test/app.copy.js +++ b/test/app.copy.js @@ -2,29 +2,31 @@ require('mocha'); var path = require('path'); var assert = require('assert'); var rimraf = require('rimraf'); -var App = require('..'); +var support = require('./support'); +var App = support.resolve(); var app; var fixtures = path.join(__dirname, 'fixtures/copy/*.txt'); var actual = path.join(__dirname, 'actual'); describe('copy()', function() { - beforeEach(function (done) { - rimraf(actual, done); + beforeEach(function (cb) { + rimraf(actual, cb); app = new App(); }); - afterEach(function (done) { - rimraf(actual, done); + afterEach(function (cb) { + rimraf(actual, cb); }); describe('streams', function () { - it('should copy files', function (done) { + it('should copy files', function (cb) { app.copy(fixtures, path.join(__dirname, 'actual')) + .on('error', cb) .on('data', function (file) { assert.equal(typeof file, 'object'); }) - .on('end', done); + .on('end', cb); }); }); }); diff --git a/test/app.dest.js b/test/app.dest.js index 1e3d67e..00e7383 100644 --- a/test/app.dest.js +++ b/test/app.dest.js @@ -5,7 +5,8 @@ var statSpy = spies.statSpy; require('mocha'); var should = require('should'); var assert = require('assert'); -var App = require('..'); +var support = require('./support'); +var App = support.resolve(); var app; var path = require('path'); diff --git a/test/app.renderFile.js b/test/app.renderFile.js index 1b3b478..330376a 100644 --- a/test/app.renderFile.js +++ b/test/app.renderFile.js @@ -1,6 +1,7 @@ 'use strict'; -var assemble = require('..'); +var support = require('./support'); +var App = support.resolve(); var assert = require('assert'); var should = require('should'); var path = require('path'); @@ -8,7 +9,7 @@ var app; describe('app.renderFile()', function() { beforeEach(function () { - app = assemble(); + app = new App(); app.engine('hbs', require('engine-handlebars')); app.engine('*', require('engine-base')); diff --git a/test/app.src.js b/test/app.src.js index bd2b089..5064fd5 100644 --- a/test/app.src.js +++ b/test/app.src.js @@ -1,6 +1,7 @@ 'use strict'; -var App = require('..'); +var support = require('./support'); +var App = support.resolve(); var assert = require('assert'); var should = require('should'); var join = require('path').join; diff --git a/test/app.symlink.js b/test/app.symlink.js index 69e8d45..e2a3747 100644 --- a/test/app.symlink.js +++ b/test/app.symlink.js @@ -6,7 +6,8 @@ var rimraf = require('rimraf'); var bufEqual = require('buffer-equal'); var through = require('through2'); var File = require('vinyl'); -var assemble = require('..'); +var support = require('./support'); +var App = support.resolve(); var spies = require('./support/spy'); var chmodSpy = spies.chmodSpy; var statSpy = spies.statSpy; @@ -17,7 +18,7 @@ var wipeOut = function(cb) { spies.setError('false'); statSpy.reset(); chmodSpy.reset(); - app = assemble(); + app = new App(); }; var dataWrap = function(fn) { diff --git a/test/app.task.js b/test/app.task.js index c0d38ff..23661eb 100644 --- a/test/app.task.js +++ b/test/app.task.js @@ -1,5 +1,8 @@ +'use strict'; + var assert = require('assert'); -var App = require('..'); +var support = require('./support'); +var App = support.resolve(); var app; describe('task()', function () { diff --git a/test/app.toStream.js b/test/app.toStream.js index 97bd30e..a524d99 100644 --- a/test/app.toStream.js +++ b/test/app.toStream.js @@ -1,13 +1,14 @@ 'use strict'; -var assemble = require('..'); +var support = require('./support'); +var App = support.resolve(); var assert = require('assert'); var should = require('should'); var app; describe('toStream()', function() { beforeEach(function () { - app = assemble(); + app = new App(); app.create('pages'); app.page('a', {content: 'this is A'}); app.page('b', {content: 'this is B'}); diff --git a/test/app.watch.js b/test/app.watch.js index 698b768..2c76792 100644 --- a/test/app.watch.js +++ b/test/app.watch.js @@ -1,8 +1,9 @@ 'use strict'; -var assert = require('assert'); var fs = require('fs'); -var App = require('..'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); var app; describe.skip('watch()', function () { diff --git a/test/store.js b/test/store.js index f830180..de4295f 100644 --- a/test/store.js +++ b/test/store.js @@ -6,7 +6,8 @@ var fs = require('fs'); var path = require('path'); var assert = require('assert'); var store = require('base-store'); -var update = require('..'); +var support = require('./support'); +var update = support.resolve(); var app; describe('store', function () { diff --git a/test/support/index.js b/test/support/index.js index 43491ce..410cd3c 100644 --- a/test/support/index.js +++ b/test/support/index.js @@ -48,7 +48,7 @@ exports.resolve = function(filepath) { var fp = tryResolve(base); if (typeof fp === 'undefined') { - throw new Error('cannot resolve: ' + fp); + throw new Error('cannot resolve: ' + base); } return (cache[key] = require(fp)); }; @@ -61,6 +61,10 @@ function tryResolve(name) { try { return require.resolve(path.resolve(name)); } catch(err) {} + + try { + return require.resolve(path.resolve(name, 'index.js')); + } catch(err) {} } function tryRequire(name) { From cfbf7c97d6c2eb75004d7510518d8801dda6b4b9 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 17 Nov 2015 06:56:20 -0500 Subject: [PATCH 126/274] adds eslint updater, just for testing --- lib/tasks/jshint.js | 19 +++++++++++++++++++ lib/tasks/lint.js | 7 +++---- 2 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 lib/tasks/jshint.js diff --git a/lib/tasks/jshint.js b/lib/tasks/jshint.js new file mode 100644 index 0000000..8dafe52 --- /dev/null +++ b/lib/tasks/jshint.js @@ -0,0 +1,19 @@ +'use strict'; + +var through = require('through2'); +var jshint = require('gulp-jshint'); +var stylish = require('jshint-stylish'); + +module.exports = function(app, base, env) { + return function() { + return base.toStream('files') + .pipe(through.obj(function(file, enc, cb) { + if (/\.js$/.test(file.path)) { + this.push(file); + } + cb(); + })) + .pipe(jshint()) + .pipe(jshint.reporter(stylish)); + }; +}; diff --git a/lib/tasks/lint.js b/lib/tasks/lint.js index 8dafe52..5e04d25 100644 --- a/lib/tasks/lint.js +++ b/lib/tasks/lint.js @@ -1,8 +1,7 @@ 'use strict'; var through = require('through2'); -var jshint = require('gulp-jshint'); -var stylish = require('jshint-stylish'); +var eslint = require('gulp-eslint'); module.exports = function(app, base, env) { return function() { @@ -13,7 +12,7 @@ module.exports = function(app, base, env) { } cb(); })) - .pipe(jshint()) - .pipe(jshint.reporter(stylish)); + .pipe(eslint()) + .pipe(eslint.format()); }; }; From 730f0ba12ba5d6857befc1fcb7ab1f3fa9c13ac2 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 17 Nov 2015 06:56:47 -0500 Subject: [PATCH 127/274] fix pkg authors --- package.json | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 40bfeae..2056b37 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,10 @@ "version": "0.1.0", "homepage": "https://github.com/jonschlinkert/update-next", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", + "authors": [ + "Jon Schlinkert (https://github.com/jonschlinkert)", + "Brian Woodward (https://github.com/doowb)" + ], "contributors": [ "Jon Schlinkert (https://github.com/jonschlinkert)", "Brian Woodward (https://github.com/doowb)" @@ -18,9 +22,7 @@ "bin", "lib/" ], - "main": [ - "index.js" - ], + "main": "index.js", "engines": { "node": ">=0.10.0" }, @@ -99,9 +101,9 @@ "is-buffer": "^1.1.0", "istanbul": "^0.4.0", "kind-of": "^2.0.1", - "mocha": "^2.3.4", + "mocha": "*", "resolve-glob": "^0.1.7", - "should": "^7.1.1", + "should": "*", "sinon": "^1.17.2", "swig": "^1.4.2", "vinyl": "^1.1.0" @@ -148,9 +150,5 @@ "set": { "aaa": "yyy" } - }, - "authors": [ - "Jon Schlinkert (https://github.com/jonschlinkert)", - "Brian Woodward (https://github.com/doowb)" - ] -} + } +} \ No newline at end of file From 4181174185bfbc9ebe251d5fb6221de6598cb551 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 17 Nov 2015 06:57:40 -0500 Subject: [PATCH 128/274] clean up --- bin/update.js | 1 - lib/middleware/json.js | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/bin/update.js b/bin/update.js index 7ed74dc..c2498c9 100755 --- a/bin/update.js +++ b/bin/update.js @@ -11,7 +11,6 @@ var argv = require('minimist')(process.argv.slice(2), { var cmd = utils.commands(argv); var runner = new Runner(argv); -console.log(runner) var task = cmd.list ? ['list', 'default'] : 'default'; diff --git a/lib/middleware/json.js b/lib/middleware/json.js index eb586e7..dd1ae54 100644 --- a/lib/middleware/json.js +++ b/lib/middleware/json.js @@ -2,12 +2,12 @@ module.exports = function(app, base, env) { base.onLoad(/\.js(on|hintrc)$/, function(file, next) { - if (file.content) file.json = JSON.parse(file.content); + file.json = JSON.parse(file.content); next(); }); base.preWrite(/\.js(on|hintrc)$/, function(file, next) { - if (file.json) file.content = JSON.stringify(file.json, null, 2); + file.content = JSON.stringify(file.json, null, 2); next(); }); }; From cd776be6b172deba0bf96e7e2ce33e302e92b214 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Thu, 10 Dec 2015 13:23:58 -0500 Subject: [PATCH 129/274] clean up --- bin/update.js | 21 ++++++++------------- lib/runner/decorate.js | 1 + lib/runner/listen.js | 5 +---- lib/runner/run.js | 3 +++ lib/runner/runner.js | 11 ----------- lib/utils.js | 11 ++++++++++- 6 files changed, 23 insertions(+), 29 deletions(-) diff --git a/bin/update.js b/bin/update.js index c2498c9..7c643f7 100755 --- a/bin/update.js +++ b/bin/update.js @@ -1,8 +1,6 @@ #!/usr/bin/env node var path = require('path'); -var stamp = require('time-stamp'); -var gray = require('ansi-gray'); var Runner = require('../lib/runner/runner')(); var utils = require('../lib/utils'); var argv = require('minimist')(process.argv.slice(2), { @@ -12,13 +10,15 @@ var argv = require('minimist')(process.argv.slice(2), { var cmd = utils.commands(argv); var runner = new Runner(argv); +runner.base.option(argv); +runner.option(argv); + var task = cmd.list ? ['list', 'default'] : 'default'; -runner.on('*', function (method, key, val) { +runner.on('*', function(method, key, val) { console.log(method + ':', key, val); }); - if (argv.verbose) { runner.on('register', function(key) { utils.ok(utils.gray('registered'), 'updater', utils.cyan(key)); @@ -27,16 +27,11 @@ if (argv.verbose) { runner.registerEach('update-*', {cwd: utils.gm}); -runner.base.task('run', function (cb) { +runner.base.task('run', function(cb) { runner.run(cb); }); -runner.base.build(task, function (err) { - if (err) console.error(err); - timestamp('finished'); +runner.base.build(task, function(err) { + if (err) return console.error(err); + utils.timestamp('done ' + utils.green(utils.successSymbol)); }); - -function timestamp(msg) { - var time = ' ' + gray(stamp('HH:mm:ss.ms', new Date())); - return console.log(time, msg, utils.green(utils.successSymbol)); -} diff --git a/lib/runner/decorate.js b/lib/runner/decorate.js index 068e068..e62a2f4 100644 --- a/lib/runner/decorate.js +++ b/lib/runner/decorate.js @@ -27,6 +27,7 @@ module.exports = function(options) { } if (!view) return null; view.basename = view.basename.replace(/^_/, '.'); + view.basename = view.basename.replace(/^$/, ''); return view; }); diff --git a/lib/runner/listen.js b/lib/runner/listen.js index 844c9df..dc48f2b 100644 --- a/lib/runner/listen.js +++ b/lib/runner/listen.js @@ -2,15 +2,13 @@ module.exports = function(options) { return function(app) { - this.define('_listen', function() { if (this.base.disabled('verbose')) return; var store = ['store.set', 'store.has', 'store.get', 'store.del']; var methods = ['set', 'has', 'get', 'del', 'option', 'data']; var names = store.concat(methods); - var len = names.length, - i = -1; + var len = names.length, i = -1; var multi = this; while (++i < len) { @@ -28,6 +26,5 @@ module.exports = function(options) { } } }); - }; }; diff --git a/lib/runner/run.js b/lib/runner/run.js index e86cd94..864451b 100644 --- a/lib/runner/run.js +++ b/lib/runner/run.js @@ -33,6 +33,9 @@ module.exports = function(options) { utils.async.eachOf(args.updaters, function(tasks, name, next) { var app = self.getApp(name); + tasks = tasks.filter(Boolean); + if (!tasks.length) return next(); + self.emit('task', name, tasks); app.build(tasks, next); }, cb); diff --git a/lib/runner/runner.js b/lib/runner/runner.js index 2c6cefe..3acc1eb 100644 --- a/lib/runner/runner.js +++ b/lib/runner/runner.js @@ -38,8 +38,6 @@ module.exports = function(namespace, config) { .set('argv', argv) .use(utils.runtimes()); - // console.log(this.base.cli.keys) - // register middleware for (var fn in fns) { fns[fn](this.base, this.base, this); @@ -82,8 +80,6 @@ module.exports = function(namespace, config) { }; Runner.prototype.registerEach = function(patterns, options) { - // var res = resolve(this, patterns, options); - utils.matchFiles(patterns, options).forEach(function(fp) { var filepath = path.resolve(fp, 'updatefile.js'); var updater = require(filepath); @@ -101,13 +97,6 @@ module.exports = function(namespace, config) { this.register(name, opts, updater); }.bind(this)); - - function resolve(patterns, options) { - var opts = utils.extend({}, options); - return opts.resolveGlobal !== true - ? utils.glob.sync(patterns, opts) - : utils.resolve(patterns, opts); - } return this; }; diff --git a/lib/utils.js b/lib/utils.js index 527c81a..bbabcb1 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -42,8 +42,17 @@ require('ansi-green', 'green'); require('ansi-gray', 'gray'); require('ansi-cyan', 'cyan'); require('ansi-red', 'red'); +require('time-stamp', 'stamp'); + require = fn; + +utils.timestamp = function(msg) { + var time = ' ' + utils.gray(utils.stamp('HH:mm:ss.ms', new Date())); + return console.log(time, msg); +}; + + function Status(status) { status = status || {}; this.err = status.err || null; @@ -152,7 +161,7 @@ utils.renameFn = function(filename, options) { if (options && typeof options.renameFn === 'function') { return options.renameFn(filename); } - return filename.split(/[-\W_.]+/).pop(); + return filename.slice(filename.indexOf('-') + 1); }; /** From 4cd6c04c72b930dc3e6fed48370105921d2b80a4 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Thu, 10 Dec 2015 13:24:07 -0500 Subject: [PATCH 130/274] add aliases --- index.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index e4ee75d..6dd7f4a 100644 --- a/index.js +++ b/index.js @@ -66,7 +66,11 @@ Update.prototype.initUpdate = function(base) { this.handler('postWrite'); // parse command line arguments - var argv = expand(minimist(process.argv.slice(2))); + var argv = expand(minimist(process.argv.slice(2)), { + alias: {v: 'verbose'} + }); + + this.option('argv', argv); // expose `argv` on the instance this.mixin('argv', function(prop) { @@ -110,6 +114,12 @@ Update.prototype.cwd = function(dir) { }; }; +Update.prototype.log = function() { + if (this.enabled('verbose')) { + console.log.apply(console, arguments); + } +}; + Update.prototype.flag = function(key) { return this.get('argv.' + key); }; @@ -156,6 +166,10 @@ Update.prototype.updater = function(name, app) { } })); + app.on('task:starting', function() { + console.log('fooo') + }) + this.emit('updater', name, app); this.updaters[name] = app; return app; From bd439113de633003483f1544282ffc4ca40ab132 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Thu, 10 Dec 2015 13:24:14 -0500 Subject: [PATCH 131/274] deps --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 2056b37..7ba0c44 100644 --- a/package.json +++ b/package.json @@ -43,8 +43,8 @@ "assemble-core": "^0.2.0", "assemble-loader": "^0.2.4", "async": "^1.5.0", - "base-cli": "^0.2.1", - "base-config": "^0.2.0", + "base-cli": "^0.3.0", + "base-config": "^0.3.0", "base-methods": "^0.4.1", "base-options": "^0.5.4", "base-pipeline": "^0.1.4", @@ -76,6 +76,7 @@ "project-name": "^0.2.1", "question-cache": "^0.3.3", "resolve-dir": "^0.1.0", + "resolve-modules": "^0.1.0", "rimraf": "^2.4.3", "set-value": "^0.3.1", "stream-exhaust": "^1.0.1", From 5051d47c0a0480bd1394803db9e39aeab57cfaf6 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 19 Dec 2015 20:42:56 -0500 Subject: [PATCH 132/274] update unit tests to latest --- test/app.collection.js | 13 ++++++++----- test/app.copy.js | 3 +-- test/app.dest.js | 3 +-- test/app.events.js | 10 ++++++++++ test/app.list.js | 2 ++ test/app.renderFile.js | 5 ++--- test/app.src.js | 3 +-- test/app.symlink.js | 5 ++--- test/app.task.js | 9 ++++----- test/app.toStream.js | 5 ++--- test/app.watch.js | 5 ++--- test/helpers.js | 5 +++-- test/item.js | 6 ++++-- test/mergePartials.js | 2 ++ test/support/index.js | 21 +++------------------ test/view.js | 6 ++++-- test/view.set.js | 3 ++- 17 files changed, 53 insertions(+), 53 deletions(-) diff --git a/test/app.collection.js b/test/app.collection.js index fde0c24..1fc0aa4 100644 --- a/test/app.collection.js +++ b/test/app.collection.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var fs = require('fs'); @@ -75,6 +77,7 @@ describe('collection', function () { .pages('test/fixtures/pages/c.hbs'); app.pages.options.should.have.property('foo', 'bar'); + app.views.pages.should.have.properties([ path.resolve('test/fixtures/pages/a.hbs'), path.resolve('test/fixtures/pages/b.hbs'), @@ -129,16 +132,16 @@ describe('collection', function () { }); it('should render a view with inherited app.render', function (done) { + app.cache.data = {}; + app.page('test/fixtures/templates/a.tmpl') - .use(function (view) { - if (!view.contents) { - view.contents = fs.readFileSync(view.path); - } + .use(function(view) { + view.contents = fs.readFileSync(view.path); }) .set('data.name', 'Brian') .render(function (err, res) { if (err) return done(err); - assert(res.content === 'Brian'); + assert(res.contents.toString() === 'Brian'); done(); }); }); diff --git a/test/app.copy.js b/test/app.copy.js index 99eb812..f3d3ceb 100644 --- a/test/app.copy.js +++ b/test/app.copy.js @@ -2,8 +2,7 @@ require('mocha'); var path = require('path'); var assert = require('assert'); var rimraf = require('rimraf'); -var support = require('./support'); -var App = support.resolve(); +var App = require('..'); var app; var fixtures = path.join(__dirname, 'fixtures/copy/*.txt'); diff --git a/test/app.dest.js b/test/app.dest.js index 00e7383..1e3d67e 100644 --- a/test/app.dest.js +++ b/test/app.dest.js @@ -5,8 +5,7 @@ var statSpy = spies.statSpy; require('mocha'); var should = require('should'); var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); +var App = require('..'); var app; var path = require('path'); diff --git a/test/app.events.js b/test/app.events.js index 3bf001e..6be9215 100644 --- a/test/app.events.js +++ b/test/app.events.js @@ -26,6 +26,16 @@ describe('events', function () { app.emit('foo', 'bar'); }); + it('should listen for error events:', function(done) { + var app = new App(); + app.on('foo', function(val) { + assert(val === 'bar'); + done(); + }); + assert(Array.isArray(app._callbacks['$foo'])); + app.emit('foo', 'bar'); + }); + it('should listen for `view` events:', function () { var app = new App(); diff --git a/test/app.list.js b/test/app.list.js index 9c9c3ea..74b3dbe 100644 --- a/test/app.list.js +++ b/test/app.list.js @@ -85,6 +85,8 @@ describe('list', function () { app = new App(); app.engine('tmpl', require('engine-base')); app.create('pages'); + + app.cache.data = {}; }); it('should render a item with inherited app.render', function (done) { diff --git a/test/app.renderFile.js b/test/app.renderFile.js index 330376a..b49fd7f 100644 --- a/test/app.renderFile.js +++ b/test/app.renderFile.js @@ -1,7 +1,6 @@ 'use strict'; -var support = require('./support'); -var App = support.resolve(); +var update = require('..'); var assert = require('assert'); var should = require('should'); var path = require('path'); @@ -9,7 +8,7 @@ var app; describe('app.renderFile()', function() { beforeEach(function () { - app = new App(); + app = update(); app.engine('hbs', require('engine-handlebars')); app.engine('*', require('engine-base')); diff --git a/test/app.src.js b/test/app.src.js index 5064fd5..bd2b089 100644 --- a/test/app.src.js +++ b/test/app.src.js @@ -1,7 +1,6 @@ 'use strict'; -var support = require('./support'); -var App = support.resolve(); +var App = require('..'); var assert = require('assert'); var should = require('should'); var join = require('path').join; diff --git a/test/app.symlink.js b/test/app.symlink.js index e2a3747..8a63fea 100644 --- a/test/app.symlink.js +++ b/test/app.symlink.js @@ -6,8 +6,7 @@ var rimraf = require('rimraf'); var bufEqual = require('buffer-equal'); var through = require('through2'); var File = require('vinyl'); -var support = require('./support'); -var App = support.resolve(); +var update = require('..'); var spies = require('./support/spy'); var chmodSpy = spies.chmodSpy; var statSpy = spies.statSpy; @@ -18,7 +17,7 @@ var wipeOut = function(cb) { spies.setError('false'); statSpy.reset(); chmodSpy.reset(); - app = new App(); + app = update(); }; var dataWrap = function(fn) { diff --git a/test/app.task.js b/test/app.task.js index 23661eb..3b73918 100644 --- a/test/app.task.js +++ b/test/app.task.js @@ -1,8 +1,7 @@ 'use strict'; var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); +var App = require('..'); var app; describe('task()', function () { @@ -73,13 +72,13 @@ describe('task()', function () { it('should emit task events', function (done) { var events = []; - app.on('starting', function (task) { + app.on('task:starting', function(task) { events.push('starting.' + task.name); }); - app.on('finished', function (task) { + app.on('task:finished', function(task) { events.push('finished.' + task.name); }); - app.on('error', function (err, task) { + app.on('task:error', function(err, task) { events.push('error.' + task.name); }); diff --git a/test/app.toStream.js b/test/app.toStream.js index a524d99..326b84c 100644 --- a/test/app.toStream.js +++ b/test/app.toStream.js @@ -1,14 +1,13 @@ 'use strict'; -var support = require('./support'); -var App = support.resolve(); +var update = require('..'); var assert = require('assert'); var should = require('should'); var app; describe('toStream()', function() { beforeEach(function () { - app = new App(); + app = update(); app.create('pages'); app.page('a', {content: 'this is A'}); app.page('b', {content: 'this is B'}); diff --git a/test/app.watch.js b/test/app.watch.js index 2c76792..698b768 100644 --- a/test/app.watch.js +++ b/test/app.watch.js @@ -1,9 +1,8 @@ 'use strict'; -var fs = require('fs'); var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); +var fs = require('fs'); +var App = require('..'); var app; describe.skip('watch()', function () { diff --git a/test/helpers.js b/test/helpers.js index 13981f0..dddc13c 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -636,17 +636,18 @@ describe('collection helpers', function () { }); it('should handle engine errors', function (done) { + app.post('foo.hbs', {content: '{{one "two"}}'}); app.page('one', {content: '{{posts "foo.hbs"}}'}) .render(function (err) { assert(err); assert(typeof err === 'object'); assert(typeof err.message === 'string'); - assert(/is not a function/.test(err.message)); + assert(/Missing helper: "one"/.test(err.message)); done(); }); }); - it('should handle engine errors', function (done) { + it('should handle engine errors2', function(done) { app.engine('tmpl', require('engine-base')); app.create('foo', {engine: 'tmpl'}); app.create('bar', {engine: 'tmpl'}); diff --git a/test/item.js b/test/item.js index 2c3becd..c44ea4d 100644 --- a/test/item.js +++ b/test/item.js @@ -477,8 +477,10 @@ describe('Item', function() { assert(copy.stat.isFile()); assert(!copy.stat.isDirectory()); - assert(item.stat instanceof fs.Stats); - assert(copy.stat instanceof fs.Stats); + + assert(item.stat.hasOwnProperty('birthtime')); + assert(copy.stat.hasOwnProperty('birthtime')); + assert.deepEqual(item.stat, copy.stat); done(); }); diff --git a/test/mergePartials.js b/test/mergePartials.js index 0c4e481..6b0835f 100644 --- a/test/mergePartials.js +++ b/test/mergePartials.js @@ -6,6 +6,8 @@ var app; describe('mergePartials', function () { beforeEach(function () { app = new App(); + // reset views + app.views = {}; }); it('should merge multiple partials collections onto one collection:', function () { diff --git a/test/support/index.js b/test/support/index.js index 410cd3c..899c846 100644 --- a/test/support/index.js +++ b/test/support/index.js @@ -1,8 +1,7 @@ 'use strict'; var path = require('path'); -var pkg = require('load-pkg'); -var lookup = require('look-up'); +var loadpkg = require('load-pkg'); var assert = require('assert'); var ignore = require('./ignore'); var cache = {}; @@ -37,6 +36,7 @@ exports.resolve = function(filepath) { return cache[key]; } + var pkg = loadpkg.sync(process.cwd()); var prefix = pkg.name !== 'templates' ? 'templates' : ''; @@ -48,7 +48,7 @@ exports.resolve = function(filepath) { var fp = tryResolve(base); if (typeof fp === 'undefined') { - throw new Error('cannot resolve: ' + base); + throw new Error('cannot resolve: ' + fp); } return (cache[key] = require(fp)); }; @@ -61,19 +61,4 @@ function tryResolve(name) { try { return require.resolve(path.resolve(name)); } catch(err) {} - - try { - return require.resolve(path.resolve(name, 'index.js')); - } catch(err) {} } - -function tryRequire(name) { - try { - return require(name); - } catch(err) {} - - try { - return require(path.resolve(name)); - } catch(err) {} -} - diff --git a/test/view.js b/test/view.js index c16e48d..c83b651 100644 --- a/test/view.js +++ b/test/view.js @@ -562,8 +562,10 @@ describe('View', function() { assert(copy.stat.isFile()); assert(!copy.stat.isDirectory()); - assert(view.stat instanceof fs.Stats); - assert(copy.stat instanceof fs.Stats); + + assert(view.stat.hasOwnProperty('birthtime')); + assert(copy.stat.hasOwnProperty('birthtime')); + assert.deepEqual(view.stat, copy.stat); done(); }); diff --git a/test/view.set.js b/test/view.set.js index 60eb98a..180bcdc 100644 --- a/test/view.set.js +++ b/test/view.set.js @@ -11,6 +11,8 @@ describe('set', function () { app = new App(); app.create('page'); app.engine('tmpl', require('engine-base')); + + app.cache.data = {}; }); it('should set a property on a view:', function (done) { @@ -26,7 +28,6 @@ describe('set', function () { .set('data.name', 'Brooke') .render(function (err, res) { if (err) return done(err); - assert(res.content === 'Brooke'); done(); }); From 65044f13767b11dc2903685be44a6949c1eba898 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 19 Dec 2015 20:43:27 -0500 Subject: [PATCH 133/274] remove runtimes from `runner` --- lib/runner/runner.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/runner/runner.js b/lib/runner/runner.js index 3acc1eb..2ae1405 100644 --- a/lib/runner/runner.js +++ b/lib/runner/runner.js @@ -36,7 +36,6 @@ module.exports = function(namespace, config) { this.base = new Update() .on('error', console.error) .set('argv', argv) - .use(utils.runtimes()); // register middleware for (var fn in fns) { From 6968e0d76df1d8ccace02e3b1c1f4706a5837541 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 19 Dec 2015 20:43:46 -0500 Subject: [PATCH 134/274] sort arrays in json, add newline --- lib/middleware/json.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/middleware/json.js b/lib/middleware/json.js index dd1ae54..9ce518f 100644 --- a/lib/middleware/json.js +++ b/lib/middleware/json.js @@ -1,5 +1,7 @@ 'use strict'; +var sortArrays = require('sort-object-arrays'); + module.exports = function(app, base, env) { base.onLoad(/\.js(on|hintrc)$/, function(file, next) { file.json = JSON.parse(file.content); @@ -7,7 +9,9 @@ module.exports = function(app, base, env) { }); base.preWrite(/\.js(on|hintrc)$/, function(file, next) { + file.json = sortArrays(file.json); file.content = JSON.stringify(file.json, null, 2); + file.content += '\n'; next(); }); }; From 8ac285e3d8980e322d6e694f6e230eb02f55ce43 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 19 Dec 2015 20:44:05 -0500 Subject: [PATCH 135/274] update `timestamp` per composer-runtimes changes --- lib/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index bbabcb1..c288293 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -48,7 +48,7 @@ require = fn; utils.timestamp = function(msg) { - var time = ' ' + utils.gray(utils.stamp('HH:mm:ss.ms', new Date())); + var time = '[' + utils.gray(utils.stamp('HH:mm:ss', new Date())) + ']'; return console.log(time, msg); }; From 118d7ac6dfc9e2635b9bd9e0806b4d3f441060d9 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 19 Dec 2015 20:45:14 -0500 Subject: [PATCH 136/274] set runtimes options, update `load-pkg` signature, minor edits --- index.js | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/index.js b/index.js index 6dd7f4a..a45ade5 100644 --- a/index.js +++ b/index.js @@ -18,7 +18,7 @@ var cli = require('base-cli'); var store = require('base-store'); var pipeline = require('base-pipeline'); var loader = require('assemble-loader'); -var Core = require('assemble-core'); +var Base = require('assemble-core'); var ask = require('assemble-ask'); var config = require('./lib/config'); @@ -41,8 +41,8 @@ function Update(options) { if (!(this instanceof Update)) { return new Update(options); } - Core.call(this, options); - this.set('name', 'update'); + Base.call(this, options); + this.name = this.options.name || 'update'; this.isUpdate = true; this.initUpdate(this); } @@ -51,7 +51,7 @@ function Update(options) { * Inherit assemble-core */ -Core.extend(Update); +Base.extend(Update); /** * Initialize Updater defaults @@ -80,11 +80,16 @@ Update.prototype.initUpdate = function(base) { }); // load the package.json for the updater - this.data(utils.pkg(this.options.path)); + this.data(utils.pkg.sync(this.options.path)); config(this); - this.use(utils.runtimes()) - .use(locals('update')) + this.use(utils.runtimes({ + displayName: function (key) { + return this.name === key ? key : (this.name + ':' + key); + } + })) + + this.use(locals('update')) .use(store()) .use(pipeline()) .use(loader()) @@ -162,14 +167,10 @@ Update.prototype.updater = function(name, app) { app.use(utils.runtimes({ displayName: function(key) { - return utils.cyan(name + ':' + key); + return name + ':' + key; } })); - app.on('task:starting', function() { - console.log('fooo') - }) - this.emit('updater', name, app); this.updaters[name] = app; return app; From 5c6411000873ea4b75ab808767b5088ea9787560 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 19 Dec 2015 20:45:19 -0500 Subject: [PATCH 137/274] update deps --- package.json | 55 ++++++++++++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index 7ba0c44..d7a5814 100644 --- a/package.json +++ b/package.json @@ -40,17 +40,17 @@ "ansi-red": "^0.1.1", "ansi-yellow": "^0.1.1", "assemble-ask": "^0.1.4", - "assemble-core": "^0.2.0", - "assemble-loader": "^0.2.4", + "assemble-core": "^0.7.0", + "assemble-loader": "^0.2.5", "async": "^1.5.0", - "base-cli": "^0.3.0", - "base-config": "^0.3.0", - "base-methods": "^0.4.1", + "base-cli": "^0.4.0", + "base-config": "^0.3.2", + "base-methods": "^0.6.1", "base-options": "^0.5.4", "base-pipeline": "^0.1.4", - "base-store": "^0.2.0", - "composer-runtimes": "^0.5.1", - "cwd": "^0.8.4", + "base-store": "^0.3.1", + "composer-runtimes": "^0.7.0", + "cwd": "^0.9.1", "define-property": "^0.2.5", "delete": "^0.2.1", "engine-base": "^0.1.2", @@ -59,30 +59,31 @@ "export-files": "^2.1.0", "extend-shallow": "^2.0.1", "for-own": "^0.1.3", - "get-value": "^2.0.0", + "get-value": "^2.0.2", "global-modules": "^0.2.0", - "gulp-jshint": "^1.12.0", + "gulp-jshint": "^2.0.0", "jshint-stylish": "^2.1.0", - "lazy-cache": "^0.2.4", - "load-pkg": "^2.0.1", - "look-up": "^0.8.1", + "lazy-cache": "^1.0.2", + "load-pkg": "^3.0.1", + "look-up": "^0.8.2", "map-config": "^0.3.0", "matched": "^0.3.2", - "micromatch": "^2.3.2", + "micromatch": "^2.3.7", "minimist": "^1.2.0", "object.omit": "^2.0.0", "object.pick": "^1.1.1", "parser-front-matter": "^1.3.0", - "project-name": "^0.2.1", - "question-cache": "^0.3.3", + "project-name": "^0.2.3", + "question-cache": "^0.3.4", "resolve-dir": "^0.1.0", "resolve-modules": "^0.1.0", - "rimraf": "^2.4.3", - "set-value": "^0.3.1", + "rimraf": "^2.4.4", + "set-value": "^0.3.2", + "sort-object-arrays": "^0.1.0", "stream-exhaust": "^1.0.1", "success-symbol": "^0.1.0", "through2": "^2.0.0", - "time-stamp": "^0.1.1", + "time-stamp": "^0.1.3", "union-value": "^0.2.1", "update-copyright": "^0.1.0", "use": "^1.1.2", @@ -91,20 +92,20 @@ "devDependencies": { "buffer-equal": "0.0.1", "consolidate": "^0.13.1", - "coveralls": "^2.11.4", + "coveralls": "^2.11.6", "engine-handlebars": "^0.8.0", "event-stream": "^3.3.2", "graceful-fs": "^4.1.2", "gulp": "^3.9.0", - "gulp-eslint": "^1.1.0", - "gulp-istanbul": "^0.10.2", + "gulp-eslint": "^1.1.1", + "gulp-istanbul": "^0.10.3", "gulp-mocha": "^2.2.0", "is-buffer": "^1.1.0", - "istanbul": "^0.4.0", - "kind-of": "^2.0.1", - "mocha": "*", + "istanbul": "^0.4.1", + "kind-of": "^3.0.2", + "mocha": "^2.3.4", "resolve-glob": "^0.1.7", - "should": "*", + "should": "^8.0.2", "sinon": "^1.17.2", "swig": "^1.4.2", "vinyl": "^1.1.0" @@ -152,4 +153,4 @@ "aaa": "yyy" } } -} \ No newline at end of file +} From 90727444d87d06e8af9ad4a9f8b032cbf4d8f358 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 19 Dec 2015 21:04:22 -0500 Subject: [PATCH 138/274] use eslint --- lib/tasks/jshint.js | 19 ------------------- lib/tasks/lint.js | 2 +- 2 files changed, 1 insertion(+), 20 deletions(-) delete mode 100644 lib/tasks/jshint.js diff --git a/lib/tasks/jshint.js b/lib/tasks/jshint.js deleted file mode 100644 index 8dafe52..0000000 --- a/lib/tasks/jshint.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; - -var through = require('through2'); -var jshint = require('gulp-jshint'); -var stylish = require('jshint-stylish'); - -module.exports = function(app, base, env) { - return function() { - return base.toStream('files') - .pipe(through.obj(function(file, enc, cb) { - if (/\.js$/.test(file.path)) { - this.push(file); - } - cb(); - })) - .pipe(jshint()) - .pipe(jshint.reporter(stylish)); - }; -}; diff --git a/lib/tasks/lint.js b/lib/tasks/lint.js index 5e04d25..3c4fe17 100644 --- a/lib/tasks/lint.js +++ b/lib/tasks/lint.js @@ -13,6 +13,6 @@ module.exports = function(app, base, env) { cb(); })) .pipe(eslint()) - .pipe(eslint.format()); + .pipe(eslint.format()) }; }; From 061aa804329ecbb5a9a10d08bbe35ba7f7e439f4 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 19 Dec 2015 21:08:16 -0500 Subject: [PATCH 139/274] lint tests --- test/app.collection.js | 3 +-- test/views.js | 12 ++++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/test/app.collection.js b/test/app.collection.js index 1fc0aa4..0521333 100644 --- a/test/app.collection.js +++ b/test/app.collection.js @@ -129,11 +129,10 @@ describe('collection', function () { app = new App(); app.engine('tmpl', require('engine-base')); app.create('pages'); + app.cache.data = {}; }); it('should render a view with inherited app.render', function (done) { - app.cache.data = {}; - app.page('test/fixtures/templates/a.tmpl') .use(function(view) { view.contents = fs.readFileSync(view.path); diff --git a/test/views.js b/test/views.js index 8f99b7d..0a25513 100644 --- a/test/views.js +++ b/test/views.js @@ -335,18 +335,18 @@ describe('views', function () { }); it('should load an array of items from an event:', function () { - var pages = new Views(); + var collection = new Views(); - pages.on('addList', function (list) { + collection.on('addList', function(list) { while (list.length) { - pages.addView({path: list.pop()}); + collection.addView({path: list.pop()}); } this.loaded = true; }); - pages.addList(['a.txt', 'b.txt', 'c.txt']); - assert(pages.views.hasOwnProperty('a.txt')); - assert(pages.views['a.txt'].path === 'a.txt'); + collection.addList(['a.txt', 'b.txt', 'c.txt']); + assert(collection.views.hasOwnProperty('a.txt')); + assert(collection.views['a.txt'].path === 'a.txt'); }); it('should load an array of items from the addList callback:', function () { From 6e33d134b4ee4183ebd52c2316f42ddf912eccd8 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 19 Dec 2015 21:12:12 -0500 Subject: [PATCH 140/274] run update --- .travis.yml | 1 + package.json | 38 +++++++++++++++++++++----------------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/.travis.yml b/.travis.yml index d6e658e..6caa76a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ sudo: false language: node_js node_js: - "stable" + - "4" - "0.12" - "0.10" matrix: diff --git a/package.json b/package.json index d7a5814..ededf7b 100644 --- a/package.json +++ b/package.json @@ -5,12 +5,12 @@ "homepage": "https://github.com/jonschlinkert/update-next", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", "authors": [ - "Jon Schlinkert (https://github.com/jonschlinkert)", - "Brian Woodward (https://github.com/doowb)" + "Brian Woodward (https://github.com/doowb)", + "Jon Schlinkert (https://github.com/jonschlinkert)" ], "contributors": [ - "Jon Schlinkert (https://github.com/jonschlinkert)", - "Brian Woodward (https://github.com/doowb)" + "Brian Woodward (https://github.com/doowb)", + "Jon Schlinkert (https://github.com/jonschlinkert)" ], "repository": "jonschlinkert/update", "bugs": { @@ -18,8 +18,8 @@ }, "license": "MIT", "files": [ - "index.js", "bin", + "index.js", "lib/" ], "main": "index.js", @@ -61,8 +61,6 @@ "for-own": "^0.1.3", "get-value": "^2.0.2", "global-modules": "^0.2.0", - "gulp-jshint": "^2.0.0", - "jshint-stylish": "^2.1.0", "lazy-cache": "^1.0.2", "load-pkg": "^3.0.1", "look-up": "^0.8.2", @@ -76,7 +74,6 @@ "project-name": "^0.2.3", "question-cache": "^0.3.4", "resolve-dir": "^0.1.0", - "resolve-modules": "^0.1.0", "rimraf": "^2.4.4", "set-value": "^0.3.2", "sort-object-arrays": "^0.1.0", @@ -103,9 +100,9 @@ "is-buffer": "^1.1.0", "istanbul": "^0.4.1", "kind-of": "^3.0.2", - "mocha": "^2.3.4", + "mocha": "*", "resolve-glob": "^0.1.7", - "should": "^8.0.2", + "should": "*", "sinon": "^1.17.2", "swig": "^1.4.2", "vinyl": "^1.1.0" @@ -119,24 +116,31 @@ "verb": { "related": { "list": [ + "assemble", + "boilerplate", "composer", "generate", - "boilerplate", "scaffold", "templates", - "verb", - "assemble", - "update" + "update", + "verb" ], "description": "" }, "reflinks": [ - "scaffold", "boilerplate", - "template", + "scaffold", "template", "verb" - ] + ], + "plugins": [ + "gulp-format-md" + ], + "data": { + "author": { + "username": "jonschlinkert" + } + } }, "update": { "note": "this is just pseudo data for tests. I'll update with real stuff soon.", From e4a9e5cd4de1fb23b68cf8f34b10a552cc824e7a Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 19 Dec 2015 21:12:18 -0500 Subject: [PATCH 141/274] run verb --- .verb.md | 2 +- README.md | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.verb.md b/.verb.md index 9796e9a..bc37ef3 100644 --- a/.verb.md +++ b/.verb.md @@ -95,7 +95,7 @@ function foo(options) { ## License {%= copyright() %} -{%= license() %} +{%= license %} *** diff --git a/README.md b/README.md index bf2d6b0..bb7d579 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# update-next [![NPM version](https://badge.fury.io/js/update-next.svg)](http://badge.fury.io/js/update-next) +# update-next [![NPM version](https://img.shields.io/npm/v/update-next.svg)](https://www.npmjs.com/package/update-next) [![Build Status](https://img.shields.io/travis/jonschlinkert/update-next.svg)](https://travis-ci.org/jonschlinkert/update-next) > Update @@ -79,7 +79,7 @@ var update = new Update(); * [assemble](https://www.npmjs.com/package/assemble): Static site generator for Grunt.js, Yeoman and Node.js. Used by Zurb Foundation, Zurb Ink, H5BP/Effeckt,… [more](https://www.npmjs.com/package/assemble) | [homepage](http://assemble.io) * [boilerplate](https://www.npmjs.com/package/boilerplate): Tools and conventions for authoring and publishing boilerplates that can be generated by any build… [more](https://www.npmjs.com/package/boilerplate) | [homepage](http://boilerplates.io) * [composer](https://www.npmjs.com/package/composer): API-first task runner with three methods: task, run and watch. | [homepage](https://github.com/jonschlinkert/composer) -* [generate](https://www.npmjs.com/package/generate): Project generator, for node.js. | [homepage](https://github.com/generate/generate) +* [generate](https://www.npmjs.com/package/generate): Fast, composable, highly extendable project generator for node.js | [homepage](https://github.com/jonschlinkert/generate) * [scaffold](https://www.npmjs.com/package/scaffold): Conventions and API for creating scaffolds that can by used by any build system or… [more](https://www.npmjs.com/package/scaffold) | [homepage](https://github.com/jonschlinkert/scaffold) * [templates](https://www.npmjs.com/package/templates): System for creating and managing template collections, and rendering templates with any node.js template engine.… [more](https://www.npmjs.com/package/templates) | [homepage](https://github.com/jonschlinkert/templates) * [update](https://www.npmjs.com/package/update): Update the year in all files in a project using glob patterns. | [homepage](https://github.com/jonschlinkert/update) @@ -87,13 +87,13 @@ var update = new Update(); ## Authoring -### Create your own updater +### Updaters -#### tasks +#### Tasks -#### tasks +#### Middleware -#### plugins +#### Plugins > Updater plugins follow the same signature as gulp plugins @@ -134,14 +134,14 @@ Pull requests and stars are always welcome. For bugs and feature requests, [plea **Jon Schlinkert** -+ [github/jonschlinkert](https://github.com/jonschlinkert) -+ [twitter/jonschlinkert](http://twitter.com/jonschlinkert) +* [github/jonschlinkert](https://github.com/jonschlinkert) +* [twitter/jonschlinkert](http://twitter.com/jonschlinkert) ## License -Copyright © 2015 Jon Schlinkert +Copyright © 2015 [Jon Schlinkert](https://github.com/jonschlinkert) Released under the MIT license. *** -_This file was generated by [verb-cli](https://github.com/assemble/verb-cli) on November 02, 2015._ \ No newline at end of file +_This file was generated by [verb](https://github.com/verbose/verb) on December 19, 2015._ \ No newline at end of file From 939929a0be695311109ac8a89a17136e93a1cce4 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 19 Dec 2015 21:13:55 -0500 Subject: [PATCH 142/274] update name --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index bb7d579..495f94e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# update-next [![NPM version](https://img.shields.io/npm/v/update-next.svg)](https://www.npmjs.com/package/update-next) [![Build Status](https://img.shields.io/travis/jonschlinkert/update-next.svg)](https://travis-ci.org/jonschlinkert/update-next) +# update [![NPM version](https://img.shields.io/npm/v/update.svg)](https://www.npmjs.com/package/update) [![Build Status](https://img.shields.io/travis/jonschlinkert/update.svg)](https://travis-ci.org/jonschlinkert/update) > Update @@ -9,7 +9,7 @@ Install globally with [npm](https://www.npmjs.com/) ```sh -$ npm i -g update-next +$ npm i -g update ``` ```sh @@ -50,11 +50,11 @@ app.onStream(/lib\//, rename); Install with [npm](https://www.npmjs.com/) ```sh -$ npm i update-next --save +$ npm i update --save ``` ```js -var update = require('update-next'); +var update = require('update'); ``` ## API @@ -128,7 +128,7 @@ $ npm i -d && npm test ## Contributing -Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](https://github.com/jonschlinkert/update-next/issues/new). +Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](https://github.com/jonschlinkert/update/issues/new). ## Author From 2072e76a281b190dc89b9740cb8bd4f29f0faa0a Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 19 Dec 2015 21:14:01 -0500 Subject: [PATCH 143/274] 0.4.0 --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index ededf7b..44efd78 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "update-next", + "name": "update", "description": "Update", - "version": "0.1.0", - "homepage": "https://github.com/jonschlinkert/update-next", + "version": "0.4.0", + "homepage": "https://github.com/jonschlinkert/update", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", "authors": [ "Brian Woodward (https://github.com/doowb)", From d9ebf528718f55144b9bae2445c2294ea4f2aa18 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 4 Jan 2016 07:19:12 -0500 Subject: [PATCH 144/274] lint --- .eslintrc | 125 ---------------------- .eslintrc.json | 278 +++++++++++++++++++++++++++++++++++++++++++++++++ .travis.yml | 1 + LICENSE | 2 +- 4 files changed, 280 insertions(+), 126 deletions(-) delete mode 100644 .eslintrc create mode 100644 .eslintrc.json diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 7b5d047..0000000 --- a/.eslintrc +++ /dev/null @@ -1,125 +0,0 @@ -{ - "ecmaFeatures": { - "modules": true, - "experimentalObjectRestSpread": true - }, - - "env": { - "browser": false, - "es6": true, - "node": true, - "mocha": true - }, - - "globals": { - "document": false, - "navigator": false, - "window": false - }, - - "rules": { - "accessor-pairs": 2, - "arrow-spacing": [2, { "before": true, "after": true }], - "block-spacing": [2, "always"], - "brace-style": [2, "1tbs", { "allowSingleLine": true }], - "comma-dangle": [2, "never"], - "comma-spacing": [2, { "before": false, "after": true }], - "comma-style": [2, "last"], - "constructor-super": 2, - "curly": [2, "multi-line"], - "dot-location": [2, "property"], - "eol-last": 2, - "eqeqeq": [2, "allow-null"], - "generator-star-spacing": [2, { "before": true, "after": true }], - "handle-callback-err": [2, "^(err|error)$" ], - "indent": [2, 2, { "SwitchCase": 1 }], - "key-spacing": [2, { "beforeColon": false, "afterColon": true }], - "new-cap": [2, { "newIsCap": true, "capIsNew": false }], - "new-parens": 2, - "no-array-constructor": 2, - "no-caller": 2, - "no-class-assign": 2, - "no-cond-assign": 2, - "no-const-assign": 2, - "no-control-regex": 2, - "no-debugger": 2, - "no-delete-var": 2, - "no-dupe-args": 2, - "no-dupe-class-members": 2, - "no-dupe-keys": 2, - "no-duplicate-case": 2, - "no-empty-character-class": 2, - "no-empty-label": 2, - "no-eval": 2, - "no-ex-assign": 2, - "no-extend-native": 2, - "no-extra-bind": 2, - "no-extra-boolean-cast": 2, - "no-extra-parens": [2, "functions"], - "no-fallthrough": 2, - "no-floating-decimal": 2, - "no-func-assign": 2, - "no-implied-eval": 2, - "no-inner-declarations": [2, "functions"], - "no-invalid-regexp": 2, - "no-irregular-whitespace": 2, - "no-iterator": 2, - "no-label-var": 2, - "no-labels": 2, - "no-lone-blocks": 2, - "no-mixed-spaces-and-tabs": 2, - "no-multi-spaces": 2, - "no-multi-str": 2, - "no-multiple-empty-lines": [2, { "max": 1 }], - "no-native-reassign": 2, - "no-negated-in-lhs": 2, - "no-new": 2, - "no-new-func": 2, - "no-new-object": 2, - "no-new-require": 2, - "no-new-wrappers": 2, - "no-obj-calls": 2, - "no-octal": 2, - "no-octal-escape": 2, - "no-proto": 0, - "no-redeclare": 2, - "no-regex-spaces": 2, - "no-return-assign": 2, - "no-self-compare": 2, - "no-sequences": 2, - "no-shadow-restricted-names": 2, - "no-spaced-func": 2, - "no-sparse-arrays": 2, - "no-this-before-super": 2, - "no-throw-literal": 2, - "no-trailing-spaces": 0, - "no-undef": 2, - "no-undef-init": 2, - "no-unexpected-multiline": 2, - "no-unneeded-ternary": [2, { "defaultAssignment": false }], - "no-unreachable": 2, - "no-unused-vars": [2, { "vars": "all", "args": "none" }], - "no-useless-call": 0, - "no-with": 2, - "one-var": [0, { "initialized": "never" }], - "operator-linebreak": [0, "after", { "overrides": { "?": "before", ":": "before" } }], - "padded-blocks": [0, "never"], - "quotes": [2, "single", "avoid-escape"], - "radix": 2, - "semi": [2, "always"], - "semi-spacing": [2, { "before": false, "after": true }], - "space-after-keywords": [2, "always"], - "space-before-blocks": [2, "always"], - "space-before-function-paren": [2, "never"], - "space-before-keywords": [2, "always"], - "space-in-parens": [2, "never"], - "space-infix-ops": 2, - "space-return-throw-case": 2, - "space-unary-ops": [2, { "words": true, "nonwords": false }], - "spaced-comment": [0, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!", ","] }], - "use-isnan": 2, - "valid-typeof": 2, - "wrap-iife": [2, "any"], - "yoda": [2, "never"] - } -} diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..cc6a867 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,278 @@ +{ + "ecmaFeatures": { + "modules": true, + "experimentalObjectRestSpread": true + }, + "env": { + "browser": false, + "es6": true, + "node": true, + "mocha": true + }, + "globals": { + "document": false, + "navigator": false, + "window": false + }, + "rules": { + "accessor-pairs": 2, + "arrow-spacing": [ + 2, + { + "before": true, + "after": true + } + ], + "block-spacing": [ + 2, + "always" + ], + "brace-style": [ + 2, + "1tbs", + { + "allowSingleLine": true + } + ], + "comma-dangle": [ + 2, + "never" + ], + "comma-spacing": [ + 2, + { + "before": false, + "after": true + } + ], + "comma-style": [ + 2, + "last" + ], + "constructor-super": 2, + "curly": [ + 2, + "multi-line" + ], + "dot-location": [ + 2, + "property" + ], + "eol-last": 2, + "eqeqeq": [ + 2, + "allow-null" + ], + "generator-star-spacing": [ + 2, + { + "before": true, + "after": true + } + ], + "handle-callback-err": [ + 2, + "^(err|error)$" + ], + "indent": [ + 2, + 2, + { + "SwitchCase": 1 + } + ], + "key-spacing": [ + 2, + { + "beforeColon": false, + "afterColon": true + } + ], + "new-cap": [ + 2, + { + "newIsCap": true, + "capIsNew": false + } + ], + "new-parens": 2, + "no-array-constructor": 2, + "no-caller": 2, + "no-class-assign": 2, + "no-cond-assign": 2, + "no-const-assign": 2, + "no-control-regex": 2, + "no-debugger": 2, + "no-delete-var": 2, + "no-dupe-args": 2, + "no-dupe-class-members": 2, + "no-dupe-keys": 2, + "no-duplicate-case": 2, + "no-empty-character-class": 2, + "no-empty-label": 2, + "no-eval": 2, + "no-ex-assign": 2, + "no-extend-native": 2, + "no-extra-bind": 2, + "no-extra-boolean-cast": 2, + "no-extra-parens": [ + 2, + "functions" + ], + "no-fallthrough": 2, + "no-floating-decimal": 2, + "no-func-assign": 2, + "no-implied-eval": 2, + "no-inner-declarations": [ + 2, + "functions" + ], + "no-invalid-regexp": 2, + "no-irregular-whitespace": 2, + "no-iterator": 2, + "no-label-var": 2, + "no-labels": 2, + "no-lone-blocks": 2, + "no-mixed-spaces-and-tabs": 2, + "no-multi-spaces": 2, + "no-multi-str": 2, + "no-multiple-empty-lines": [ + 2, + { + "max": 1 + } + ], + "no-native-reassign": 2, + "no-negated-in-lhs": 2, + "no-new": 2, + "no-new-func": 2, + "no-new-object": 2, + "no-new-require": 2, + "no-new-wrappers": 2, + "no-obj-calls": 2, + "no-octal": 2, + "no-octal-escape": 2, + "no-proto": 0, + "no-redeclare": 2, + "no-regex-spaces": 2, + "no-return-assign": 2, + "no-self-compare": 2, + "no-sequences": 2, + "no-shadow-restricted-names": 2, + "no-spaced-func": 2, + "no-sparse-arrays": 2, + "no-this-before-super": 2, + "no-throw-literal": 2, + "no-trailing-spaces": 0, + "no-undef": 2, + "no-undef-init": 2, + "no-unexpected-multiline": 2, + "no-unneeded-ternary": [ + 2, + { + "defaultAssignment": false + } + ], + "no-unreachable": 2, + "no-unused-vars": [ + 2, + { + "vars": "all", + "args": "none" + } + ], + "no-useless-call": 0, + "no-with": 2, + "one-var": [ + 0, + { + "initialized": "never" + } + ], + "operator-linebreak": [ + 0, + "after", + { + "overrides": { + "?": "before", + ":": "before" + } + } + ], + "padded-blocks": [ + 0, + "never" + ], + "quotes": [ + 2, + "single", + "avoid-escape" + ], + "radix": 2, + "semi": [ + 2, + "always" + ], + "semi-spacing": [ + 2, + { + "before": false, + "after": true + } + ], + "space-after-keywords": [ + 2, + "always" + ], + "space-before-blocks": [ + 2, + "always" + ], + "space-before-function-paren": [ + 2, + "never" + ], + "space-before-keywords": [ + 2, + "always" + ], + "space-in-parens": [ + 2, + "never" + ], + "space-infix-ops": 2, + "space-return-throw-case": 2, + "space-unary-ops": [ + 2, + { + "words": true, + "nonwords": false + } + ], + "spaced-comment": [ + 0, + "always", + { + "markers": [ + "global", + "globals", + "eslint", + "eslint-disable", + "*package", + "!", + "," + ] + } + ], + "use-isnan": 2, + "valid-typeof": 2, + "wrap-iife": [ + 2, + "any" + ], + "yoda": [ + 2, + "never" + ] + } +} diff --git a/.travis.yml b/.travis.yml index 6caa76a..09768f0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ sudo: false language: node_js node_js: - "stable" + - "5" - "4" - "0.12" - "0.10" diff --git a/LICENSE b/LICENSE index 65f90ac..1e49edf 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015, Jon Schlinkert. +Copyright (c) 2015-2016, Jon Schlinkert. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 3c74eb6ee9c129526bffd24ef12652258f8281a5 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 4 Jan 2016 07:19:35 -0500 Subject: [PATCH 145/274] add eslint --- lib/middleware/json.js | 7 ++++++- lib/tasks/del.js | 2 +- updatefile.js | 6 +++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/middleware/json.js b/lib/middleware/json.js index 9ce518f..b7cbd14 100644 --- a/lib/middleware/json.js +++ b/lib/middleware/json.js @@ -3,12 +3,17 @@ var sortArrays = require('sort-object-arrays'); module.exports = function(app, base, env) { - base.onLoad(/\.js(on|hintrc)$/, function(file, next) { + base.onLoad(/(\.eslintrc|\.js(on|hintrc))$/, function(file, next) { file.json = JSON.parse(file.content); next(); }); base.preWrite(/\.js(on|hintrc)$/, function(file, next) { + if (typeof file.json === 'undefined') { + next(new Error('json middleware expects `file.json` to be an object')); + return; + } + file.json = sortArrays(file.json); file.content = JSON.stringify(file.json, null, 2); file.content += '\n'; diff --git a/lib/tasks/del.js b/lib/tasks/del.js index acd270d..98b46cd 100644 --- a/lib/tasks/del.js +++ b/lib/tasks/del.js @@ -4,7 +4,7 @@ var path = require('path'); var async = require('async'); var rimraf = require('rimraf'); -var list = ['.npmignore', '.jshintrc']; +var list = ['.npmignore', '.jshintrc', '.eslintrc']; module.exports = function(app, base, env) { var files = base.option('delete') || list; diff --git a/updatefile.js b/updatefile.js index e3004c0..28fa62b 100644 --- a/updatefile.js +++ b/updatefile.js @@ -1,5 +1,9 @@ 'use strict'; +/** + * temporary test code + */ + module.exports = function (app) { - app.task('jshint', require('./lib/tasks/jshint')); + app.task('lint', require('./lib/tasks/lint')); }; From 46739d94417a1a4eae448e6583e3a77704feea4c Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 4 Jan 2016 07:33:22 -0500 Subject: [PATCH 146/274] ensure `name` is set correctly on runtimes --- index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index a45ade5..a7eb51d 100644 --- a/index.js +++ b/index.js @@ -85,7 +85,7 @@ Update.prototype.initUpdate = function(base) { this.use(utils.runtimes({ displayName: function (key) { - return this.name === key ? key : (this.name + ':' + key); + return base.name === key ? key : (base.name + ':' + key); } })) @@ -167,7 +167,7 @@ Update.prototype.updater = function(name, app) { app.use(utils.runtimes({ displayName: function(key) { - return name + ':' + key; + return app.name === key ? key : (app.name + ':' + key); } })); From c5e8c00f8ca620367ec6e20c0290493b146e00c2 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 4 Jan 2016 07:33:31 -0500 Subject: [PATCH 147/274] update deps --- package.json | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 44efd78..a311316 100644 --- a/package.json +++ b/package.json @@ -40,43 +40,43 @@ "ansi-red": "^0.1.1", "ansi-yellow": "^0.1.1", "assemble-ask": "^0.1.4", - "assemble-core": "^0.7.0", - "assemble-loader": "^0.2.5", - "async": "^1.5.0", + "assemble-core": "^0.8.0", + "assemble-loader": "^0.2.6", + "async": "^1.5.1", "base-cli": "^0.4.0", - "base-config": "^0.3.2", - "base-methods": "^0.6.1", + "base-config": "^0.3.3", + "base-methods": "^0.6.2", "base-options": "^0.5.4", "base-pipeline": "^0.1.4", - "base-store": "^0.3.1", + "base-store": "^0.3.2", "composer-runtimes": "^0.7.0", "cwd": "^0.9.1", "define-property": "^0.2.5", - "delete": "^0.2.1", + "delete": "^0.3.0", "engine-base": "^0.1.2", - "expand-args": "^0.3.0", - "expand-object": "^0.4.0", + "expand-args": "^0.3.1", + "expand-object": "^0.4.1", "export-files": "^2.1.0", "extend-shallow": "^2.0.1", "for-own": "^0.1.3", "get-value": "^2.0.2", "global-modules": "^0.2.0", - "lazy-cache": "^1.0.2", + "lazy-cache": "^1.0.3", "load-pkg": "^3.0.1", "look-up": "^0.8.2", "map-config": "^0.3.0", - "matched": "^0.3.2", + "matched": "^0.4.1", "micromatch": "^2.3.7", "minimist": "^1.2.0", "object.omit": "^2.0.0", "object.pick": "^1.1.1", "parser-front-matter": "^1.3.0", "project-name": "^0.2.3", - "question-cache": "^0.3.4", + "question-cache": "^0.3.5", "resolve-dir": "^0.1.0", - "rimraf": "^2.4.4", + "rimraf": "^2.5.0", "set-value": "^0.3.2", - "sort-object-arrays": "^0.1.0", + "sort-object-arrays": "^0.1.1", "stream-exhaust": "^1.0.1", "success-symbol": "^0.1.0", "through2": "^2.0.0", @@ -87,7 +87,7 @@ "write": "^0.2.1" }, "devDependencies": { - "buffer-equal": "0.0.1", + "buffer-equal": "^1.0.0", "consolidate": "^0.13.1", "coveralls": "^2.11.6", "engine-handlebars": "^0.8.0", @@ -95,9 +95,10 @@ "graceful-fs": "^4.1.2", "gulp": "^3.9.0", "gulp-eslint": "^1.1.1", + "gulp-format-md": "^0.1.4", "gulp-istanbul": "^0.10.3", "gulp-mocha": "^2.2.0", - "is-buffer": "^1.1.0", + "is-buffer": "^1.1.1", "istanbul": "^0.4.1", "kind-of": "^3.0.2", "mocha": "*", From f0f621c4089fe44238167d421f00de5c2dc89cbe Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 4 Jan 2016 07:33:41 -0500 Subject: [PATCH 148/274] formatting, typo --- bin/update.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/update.js b/bin/update.js index 7c643f7..b59fb41 100755 --- a/bin/update.js +++ b/bin/update.js @@ -13,7 +13,7 @@ var runner = new Runner(argv); runner.base.option(argv); runner.option(argv); -var task = cmd.list ? ['list', 'default'] : 'default'; +var task = cmd.list ? ['list', 'default'] : ['default']; runner.on('*', function(method, key, val) { console.log(method + ':', key, val); @@ -33,5 +33,5 @@ runner.base.task('run', function(cb) { runner.base.build(task, function(err) { if (err) return console.error(err); - utils.timestamp('done ' + utils.green(utils.successSymbol)); + utils.timestamp('finished ' + utils.green(utils.successSymbol)); }); From bf89ea36e82a00fce6173a0ba3cd56224529c682 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 4 Jan 2016 07:35:38 -0500 Subject: [PATCH 149/274] 0.4.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a311316..0d94d38 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "update", "description": "Update", - "version": "0.4.0", + "version": "0.4.1", "homepage": "https://github.com/jonschlinkert/update", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", "authors": [ From b95f46d0025624ccf22390b96caa43d495600e8b Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 4 Jan 2016 08:11:39 -0500 Subject: [PATCH 150/274] remove `ask` plugin for now --- index.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/index.js b/index.js index a7eb51d..9c519c3 100644 --- a/index.js +++ b/index.js @@ -19,7 +19,6 @@ var store = require('base-store'); var pipeline = require('base-pipeline'); var loader = require('assemble-loader'); var Base = require('assemble-core'); -var ask = require('assemble-ask'); var config = require('./lib/config'); var locals = require('./lib/locals'); @@ -93,7 +92,6 @@ Update.prototype.initUpdate = function(base) { .use(store()) .use(pipeline()) .use(loader()) - .use(ask()) .use(cli()) .use(utils.defaults()) From c1279aefd76ca540d5806876b6fa294fa670a87a Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 4 Jan 2016 08:11:51 -0500 Subject: [PATCH 151/274] update unit tests --- .verb.md | 12 +- README.md | 25 +- lib/tasks/dest.js | 3 + package.json | 4 +- test/app.applyLayout.js | 62 +++-- test/app.collection.compile.js | 18 +- test/app.collection.js | 97 ++++--- test/app.collection.render.js | 56 ++-- test/app.compile.js | 18 +- test/app.copy.js | 18 +- test/app.create.js | 144 +++++++--- test/app.data.js | 24 +- test/app.dest.js | 68 ++--- test/app.engines.js | 97 +++---- test/app.events.js | 100 +++++-- test/app.get-set.js | 24 +- test/app.handle.js | 16 +- test/app.handlers.js | 26 +- test/app.js | 76 +++--- test/app.list.compile.js | 10 +- test/app.list.js | 54 ++-- test/app.lookups.js | 54 ++-- test/app.middleware.js | 22 +- test/app.onLoad.js | 48 ++++ test/app.option.js | 8 +- test/app.render.js | 32 +-- test/app.renderFile.js | 46 ++-- test/app.route.js | 24 +- test/app.src.js | 104 +++---- test/app.symlink.js | 4 +- test/app.task.js | 65 ++--- test/app.toStream.js | 20 +- test/app.use.js | 88 +++--- test/app.view.compile.js | 12 +- test/app.view.render.js | 30 +- test/app.watch.js | 16 +- test/collection.engines.js | 67 +++-- test/collection.events.js | 8 +- test/collection.getView.js | 34 +++ test/collection.js | 157 +++++------ test/collection.options.js | 8 +- test/collection.render.js | 48 ++-- test/collection.use.js | 60 ++-- test/fixtures/copy/a.txt | 1 + test/fixtures/copy/b.txt | 1 + test/fixtures/copy/c.txt | 1 + test/fixtures/pipeline/a.js | 2 +- test/fixtures/pipeline/b.js | 2 +- test/fixtures/pipeline/c.js | 2 +- test/fixtures/pipeline/d.js | 2 +- test/group.js | 90 +++--- test/handlers.js | 16 +- test/helpers.js | 486 ++++++++++++++++++++------------- test/item.js | 138 +++++----- test/layouts.js | 32 +-- test/list.js | 164 +++++------ test/list.render.js | 48 ++-- test/list.use.js | 60 ++-- test/mergePartials.js | 24 +- test/partials.js | 54 ++-- test/questions.js | 18 +- test/renameKey.js | 84 +++--- test/render.js | 38 +-- test/routes.js | 30 +- test/store.js | 226 +++++++-------- test/support/ignore.js | 2 +- test/support/index.js | 4 +- test/view.content.js | 8 +- test/view.events.js | 8 +- test/view.js | 143 +++++----- test/view.methods.js | 19 +- test/view.option.js | 8 +- test/view.render.js | 24 +- test/view.set.js | 13 +- test/view.use.js | 34 +-- test/viewTypes.js | 31 ++- test/views.js | 174 ++++++------ test/views.use.js | 60 ++-- 78 files changed, 2131 insertions(+), 1823 deletions(-) create mode 100644 test/app.onLoad.js create mode 100644 test/collection.getView.js create mode 100644 test/fixtures/copy/a.txt create mode 100644 test/fixtures/copy/b.txt create mode 100644 test/fixtures/copy/c.txt diff --git a/.verb.md b/.verb.md index bc37ef3..c414fa3 100644 --- a/.verb.md +++ b/.verb.md @@ -65,13 +65,12 @@ var update = require('{%= name %}'); **Example** ```js -function foo(options) { - return through.obj(function (file, enc, cb) { +function myPlugin(options) { + return through.obj(function(file, enc, next) { var str = file.contents.toString(); - // do stuff + // do stuff to `file` file.contents = new Buffer(file.contents); - this.push(file); - cb(); + next(null, file); }); } ``` @@ -80,10 +79,9 @@ function foo(options) { 1. Name your project following the convention: `updater-*` 2. Don't use dots in the name (e.g `.js`) -3. Make sure you add `updater` to the keywords in package.json +3. Make sure you add `updater` to the keywords in `package.json` 4. Tweet about your updater! - ## Running tests {%= include("tests") %} diff --git a/README.md b/README.md index 495f94e..6dba885 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# update [![NPM version](https://img.shields.io/npm/v/update.svg)](https://www.npmjs.com/package/update) [![Build Status](https://img.shields.io/travis/jonschlinkert/update.svg)](https://travis-ci.org/jonschlinkert/update) +# update [![NPM version](https://img.shields.io/npm/v/update.svg)](https://www.npmjs.com/package/update) + +[![Build Status](https://img.shields.io/travis/jonschlinkert/update.svg)](https://travis-ci.org/jonschlinkert/update) > Update @@ -47,7 +49,7 @@ app.onStream(/lib\//, rename); ### Install -Install with [npm](https://www.npmjs.com/) +Install with [npm](https://www.npmjs.com/): ```sh $ npm i update --save @@ -80,9 +82,9 @@ var update = new Update(); * [boilerplate](https://www.npmjs.com/package/boilerplate): Tools and conventions for authoring and publishing boilerplates that can be generated by any build… [more](https://www.npmjs.com/package/boilerplate) | [homepage](http://boilerplates.io) * [composer](https://www.npmjs.com/package/composer): API-first task runner with three methods: task, run and watch. | [homepage](https://github.com/jonschlinkert/composer) * [generate](https://www.npmjs.com/package/generate): Fast, composable, highly extendable project generator for node.js | [homepage](https://github.com/jonschlinkert/generate) -* [scaffold](https://www.npmjs.com/package/scaffold): Conventions and API for creating scaffolds that can by used by any build system or… [more](https://www.npmjs.com/package/scaffold) | [homepage](https://github.com/jonschlinkert/scaffold) +* [scaffold](https://www.npmjs.com/package/scaffold): Conventions and API for creating declarative configuration objects for project scaffolds - similar in format… [more](https://www.npmjs.com/package/scaffold) | [homepage](https://github.com/jonschlinkert/scaffold) * [templates](https://www.npmjs.com/package/templates): System for creating and managing template collections, and rendering templates with any node.js template engine.… [more](https://www.npmjs.com/package/templates) | [homepage](https://github.com/jonschlinkert/templates) -* [update](https://www.npmjs.com/package/update): Update the year in all files in a project using glob patterns. | [homepage](https://github.com/jonschlinkert/update) +* [update](https://www.npmjs.com/package/update): Update | [homepage](https://github.com/jonschlinkert/update) * [verb](https://www.npmjs.com/package/verb): Documentation generator for GitHub projects. Verb is extremely powerful, easy to use, and is used… [more](https://www.npmjs.com/package/verb) | [homepage](https://github.com/verbose/verb) ## Authoring @@ -100,13 +102,12 @@ var update = new Update(); **Example** ```js -function foo(options) { - return through.obj(function (file, enc, cb) { +function myPlugin(options) { + return through.obj(function(file, enc, next) { var str = file.contents.toString(); - // do stuff + // do stuff to `file` file.contents = new Buffer(file.contents); - this.push(file); - cb(); + next(null, file); }); } ``` @@ -115,7 +116,7 @@ function foo(options) { 1. Name your project following the convention: `updater-*` 2. Don't use dots in the name (e.g `.js`) -3. Make sure you add `updater` to the keywords in package.json +3. Make sure you add `updater` to the keywords in `package.json` 4. Tweet about your updater! ## Running tests @@ -139,9 +140,9 @@ Pull requests and stars are always welcome. For bugs and feature requests, [plea ## License -Copyright © 2015 [Jon Schlinkert](https://github.com/jonschlinkert) +Copyright © 2016 [Jon Schlinkert](https://github.com/jonschlinkert) Released under the MIT license. *** -_This file was generated by [verb](https://github.com/verbose/verb) on December 19, 2015._ \ No newline at end of file +_This file was generated by [verb](https://github.com/verbose/verb) on January 04, 2016._ \ No newline at end of file diff --git a/lib/tasks/dest.js b/lib/tasks/dest.js index 7422459..dc354e0 100644 --- a/lib/tasks/dest.js +++ b/lib/tasks/dest.js @@ -16,8 +16,11 @@ module.exports = function(app, base, env) { app.toStream('files') .on('error', cb) .pipe(handle('onStream')) + .on('error', cb) .pipe(app.pipeline(plugins)) + .on('error', cb) .pipe(handle('preWrite')) + .on('error', cb) .pipe(app.dest('.')) .pipe(utils.exhaust(handle('postWrite'))) .on('error', cb) diff --git a/package.json b/package.json index 0d94d38..bb3afd9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "update", - "description": "Update", + "description": "Easily keep anything in your project up-to-date by installing the updaters you want to use and running `update` in the command line! Update the copyright date, licence type, ensure that a project uses your latest eslint or jshint configuration, remove deprecated package.json fields, or anything you can think of!", "version": "0.4.1", "homepage": "https://github.com/jonschlinkert/update", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", @@ -39,7 +39,6 @@ "ansi-green": "^0.1.1", "ansi-red": "^0.1.1", "ansi-yellow": "^0.1.1", - "assemble-ask": "^0.1.4", "assemble-core": "^0.8.0", "assemble-loader": "^0.2.6", "async": "^1.5.1", @@ -90,6 +89,7 @@ "buffer-equal": "^1.0.0", "consolidate": "^0.13.1", "coveralls": "^2.11.6", + "data-store": "^0.12.1", "engine-handlebars": "^0.8.0", "event-stream": "^3.3.2", "graceful-fs": "^4.1.2", diff --git a/test/app.applyLayout.js b/test/app.applyLayout.js index 6fdf390..7c8d782 100644 --- a/test/app.applyLayout.js +++ b/test/app.applyLayout.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var assert = require('assert'); @@ -12,72 +14,72 @@ var page = { } }; -describe('helpers', function () { - describe('rendering', function () { - beforeEach(function () { +describe('helpers', function() { + describe('rendering', function() { + beforeEach(function() { app = new App(); app.engine('tmpl', require('engine-base')); - app.create('layout', {viewType: 'layout'}); + app.create('layout', { viewType: 'layout' }); app.create('page'); }); - it('should throw an error when a layout cannot be found:', function (done) { + it('should throw an error when a layout cannot be found:', function(cb) { app.layout('fofof.tmpl', {content: '..'}); app.page('a.tmpl', page) - .render(function (err) { - assert(err.message === 'Templates#layouts no layouts are registered, but one is defined: default.tmpl'); - done(); + .render(function(err) { + assert.equal(err.message, 'Templates#layouts layout "default.tmpl" was defined on view "a.tmpl"\nbut cannot be not found (common causes are incorrect glob patterns,\nrenameKey function modifying the key, and typos in search pattern)'); + cb(); }); }); - it('should emit an error when a layout cannot be found:', function (done) { + it('should emit an error when a layout cannot be found:', function(cb) { app.layout('fofof.tmpl', {content: '..'}); - app.on('error', function (err) { - assert(err.message === 'Templates#layouts no layouts are registered, but one is defined: default.tmpl'); - done(); + app.on('error', function(err) { + assert.equal(err.message, 'Templates#layouts layout "default.tmpl" was defined on view "a.tmpl"\nbut cannot be not found (common causes are incorrect glob patterns,\nrenameKey function modifying the key, and typos in search pattern)'); + cb(); }); app.page('a.tmpl', page) - .render(function () { + .render(function() { }); }); - it('should throw an error - layout defined but no layouts registered:', function (done) { + it('should throw an error - layout defined but no layouts registered:', function(cb) { app.page('a.tmpl', page) - .render(function (err) { - assert(err.message === 'Templates#layouts no layouts are registered, but one is defined: default.tmpl'); - done(); + .render(function(err) { + assert.equal(err.message, 'Templates#layouts layout "default.tmpl" was defined on view "a.tmpl"\nbut cannot be not found (common causes are incorrect glob patterns,\nrenameKey function modifying the key, and typos in search pattern)'); + cb(); }); }); - it('should emit an error - layout defined but no layouts registered:', function (done) { - app.on('error', function (err) { - assert(err.message === 'Templates#layouts no layouts are registered, but one is defined: default.tmpl'); - done(); + it('should emit an error - layout defined but no layouts registered:', function(cb) { + app.on('error', function(err) { + assert.equal(err.message, 'Templates#layouts layout "default.tmpl" was defined on view "a.tmpl"\nbut cannot be not found (common causes are incorrect glob patterns,\nrenameKey function modifying the key, and typos in search pattern)'); + cb(); }); app.page('a.tmpl', page) - .render(function () { + .render(function() { }); }); - it('should wrap a view with a layout (view.render):', function (done) { + it('should wrap a view with a layout (view.render):', function(cb) { app.layout('default.tmpl', {content: 'before {% body %} after'}); app.page('a.tmpl', page) - .render(function (err) { - if (err) return done(err); - done(); + .render(function(err) { + if (err) return cb(err); + cb(); }); }); - it('should wrap a view with a layout (app.render):', function (done) { + it('should wrap a view with a layout (app.render):', function(cb) { app.layout('default.tmpl', {content: 'before {% body %} after'}); app.page('a.tmpl', page); var view = app.pages.getView('a.tmpl'); - app.render(view, function (err, res) { - if (err) return done(err); + app.render(view, function(err, res) { + if (err) return cb(err); assert(res.contents.toString() === 'before Halle after'); - done(); + cb(); }); }); }); diff --git a/test/app.collection.compile.js b/test/app.collection.compile.js index 6bd595b..4d23e93 100644 --- a/test/app.collection.compile.js +++ b/test/app.collection.compile.js @@ -5,20 +5,20 @@ var App = support.resolve(); var Views = App.Views; var views; -describe('compile', function () { - beforeEach(function () { +describe('compile', function() { + beforeEach(function() { views = new Views(); }); - it('should throw an error when an engine cannot be found:', function () { + it('should throw an error when an engine cannot be found:', function() { views.addView('foo.bar', {content: '<%= name %>'}); var page = views.getView('foo.bar'); (function() { views.compile(page); - }).should.throw('Views#compile cannot find an engine for: .bar'); + }).should.throw('Views#compile cannot find engine: .bar'); }); - it('should compile a template:', function () { + it('should compile a template:', function() { views.engine('tmpl', require('engine-base')); views.addView('a.tmpl', {path: 'a.tmpl', content: '<%= a %>', a: 'b'}); @@ -27,7 +27,7 @@ describe('compile', function () { assert.equal(typeof view.fn, 'function'); }); - it('should compile a template by name:', function () { + it('should compile a template by name:', function() { views.engine('tmpl', require('engine-base')); views.addView('a.tmpl', {path: 'a.tmpl', content: '<%= a %>', a: 'b'}); @@ -35,16 +35,16 @@ describe('compile', function () { assert.equal(typeof view.fn, 'function'); }); - it('should throw an error when a callback is given:', function () { + it('should throw an error when a callback is given:', function() { views.engine('md', require('engine-base')); views.addView('foo.md', {content: '<%= name %>'}); var page = views.getView('foo.md'); (function() { - views.compile(page, function () {}); + views.compile(page, function() {}); }).should.throw('Views#compile is sync and does not take a callback function'); (function() { - views.compile(page, {}, function () {}); + views.compile(page, {}, function() {}); }).should.throw('Views#compile is sync and does not take a callback function'); }); }); diff --git a/test/app.collection.js b/test/app.collection.js index 0521333..ef8c548 100644 --- a/test/app.collection.js +++ b/test/app.collection.js @@ -11,37 +11,37 @@ var App = support.resolve(); var Collection = App.Collection; var app; -describe('collection', function () { - describe('method', function () { - beforeEach(function () { +describe('collection', function() { + describe('method', function() { + beforeEach(function() { app = new App(); }); - it('should expose the collection method', function () { + it('should expose the collection method', function() { assert(typeof app.collection === 'function'); }); - it('should return a new collection', function () { + it('should return a new collection', function() { var collection = app.collection(); assert(typeof collection === 'object'); }); - it('should have isCollection property', function () { + it('should have isCollection property', function() { var collection = app.collection(); assert(collection.isCollection === true); }); }); - describe('adding views', function () { - beforeEach(function () { + describe('adding views', function() { + beforeEach(function() { app = new App() - .use(function () { - return function () { + .use(function() { + return function() { define(this, 'count', { get: function() { return Object.keys(this.views).length; }, - set: function () { + set: function() { throw new Error('count is a read-only getter and cannot be defined.'); } }); @@ -49,43 +49,46 @@ describe('collection', function () { }); app.engine('tmpl', require('engine-base')); - app.create('pages'); + app.create('pages', { + renameKey: function(fp) { + return path.relative(process.cwd(), fp); + } + }); }); - it('should load a view onto the respective collection:', function () { + it('should load a view onto the respective collection:', function() { app.pages('test/fixtures/pages/a.hbs'); - app.views.pages.should.have.property(path.resolve('test/fixtures/pages/a.hbs')); + app.views.pages.should.have.property('test/fixtures/pages/a.hbs'); }); - it('should allow collection methods to be chained:', function () { + it('should allow collection methods to be chained:', function() { app .pages('test/fixtures/pages/a.hbs') .pages('test/fixtures/pages/b.hbs') .pages('test/fixtures/pages/c.hbs'); app.views.pages.should.have.properties([ - path.resolve('test/fixtures/pages/a.hbs'), - path.resolve('test/fixtures/pages/b.hbs'), - path.resolve('test/fixtures/pages/c.hbs') + 'test/fixtures/pages/a.hbs', + 'test/fixtures/pages/b.hbs', + 'test/fixtures/pages/c.hbs' ]); }); - it('should expose the `option` method:', function () { + it('should expose the `option` method:', function() { app.pages.option('foo', 'bar') .pages('test/fixtures/pages/a.hbs') .pages('test/fixtures/pages/b.hbs') .pages('test/fixtures/pages/c.hbs'); app.pages.options.should.have.property('foo', 'bar'); - app.views.pages.should.have.properties([ - path.resolve('test/fixtures/pages/a.hbs'), - path.resolve('test/fixtures/pages/b.hbs'), - path.resolve('test/fixtures/pages/c.hbs') + 'test/fixtures/pages/a.hbs', + 'test/fixtures/pages/b.hbs', + 'test/fixtures/pages/c.hbs' ]); }); - it('should expose the `option` method:', function () { + it('should expose the `option` method:', function() { app.pages.option('foo', 'bar') .pages('test/fixtures/pages/a.hbs') .pages('test/fixtures/pages/b.hbs') @@ -95,12 +98,12 @@ describe('collection', function () { }); }); - describe('addItem', function () { - beforeEach(function () { + describe('addItem', function() { + beforeEach(function() { app = new App(); }); - it('should add items to a collection', function () { + it('should add items to a collection', function() { var pages = app.collection({Collection: Collection}); pages.addItem('foo'); pages.addItem('bar'); @@ -111,7 +114,7 @@ describe('collection', function () { pages.items.hasOwnProperty('baz'); }); - it('should create a collection from an existing collection:', function () { + it('should create a collection from an existing collection:', function() { var pages = app.collection({Collection: Collection}); pages.addItem('foo'); pages.addItem('bar'); @@ -124,54 +127,58 @@ describe('collection', function () { }); }); - describe('rendering views', function () { - beforeEach(function () { + describe('rendering views', function() { + beforeEach(function() { app = new App(); app.engine('tmpl', require('engine-base')); app.create('pages'); app.cache.data = {}; }); - it('should render a view with inherited app.render', function (done) { + it('should render a view with inherited app.render', function(cb) { app.page('test/fixtures/templates/a.tmpl') .use(function(view) { view.contents = fs.readFileSync(view.path); }) .set('data.name', 'Brian') - .render(function (err, res) { - if (err) return done(err); - assert(res.contents.toString() === 'Brian'); - done(); + .render(function(err, res) { + if (err) return cb(err); + assert(res.content === 'Brian'); + cb(); }); }); }); }); -describe('collection singular method', function () { - describe('create', function () { - beforeEach(function () { +describe('collection singular method', function() { + describe('create', function() { + beforeEach(function() { app = new App(); }); - it('should add a pluralized collection from singular name', function () { + it('should add a pluralized collection from singular name', function() { app.create('page'); assert(typeof app.views.pages === 'object'); }); }); - describe('adding views', function () { - beforeEach(function () { + describe('adding views', function() { + beforeEach(function() { app = new App(); app.engine('tmpl', require('engine-base')); - app.create('page'); + app.create('page', { + renameKey: function(fp) { + return path.relative(process.cwd(), fp); + } + }); }); - it('should add a view to the created collection:', function () { + it('should add a view to the created collection:', function() { app.page('test/fixtures/pages/a.hbs'); - assert(typeof app.views.pages[path.resolve('test/fixtures/pages/a.hbs')] === 'object'); + assert(typeof app.views.pages['test/fixtures/pages/a.hbs'] === 'object'); }); - it('should expose the `option` method:', function () { + it('should expose the `option` method:', function() { app.pages.option('foo', 'bar'); app.pages.options.should.have.property('foo', 'bar'); }); diff --git a/test/app.collection.render.js b/test/app.collection.render.js index 135b134..12f06f7 100644 --- a/test/app.collection.render.js +++ b/test/app.collection.render.js @@ -9,41 +9,41 @@ var App = support.resolve(); var List = App.List; var pages, app; -describe('render', function () { - describe('rendering', function () { - beforeEach(function () { +describe('render', function() { + describe('rendering', function() { + beforeEach(function() { app = App(); pages = app.create('pages'); app.engine('tmpl', require('engine-base')); pages.engine('tmpl', require('engine-base')); }); - it('should throw an error when no callback is given:', function () { + it('should throw an error when no callback is given:', function() { (function() { app.pages.render({}); }).should.throw('Views#render is async and expects a callback function'); }); - it('should throw an error when an engine is not defined:', function (done) { - pages.addView('foo.bar', {content: '<%= name %>'}); + it('should throw an error when an engine is not defined:', function(done) { + pages.addView('foo.bar', { content: '<%= name %>' }); var page = pages.getView('foo.bar'); app.pages.render(page, function(err) { - assert(err.message === 'Views#render cannot find an engine for: .bar'); + assert(err.message === 'Views#render cannot find engine: .bar'); done(); }); }); - it('should use helpers defined on app to render a view:', function (done) { + it('should use helpers defined on app to render a view:', function(done) { var locals = {name: 'Halle'}; - app.helper('upper', function (str) { + app.helper('upper', function(str) { return str.toUpperCase(str) + 'app'; }); pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); var page = pages.getView('a.tmpl'); - app.render(page, function (err, res) { + app.render(page, function(err, res) { if (err) return done(err); assert(res.content === 'a HALLEapp b'); @@ -51,9 +51,9 @@ describe('render', function () { }); }); - it('should use helpers defined on app to render a view with collection.render:', function (done) { + it('should use helpers defined on app to render a view with collection.render:', function(done) { var locals = {name: 'Halle'}; - app.helper('upper', function (str) { + app.helper('upper', function(str) { return str.toUpperCase(str) + 'app'; }); @@ -61,7 +61,7 @@ describe('render', function () { pages.helper('upper', app._.helpers.sync.upper); var page = pages.getView('a.tmpl'); - pages.render(page, function (err, res) { + pages.render(page, function(err, res) { if (err) return done(err); assert(res.content === 'a HALLEapp b'); @@ -69,55 +69,55 @@ describe('render', function () { }); }); - it('should use helpers when rendering a view:', function (done) { + it('should use helpers when rendering a view:', function(done) { var locals = {name: 'Halle'}; - pages.helper('upper', function (str) { + pages.helper('upper', function(str) { return str.toUpperCase(str); }); pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); var page = pages.getView('a.tmpl'); - pages.render(page, function (err, res) { + pages.render(page, function(err, res) { if (err) return done(err); assert(res.content === 'a HALLE b'); done(); }); }); - it('should render a template when contents is a buffer:', function (done) { + it('should render a template when contents is a buffer:', function(done) { pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); var view = pages.getView('a.tmpl'); - pages.render(view, function (err, view) { + pages.render(view, function(err, view) { if (err) return done(err); assert(view.contents.toString() === 'b'); done(); }); }); - it('should render a template when content is a string:', function (done) { + it('should render a template when content is a string:', function(done) { pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); var view = pages.getView('a.tmpl'); - pages.render(view, function (err, view) { + pages.render(view, function(err, view) { if (err) return done(err); assert(view.contents.toString() === 'b'); done(); }); }); - it('should render a view from its path:', function (done) { + it('should render a view from its path:', function(done) { pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - pages.render('a.tmpl', function (err, view) { + pages.render('a.tmpl', function(err, view) { if (err) return done(err); assert(view.content === 'b'); done(); }); }); - it('should use a plugin for rendering:', function (done) { + it('should use a plugin for rendering:', function(done) { pages.engine('tmpl', require('engine-base')); pages.option('engine', 'tmpl'); @@ -131,21 +131,21 @@ describe('render', function () { 'g': {content: '<%= title %>', locals: {title: 'ggg'}}, 'h': {content: '<%= title %>', locals: {title: 'hhh'}}, 'i': {content: '<%= title %>', locals: {title: 'iii'}}, - 'j': {content: '<%= title %>', locals: {title: 'jjj'}}, + 'j': {content: '<%= title %>', locals: {title: 'jjj'}} }); - pages.use(function (collection) { + pages.use(function(collection) { collection.option('pager', false); - collection.renderEach = function (cb) { + collection.renderEach = function(cb) { var list = new List(collection); - async.map(list.items, function (item, next) { + async.map(list.items, function(item, next) { collection.render(item, next); }, cb); }; }); - pages.renderEach(function (err, items) { + pages.renderEach(function(err, items) { if (err) return done(err); assert(items[0].content === 'aaa'); assert(items[9].content === 'jjj'); diff --git a/test/app.compile.js b/test/app.compile.js index 423c9f9..d2ffe99 100644 --- a/test/app.compile.js +++ b/test/app.compile.js @@ -4,21 +4,21 @@ var support = require('./support'); var App = support.resolve(); var app; -describe('compile', function () { - beforeEach(function () { +describe('compile', function() { + beforeEach(function() { app = new App(); app.create('page'); }); - it('should throw an error when an engine cannot be found:', function () { + it('should throw an error when an engine cannot be found:', function() { app.page('foo.bar', {content: '<%= name %>'}); var page = app.pages.getView('foo.bar'); (function() { app.compile(page); - }).should.throw('Templates#compile cannot find an engine for: .bar'); + }).should.throw('Templates#compile cannot find engine: .bar'); }); - it('should compile a template:', function () { + it('should compile a template:', function() { app.engine('tmpl', require('engine-base')); app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>', a: 'b'}); @@ -27,7 +27,7 @@ describe('compile', function () { assert.equal(typeof view.fn, 'function'); }); - it('should compile a template by name:', function () { + it('should compile a template by name:', function() { app.engine('tmpl', require('engine-base')); app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>', a: 'b'}); @@ -35,17 +35,17 @@ describe('compile', function () { assert.equal(typeof view.fn, 'function'); }); - it('should throw an error when a callback is given:', function () { + it('should throw an error when a callback is given:', function() { app.engine('md', require('engine-base')); app.page('foo.md', {content: '<%= name %>'}); var page = app.pages.getView('foo.md'); (function() { - app.compile(page, function () { + app.compile(page, function() { }); }).should.throw('Templates#compile is sync and does not take a callback function'); (function() { - app.compile(page, {}, function () { + app.compile(page, {}, function() { }); }).should.throw('Templates#compile is sync and does not take a callback function'); }); diff --git a/test/app.copy.js b/test/app.copy.js index f3d3ceb..cf18b8c 100644 --- a/test/app.copy.js +++ b/test/app.copy.js @@ -9,23 +9,23 @@ var fixtures = path.join(__dirname, 'fixtures/copy/*.txt'); var actual = path.join(__dirname, 'actual'); describe('copy()', function() { - beforeEach(function (cb) { - rimraf(actual, cb); + beforeEach(function(done) { + rimraf(actual, done); app = new App(); }); - afterEach(function (cb) { - rimraf(actual, cb); + afterEach(function(done) { + rimraf(actual, done); }); - describe('streams', function () { - it('should copy files', function (cb) { + describe('streams', function() { + it('should copy files', function(done) { app.copy(fixtures, path.join(__dirname, 'actual')) - .on('error', cb) - .on('data', function (file) { + .on('error', done) + .on('data', function(file) { assert.equal(typeof file, 'object'); }) - .on('end', cb); + .on('end', done); }); }); }); diff --git a/test/app.create.js b/test/app.create.js index 9178245..5486eb2 100644 --- a/test/app.create.js +++ b/test/app.create.js @@ -7,33 +7,86 @@ var support = require('./support'); var App = support.resolve(); var app; -describe('create', function () { - describe('inflections', function () { - beforeEach(function () { +describe('create', function() { + describe('inflections', function() { + beforeEach(function() { app = new App(); }); - it('should expose the create method', function () { + it('should expose the create method', function() { assert(typeof app.create === 'function'); }); - it('should add a collection to `views`', function () { + it('should add a collection to `views`', function() { app.create('pages'); assert(typeof app.views.pages === 'object'); assert(typeof app.pages === 'function'); }); - it('should add a pluralized collection to `views`', function () { + it('should add a pluralized collection to `views`', function() { app.create('page'); assert(typeof app.views.pages === 'object'); assert(typeof app.page === 'function'); }); }); - describe('custom constructors', function () { - beforeEach(function () { + describe('renderable views', function() { + beforeEach(function() { + app = new App(); + app.create('pages'); + app.create('partials', {viewType: 'partial'}); + app.create('layout', {viewType: 'layout'}); + }); + + it('should add renderable views when no type is defined', function() { + app.pages.addView('foo', {content: 'bar'}); + assert(app.views.pages.hasOwnProperty('foo')); + }); + + it('should add view Ctor names to views', function() { + app.pages.addView('foo', {content: 'bar'}); + assert(app.views.pages.foo._name === 'Page'); + }); + + it('should add partial views when partial type is defined', function() { + app.partials.addView('abc', {content: 'xyz'}); + assert(app.views.partials.hasOwnProperty('abc')); + }); + + it('should add layout views when layout type is defined', function() { + app.layouts.addView('foo', {content: 'bar'}); + assert(app.views.layouts.hasOwnProperty('foo')); + }); + + it('should set viewType on renderable views', function() { + app.pages.addView('foo', {content: 'bar'}); + var view = app.pages.getView('foo'); + assert(view.isType('renderable')); + assert(!view.isType('layout')); + assert(!view.isType('partial')); + }); + + it('should set viewType on partial views', function() { + app.partials.addView('foo', {content: 'bar'}); + var view = app.partials.getView('foo'); + assert(view.isType('partial')); + assert(!view.isType('layout')); + assert(!view.isType('renderable')); + }); + + it('should set viewType on layout views', function() { + app.layouts.addView('foo', {content: 'bar'}); + var view = app.layouts.getView('foo'); + assert(view.isType('layout')); + assert(!view.isType('renderable')); + assert(!view.isType('partial')); + }); + }); + + describe('custom constructors', function() { + beforeEach(function() { var Vinyl = require('vinyl'); - Vinyl.prototype.custom = function (key) { + Vinyl.prototype.custom = function(key) { this[key] = 'nonsense'; return this; }; @@ -41,7 +94,7 @@ describe('create', function () { app.create('pages'); }); - it('should create views from key-value pairs:', function () { + it('should create views from key-value pairs:', function() { app.page('a.hbs', {path: 'a.hbs', content: 'a'}); app.page('b.hbs', {path: 'b.hbs', content: 'b'}); app.page('c.hbs', {path: 'c.hbs', content: 'c'}); @@ -51,10 +104,10 @@ describe('create', function () { }); }); - describe('custom instances', function () { - it('should create views from custom `View` and `Views` instance/ctor:', function () { + describe('custom instances', function() { + it('should create views from custom `View` and `Views` instance/ctor:', function() { var Vinyl = require('vinyl'); - Vinyl.prototype.read = function (file) { + Vinyl.prototype.read = function(file) { return fs.readFileSync(file.path); }; @@ -80,89 +133,90 @@ describe('create', function () { }); }); - describe('chaining', function () { - beforeEach(function () { + describe('chaining', function() { + beforeEach(function() { app = new App(); app.engine('tmpl', require('engine-base')); - app.create('page'); + app.create('page', { + renameKey: function(fp) { + return path.relative(process.cwd(), fp); + } + }); }); - it('should create views from key-value pairs:', function () { + it('should create views from key-value pairs:', function() { app.page('a.hbs', {content: 'a'}); app.page('b.hbs', {content: 'b'}); app.page('c.hbs', {content: 'c'}); app.views.pages.should.have.properties(['a.hbs', 'b.hbs', 'c.hbs']); - assert(app.views.pages['a.hbs'].content === 'a'); + assert(app.views.pages['a.hbs'].contents.toString() === 'a'); }); - it('should create views from file paths:', function () { + it('should create views from file paths:', function() { app.page('test/fixtures/pages/a.hbs'); app.page('test/fixtures/pages/b.hbs'); app.page('test/fixtures/pages/c.hbs'); app.views.pages.should.have.properties([ - path.resolve('test/fixtures/pages/a.hbs'), - path.resolve('test/fixtures/pages/b.hbs'), - path.resolve('test/fixtures/pages/c.hbs') + 'test/fixtures/pages/a.hbs', + 'test/fixtures/pages/b.hbs', + 'test/fixtures/pages/c.hbs' ]); }); }); - - describe('instance', function () { - beforeEach(function () { + describe('instance', function() { + beforeEach(function() { app = new App(); app.engine('tmpl', require('engine-base')); }); - it('should return the collection instance', function () { + it('should return the collection instance', function() { var collection = app.create('pages'); assert(collection instanceof App.Views); - collection.option('renameKey', function (key) { + collection.option('renameKey', function(key) { return path.basename(key); }); collection - .use(function (views) { - views.read = function (name) { + .use(function(views) { + views.read = function(name) { var view = this.getView(name); - if (!view.contents) { - view.contents = fs.readFileSync(view.path); - } + view.contents = fs.readFileSync(view.path); }; }); collection.addView('test/fixtures/templates/a.tmpl'); collection.read('a.tmpl'); - assert(collection.getView('a.tmpl').content === '<%= name %>'); + assert(collection.getView('a.tmpl').contents.toString() === '<%= name %>'); }); }); - describe('viewType', function () { - beforeEach(function () { + describe('viewType', function() { + beforeEach(function() { app = new App(); app.engine('tmpl', require('engine-base')); }); - it('should add collection to the given viewType', function () { + it('should add collection to the given viewType', function() { app.create('layout', {viewType: 'layout'}); assert(app.layouts.options.viewType[0] === 'layout'); }); - it('should add a collection to multiple viewTypes', function () { + it('should add a collection to multiple viewTypes', function() { app.create('foo', {viewType: ['layout', 'renderable']}); assert.deepEqual(app.foos.options.viewType, ['layout', 'renderable']); }); }); - describe('events', function () { - beforeEach(function () { + describe('events', function() { + beforeEach(function() { app = new App(); app.engine('tmpl', require('engine-base')); }); - it('should emit `create` when a collection is created:', function () { - app.on('create', function (collection) { + it('should emit `create` when a collection is created:', function() { + app.on('create', function(collection) { if (collection.options.plural === 'layouts') { collection.options.foo = 'bar'; } @@ -174,11 +228,11 @@ describe('create', function () { }); }); - describe('collection instantiation', function () { - it('should expose collection instance methods that are created after instantiation on the app collection loader', function () { + describe('collection instantiation', function() { + it('should expose collection instance methods that are created after instantiation on the app collection loader', function() { app.create('pages'); - app.pages.use(function (collection) { - collection.define('foo', function (msg) { + app.pages.use(function(collection) { + collection.define('foo', function(msg) { return 'foo ' + msg; }); }); diff --git a/test/app.data.js b/test/app.data.js index 9cf2cd3..f3a1992 100644 --- a/test/app.data.js +++ b/test/app.data.js @@ -6,35 +6,35 @@ var support = require('./support'); var App = support.resolve(); var app; -describe('app.data', function () { - beforeEach(function () { +describe('app.data', function() { + beforeEach(function() { app = new App(); }); - it('should set a key-value pair on cache.data:', function () { + it('should set a key-value pair on cache.data:', function() { app.data('a', 'b'); assert(app.cache.data.a === 'b'); }); - it('should set an object on cache.data:', function () { + it('should set an object on cache.data:', function() { app.data({c: 'd'}); assert(app.cache.data.c === 'd'); }); - it('should load data from a file onto cache.data:', function () { + it('should load data from a file onto cache.data:', function() { app.data('test/fixtures/data/a.json'); assert(app.cache.data.a.one.a === 'aaa'); }); - it('should load a glob of data onto cache.data:', function () { + it('should load a glob of data onto cache.data:', function() { app.data('test/fixtures/data/*.json'); assert(app.cache.data.a.one.a === 'aaa'); assert(app.cache.data.b.two.b === 'bbb'); assert(app.cache.data.c.three.c === 'ccc'); }); - it('should use `namespace` defined on global opts:', function () { - app.option('namespace', function (key) { + it('should use `namespace` defined on global opts:', function() { + app.option('namespace', function(key) { return 'prefix_' + path.basename(key, path.extname(key)); }); app.data('test/fixtures/data/*.json'); @@ -43,9 +43,9 @@ describe('app.data', function () { assert(app.cache.data.prefix_c.three.c === 'ccc'); }); - it('should use `namespace` defined on data opts:', function () { + it('should use `namespace` defined on data opts:', function() { app.data('test/fixtures/data/*.json', { - namespace: function (key) { + namespace: function(key) { return 'prefix_' + path.basename(key, path.extname(key)); } }); @@ -54,9 +54,9 @@ describe('app.data', function () { assert(app.cache.data.prefix_c.three.c === 'ccc'); }); - it('should use `renameKey` defined on data opts:', function () { + it('should use `renameKey` defined on data opts:', function() { app.data('test/fixtures/data/*.json', { - renameKey: function (key) { + renameKey: function(key) { return 'prefix_' + path.basename(key, path.extname(key)); } }); diff --git a/test/app.dest.js b/test/app.dest.js index 1e3d67e..ff285e8 100644 --- a/test/app.dest.js +++ b/test/app.dest.js @@ -855,7 +855,7 @@ describe('dest stream', function() { stream.end(); }); - it('should create symlinks when the `symlink` attribute is set on the file', function (done) { + it('should create symlinks when the `symlink` attribute is set on the file', function(done) { var inputPath = path.join(__dirname, 'fixtures/vinyl/test-create-dir-symlink'); var inputBase = path.join(__dirname, 'fixtures/vinyl/'); var inputRelativeSymlinkPath = 'wow'; @@ -873,7 +873,7 @@ describe('dest stream', function() { inputFile.symlink = inputRelativeSymlinkPath; var onEnd = function(){ - fs.readlink(buffered[0].path, function () { + fs.readlink(buffered[0].path, function() { buffered[0].symlink.should.equal(inputFile.symlink); buffered[0].path.should.equal(expectedPath); done(); @@ -909,30 +909,30 @@ describe('dest stream', function() { }); describe('dest', function() { - beforeEach(function (done) { + beforeEach(function(done) { rimraf(actual, done); app = new App(); }); - afterEach(function (done) { + afterEach(function(done) { rimraf(actual, done); }); - describe('streams', function () { - it('should return a stream', function (done) { + describe('streams', function() { + it('should return a stream', function(done) { var stream = app.dest(path.join(__dirname, 'fixtures/')); should.exist(stream); should.exist(stream.on); done(); }); - it('should return an output stream that writes files', function (done) { + it('should return an output stream that writes files', function(done) { var instream = app.src(path.join(__dirname, 'fixtures/copy/e*.txt')); var outstream = app.dest(actual); instream.pipe(outstream); outstream.on('error', done); - outstream.on('data', function (file) { + outstream.on('data', function(file) { // data should be re-emitted correctly should.exist(file); should.exist(file.path); @@ -940,8 +940,8 @@ describe('dest', function() { path.join(file.path, '').should.equal(path.join(actual, 'example.txt')); String(file.contents).should.equal('Hello world!'); }); - outstream.on('end', function () { - fs.readFile(path.join(actual, 'example.txt'), function (err, contents) { + outstream.on('end', function() { + fs.readFile(path.join(actual, 'example.txt'), function(err, contents) { should.not.exist(err); should.exist(contents); String(contents).should.equal('Hello world!'); @@ -950,13 +950,13 @@ describe('dest', function() { }); }); - it('should return an output stream that does not write non-read files', function (done) { + it('should return an output stream that does not write non-read files', function(done) { var instream = app.src(path.join(__dirname, 'fixtures/copy/e*.txt'), {read: false}); var outstream = app.dest(actual); instream.pipe(outstream); outstream.on('error', done); - outstream.on('data', function (file) { + outstream.on('data', function(file) { // data should be re-emitted correctly should.exist(file); should.exist(file.path); @@ -964,8 +964,8 @@ describe('dest', function() { path.join(file.path, '').should.equal(path.join(actual, 'example.txt')); }); - outstream.on('end', function () { - fs.readFile(path.join(actual, 'example.txt'), function (err, contents) { + outstream.on('end', function() { + fs.readFile(path.join(actual, 'example.txt'), function(err, contents) { should.exist(err); should.not.exist(contents); done(); @@ -973,20 +973,20 @@ describe('dest', function() { }); }); - it('should return an output stream that writes streaming files', function (done) { + it('should return an output stream that writes streaming files', function(done) { var instream = app.src(path.join(__dirname, 'fixtures/copy/e*.txt'), {buffer: false}); var outstream = instream.pipe(app.dest(actual)); outstream.on('error', done); - outstream.on('data', function (file) { + outstream.on('data', function(file) { // data should be re-emitted correctly should.exist(file); should.exist(file.path); should.exist(file.contents); path.join(file.path, '').should.equal(path.join(actual, 'example.txt')); }); - outstream.on('end', function () { - fs.readFile(path.join(actual, 'example.txt'), function (err, contents) { + outstream.on('end', function() { + fs.readFile(path.join(actual, 'example.txt'), function(err, contents) { should.not.exist(err); should.exist(contents); String(contents).should.equal('Hello world!'); @@ -995,48 +995,48 @@ describe('dest', function() { }); }); - it('should return an output stream that writes streaming files to new directories', function (done) { + it('should return an output stream that writes streaming files to new directories', function(done) { testWriteDir({}, done); }); - it('should return an output stream that writes streaming files to new directories (buffer: false)', function (done) { + it('should return an output stream that writes streaming files to new directories (buffer: false)', function(done) { testWriteDir({buffer: false}, done); }); - it('should return an output stream that writes streaming files to new directories (read: false)', function (done) { + it('should return an output stream that writes streaming files to new directories (read: false)', function(done) { testWriteDir({read: false}, done); }); - it('should return an output stream that writes streaming files to new directories (read: false, buffer: false)', function (done) { + it('should return an output stream that writes streaming files to new directories (read: false, buffer: false)', function(done) { testWriteDir({buffer: false, read: false}, done); }); }); - describe('ext', function () { - beforeEach(function () { + describe('ext', function() { + beforeEach(function() { app = new App(); app.set('ext', '.txt'); }); - afterEach(function () { + afterEach(function() { app.set('ext', '.html'); }); - it('should return a stream', function (done) { + it('should return a stream', function(done) { var stream = app.dest(path.join(__dirname, 'fixtures/')); should.exist(stream); should.exist(stream.on); done(); }); - it('should return an output stream that writes files', function (done) { + it('should return an output stream that writes files', function(done) { var instream = app.src(path.join(__dirname, 'fixtures/copy/e*.txt')); var outstream = app.dest(actual); instream.pipe(outstream); outstream.on('error', done); - outstream.on('data', function (file) { + outstream.on('data', function(file) { // data should be re-emitted correctly should.exist(file); should.exist(file.path); @@ -1044,8 +1044,8 @@ describe('dest', function() { path.join(file.path, '').should.equal(path.join(actual, 'example.txt')); String(file.contents).should.equal('Hello world!'); }); - outstream.on('end', function () { - fs.readFile(path.join(actual, 'example.txt'), function (err, contents) { + outstream.on('end', function() { + fs.readFile(path.join(actual, 'example.txt'), function(err, contents) { should.not.exist(err); should.exist(contents); String(contents).should.equal('Hello world!'); @@ -1054,13 +1054,13 @@ describe('dest', function() { }); }); - it('should return an output stream that does not write non-read files', function (done) { + it('should return an output stream that does not write non-read files', function(done) { var instream = app.src(path.join(__dirname, 'fixtures/dest/*.txt'), {read: false}); var outstream = app.dest(actual); instream.pipe(outstream); outstream.on('error', done); - outstream.on('data', function (file) { + outstream.on('data', function(file) { // data should be re-emitted correctly should.exist(file); should.exist(file.path); @@ -1068,8 +1068,8 @@ describe('dest', function() { path.join(file.path, '').should.equal(path.join(actual, 'example.txt')); }); - outstream.on('end', function () { - fs.readFile(path.join(actual, 'example.txt'), function (err, contents) { + outstream.on('end', function() { + fs.readFile(path.join(actual, 'example.txt'), function(err, contents) { should.exist(err); should.not.exist(contents); done(); diff --git a/test/app.engines.js b/test/app.engines.js index 17c0669..16bd062 100644 --- a/test/app.engines.js +++ b/test/app.engines.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var assert = require('assert'); @@ -10,18 +12,18 @@ describe('engine support', function() { app = new App(); }); - it('should throw an error when engine name is invalid:', function () { - (function () { + it('should throw an error when engine name is invalid:', function() { + (function() { app.engine(null, {}); }).should.throw('expected engine ext to be a string or array.'); }); - it('should register an engine to the given extension', function () { - app.engine('hbs', function () {}); + it('should register an engine to the given extension', function() { + app.engine('hbs', function() {}); assert(typeof app.engines['.hbs'] === 'object'); }); - it('should set an engine with the given extension', function () { + it('should set an engine with the given extension', function() { var hbs = function() {}; hbs.render = function() {}; hbs.renderFile = function() {}; @@ -31,129 +33,128 @@ describe('engine support', function() { assert(app.engines['.hbs'].render); }); - it('should get an engine:', function () { - app.engine('hbs', function () {}); + it('should get an engine:', function() { + app.engine('hbs', function() {}); var hbs = app.engine('hbs'); assert(typeof hbs === 'object'); assert(hbs.hasOwnProperty('render')); assert(hbs.hasOwnProperty('compile')); }); - it('should return undefined if no engine is found:', function () { + it('should return undefined if no engine is found:', function() { var hbs = app.getEngine(); assert.equal(typeof hbs, 'undefined'); }); - it('should register multiple engines to the given extension', function () { - app.engine(['hbs', 'md'], function () {}); + it('should register multiple engines to the given extension', function() { + app.engine(['hbs', 'md'], function() {}); assert(typeof app.engines['.hbs'] === 'object'); assert(typeof app.engines['.md'] === 'object'); }); }); -describe('engines', function () { - beforeEach(function () { +describe('engines', function() { + beforeEach(function() { app = new App(); app.create('pages'); app.pages('foo.tmpl', {content: 'A <%= letter %> {{= letter }} C'}); app.pages('bar.tmpl', {content: 'A <%= letter %> {{ letter }} C'}); }); - it('should register an engine:', function () { - app.engine('a', {render: function () {}}); + it('should register an engine:', function() { + app.engine('a', {render: function() {}}); app.engines.should.have.property('.a'); }); - it('should use custom delimiters:', function (done) { + it('should use custom delimiters:', function(cb) { app.engine('tmpl', require('engine-base'), { delims: ['{{', '}}'] }); - app.render('foo.tmpl', {letter: 'B'}, function (err, res) { - if (err) return done(err); + app.render('foo.tmpl', {letter: 'B'}, function(err, res) { + if (err) return cb(err); res.contents.toString().should.equal('A <%= letter %> B C'); - done(); + cb(); }); }); - it('should override individual delims values:', function (done) { + it('should override individual delims values:', function(cb) { app.engine('tmpl', require('engine-base'), { interpolate: /\{{([^}]+)}}/g, evaluate: /\{{([^}]+)}}/g, escape: /\{{-([^}]+)}}/g }); - app.render('bar.tmpl', {letter: 'B'}, function (err, res) { - if (err) return done(err); + app.render('bar.tmpl', {letter: 'B'}, function(err, res) { + if (err) return cb(err); res.contents.toString().should.equal('A <%= letter %> B C'); - done(); + cb(); }); }); - it('should get an engine:', function () { + it('should get an engine:', function() { app.engine('a', { - render: function () {} + render: function() {} }); var a = app.engine('a'); a.should.have.property('render'); }); }); - -describe('engine selection:', function () { - beforeEach(function (done) { +describe('engine selection:', function() { + beforeEach(function(cb) { app = new App(); app.engine('tmpl', require('engine-base')); app.engine('hbs', require('engine-handlebars')); app.create('pages'); - done(); + cb(); }); - it('should get the engine from file extension:', function (done) { + it('should get the engine from file extension:', function(cb) { app.page('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}) - .render(function (err, view) { - if (err) return done(err); + .render(function(err, view) { + if (err) return cb(err); assert(view.content === 'b'); - done(); + cb(); }); }); - it('should use the engine defined on the collection:', function (done) { + it('should use the engine defined on the collection:', function(cb) { app.create('posts', {engine: 'hbs'}); app.post('a', {content: '{{a}}', locals: {a: 'b'}}) - .render(function (err, view) { - if (err) return done(err); + .render(function(err, view) { + if (err) return cb(err); assert(view.content === 'b'); - done(); + cb(); }); }); - it('should use the engine defined on the view:', function (done) { + it('should use the engine defined on the view:', function(cb) { app.create('posts'); app.post('a', {content: '{{a}}', engine: 'hbs', locals: {a: 'b'}}) - .render(function (err, view) { - if (err) return done(err); + .render(function(err, view) { + if (err) return cb(err); assert(view.content === 'b'); - done(); + cb(); }); }); - it('should use the engine defined on `view.data`:', function (done) { + it('should use the engine defined on `view.data`:', function(cb) { app.create('posts'); app.post('a', {content: '{{a}}', locals: {a: 'b'}, data: {engine: 'hbs'}}) - .render(function (err, view) { - if (err) return done(err); + .render(function(err, view) { + if (err) return cb(err); assert(view.content === 'b'); - done(); + cb(); }); }); - it('should use the engine defined on render locals:', function (done) { + it('should use the engine defined on render locals:', function(cb) { app.create('posts'); app.post('a', {content: '{{a}}', locals: {a: 'b'}}) - .render({engine: 'hbs'}, function (err, view) { - if (err) return done(err); + .render({engine: 'hbs'}, function(err, view) { + if (err) return cb(err); assert(view.content === 'b'); - done(); + cb(); }); }); }); diff --git a/test/app.events.js b/test/app.events.js index 6be9215..bae16fe 100644 --- a/test/app.events.js +++ b/test/app.events.js @@ -5,28 +5,18 @@ var support = require('./support'); var App = support.resolve(); var app; -describe('events', function () { - beforeEach(function () { +describe('events', function() { + beforeEach(function() { app = new App(); }); - it('should listen for an event:', function () { + it('should listen for an event:', function() { var app = new App(); - app.on('foo', function () {}); + app.on('foo', function() {}); assert(Array.isArray(app._callbacks['$foo'])); }); - it('should emit an event:', function (done) { - var app = new App(); - app.on('foo', function (val) { - assert(val === 'bar'); - done(); - }); - assert(Array.isArray(app._callbacks['$foo'])); - app.emit('foo', 'bar'); - }); - - it('should listen for error events:', function(done) { + it('should emit an event:', function(done) { var app = new App(); app.on('foo', function(val) { assert(val === 'bar'); @@ -36,10 +26,10 @@ describe('events', function () { app.emit('foo', 'bar'); }); - it('should listen for `view` events:', function () { - var app = new App(); + it('should listen for `view` events:', function() { + app = new App(); - app.on('view', function (view) { + app.on('view', function(view) { view.foo = 'bar'; }); @@ -47,3 +37,77 @@ describe('events', function () { assert(view.foo === 'bar'); }); }); + +describe('onLoad', function() { + beforeEach(function() { + app = new App(); + }); + + describe('app.collection', function() { + it('should emit a `view` event when view is created', function(done) { + var collection = app.collection(); + + app.on('view', function(view) { + assert(view.path === 'blog/foo.js'); + done(); + }); + + app.onLoad('blog/:title', function(view, next) { + assert(view.path === 'blog/foo.js'); + next(); + }); + + collection.addView('whatever', {path: 'blog/foo.js', content: 'bar baz'}); + }); + + it('should emit an onLoad event when view is created', function(done) { + var collection = app.collection(); + + app.on('onLoad', function(view) { + assert(view.path === 'blog/foo.js'); + done(); + }); + + app.onLoad('blog/:title', function(view, next) { + assert(view.path === 'blog/foo.js'); + next(); + }); + + collection.addView('whatever', {path: 'blog/foo.js', content: 'bar baz'}); + }); + }); + + describe('view collections', function() { + it('should emit a view event when view is created', function(done) { + app.create('posts'); + + app.on('view', function(view) { + assert(view.path === 'blog/foo.js'); + done(); + }); + + app.onLoad('blog/:title', function(view, next) { + assert(view.path === 'blog/foo.js'); + next(); + }); + + app.post('whatever', {path: 'blog/foo.js', content: 'bar baz'}); + }); + + it('should emit an onLoad event when view is created', function(done) { + app.create('posts'); + + app.on('onLoad', function(view) { + assert(view.path === 'blog/foo.js'); + done(); + }); + + app.onLoad('blog/:title', function(view, next) { + assert(view.path === 'blog/foo.js'); + next(); + }); + + app.post('whatever', {path: 'blog/foo.js', content: 'bar baz'}); + }); + }); +}); diff --git a/test/app.get-set.js b/test/app.get-set.js index 8bee52c..8e8a597 100644 --- a/test/app.get-set.js +++ b/test/app.get-set.js @@ -4,43 +4,43 @@ var support = require('./support'); var App = support.resolve(); var app; -describe('app.set()', function () { +describe('app.set()', function() { beforeEach(function() { app = new App(); }); - it('should set a value', function () { + it('should set a value', function() { app.set('a', 'b'); app.get('a').should.equal('b'); }); - it('should set properties on the instance.', function () { + it('should set properties on the instance.', function() { app.set('a', 'b'); app.a.should.equal('b'); }); - it('should allow an object to be set directly.', function () { + it('should allow an object to be set directly.', function() { app.set({x: 'y'}); app.x.should.equal('y'); app.get('x').should.equal('y'); }); - it('should set nested properties on the instance.', function () { + it('should set nested properties on the instance.', function() { app.set('c', {d: 'e'}); app.get('c').d.should.equal('e'); }); - it('should use dot notation to `set` values.', function () { + it('should use dot notation to `set` values.', function() { app.set('h.i', 'j'); app.get('h').should.eql({i: 'j'}); }); - it('should use dot notation to `get` values.', function () { + it('should use dot notation to `get` values.', function() { app.set('h', {i: 'j'}); app.get('h.i').should.equal('j'); }); - it('should return `this` for chaining', function () { + it('should return `this` for chaining', function() { app.set('a', 'b').should.equal(app); app .set('aa', 'bb') @@ -51,21 +51,21 @@ describe('app.set()', function () { app.get('cc').should.equal('dd'); }); - it('should return undefined when not set', function () { + it('should return undefined when not set', function() { app.set('a', undefined).should.equal(app); }); }); -describe('app.get()', function () { +describe('app.get()', function() { beforeEach(function() { app = new App(); }); - it('should return undefined when no set', function () { + it('should return undefined when no set', function() { assert(app.get('a') === undefined); }); - it('should otherwise return the value', function () { + it('should otherwise return the value', function() { app.set('a', 'b'); app.get('a').should.equal('b'); }); diff --git a/test/app.handle.js b/test/app.handle.js index 8ce9902..d27d82d 100644 --- a/test/app.handle.js +++ b/test/app.handle.js @@ -5,27 +5,31 @@ var support = require('./support'); var App = support.resolve(); var app; -describe('handler', function () { - beforeEach(function () { +describe('handler', function() { + beforeEach(function() { app = new App(); app.create('pages'); app.handlers(['foo']); }); - it('should support custom handle methods:', function (done) { + it('should support custom handle methods:', function(done) { var page = app.page('foo', {contents: null}); - app.handle('foo', page, function (err, view) { + app.handle('foo', page, function(err, view) { + if (err) return done(err); + assert(typeof view.path === 'string'); done(); }); }); - it('should not blow up if `options.handled` does not exist:', function (done) { + it('should not blow up if `options.handled` does not exist:', function(done) { var page = app.page('foo', {contents: null}); delete page.options.handled; - app.handle('foo', page, function (err, view) { + app.handle('foo', page, function(err, view) { + if (err) return done(err); + assert(typeof view.path === 'string'); done(); }); diff --git a/test/app.handlers.js b/test/app.handlers.js index 71712f6..f2b94d2 100644 --- a/test/app.handlers.js +++ b/test/app.handlers.js @@ -9,18 +9,18 @@ var app; function decorateViews(views) { var fn = views.decorateView; - views.decorateView = function () { + views.decorateView = function() { var view = fn.apply(fn, arguments); - view.read = function () { + view.read = function() { if (!this.contents) { this.contents = fs.readFileSync(this.path); } }; return view; }; - views.loader = function (pattern) { + views.loader = function(pattern) { var files = resolve.sync(pattern); - return files.reduce(function (acc, fp) { + return files.reduce(function(acc, fp) { acc[fp] = {path: fp}; return acc; }, {}); @@ -28,39 +28,39 @@ function decorateViews(views) { return views; } -describe('handlers', function () { - describe('custom handlers', function () { - beforeEach(function () { +describe('handlers', function() { + describe('custom handlers', function() { + beforeEach(function() { app = new App(); app.create('pages') .use(decorateViews) - .option('renameKey', function (key) { + .option('renameKey', function(key) { return path.basename(key); }); }); - it('should add custom middleware handlers:', function () { + it('should add custom middleware handlers:', function() { app.handler('foo'); app.router.should.have.property('foo'); assert.equal(typeof app.router.foo, 'function'); }); - it('should add custom middleware handlers:', function () { + it('should add custom middleware handlers:', function() { app.handler('foo'); app.handler('bar'); - app.foo(/./, function (view, next) { + app.foo(/./, function(view, next) { view.one = 'aaa'; next(); }); - app.bar(/./, function (view, next) { + app.bar(/./, function(view, next) { view.two = 'zzz'; next(); }); app.page('abc', {content: '...'}) - .use(function (view) { + .use(function(view) { app.handleView('foo', view); app.handleView('bar', view); }); diff --git a/test/app.js b/test/app.js index ef8e463..5ff835b 100644 --- a/test/app.js +++ b/test/app.js @@ -7,90 +7,90 @@ var App = support.resolve(); var Base = App.Base; var app; -describe('app', function () { - describe('constructor', function () { - it('should create an instance of App:', function () { +describe('app', function() { + describe('constructor', function() { + it('should create an instance of App:', function() { app = new App(); assert(app instanceof App); }); - it('should new up without new:', function () { + it('should new up without new:', function() { app = App(); assert(app instanceof App); }); }); - describe('static methods', function () { - it('should expose `extend`:', function () { - assert(typeof App.extend ==='function'); + describe('static methods', function() { + it('should expose `extend`:', function() { + assert(typeof App.extend === 'function'); }); }); - describe('prototype methods', function () { + describe('prototype methods', function() { beforeEach(function() { app = new App(); }); - it('should expose `set`', function () { - assert(typeof app.set ==='function'); + it('should expose `set`', function() { + assert(typeof app.set === 'function'); }); - it('should expose `get`', function () { - assert(typeof app.get ==='function'); + it('should expose `get`', function() { + assert(typeof app.get === 'function'); }); - it('should expose `visit`', function () { - assert(typeof app.visit ==='function'); + it('should expose `visit`', function() { + assert(typeof app.visit === 'function'); }); - it('should expose `define`', function () { - assert(typeof app.define ==='function'); + it('should expose `define`', function() { + assert(typeof app.define === 'function'); }); - it('should expose `views`', function () { + it('should expose `views`', function() { assert(typeof app.views === 'object'); }); }); - describe('instance', function () { + describe('instance', function() { beforeEach(function() { app = new App(); }); - it('should set a value on the instance:', function () { + it('should set a value on the instance:', function() { app.set('a', 'b'); - assert(app.a ==='b'); + assert(app.a === 'b'); }); - it('should get a value from the instance:', function () { + it('should get a value from the instance:', function() { app.set('a', 'b'); - assert(app.get('a') ==='b'); + assert(app.get('a') === 'b'); }); }); - describe('initialization', function () { - it('should listen for errors:', function (done) { + describe('initialization', function() { + it('should listen for errors:', function(done) { app = new App(); - app.on('error', function (err) { + app.on('error', function(err) { assert(err.message === 'foo'); done(); }); app.emit('error', new Error('foo')); }); - it('should mixin methods after init:', function () { + it('should mixin methods after init:', function() { app = new App(); app.option({ mixins: { - foo: function () {} + foo: function() {} } }); - assert(typeof app.foo ==='function'); + assert(typeof app.foo === 'function'); }); - it('should expose constructors from `lib`:', function () { + it('should expose constructors from `lib`:', function() { app = new App(); app.expose('Collection'); - assert(typeof app.Collection ==='function'); + assert(typeof app.Collection === 'function'); }); - it('should update constructors after init:', function () { + it('should update constructors after init:', function() { var Group = App.Group; function MyGroup() { Base.call(this); @@ -105,26 +105,26 @@ describe('app', function () { assert.equal(app.get('Group'), MyGroup); }); - it('should mixin prototype methods defined on options:', function () { + it('should mixin prototype methods defined on options:', function() { app = new App({ mixins: { - foo: function () {} + foo: function() {} } }); - assert(typeof app.foo ==='function'); + assert(typeof app.foo === 'function'); delete App.prototype.foo; }); - it('should expose `_` on app:', function () { + it('should expose `_` on app:', function() { app = new App(); - assert(typeof app._ ==='object'); + assert(typeof app._ === 'object'); }); - it('should not re-add `_` in init:', function () { + it('should not re-add `_` in init:', function() { app = new App(); app._.foo = 'bar'; app.defaultConfig(); - assert(app._.foo ==='bar'); + assert(app._.foo === 'bar'); }); }); }); diff --git a/test/app.list.compile.js b/test/app.list.compile.js index a49f81c..1efa791 100644 --- a/test/app.list.compile.js +++ b/test/app.list.compile.js @@ -6,13 +6,13 @@ var App = support.resolve(); var List = App.List; var list; -describe('app.list.compile', function () { - beforeEach(function () { +describe('app.list.compile', function() { + beforeEach(function() { list = new List(); list.engine('tmpl', require('engine-base')); }); - it('should compile an item:', function () { + it('should compile an item:', function() { var buffer = new Buffer('a b c'); var item = list.addItem('a.tmpl', {contents: buffer}) .compile(); @@ -20,7 +20,7 @@ describe('app.list.compile', function () { assert(typeof item.fn === 'function'); }); - it('should use the compiled function to render:', function () { + it('should use the compiled function to render:', function() { var buffer = new Buffer('a <%= title %> c'); var item = list.addItem('a.tmpl', {contents: buffer}) .compile(); @@ -30,7 +30,7 @@ describe('app.list.compile', function () { assert(item.fn({title: 'z'}) === 'a z c'); }); - it('should compile a view by name:', function () { + it('should compile a view by name:', function() { var buffer = new Buffer('a <%= title %> c'); list.addItem('a.tmpl', {contents: buffer}); diff --git a/test/app.list.js b/test/app.list.js index 74b3dbe..84527ec 100644 --- a/test/app.list.js +++ b/test/app.list.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var fs = require('fs'); @@ -8,42 +10,46 @@ var App = support.resolve(); var List = App.List; var app; -describe('list', function () { - describe('method', function () { - beforeEach(function () { +describe('list', function() { + describe('method', function() { + beforeEach(function() { app = new App(); }); - it('should expose the list method', function () { + it('should expose the list method', function() { assert(typeof app.list === 'function'); }); - it('should return a new list', function () { + it('should return a new list', function() { var list = app.list(); assert(typeof list === 'object'); }); - it('should have isList property', function () { + it('should have isList property', function() { var list = app.list(); assert(list.isList === true); }); }); - describe('adding items', function () { - beforeEach(function () { + describe('adding items', function() { + beforeEach(function() { app = new App(); app.engine('tmpl', require('engine-base')); - app.create('pages'); + app.create('pages', { + renameKey: function(fp) { + return path.relative(process.cwd(), fp); + } + }); }); - it('should add an item to a list:', function () { + it('should add an item to a list:', function() { app.pages('test/fixtures/pages/a.hbs'); var list = app.list(); list.addItem(app.pages.getView('test/fixtures/pages/a.hbs')); - assert(list.hasItem(path.resolve('test/fixtures/pages/a.hbs'))); + assert(list.hasItem('test/fixtures/pages/a.hbs')); }); - it('should expose the `option` method from a list:', function () { + it('should expose the `option` method from a list:', function() { var list = app.list(); list.option('a', 'b'); assert(list.options); @@ -51,12 +57,12 @@ describe('list', function () { }); }); - describe('addItem', function () { - beforeEach(function () { + describe('addItem', function() { + beforeEach(function() { app = new App(); }); - it('should add items to a list', function () { + it('should add items to a list', function() { var pages = app.list({List: List}); pages.addItem('foo'); pages.addItem('bar'); @@ -67,7 +73,7 @@ describe('list', function () { pages.items.hasOwnProperty('baz'); }); - it('should create a list from an existing list:', function () { + it('should create a list from an existing list:', function() { var pages = app.list({List: List}); pages.addItem('foo'); pages.addItem('bar'); @@ -80,26 +86,24 @@ describe('list', function () { }); }); - describe('rendering items', function () { - beforeEach(function () { + describe('rendering items', function() { + beforeEach(function() { app = new App(); app.engine('tmpl', require('engine-base')); app.create('pages'); - - app.cache.data = {}; }); - it('should render a item with inherited app.render', function (done) { + it('should render a item with inherited app.render', function(done) { app.page('test/fixtures/templates/a.tmpl') - .use(function (item) { - if (!item.contents) { + .use(function(item) { + if (!item.content) { item.contents = fs.readFileSync(item.path); } }) .set('data.name', 'Brian') - .render(function (err, res) { + .render(function(err, res) { if (err) return done(err); - assert(res.content === 'Brian'); + assert(res.contents.toString() === 'Brian'); done(); }); }); diff --git a/test/app.lookups.js b/test/app.lookups.js index 27ecfac..afab53b 100644 --- a/test/app.lookups.js +++ b/test/app.lookups.js @@ -8,23 +8,23 @@ var support = require('./support'); var App = support.resolve(); var app; -describe('lookups', function () { - beforeEach(function () { +describe('lookups', function() { + beforeEach(function() { app = new App(); - app.option('renameKey', function (key) { + app.option('renameKey', function(key) { return path.basename(key); }); app.create('pages') - .use(function (pages) { - pages.on('addViews', function (glob) { + .use(function(pages) { + pages.on('addViews', function(glob) { var files = resolve.sync(glob); - files.forEach(function (fp) { + files.forEach(function(fp) { pages.addView(fp, {path: fp}); }); pages.loaded = true; }); - return function (view) { - view.read = function () { + return function(view) { + view.read = function() { this.contents = fs.readFileSync(this.path); }; return view; @@ -34,68 +34,68 @@ describe('lookups', function () { app.pages('test/fixtures/templates/*.tmpl'); }); - describe('getView', function () { - it('should find a view', function () { + describe('getView', function() { + it('should find a view', function() { var view = app.getView('pages', 'a.tmpl'); assert(typeof view.path === 'string'); }); - it('should find a view using the renameKey function', function () { + it('should find a view using the renameKey function', function() { var view = app.getView('pages', 'test/fixtures/templates/a.tmpl'); assert(typeof view.path === 'string'); }); - it('should return null when nothing is found', function () { + it('should return null when nothing is found', function() { var view = app.getView('pages', 'test/fixtures/templates/foo.tmpl'); assert(view === null); }); - it('should find a view using a glob pattern', function () { - var view = app.getView('pages', 'a', function (key) { + it('should find a view using a glob pattern', function() { + var view = app.getView('pages', 'a', function(key) { return key + '.tmpl'; }); assert(typeof view.path === 'string'); }); }); - describe('getViews', function () { - it('should return the collection object if passed:', function () { + describe('getViews', function() { + it('should return the collection object if passed:', function() { var views = app.getViews(app.views.pages); assert(Object.keys(views).length > 1); }); - it('should return the specified collection with the plural name:', function () { + it('should return the specified collection with the plural name:', function() { var views = app.getViews('pages'); assert(Object.keys(views).length > 1); }); - it('should return the specified collection with the singular name:', function () { + it('should return the specified collection with the singular name:', function() { var views = app.getViews('page'); assert(Object.keys(views).length > 1); }); - it('should return null when the collection is not found:', function () { - (function () { + it('should return null when the collection is not found:', function() { + (function() { app.getViews('nada'); }).should.throw('getViews cannot find collection: nada'); }); }); - describe('find', function () { - it('should return null when a view is not found:', function () { - (function () { + describe('find', function() { + it('should return null when a view is not found:', function() { + (function() { app.find({}); }).should.throw('expected name to be a string.'); }); - it('should find a view by collection name:', function () { + it('should find a view by collection name:', function() { var view = app.find('a.tmpl', 'pages'); assert(typeof view.path === 'string'); }); - it('should find a view by collection name:', function () { + it('should find a view by collection name:', function() { app = new App(); - app.option('renameKey', function (key) { + app.option('renameKey', function(key) { return path.basename(key); }); app.create('pages'); @@ -104,7 +104,7 @@ describe('lookups', function () { assert(typeof view.path === 'string'); }); - it('should find a view without a collection name:', function () { + it('should find a view without a collection name:', function() { var view = app.find('a.tmpl'); assert(typeof view.path === 'string'); }); diff --git a/test/app.middleware.js b/test/app.middleware.js index 25843ed..e32ca1a 100644 --- a/test/app.middleware.js +++ b/test/app.middleware.js @@ -5,16 +5,16 @@ var support = require('./support'); var App = support.resolve(); var app; -describe('middleware', function () { - beforeEach(function () { +describe('middleware', function() { + beforeEach(function() { app = new App(); app.engine('tmpl', require('engine-base')); app.create('pages'); }); - it('should call the all method for every middleware method:', function () { + it('should call the all method for every middleware method:', function() { var i = 0; - app.all(/./, function (view, next) { + app.all(/./, function(view, next) { assert(typeof view.path === 'string'); i++; next(); @@ -25,9 +25,9 @@ describe('middleware', function () { assert(i === 1); }); - it('should call the onLoad method when a view is loaded:', function () { + it('should call the onLoad method when a view is loaded:', function() { var i = 0; - app.onLoad(/./, function (view, next) { + app.onLoad(/./, function(view, next) { assert(typeof view.path === 'string'); i++; next(); @@ -38,20 +38,20 @@ describe('middleware', function () { assert(i === 1); }); - it('should emit an event when a handler is called:', function (done) { + it('should emit an event when a handler is called:', function(done) { var i = 0; - app.on('onLoad', function () { + app.on('onLoad', function() { i++; }); - app.on('preRender', function () { + app.on('preRender', function() { i++; }); - app.on('preCompile', function () { + app.on('preCompile', function() { i++; }); app.page('foo.tmpl', {content: 'foo'}) - .render(function (err) { + .render(function(err) { if (err) return done(err); assert(i === 3); done(); diff --git a/test/app.onLoad.js b/test/app.onLoad.js new file mode 100644 index 0000000..66bffbe --- /dev/null +++ b/test/app.onLoad.js @@ -0,0 +1,48 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('onLoad', function() { + beforeEach(function() { + app = new App(); + }); + + describe('app.collection', function() { + it('should emit an onLoad when view is created', function(done) { + var collection = app.collection(); + + app.on('onLoad', function(view) { + assert(view.path === 'blog/foo.js'); + done(); + }); + + app.onLoad('blog/:title', function(view, next) { + assert(view.path === 'blog/foo.js'); + next(); + }); + + collection.addView('whatever', {path: 'blog/foo.js', content: 'bar baz'}); + }); + }); + + describe('view collections', function() { + it('should emit an onLoad when view is created', function(done) { + app.create('posts'); + + app.on('onLoad', function(view) { + assert(view.path === 'blog/foo.js'); + done(); + }); + + app.onLoad('blog/:title', function(view, next) { + assert(view.path === 'blog/foo.js'); + next(); + }); + + app.post('whatever', {path: 'blog/foo.js', content: 'bar baz'}); + }); + }); +}); diff --git a/test/app.option.js b/test/app.option.js index 12bdd6f..c4043e7 100644 --- a/test/app.option.js +++ b/test/app.option.js @@ -5,17 +5,17 @@ var support = require('./support'); var App = support.resolve(); var app; -describe('app.option', function () { - beforeEach(function () { +describe('app.option', function() { + beforeEach(function() { app = new App(); }); - it('should set a key-value pair on options:', function () { + it('should set a key-value pair on options:', function() { app.option('a', 'b'); assert(app.options.a === 'b'); }); - it('should set an object on options:', function () { + it('should set an object on options:', function() { app.option({c: 'd'}); assert(app.options.c === 'd'); }); diff --git a/test/app.render.js b/test/app.render.js index e796d95..ddcb352 100644 --- a/test/app.render.js +++ b/test/app.render.js @@ -5,41 +5,41 @@ var support = require('./support'); var App = support.resolve(); var app; -describe('render', function () { - describe('rendering', function () { - beforeEach(function () { +describe('render', function() { + describe('rendering', function() { + beforeEach(function() { app = new App(); app.engine('tmpl', require('engine-base')); app.create('page'); }); - it('should throw an error when no callback is given:', function () { + it('should throw an error when no callback is given:', function() { (function() { app.render({}); }).should.throw('Templates#render is async and expects a callback function'); }); - it('should throw an error when an engine is not defined:', function (done) { + it('should throw an error when an engine is not defined:', function(done) { app.page('foo.bar', {content: '<%= name %>'}); var page = app.pages.getView('foo.bar'); app.render(page, function(err) { - assert(err.message === 'Templates#render cannot find an engine for: .bar'); + assert(err.message === 'Templates#render cannot find engine: .bar'); done(); }); }); - it('should use helpers to render a view:', function (done) { + it('should use helpers to render a view:', function(done) { var locals = {name: 'Halle'}; - app.helper('upper', function (str) { + app.helper('upper', function(str) { return str.toUpperCase(str); }); app.page('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); var page = app.pages.getView('a.tmpl'); - app.render(page, function (err, res) { + app.render(page, function(err, res) { if (err) return done(err); assert(res.contents.toString() === 'a HALLE b'); @@ -47,38 +47,38 @@ describe('render', function () { }); }); - it('should use helpers when rendering a view:', function (done) { + it('should use helpers when rendering a view:', function(done) { var locals = {name: 'Halle'}; - app.helper('upper', function (str) { + app.helper('upper', function(str) { return str.toUpperCase(str); }); app.page('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); var page = app.pages.getView('a.tmpl'); - app.render(page, function (err, res) { + app.render(page, function(err, res) { if (err) return done(err); assert(res.contents.toString() === 'a HALLE b'); done(); }); }); - it('should render a template when contents is a buffer:', function (done) { + it('should render a template when contents is a buffer:', function(done) { app.pages('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); var view = app.pages.getView('a.tmpl'); - app.render(view, function (err, view) { + app.render(view, function(err, view) { if (err) return done(err); assert(view.contents.toString() === 'b'); done(); }); }); - it('should render a template when content is a string:', function (done) { + it('should render a template when content is a string:', function(done) { app.pages('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); var view = app.pages.getView('a.tmpl'); - app.render(view, function (err, view) { + app.render(view, function(err, view) { if (err) return done(err); assert(view.contents.toString() === 'b'); done(); diff --git a/test/app.renderFile.js b/test/app.renderFile.js index b49fd7f..4e3041a 100644 --- a/test/app.renderFile.js +++ b/test/app.renderFile.js @@ -1,14 +1,14 @@ 'use strict'; -var update = require('..'); +var assemble = require('..'); var assert = require('assert'); var should = require('should'); var path = require('path'); var app; describe('app.renderFile()', function() { - beforeEach(function () { - app = update(); + beforeEach(function() { + app = assemble(); app.engine('hbs', require('engine-handlebars')); app.engine('*', require('engine-base')); @@ -17,11 +17,11 @@ describe('app.renderFile()', function() { app.file('b', {content: 'this is <%= title() %>'}); app.file('c', {content: 'this is <%= title() %>'}); - app.option('renameKey', function (key) { + app.option('renameKey', function(key) { return path.basename(key, path.extname(key)); }); - app.helper('title', function () { + app.helper('title', function() { var view = this.context.view; var key = view.key; var ctx = this.context[key]; @@ -30,16 +30,16 @@ describe('app.renderFile()', function() { }); }); - it('should render views from src', function (done) { + it('should render views from src', function(done) { var stream = app.src(path.join(__dirname, 'fixtures/pages/*.hbs')); var files = []; stream.pipe(app.renderFile()) .on('error', done) - .on('data', function (file) { + .on('data', function(file) { files.push(file); }) - .on('end', function () { + .on('end', function() { assert.equal(files[0].basename, 'a.hbs'); assert.equal(files[1].basename, 'b.hbs'); assert.equal(files[2].basename, 'c.hbs'); @@ -47,16 +47,16 @@ describe('app.renderFile()', function() { }); }); - it('should render views with the engine that matches the file extension', function (done) { + it('should render views with the engine that matches the file extension', function(done) { var stream = app.src(path.join(__dirname, 'fixtures/pages/*.hbs')); var files = []; stream.pipe(app.renderFile()) .on('error', done) - .on('data', function (file) { + .on('data', function(file) { files.push(file); }) - .on('end', function () { + .on('end', function() { assert(/

a<\/h1>/.test(files[0].content)); assert(/

b<\/h1>/.test(files[1].content)); assert(/

c<\/h1>/.test(files[2].content)); @@ -64,16 +64,16 @@ describe('app.renderFile()', function() { }); }); - it('should render views from src with the engine passed on the opts', function (done) { + it('should render views from src with the engine passed on the opts', function(done) { var stream = app.src(path.join(__dirname, 'fixtures/pages/*.hbs')); var files = []; stream.pipe(app.renderFile({engine: '*'})) .on('error', done) - .on('data', function (file) { + .on('data', function(file) { files.push(file); }) - .on('end', function () { + .on('end', function() { assert(/

a<\/h2>/.test(files[0].content)); assert(/

b<\/h2>/.test(files[1].content)); assert(/

c<\/h2>/.test(files[2].content)); @@ -81,16 +81,16 @@ describe('app.renderFile()', function() { }); }); - it('should use the context passed on the opts', function (done) { + it('should use the context passed on the opts', function(done) { var stream = app.src(path.join(__dirname, 'fixtures/pages/*.hbs')); var files = []; stream.pipe(app.renderFile({a: {title: 'foo'}})) .on('error', done) - .on('data', function (file) { + .on('data', function(file) { files.push(file); }) - .on('end', function () { + .on('end', function() { assert(/

foo<\/h1>/.test(files[0].content)); assert(/

b<\/h1>/.test(files[1].content)); assert(/

c<\/h1>/.test(files[2].content)); @@ -98,18 +98,18 @@ describe('app.renderFile()', function() { }); }); - it('should render the files in a collection', function (cb) { + it('should render the files in a collection', function(cb) { var files = []; app.toStream('files') .pipe(app.renderFile()) .on('error', cb) - .on('data', function (file) { + .on('data', function(file) { should.exist(file); should.exist(file.path); should.exist(file.contents); files.push(file); }) - .on('end', function () { + .on('end', function() { assert(/this is a/.test(files[0].content)); assert(/this is b/.test(files[1].content)); assert(/this is c/.test(files[2].content)); @@ -118,17 +118,17 @@ describe('app.renderFile()', function() { }); }); - it('should handle engine errors', function (cb) { + it('should handle engine errors', function(cb) { app.create('notdefined', {engine: '*'}); app.notdefined('foo', {content: '<%= bar %>'}); app.toStream('notdefined') .pipe(app.renderFile()) - .on('error', function (err) { + .on('error', function(err) { assert.equal(typeof err, 'object'); assert.equal(err.message, 'bar is not defined'); cb(); }) - .on('end', function () { + .on('end', function() { cb(new Error('expected renderFile to handle the error.')); }); }); diff --git a/test/app.route.js b/test/app.route.js index c7ab262..213dd56 100644 --- a/test/app.route.js +++ b/test/app.route.js @@ -5,13 +5,13 @@ var support = require('./support'); var App = support.resolve(); var app; -describe('routes', function () { +describe('routes', function() { beforeEach(function() { app = new App(); }); describe('routes', function() { - it('should create a route for the given path:', function (done) { + it('should create a route for the given path:', function(done) { app = new App(); app.create('posts'); @@ -29,7 +29,7 @@ describe('routes', function () { app.post('whatever', {path: 'blog/foo.js', content: 'bar baz'}); }); - it('should emit events when a route method is called:', function (done) { + it('should emit events when a route method is called:', function(done) { app = new App(); app.create('posts'); @@ -38,12 +38,12 @@ describe('routes', function () { done(); }); - app.param('title', function (view, next, title) { + app.param('title', function(view, next, title) { assert(title === 'foo.js'); next(); }); - app.onLoad('blog/:title', function (view, next) { + app.onLoad('blog/:title', function(view, next) { assert(view.path === 'blog/foo.js'); next(); }); @@ -51,7 +51,7 @@ describe('routes', function () { app.post('whatever', {path: 'blog/foo.js', content: 'bar baz'}); }); - it('should emit errors', function (done) { + it('should emit errors', function(done) { app = new App(); app.create('posts'); @@ -61,12 +61,12 @@ describe('routes', function () { }); // wrong... - app.param('title', function (view, next, title) { + app.param('title', function(view, next, title) { assert(title === 'fo.js'); next(); }); - app.onLoad('/blog/:title', function (view, next) { + app.onLoad('/blog/:title', function(view, next) { assert(view.path === '/blog/foo.js'); next(); }); @@ -74,16 +74,16 @@ describe('routes', function () { app.post('whatever', {path: '/blog/foo.js', content: 'bar baz'}); }); - it('should have path property', function () { + it('should have path property', function() { var route = new app.Route('/blog/:year/:month/:day/:slug').all([ - function () {} + function() {} ]); route.path.should.equal('/blog/:year/:month/:day/:slug'); }); - it('should have stack property', function () { + it('should have stack property', function() { var route = new app.Route('/blog/:year/:month/:day/:slug').all([ - function () {} + function() {} ]); route.stack.should.be.instanceof(Array); diff --git a/test/app.src.js b/test/app.src.js index bd2b089..034917b 100644 --- a/test/app.src.js +++ b/test/app.src.js @@ -7,11 +7,11 @@ var join = require('path').join; var app; describe('src()', function() { - beforeEach(function () { + beforeEach(function() { app = new App(); }); - it('should return a stream', function (done) { + it('should return a stream', function(done) { var stream = app.src(join(__dirname, './fixtures/*.coffee')); assert(stream); assert.equal(typeof stream.on, 'function'); @@ -19,22 +19,22 @@ describe('src()', function() { done(); }); - it('should return an input stream from a flat glob', function (done) { + it('should return an input stream from a flat glob', function(done) { var stream = app.src(join(__dirname, './fixtures/*.coffee')); stream.on('error', done); - stream.on('data', function (file) { + stream.on('data', function(file) { should.exist(file); should.exist(file.path); should.exist(file.contents); join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); String(file.contents).should.equal('Hello world!'); }); - stream.on('end', function () { + stream.on('end', function() { done(); }); }); - it('should return an input stream for multiple globs', function (done) { + it('should return an input stream for multiple globs', function(done) { var globArray = [ join(__dirname, './fixtures/generic/run.dmc'), join(__dirname, './fixtures/generic/test.dmc') @@ -43,12 +43,12 @@ describe('src()', function() { var files = []; stream.on('error', done); - stream.on('data', function (file) { + stream.on('data', function(file) { should.exist(file); should.exist(file.path); files.push(file); }); - stream.on('end', function () { + stream.on('end', function() { files.length.should.equal(2); files[0].path.should.equal(globArray[0]); files[1].path.should.equal(globArray[1]); @@ -56,7 +56,7 @@ describe('src()', function() { }); }); - it('should return an input stream for multiple globs with negation', function (done) { + it('should return an input stream for multiple globs with negation', function(done) { var expectedPath = join(__dirname, './fixtures/generic/run.dmc'); var globArray = [ join(__dirname, './fixtures/generic/*.dmc'), @@ -66,44 +66,44 @@ describe('src()', function() { var files = []; stream.on('error', done); - stream.on('data', function (file) { + stream.on('data', function(file) { should.exist(file); should.exist(file.path); files.push(file); }); - stream.on('end', function () { + stream.on('end', function() { files.length.should.equal(1); files[0].path.should.equal(expectedPath); done(); }); }); - it('should return an input stream with no contents when read is false', function (done) { + it('should return an input stream with no contents when read is false', function(done) { var stream = app.src(join(__dirname, './fixtures/*.coffee'), {read: false}); stream.on('error', done); - stream.on('data', function (file) { + stream.on('data', function(file) { should.exist(file); should.exist(file.path); should.not.exist(file.contents); join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); }); - stream.on('end', function () { + stream.on('end', function() { done(); }); }); - it('should return an input stream with contents as stream when buffer is false', function (done) { + it('should return an input stream with contents as stream when buffer is false', function(done) { var stream = app.src(join(__dirname, './fixtures/*.coffee'), {buffer: false}); stream.on('error', done); - stream.on('data', function (file) { + stream.on('data', function(file) { should.exist(file); should.exist(file.path); should.exist(file.contents); var buf = ''; - file.contents.on('data', function (d) { + file.contents.on('data', function(d) { buf += d; }); - file.contents.on('end', function () { + file.contents.on('end', function() { buf.should.equal('Hello world!'); done(); }); @@ -111,39 +111,39 @@ describe('src()', function() { }); }); - it('should return an input stream from a deep glob', function (done) { + it('should return an input stream from a deep glob', function(done) { var stream = app.src(join(__dirname, './fixtures/**/*.jade')); stream.on('error', done); - stream.on('data', function (file) { + stream.on('data', function(file) { should.exist(file); should.exist(file.path); should.exist(file.contents); join(file.path, '').should.equal(join(__dirname, './fixtures/test/run.jade')); String(file.contents).should.equal('test template'); }); - stream.on('end', function () { + stream.on('end', function() { done(); }); }); - it('should return an input stream from a deeper glob', function (done) { + it('should return an input stream from a deeper glob', function(done) { var stream = app.src(join(__dirname, './fixtures/**/*.dmc')); var a = 0; stream.on('error', done); - stream.on('data', function () { + stream.on('data', function() { ++a; }); - stream.on('end', function () { + stream.on('end', function() { a.should.equal(2); done(); }); }); - it('should return a file stream from a flat path', function (done) { + it('should return a file stream from a flat path', function(done) { var a = 0; var stream = app.src(join(__dirname, './fixtures/test.coffee')); stream.on('error', done); - stream.on('data', function (file) { + stream.on('data', function(file) { ++a; should.exist(file); should.exist(file.path); @@ -151,35 +151,35 @@ describe('src()', function() { join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); String(file.contents).should.equal('Hello world!'); }); - stream.on('end', function () { + stream.on('end', function() { a.should.equal(1); done(); }); }); - it('should return a stream', function (done) { + it('should return a stream', function(done) { var stream = app.src(join(__dirname, './fixtures/*.coffee')); should.exist(stream); should.exist(stream.on); done(); }); - it('should return an input stream from a flat glob', function (done) { + it('should return an input stream from a flat glob', function(done) { var stream = app.src(join(__dirname, './fixtures/*.coffee')); stream.on('error', done); - stream.on('data', function (file) { + stream.on('data', function(file) { should.exist(file); should.exist(file.path); should.exist(file.contents); join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); String(file.contents).should.equal('Hello world!'); }); - stream.on('end', function () { + stream.on('end', function() { done(); }); }); - it('should return an input stream for multiple globs', function (done) { + it('should return an input stream for multiple globs', function(done) { var globArray = [ join(__dirname, './fixtures/generic/run.dmc'), join(__dirname, './fixtures/generic/test.dmc') @@ -188,12 +188,12 @@ describe('src()', function() { var files = []; stream.on('error', done); - stream.on('data', function (file) { + stream.on('data', function(file) { should.exist(file); should.exist(file.path); files.push(file); }); - stream.on('end', function () { + stream.on('end', function() { files.length.should.equal(2); files[0].path.should.equal(globArray[0]); files[1].path.should.equal(globArray[1]); @@ -201,7 +201,7 @@ describe('src()', function() { }); }); - it('should return an input stream for multiple globs, with negation', function (done) { + it('should return an input stream for multiple globs, with negation', function(done) { var expectedPath = join(__dirname, './fixtures/generic/run.dmc'); var globArray = [ join(__dirname, './fixtures/generic/*.dmc'), @@ -211,75 +211,75 @@ describe('src()', function() { var files = []; stream.on('error', done); - stream.on('data', function (file) { + stream.on('data', function(file) { should.exist(file); should.exist(file.path); files.push(file); }); - stream.on('end', function () { + stream.on('end', function() { files.length.should.equal(1); files[0].path.should.equal(expectedPath); done(); }); }); - it('should return an input stream with no contents when read is false', function (done) { + it('should return an input stream with no contents when read is false', function(done) { var stream = app.src(join(__dirname, './fixtures/*.coffee'), {read: false}); stream.on('error', done); - stream.on('data', function (file) { + stream.on('data', function(file) { should.exist(file); should.exist(file.path); should.not.exist(file.contents); join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); }); - stream.on('end', function () { + stream.on('end', function() { done(); }); }); - it.skip('should throw an error when buffer is false', function (done) { + it.skip('should throw an error when buffer is false', function(done) { app.src(join(__dirname, './fixtures/*.coffee'), {buffer: false}) - .on('error', function () { + .on('error', function() { done(); }) - .on('data', function () { + .on('data', function() { done(new Error('should have thrown an error')); }); }); - it('should return an input stream from a deep glob', function (done) { + it('should return an input stream from a deep glob', function(done) { app.src(join(__dirname, './fixtures/**/*.jade')) .on('error', done) - .on('data', function (file) { + .on('data', function(file) { should.exist(file); should.exist(file.path); should.exist(file.contents); join(file.path, '').should.equal(join(__dirname, './fixtures/test/run.jade')); String(file.contents).should.equal('test template'); }) - .on('end', function () { + .on('end', function() { done(); }); }); - it('should return an input stream from a deeper glob', function (done) { + it('should return an input stream from a deeper glob', function(done) { var stream = app.src(join(__dirname, './fixtures/**/*.dmc')); var a = 0; stream.on('error', done); - stream.on('data', function () { + stream.on('data', function() { ++a; }); - stream.on('end', function () { + stream.on('end', function() { a.should.equal(2); done(); }); }); - it('should return a file stream from a flat path', function (done) { + it('should return a file stream from a flat path', function(done) { var a = 0; var stream = app.src(join(__dirname, './fixtures/test.coffee')); stream.on('error', done); - stream.on('data', function (file) { + stream.on('data', function(file) { ++a; should.exist(file); should.exist(file.path); @@ -287,7 +287,7 @@ describe('src()', function() { join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); String(file.contents).should.equal('Hello world!'); }); - stream.on('end', function () { + stream.on('end', function() { a.should.equal(1); done(); }); diff --git a/test/app.symlink.js b/test/app.symlink.js index 8a63fea..69e8d45 100644 --- a/test/app.symlink.js +++ b/test/app.symlink.js @@ -6,7 +6,7 @@ var rimraf = require('rimraf'); var bufEqual = require('buffer-equal'); var through = require('through2'); var File = require('vinyl'); -var update = require('..'); +var assemble = require('..'); var spies = require('./support/spy'); var chmodSpy = spies.chmodSpy; var statSpy = spies.statSpy; @@ -17,7 +17,7 @@ var wipeOut = function(cb) { spies.setError('false'); statSpy.reset(); chmodSpy.reset(); - app = update(); + app = assemble(); }; var dataWrap = function(fn) { diff --git a/test/app.task.js b/test/app.task.js index 3b73918..333f4fd 100644 --- a/test/app.task.js +++ b/test/app.task.js @@ -4,13 +4,14 @@ var assert = require('assert'); var App = require('..'); var app; -describe('task()', function () { - beforeEach(function () { +describe('task()', function() { + beforeEach(function() { app = new App(); + app.tasks = {}; }); - it('should register a task', function () { - var fn = function (done) { + it('should register a task', function() { + var fn = function(done) { done(); }; app.task('default', fn); @@ -18,51 +19,51 @@ describe('task()', function () { assert.equal(app.tasks.default.fn, fn); }); - it('should register a task with an array of dependencies', function () { - app.task('default', ['foo', 'bar'], function (done) { + it('should register a task with an array of dependencies', function() { + app.task('default', ['foo', 'bar'], function(done) { done(); }); assert.equal(typeof app.tasks.default, 'object'); assert.deepEqual(app.tasks.default.deps, ['foo', 'bar']); }); - it('should register a task with a list of strings as dependencies', function () { - app.task('default', 'foo', 'bar', function (done) { + it('should register a task with a list of strings as dependencies', function() { + app.task('default', 'foo', 'bar', function(done) { done(); }); assert.equal(typeof app.tasks.default, 'object'); assert.deepEqual(app.tasks.default.deps, ['foo', 'bar']); }); - it('should run a task', function (done) { + it('should run a task', function(done) { var count = 0; - app.task('default', function (cb) { + app.task('default', function(cb) { count++; cb(); }); - app.build('default', function (err) { + app.build('default', function(err) { if (err) return done(err); assert.equal(count, 1); done(); }); }); - it('should throw an error when a task with unregistered dependencies is run', function (done) { + it('should throw an error when a task with unregistered dependencies is run', function(done) { var count = 0; - app.task('default', ['foo', 'bar'], function (cb) { + app.task('default', ['foo', 'bar'], function(cb) { count++; cb(); }); - app.build('default', function (err) { + app.build('default', function(err) { if (!err) return done(new Error('Expected an error to be thrown.')); assert.equal(count, 0); done(); }); }); - it('should throw an error when `.build` is called without a callback function.', function () { + it('should throw an error when `.build` is called without a callback function.', function() { try { app.build('default'); throw new Error('Expected an error to be thrown.'); @@ -70,7 +71,7 @@ describe('task()', function () { } }); - it('should emit task events', function (done) { + it('should emit task events', function(done) { var events = []; app.on('task:starting', function(task) { events.push('starting.' + task.name); @@ -82,14 +83,14 @@ describe('task()', function () { events.push('error.' + task.name); }); - app.task('foo', function (cb) { + app.task('foo', function(cb) { cb(); }); - app.task('bar', ['foo'], function (cb) { + app.task('bar', ['foo'], function(cb) { cb(); }); app.task('default', ['bar']); - app.build('default', function (err) { + app.build('default', function(err) { if (err) return done(err); assert.deepEqual(events, [ 'starting.default', @@ -103,53 +104,53 @@ describe('task()', function () { }); }); - it('should emit an error event when an error is passed back in a task', function (done) { - app.on('error', function (err) { + it('should emit an error event when an error is passed back in a task', function(done) { + app.on('error', function(err) { assert(err); assert.equal(err.message, 'This is an error'); }); - app.task('default', function (cb) { + app.task('default', function(cb) { return cb(new Error('This is an error')); }); - app.build('default', function (err) { + app.build('default', function(err) { if (err) return done(); done(new Error('Expected an error')); }); }); - it('should emit an error event when an error is thrown in a task', function (done) { + it('should emit an error event when an error is thrown in a task', function(done) { var errors = 0; - app.on('error', function (err) { + app.on('error', function(err) { errors++; assert(err); assert.equal(err.message, 'This is an error'); }); - app.task('default', function (cb) { + app.task('default', function(cb) { cb(new Error('This is an error')); }); - app.build('default', function (err) { + app.build('default', function(err) { assert.equal(errors, 1); if (err) return done(); done(new Error('Expected an error')); }); }); - it('should run dependencies before running the dependent task.', function (done) { + it('should run dependencies before running the dependent task.', function(done) { var seq = []; - app.task('foo', function (cb) { + app.task('foo', function(cb) { seq.push('foo'); cb(); }); - app.task('bar', function (cb) { + app.task('bar', function(cb) { seq.push('bar'); cb(); }); - app.task('default', ['foo', 'bar'], function (cb) { + app.task('default', ['foo', 'bar'], function(cb) { seq.push('default'); cb(); }); - app.build('default', function (err) { + app.build('default', function(err) { if (err) return done(err); assert.deepEqual(seq, ['foo', 'bar', 'default']); done(); diff --git a/test/app.toStream.js b/test/app.toStream.js index 326b84c..fddfb93 100644 --- a/test/app.toStream.js +++ b/test/app.toStream.js @@ -1,13 +1,13 @@ 'use strict'; -var update = require('..'); +var assemble = require('..'); var assert = require('assert'); var should = require('should'); var app; describe('toStream()', function() { - beforeEach(function () { - app = update(); + beforeEach(function() { + app = assemble(); app.create('pages'); app.page('a', {content: 'this is A'}); app.page('b', {content: 'this is B'}); @@ -19,44 +19,44 @@ describe('toStream()', function() { app.post('z', {content: 'this is Z'}); }); - it('should return a stream', function (cb) { + it('should return a stream', function(cb) { var stream = app.toStream(); should.exist(stream); should.exist(stream.on); cb(); }); - it('should return a stream for a collection', function (cb) { + it('should return a stream for a collection', function(cb) { var stream = app.toStream('pages'); should.exist(stream); should.exist(stream.on); cb(); }); - it('should stack handle multiple collections', function (cb) { + it('should stack handle multiple collections', function(cb) { var files = []; app.toStream('pages') .pipe(app.toStream('posts')) .on('data', function(file) { files.push(file); }) - .on('end', function () { + .on('end', function() { assert.equal(files.length, 6); cb(); }); }); - it('should push each item in the collection into the stream', function (cb) { + it('should push each item in the collection into the stream', function(cb) { var files = []; app.toStream('pages') .on('error', cb) - .on('data', function (file) { + .on('data', function(file) { should.exist(file); should.exist(file.path); should.exist(file.contents); files.push(file.path); }) - .on('end', function () { + .on('end', function() { assert.equal(files.length, 3); cb(); }); diff --git a/test/app.use.js b/test/app.use.js index 875e858..6ca99ac 100644 --- a/test/app.use.js +++ b/test/app.use.js @@ -7,35 +7,35 @@ var Views = App.Views; var View = App.View; var app; -describe('app.use', function () { - beforeEach(function () { +describe('app.use', function() { + beforeEach(function() { app = new App(); }); - it('should expose the instance to `use`:', function (done) { - app.use(function (inst) { + it('should expose the instance to `use`:', function(done) { + app.use(function(inst) { assert(inst instanceof App); done(); }); }); - it('should be chainable:', function (done) { - app.use(function (inst) { - assert(inst instanceof App); - }) - .use(function (inst) { + it('should be chainable:', function(done) { + app.use(function(inst) { + assert(inst instanceof App); + }) + .use(function(inst) { assert(inst instanceof App); }) - .use(function (inst) { + .use(function(inst) { assert(inst instanceof App); done(); }); }); - it('should pass to collection `use` if a function is returned:', function () { - app.use(function (inst) { + it('should pass to collection `use` if a function is returned:', function() { + app.use(function(inst) { assert(inst instanceof App); - return function (collection) { + return function(collection) { collection.foo = collection.addView; assert(collection instanceof Views); return collection; @@ -52,27 +52,27 @@ describe('app.use', function () { assert(app.views.pages.hasOwnProperty('c.md')); }); - it('should be chainable when a collection function is returned:', function () { + it('should be chainable when a collection function is returned:', function() { app - .use(function (inst) { + .use(function(inst) { assert(inst instanceof App); - return function (collection) { + return function(collection) { collection.foo = collection.addView; assert(collection instanceof Views); return collection; }; }) - .use(function (inst) { + .use(function(inst) { assert(inst instanceof App); - return function (collection) { + return function(collection) { collection.bar = collection.addView; assert(collection instanceof Views); return collection; }; }) - .use(function (inst) { + .use(function(inst) { assert(inst instanceof App); - return function (collection) { + return function(collection) { collection.baz = collection.addView; assert(collection instanceof Views); return collection; @@ -90,15 +90,15 @@ describe('app.use', function () { assert(app.views.pages.hasOwnProperty('c.md')); }); - it('should pass to view `use` if collection.use returns a function:', function () { - app.use(function (inst) { + it('should pass to view `use` if collection.use returns a function:', function() { + app.use(function(inst) { assert(inst instanceof App); - return function (collection) { + return function(collection) { assert(collection instanceof Views); collection.foo = collection.addView; - return function (view) { + return function(view) { assert(view instanceof View); view.foo = collection.addView.bind(collection); return view; @@ -116,44 +116,44 @@ describe('app.use', function () { assert(app.views.pages.hasOwnProperty('c.md')); }); - it('should be chainable when a view function is returned:', function () { + it('should be chainable when a view function is returned:', function() { app - .use(function (inst) { + .use(function(inst) { assert(inst instanceof App); - return function (collection) { + return function(collection) { assert(collection instanceof Views); collection.foo = collection.addView; - return function (view) { + return function(view) { assert(view instanceof View); view.a = collection.addView.bind(collection); return view; }; }; }) - .use(function (inst) { + .use(function(inst) { assert(inst instanceof App); - return function (collection) { + return function(collection) { assert(collection instanceof Views); collection.bar = collection.addView; - return function (view) { + return function(view) { assert(view instanceof View); view.b = collection.addView.bind(collection); return view; }; }; }) - .use(function (inst) { + .use(function(inst) { assert(inst instanceof App); - return function (collection) { + return function(collection) { assert(collection instanceof Views); collection.baz = collection.addView; - return function (view) { + return function(view) { assert(view instanceof View); view.c = collection.addView.bind(collection); return view; @@ -179,46 +179,46 @@ describe('app.use', function () { assert(app.views.pages.hasOwnProperty('z.md')); }); - it('should work with multiple collections:', function () { + it('should work with multiple collections:', function() { app - .use(function (inst) { + .use(function(inst) { assert(inst instanceof App); - return function (collection) { + return function(collection) { assert(collection instanceof Views); collection.foo = collection.addView; - return function (view) { + return function(view) { assert(view instanceof View); view.a = collection.addView.bind(collection); return view; }; }; }) - .use(function (inst) { + .use(function(inst) { assert(inst instanceof App); - return function (collection) { + return function(collection) { assert(collection instanceof Views); collection.bar = collection.addView; - return function (view) { + return function(view) { assert(view instanceof View); view.b = collection.addView.bind(collection); return view; }; }; }) - .use(function (inst) { + .use(function(inst) { assert(inst instanceof App); assert(this instanceof App); - return function (collection) { + return function(collection) { collection.baz = collection.addView; assert(collection instanceof Views); assert(this instanceof Views); - return function (view) { + return function(view) { assert(this instanceof View); assert(view instanceof View); view.c = collection.addView.bind(collection); diff --git a/test/app.view.compile.js b/test/app.view.compile.js index e325c92..4dca5cd 100644 --- a/test/app.view.compile.js +++ b/test/app.view.compile.js @@ -5,29 +5,29 @@ var support = require('./support'); var App = support.resolve(); var app; -describe('app.view.compile', function () { - describe('compile method', function () { - beforeEach(function () { +describe('app.view.compile', function() { + describe('compile method', function() { + beforeEach(function() { app = new App(); app.engine('tmpl', require('engine-base')); app.create('page'); }); - it('should compile a view:', function () { + it('should compile a view:', function() { var buffer = new Buffer('a b c'); var view = app.page('a.tmpl', {contents: buffer}) .compile(); assert(typeof view.fn === 'function'); }); - it('should compile a view with settings:', function () { + it('should compile a view with settings:', function() { var buffer = new Buffer('a b c'); var view = app.page('a.tmpl', {contents: buffer}) .compile({foo: 'bar'}); assert(typeof view.fn === 'function'); }); - it('should compile a view with isAsync flag:', function () { + it('should compile a view with isAsync flag:', function() { var buffer = new Buffer('a b c'); var view = app.page('a.tmpl', {contents: buffer}) .compile(true); diff --git a/test/app.view.render.js b/test/app.view.render.js index fcf3087..1cfd929 100644 --- a/test/app.view.render.js +++ b/test/app.view.render.js @@ -5,24 +5,24 @@ var support = require('./support'); var App = support.resolve(); var app; -describe('helpers', function () { - describe('rendering', function () { - beforeEach(function () { +describe('helpers', function() { + describe('rendering', function() { + beforeEach(function() { app = new App(); app.engine('tmpl', require('engine-base')); app.create('page'); }); - it('should use helpers to render a view:', function (done) { + it('should use helpers to render a view:', function(done) { var locals = {name: 'Halle'}; - app.helper('upper', function (str) { + app.helper('upper', function(str) { return str.toUpperCase(str); }); var buffer = new Buffer('a <%= upper(name) %> b'); app.page('a.tmpl', {contents: buffer, locals: locals}) - .render(function (err, res) { + .render(function(err, res) { if (err) return done(err); assert(res.contents.toString() === 'a HALLE b'); @@ -30,12 +30,12 @@ describe('helpers', function () { }); }); - it('should support helpers as an array:', function (done) { + it('should support helpers as an array:', function(done) { var locals = {name: 'Halle'}; app.helpers([ { - lower: function (str) { + lower: function(str) { return str.toLowerCase(str); } } @@ -43,7 +43,7 @@ describe('helpers', function () { var buffer = new Buffer('a <%= lower(name) %> b'); app.page('a.tmpl', {contents: buffer, locals: locals}) - .render(function (err, res) { + .render(function(err, res) { if (err) return done(err); assert(res.contents.toString() === 'a halle b'); @@ -51,37 +51,37 @@ describe('helpers', function () { }); }); - it('should support helpers as an object:', function (done) { + it('should support helpers as an object:', function(done) { var locals = {name: 'Halle'}; app.helpers({ - prepend: function (prefix, str) { + prepend: function(prefix, str) { return prefix + str; } }); var buffer = new Buffer('a <%= prepend("foo ", name) %> b'); app.page('a.tmpl', {contents: buffer, locals: locals}) - .render(function (err, res) { + .render(function(err, res) { if (err) return done(err); assert(res.contents.toString() === 'a foo Halle b'); done(); }); }); - it('should use the engine defined on view options:', function (done) { + it('should use the engine defined on view options:', function(done) { app.engine('hbs', require('engine-handlebars')); var locals = {name: 'Halle'}; app.helpers({ - prepend: function (prefix, str) { + prepend: function(prefix, str) { return prefix + str; } }); var buffer = new Buffer('a {{prepend "foo " name}} b'); app.page('a.tmpl', {contents: buffer, locals: locals, options: {engine: 'hbs'}}) - .render(function (err, res) { + .render(function(err, res) { if (err) return done(err); assert(res.contents.toString() === 'a foo Halle b'); done(); diff --git a/test/app.watch.js b/test/app.watch.js index 698b768..0990e70 100644 --- a/test/app.watch.js +++ b/test/app.watch.js @@ -5,35 +5,35 @@ var fs = require('fs'); var App = require('..'); var app; -describe.skip('watch()', function () { - beforeEach(function () { +describe.skip('watch()', function() { + beforeEach(function() { app = new App({runtimes: false}); }); - it('should watch files and run a task when files change', function (done) { + it('should watch files and run a task when files change', function(done) { this.timeout(750); var count = 0, watch; - app.task('default', function (cb) { + app.task('default', function(cb) { count++; cb(); }); - app.task('close', function (cb) { + app.task('close', function(cb) { watch.close(); app.emit('close'); cb(); }); - app.task('watch', function (cb) { + app.task('watch', function(cb) { watch = app.watch('test/fixtures/watch/*.txt', ['default', 'close']); - fs.writeFile('test/fixtures/watch/test.txt', 'test', function (err) { + fs.writeFile('test/fixtures/watch/test.txt', 'test', function(err) { if (err) return cb(err); app.on('close', cb); }); }); - app.build(['watch'], function (err) { + app.build(['watch'], function(err) { if (err) return done(err); assert.equal(count, 1); done(); diff --git a/test/collection.engines.js b/test/collection.engines.js index 4c2a619..3465c9e 100644 --- a/test/collection.engines.js +++ b/test/collection.engines.js @@ -11,18 +11,18 @@ describe('collection engines', function() { pages = new Views(); }); - it('should throw an error when engine name is invalid:', function () { - (function () { + it('should throw an error when engine name is invalid:', function() { + (function() { pages.engine(null, {}); }).should.throw('expected engine ext to be a string or array.'); }); - it('should register an engine to the given extension', function () { - pages.engine('hbs', function () {}); + it('should register an engine to the given extension', function() { + pages.engine('hbs', function() {}); assert(typeof pages.engines['.hbs'] === 'object'); }); - it('should set an engine with the given extension', function () { + it('should set an engine with the given extension', function() { var hbs = function() {}; hbs.render = function() {}; hbs.renderFile = function() {}; @@ -32,143 +32,142 @@ describe('collection engines', function() { assert(pages.engines['.hbs'].render); }); - it('should get an engine:', function () { - pages.engine('hbs', function () {}); + it('should get an engine:', function() { + pages.engine('hbs', function() {}); var hbs = pages.engine('hbs'); assert(typeof hbs === 'object'); assert(hbs.hasOwnProperty('render')); assert(hbs.hasOwnProperty('compile')); }); - it('should register multiple engines to the given extension', function () { - pages.engine(['hbs', 'md'], function () {}); + it('should register multiple engines to the given extension', function() { + pages.engine(['hbs', 'md'], function() {}); assert(typeof pages.engines['.hbs'] === 'object'); assert(typeof pages.engines['.md'] === 'object'); }); }); -describe('engines', function () { - beforeEach(function () { +describe('engines', function() { + beforeEach(function() { pages = new Views(); pages.addView('foo.tmpl', {content: 'A <%= letter %> {{= letter }} C'}); pages.addView('bar.tmpl', {content: 'A <%= letter %> {{ letter }} C'}); }); - it('should register an engine:', function () { - pages.engine('a', {render: function () {}}); + it('should register an engine:', function() { + pages.engine('a', {render: function() {}}); pages.engines.should.have.property('.a'); }); - it('should use custom delimiters:', function (done) { + it('should use custom delimiters:', function(done) { pages.engine('tmpl', require('engine-base'), { delims: ['{{', '}}'] }); - pages.render('foo.tmpl', {letter: 'B'}, function (err, res) { + pages.render('foo.tmpl', {letter: 'B'}, function(err, res) { if (err) return done(err); res.content.should.equal('A <%= letter %> B C'); done(); }); }); - it('should override individual delims values:', function (done) { + it('should override individual delims values:', function(done) { pages.engine('tmpl', require('engine-base'), { interpolate: /\{{([^}]+)}}/g, evaluate: /\{{([^}]+)}}/g, escape: /\{{-([^}]+)}}/g }); - pages.render('bar.tmpl', {letter: 'B'}, function (err, res) { + pages.render('bar.tmpl', {letter: 'B'}, function(err, res) { if (err) return done(err); res.content.should.equal('A <%= letter %> B C'); done(); }); }); - it('should get an engine:', function () { + it('should get an engine:', function() { pages.engine('a', { - render: function () {} + render: function() {} }); var a = pages.engine('a'); a.should.have.property('render'); }); }); - -describe('engine selection:', function () { - beforeEach(function (done) { +describe('engine selection:', function() { + beforeEach(function(done) { collection = new Views(); collection.engine('tmpl', require('engine-base')); collection.engine('hbs', require('engine-handlebars')); done(); }); - it('should get the engine from file extension:', function (done) { + it('should get the engine from file extension:', function(done) { var pages = new Views(); pages.engine('tmpl', require('engine-base')); pages.engine('hbs', require('engine-handlebars')); pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}) - .render(function (err, view) { + .render(function(err, view) { if (err) return done(err); assert(view.content === 'b'); done(); }); }); - it('should use the engine defined on the collection:', function (done) { + it('should use the engine defined on the collection:', function(done) { var posts = new Views({engine: 'hbs'}); posts.engine('tmpl', require('engine-base')); posts.engine('hbs', require('engine-handlebars')); posts.addView('a', {content: '{{a}}', locals: {a: 'b'}}) - .render(function (err, view) { + .render(function(err, view) { if (err) return done(err); assert(view.content === 'b'); done(); }); }); - it('should use the engine defined on the view:', function (done) { + it('should use the engine defined on the view:', function(done) { var posts = new Views(); posts.engine('tmpl', require('engine-base')); posts.engine('hbs', require('engine-handlebars')); posts.addView('a', {content: '{{a}}', engine: 'hbs', locals: {a: 'b'}}) - .render(function (err, view) { + .render(function(err, view) { if (err) return done(err); assert(view.content === 'b'); done(); }); }); - it('should use the engine defined on view.options:', function (done) { + it('should use the engine defined on view.options:', function(done) { var posts = new Views(); posts.engine('tmpl', require('engine-base')); posts.engine('hbs', require('engine-handlebars')); posts.addView('a', {content: '{{a}}', data: {a: 'b'}, options: {engine: 'hbs'}}) - .render(function (err, view) { + .render(function(err, view) { if (err) return done(err); assert(view.content === 'b'); done(); }); }); - it('should use the engine defined on view.data:', function (done) { + it('should use the engine defined on view.data:', function(done) { var posts = new Views(); posts.engine('tmpl', require('engine-base')); posts.engine('hbs', require('engine-handlebars')); posts.addView('a', {content: '{{a}}', locals: {a: 'b'}, data: {engine: 'hbs'}}) - .render(function (err, view) { + .render(function(err, view) { if (err) return done(err); assert(view.content === 'b'); done(); }); }); - it('should use the engine defined on render locals:', function (done) { + it('should use the engine defined on render locals:', function(done) { var posts = new Views(); posts.engine('tmpl', require('engine-base')); posts.engine('hbs', require('engine-handlebars')); posts.addView('a', {content: '{{a}}', locals: {a: 'b'}}) - .render({engine: 'hbs'}, function (err, view) { + .render({engine: 'hbs'}, function(err, view) { if (err) return done(err); assert(view.content === 'b'); done(); diff --git a/test/collection.events.js b/test/collection.events.js index e919835..bdf9eaf 100644 --- a/test/collection.events.js +++ b/test/collection.events.js @@ -3,17 +3,17 @@ var support = require('./support'); var App = support.resolve(); var app; -describe('collection events', function () { - beforeEach(function () { +describe('collection events', function() { + beforeEach(function() { app = new App(); app.create('page'); }); - it('should emit events:', function () { + it('should emit events:', function() { app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}); var events = []; - app.pages.on('option', function (key) { + app.pages.on('option', function(key) { events.push(key); }); diff --git a/test/collection.getView.js b/test/collection.getView.js new file mode 100644 index 0000000..fb58723 --- /dev/null +++ b/test/collection.getView.js @@ -0,0 +1,34 @@ +'use strict'; + +var path = require('path'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('collection.getView', function() { + beforeEach(function() { + app = new App(); + app.create('page'); + + app.page('foo', {content: 'this is foo'}); + app.page('bar.md', {content: 'this is bar'}); + app.page('a/b/c/baz.md', {content: 'this is baz'}); + app.page('test/fixtures/templates/a.tmpl'); + }); + + it('should get a view by name', function() { + assert(app.pages.getView('foo')); + }); + + it('should get a view with the key modified by the given function', function() { + var view = app.pages.getView('foo.md', function(key) { + return path.basename(key, path.extname(key)); + }); + assert(view); + }); + + it('should get a view by full path', function() { + assert(app.pages.getView('a/b/c/baz.md')); + }); +}); diff --git a/test/collection.js b/test/collection.js index 2ddee92..7c0982c 100644 --- a/test/collection.js +++ b/test/collection.js @@ -4,7 +4,6 @@ var path = require('path'); var assert = require('assert'); var typeOf = require('kind-of'); var isBuffer = require('is-buffer'); - var support = require('./support'); var App = support.resolve(); var List = App.List; @@ -12,28 +11,28 @@ var Item = App.Item; var Collection = App.Collection; var collection; -describe('collection', function () { - describe('constructor', function () { - it('should create an instance of Collection', function () { +describe('collection', function() { + describe('constructor', function() { + it('should create an instance of Collection', function() { var collection = new Collection(); assert(collection instanceof Collection); assert(typeof collection === 'object'); }); - it('should instantiate without new', function () { + it('should instantiate without new', function() { var collection = Collection(); assert(collection instanceof Collection); assert(typeof collection === 'object'); }); }); - describe('static methods', function () { - it('should expose `extend`', function () { - assert(typeof Collection.extend ==='function'); + describe('static methods', function() { + it('should expose `extend`', function() { + assert(typeof Collection.extend === 'function'); }); }); - describe('prototype methods', function () { + describe('prototype methods', function() { beforeEach(function() { collection = new Collection(); }); @@ -59,37 +58,37 @@ describe('collection', function () { 'hasListeners' ]; - methods.forEach(function (method) { - it('should expose ' + method + ' method', function () { + methods.forEach(function(method) { + it('should expose ' + method + ' method', function() { assert(typeof collection[method] === 'function'); }); }); - it('should expose isCollection property', function () { + it('should expose isCollection property', function() { assert(typeof collection.isCollection === 'boolean'); }); - it('should expose queue property', function () { + it('should expose queue property', function() { assert(Array.isArray(collection.queue)); }); - it('should expose items property', function () { + it('should expose items property', function() { assert(typeOf(collection.items) === 'object'); }); - it('should expose options property', function () { + it('should expose options property', function() { assert(typeOf(collection.options) === 'object'); }); }); }); -describe('methods', function () { +describe('methods', function() { beforeEach(function() { collection = new Collection(); }); - describe('chaining', function () { - it('should allow collection methods to be chained', function () { + describe('chaining', function() { + it('should allow collection methods to be chained', function() { collection .addItems({'a.hbs': {path: 'a.hbs'}}) .addItems({'b.hbs': {path: 'b.hbs'}}) @@ -103,20 +102,20 @@ describe('methods', function () { }); }); - describe('use', function () { - it('should expose the instance to plugins', function () { + describe('use', function() { + it('should expose the instance to plugins', function() { collection - .use(function (inst) { + .use(function(inst) { inst.foo = 'bar'; }); assert(collection.foo === 'bar'); }); - it('should expose `item` when the plugin returns a function', function () { + it('should expose `item` when the plugin returns a function', function() { collection - .use(function () { - return function (item) { + .use(function() { + return function(item) { item.foo = 'bar'; }; }); @@ -131,24 +130,24 @@ describe('methods', function () { }); }); - describe('get / set', function () { - it('should set a value on the instance', function () { + describe('get / set', function() { + it('should set a value on the instance', function() { collection.set('a', 'b'); - assert(collection.a ==='b'); + assert(collection.a === 'b'); }); - it('should get a value from the instance', function () { + it('should get a value from the instance', function() { collection.set('a', 'b'); - assert(collection.get('a') ==='b'); + assert(collection.get('a') === 'b'); }); }); - describe('adding items', function () { - beforeEach(function () { + describe('adding items', function() { + beforeEach(function() { collection = new Collection(); }); - it('should load a item onto the respective collection', function () { + it('should load a item onto the respective collection', function() { collection.addItem('a.hbs'); collection.items.should.have.property('a.hbs'); }); @@ -159,7 +158,7 @@ describe('methods', function () { collection = new Collection(); }); - it('should return a single collection item from a key-value pair', function () { + it('should return a single collection item from a key-value pair', function() { var one = collection.item('one', {content: 'foo'}); var two = collection.item('two', {content: 'bar'}); @@ -171,7 +170,7 @@ describe('methods', function () { assert(two.path === 'two'); }); - it('should return a single collection item from an object', function () { + it('should return a single collection item from an object', function() { var one = collection.item({path: 'one', content: 'foo'}); var two = collection.item({path: 'two', content: 'bar'}); @@ -187,13 +186,13 @@ describe('methods', function () { collection = new Collection(); }); - it('should throw an error when args are invalid', function () { - (function () { + it('should throw an error when args are invalid', function() { + (function() { collection.addItem(function() {}); }).should.throw('expected value to be an object.'); }); - it('should add a item to `items`', function () { + it('should add a item to `items`', function() { collection.addItem('foo'); collection.items.should.have.property('foo'); @@ -202,12 +201,12 @@ describe('methods', function () { assert(isBuffer(collection.items.one.contents)); }); - it('should create an instance of `Item`', function () { + it('should create an instance of `Item`', function() { collection.addItem('one', {content: '...'}); assert(collection.items.one instanceof collection.Item); }); - it('should allow an `Item` constructor to be passed', function () { + it('should allow an `Item` constructor to be passed', function() { Item.prototype.foo = function(key, value) { this[key] = value; }; @@ -217,7 +216,7 @@ describe('methods', function () { assert(collection.items.one.bar === 'baz'); }); - it('should allow an instance of `Item` to be passed', function () { + it('should allow an instance of `Item` to be passed', function() { var collection = new Collection({Item: Item}); var item = new Item({content: '...'}); collection.addItem('one', item); @@ -228,8 +227,12 @@ describe('methods', function () { }); }); - describe('addItems', function () { - it('should add multiple items', function () { + describe('addItems', function() { + beforeEach(function() { + collection = new Collection(); + }); + + it('should add multiple items', function() { collection.addItems({ one: {content: 'foo'}, two: {content: 'bar'} @@ -238,7 +241,7 @@ describe('methods', function () { assert(isBuffer(collection.items.two.contents)); }); - it('should create items from an instance of Collection', function () { + it('should create items from an instance of Collection', function() { collection.addItems({ one: {content: 'foo'}, two: {content: 'bar'} @@ -248,7 +251,7 @@ describe('methods', function () { assert(isBuffer(pages.items.two.contents)); }); - it('should add an array of plain-objects', function () { + it('should add an array of plain-objects', function() { collection.addItems([ {path: 'one', content: 'foo'}, {path: 'two', content: 'bar'} @@ -257,7 +260,7 @@ describe('methods', function () { assert(isBuffer(collection.items.two.contents)); }); - it('should add an array of items', function () { + it('should add an array of items', function() { var list = new List([ {path: 'one', content: 'foo'}, {path: 'two', content: 'bar'} @@ -274,7 +277,7 @@ describe('methods', function () { collection = new Collection(); }); - it('should add a list of items', function () { + it('should add a list of items', function() { collection.addList([ {path: 'one', content: 'foo'}, {path: 'two', content: 'bar'} @@ -283,7 +286,7 @@ describe('methods', function () { assert(isBuffer(collection.items.two.contents)); }); - it('should add a list of items from the constructor', function () { + it('should add a list of items from the constructor', function() { var list = new List([ {path: 'one', content: 'foo'}, {path: 'two', content: 'bar'} @@ -294,25 +297,25 @@ describe('methods', function () { assert(isBuffer(collection.items.two.contents)); }); - it('should throw an error when list is not an array', function () { + it('should throw an error when list is not an array', function() { var items = new Collection(); - (function () { + (function() { items.addList(); }).should.throw('expected list to be an array.'); - (function () { + (function() { items.addList({}); }).should.throw('expected list to be an array.'); - (function () { + (function() { items.addList('foo'); }).should.throw('expected list to be an array.'); }); - it('should load an array of items from an event', function () { + it('should load an array of items from an event', function() { var collection = new Collection(); - collection.on('addList', function (list) { + collection.on('addList', function(list) { while (list.length) { collection.addItem({path: list.pop()}); } @@ -323,20 +326,20 @@ describe('methods', function () { assert(collection.items['a.txt'].path === 'a.txt'); }); - it('should load an array of items from the addList callback:', function () { + it('should load an array of items from the addList callback:', function() { var collection = new Collection(); - collection.addList(['a.txt', 'b.txt', 'c.txt'], function (fp) { + collection.addList(['a.txt', 'b.txt', 'c.txt'], function(fp) { return {path: fp}; }); assert(collection.items.hasOwnProperty('a.txt')); assert(collection.items['a.txt'].path === 'a.txt'); }); - it('should load an object of items from an event', function () { + it('should load an object of items from an event', function() { var collection = new Collection(); - collection.on('addItems', function (items) { + collection.on('addItems', function(items) { for (var key in items) { collection.addItem('foo/' + key, items[key]); delete items[key]; @@ -353,10 +356,10 @@ describe('methods', function () { assert(collection.items['foo/a'].path === 'a.txt'); }); - it('should signal `loaded` when finished (addItems)', function () { + it('should signal `loaded` when finished (addItems)', function() { var collection = new Collection(); - collection.on('addItems', function (items) { + collection.on('addItems', function(items) { for (var key in items) { if (key === 'c') { collection.loaded = true; @@ -377,10 +380,10 @@ describe('methods', function () { assert(collection.items['foo/a'].path === 'a.txt'); }); - it('should signal `loaded` when finished (addList)', function () { + it('should signal `loaded` when finished (addList)', function() { var collection = new Collection(); - collection.on('addList', function (items) { + collection.on('addList', function(items) { for (var i = 0; i < items.length; i++) { var item = items[i]; if (item.key === 'c') { @@ -408,7 +411,7 @@ describe('methods', function () { beforeEach(function() { collection = new Collection(); }); - it('should get a item from `items`', function () { + it('should get a item from `items`', function() { collection.addItem('one', {content: 'aaa'}); collection.addItem('two', {content: 'zzz'}); assert(isBuffer(collection.items.one.contents)); @@ -419,13 +422,13 @@ describe('methods', function () { }); }); -describe('queue', function () { - beforeEach(function () { +describe('queue', function() { + beforeEach(function() { collection = new Collection(); }); - it('should emit arguments on addItem', function (done) { - collection.on('addItem', function (args) { + it('should emit arguments on addItem', function(done) { + collection.on('addItem', function(args) { assert(args[0] === 'a'); assert(args[1] === 'b'); assert(args[2] === 'c'); @@ -437,7 +440,7 @@ describe('queue', function () { collection.addItem('a', 'b', 'c', 'd', 'e'); }); - it('should expose the `queue` property for loading items', function () { + it('should expose the `queue` property for loading items', function() { collection.queue.push(collection.item('b', {path: 'b'})); collection.addItem('a', {path: 'a'}); @@ -445,8 +448,8 @@ describe('queue', function () { assert(collection.items.hasOwnProperty('b')); }); - it('should load all items on the queue when addItem is called', function () { - collection.on('addItem', function (args) { + it('should load all items on the queue when addItem is called', function() { + collection.on('addItem', function(args) { var len = args.length; var last = args[len - 1]; if (typeof last === 'string') { @@ -473,12 +476,12 @@ describe('options', function() { collection = new Collection(); }); - it('should expose the `option` method', function () { + it('should expose the `option` method', function() { collection.option('foo', 'bar'); collection.options.should.have.property('foo', 'bar'); }); - it('should be chainable', function () { + it('should be chainable', function() { collection.option('foo', 'bar') .addItems('a.hbs') .addItems('b.hbs') @@ -492,17 +495,17 @@ describe('options', function() { ]); }); - it('should set a key/value pair on options', function () { + it('should set a key/value pair on options', function() { collection.option('a', 'b'); assert(collection.options.a === 'b'); }); - it('should set an object on options', function () { + it('should set an object on options', function() { collection.option({c: 'd'}); assert(collection.options.c === 'd'); }); - it('should get an option', function () { + it('should get an option', function() { collection.option({c: 'd'}); var c = collection.option('c'); assert(c === 'd'); @@ -512,7 +515,7 @@ describe('options', function() { describe('options.renameKey', function() { beforeEach(function() { collection = new Collection({ - renameKey: function (key) { + renameKey: function(key) { return path.basename(key); } }); @@ -523,12 +526,12 @@ describe('options', function() { assert(collection.items['d.hbs'].contents.toString() === 'foo bar baz'); }); - it('should get a item with the renamed key', function () { + it('should get a item with the renamed key', function() { collection.addItem('a/b/c/d.hbs', {content: 'foo bar baz'}); assert(collection.getItem('d.hbs').contents.toString() === 'foo bar baz'); }); - it('should get a item with the original key', function () { + it('should get a item with the original key', function() { collection.addItem('a/b/c/d.hbs', {content: 'foo bar baz'}); assert(collection.getItem('a/b/c/d.hbs').contents.toString() === 'foo bar baz'); }); diff --git a/test/collection.options.js b/test/collection.options.js index 4994fd4..d1af0af 100644 --- a/test/collection.options.js +++ b/test/collection.options.js @@ -3,19 +3,19 @@ var support = require('./support'); var App = support.resolve(); var app; -describe('collection.option()', function () { - beforeEach(function () { +describe('collection.option()', function() { + beforeEach(function() { app = new App(); app.create('page'); }); - it('should set an option:', function () { + it('should set an option:', function() { app.pages.options.should.not.have.property('foo'); app.pages.option('foo', 'bar'); app.pages.options.should.have.property('foo'); }); - it('should extend options:', function () { + it('should extend options:', function() { app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}); app.pages.option('a', 'b'); app.pages.option('c', 'd'); diff --git a/test/collection.render.js b/test/collection.render.js index d7c1579..39b5895 100644 --- a/test/collection.render.js +++ b/test/collection.render.js @@ -8,40 +8,40 @@ var List = App.List; var Views = App.Views; var pages; -describe('render', function () { - describe('rendering', function () { - beforeEach(function () { +describe('render', function() { + describe('rendering', function() { + beforeEach(function() { pages = new Views(); pages.engine('tmpl', require('engine-base')); }); - it('should throw an error when no callback is given:', function () { + it('should throw an error when no callback is given:', function() { (function() { pages.render({}); }).should.throw('Views#render is async and expects a callback function'); }); - it('should throw an error when an engine is not defined:', function (done) { + it('should throw an error when an engine is not defined:', function(done) { pages.addView('foo.bar', {content: '<%= name %>'}); var page = pages.getView('foo.bar'); pages.render(page, function(err) { - assert(err.message === 'Views#render cannot find an engine for: .bar'); + assert(err.message === 'Views#render cannot find engine: .bar'); done(); }); }); - it('should use helpers to render a view:', function (done) { + it('should use helpers to render a view:', function(done) { var locals = {name: 'Halle'}; - pages.helper('upper', function (str) { + pages.helper('upper', function(str) { return str.toUpperCase(str); }); pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); var page = pages.getView('a.tmpl'); - pages.render(page, function (err, res) { + pages.render(page, function(err, res) { if (err) return done(err); assert(res.content === 'a HALLE b'); @@ -49,55 +49,55 @@ describe('render', function () { }); }); - it('should use helpers when rendering a view:', function (done) { + it('should use helpers when rendering a view:', function(done) { var locals = {name: 'Halle'}; - pages.helper('upper', function (str) { + pages.helper('upper', function(str) { return str.toUpperCase(str); }); pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); var page = pages.getView('a.tmpl'); - pages.render(page, function (err, res) { + pages.render(page, function(err, res) { if (err) return done(err); assert(res.content === 'a HALLE b'); done(); }); }); - it('should render a template when contents is a buffer:', function (done) { + it('should render a template when contents is a buffer:', function(done) { pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); var view = pages.getView('a.tmpl'); - pages.render(view, function (err, view) { + pages.render(view, function(err, view) { if (err) return done(err); assert(view.contents.toString() === 'b'); done(); }); }); - it('should render a template when content is a string:', function (done) { + it('should render a template when content is a string:', function(done) { pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); var view = pages.getView('a.tmpl'); - pages.render(view, function (err, view) { + pages.render(view, function(err, view) { if (err) return done(err); assert(view.contents.toString() === 'b'); done(); }); }); - it('should render a view from its path:', function (done) { + it('should render a view from its path:', function(done) { pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - pages.render('a.tmpl', function (err, view) { + pages.render('a.tmpl', function(err, view) { if (err) return done(err); assert(view.content === 'b'); done(); }); }); - it('should use a plugin for rendering:', function (done) { + it('should use a plugin for rendering:', function(done) { pages.engine('tmpl', require('engine-base')); pages.option('engine', 'tmpl'); @@ -111,22 +111,22 @@ describe('render', function () { 'g': {content: '<%= title %>', locals: {title: 'ggg'}}, 'h': {content: '<%= title %>', locals: {title: 'hhh'}}, 'i': {content: '<%= title %>', locals: {title: 'iii'}}, - 'j': {content: '<%= title %>', locals: {title: 'jjj'}}, + 'j': {content: '<%= title %>', locals: {title: 'jjj'}} }); - pages.use(function (collection) { + pages.use(function(collection) { collection.option('pager', false); - collection.renderEach = function (cb) { + collection.renderEach = function(cb) { var list = new List(collection); - async.map(list.items, function (item, next) { + async.map(list.items, function(item, next) { collection.render(item, next); }, cb); }; }); - pages.renderEach(function (err, items) { + pages.renderEach(function(err, items) { if (err) return done(err); assert(items[0].content === 'aaa'); assert(items[9].content === 'jjj'); diff --git a/test/collection.use.js b/test/collection.use.js index bc1c045..f09bd86 100644 --- a/test/collection.use.js +++ b/test/collection.use.js @@ -7,33 +7,33 @@ var Collection = App.Collection; var Item = App.Item; var collection; -describe('collection.use', function () { - beforeEach(function () { +describe('collection.use', function() { + beforeEach(function() { collection = new Collection(); }); - it('should expose the instance to `use`:', function (done) { - collection.use(function (inst) { + it('should expose the instance to `use`:', function(done) { + collection.use(function(inst) { assert(inst instanceof Collection); done(); }); }); - it('should be chainable:', function (done) { - collection.use(function (inst) { - assert(inst instanceof Collection); - }) - .use(function (inst) { + it('should be chainable:', function(done) { + collection.use(function(inst) { + assert(inst instanceof Collection); + }) + .use(function(inst) { assert(inst instanceof Collection); }) - .use(function (inst) { + .use(function(inst) { assert(inst instanceof Collection); done(); }); }); - it('should expose the collection to a plugin:', function () { - collection.use(function (items) { + it('should expose the collection to a plugin:', function() { + collection.use(function(items) { assert(items instanceof Collection); items.foo = items.addItem.bind(items); }); @@ -42,17 +42,17 @@ describe('collection.use', function () { assert(collection.items.hasOwnProperty('a')); }); - it('should expose collection when chained:', function () { + it('should expose collection when chained:', function() { collection - .use(function (items) { + .use(function(items) { assert(items instanceof Collection); items.foo = items.addItem.bind(items); }) - .use(function (items) { + .use(function(items) { assert(items instanceof Collection); items.bar = items.addItem.bind(items); }) - .use(function (items) { + .use(function(items) { assert(items instanceof Collection); items.baz = items.addItem.bind(items); }); @@ -68,18 +68,18 @@ describe('collection.use', function () { assert(collection.items.hasOwnProperty('c')); }); - it('should work when a custom `Item` constructor is passed:', function () { + it('should work when a custom `Item` constructor is passed:', function() { collection = new Collection({Item: require('vinyl')}); collection - .use(function (items) { + .use(function(items) { assert(items instanceof Collection); items.foo = items.addItem.bind(items); }) - .use(function (items) { + .use(function(items) { assert(items instanceof Collection); items.bar = items.addItem.bind(items); }) - .use(function (items) { + .use(function(items) { assert(items instanceof Collection); items.baz = items.addItem.bind(items); }); @@ -95,11 +95,11 @@ describe('collection.use', function () { assert(collection.items.hasOwnProperty('c')); }); - it('should pass to item `use` if a function is returned:', function () { - collection.use(function (items) { + it('should pass to item `use` if a function is returned:', function() { + collection.use(function(items) { assert(items instanceof Collection); - return function (item) { + return function(item) { item.foo = items.addItem.bind(items); assert(item instanceof Item); }; @@ -116,28 +116,28 @@ describe('collection.use', function () { assert(collection.items.hasOwnProperty('d')); }); - it('should be chainable when a item function is returned:', function () { + it('should be chainable when a item function is returned:', function() { collection - .use(function (items) { + .use(function(items) { assert(items instanceof Collection); - return function (item) { + return function(item) { item.foo = items.addItem.bind(items); assert(item instanceof Item); }; }) - .use(function (items) { + .use(function(items) { assert(items instanceof Collection); - return function (item) { + return function(item) { item.bar = items.addItem.bind(items); assert(item instanceof Item); }; }) - .use(function (items) { + .use(function(items) { assert(items instanceof Collection); - return function (item) { + return function(item) { item.baz = items.addItem.bind(items); assert(item instanceof Item); }; diff --git a/test/fixtures/copy/a.txt b/test/fixtures/copy/a.txt new file mode 100644 index 0000000..7c4a013 --- /dev/null +++ b/test/fixtures/copy/a.txt @@ -0,0 +1 @@ +aaa \ No newline at end of file diff --git a/test/fixtures/copy/b.txt b/test/fixtures/copy/b.txt new file mode 100644 index 0000000..01f02e3 --- /dev/null +++ b/test/fixtures/copy/b.txt @@ -0,0 +1 @@ +bbb \ No newline at end of file diff --git a/test/fixtures/copy/c.txt b/test/fixtures/copy/c.txt new file mode 100644 index 0000000..2383bd5 --- /dev/null +++ b/test/fixtures/copy/c.txt @@ -0,0 +1 @@ +ccc \ No newline at end of file diff --git a/test/fixtures/pipeline/a.js b/test/fixtures/pipeline/a.js index 3361998..1085b60 100644 --- a/test/fixtures/pipeline/a.js +++ b/test/fixtures/pipeline/a.js @@ -3,7 +3,7 @@ var through = require('through2'); module.exports = function(options) { - return through.obj(function (file, enc, cb) { + return through.obj(function(file, enc, cb) { var str = file.contents.toString(); str += 'aaa\n'; diff --git a/test/fixtures/pipeline/b.js b/test/fixtures/pipeline/b.js index 5f69bb6..e0b0521 100644 --- a/test/fixtures/pipeline/b.js +++ b/test/fixtures/pipeline/b.js @@ -3,7 +3,7 @@ var through = require('through2'); module.exports = function(options) { - return through.obj(function (file, enc, cb) { + return through.obj(function(file, enc, cb) { var str = file.contents.toString(); str += 'bbb\n'; diff --git a/test/fixtures/pipeline/c.js b/test/fixtures/pipeline/c.js index 84a1706..f5c0a47 100644 --- a/test/fixtures/pipeline/c.js +++ b/test/fixtures/pipeline/c.js @@ -3,7 +3,7 @@ var through = require('through2'); module.exports = function(options) { - return through.obj(function (file, enc, cb) { + return through.obj(function(file, enc, cb) { var str = file.contents.toString(); str += 'ccc\n'; diff --git a/test/fixtures/pipeline/d.js b/test/fixtures/pipeline/d.js index 79a5dfb..636f53e 100644 --- a/test/fixtures/pipeline/d.js +++ b/test/fixtures/pipeline/d.js @@ -3,7 +3,7 @@ var through = require('through2'); module.exports = function(options) { - return through.obj(function (file, enc, cb) { + return through.obj(function(file, enc, cb) { var str = file.contents.toString(); str += 'ddd\n'; diff --git a/test/group.js b/test/group.js index 601e91e..25bf7c6 100644 --- a/test/group.js +++ b/test/group.js @@ -1,30 +1,34 @@ +'use strict'; + require('mocha'); require('should'); - var assert = require('assert'); var support = require('./support/'); assert.containEql = support.containEql; - -var support = require('./support'); var App = support.resolve(); var List = App.List; var Group = App.Group; var group; -describe('group', function () { - describe('constructor', function () { - it('should create an instance of Group:', function () { +describe('group', function() { + describe('constructor', function() { + it('should create an instance of Group:', function() { var group = new Group(); assert(group instanceof Group); }); - it('should create an instance of Group with default List:', function () { + it('should instantiate without new', function() { + var group = Group(); + assert(group instanceof Group); + }); + + it('should create an instance of Group with default List:', function() { var group = new Group(); assert.deepEqual(group.List, List); }); - it('should create an instance of Group with custom List:', function () { - function CustomList () { + it('should create an instance of Group with custom List:', function() { + function CustomList() { List.apply(this, arguments); } List.extend(CustomList); @@ -33,77 +37,77 @@ describe('group', function () { }); }); - describe('static methods', function () { - it('should expose `extend`:', function () { - assert(typeof Group.extend ==='function'); + describe('static methods', function() { + it('should expose `extend`:', function() { + assert(typeof Group.extend === 'function'); }); }); - describe('prototype methods', function () { + describe('prototype methods', function() { beforeEach(function() { group = new Group(); }); - it('should expose `use`', function () { - assert(typeof group.use ==='function'); + it('should expose `use`', function() { + assert(typeof group.use === 'function'); }); - it('should expose `set`', function () { - assert(typeof group.set ==='function'); + it('should expose `set`', function() { + assert(typeof group.set === 'function'); }); - it('should expose `get`', function () { - assert(typeof group.get ==='function'); + it('should expose `get`', function() { + assert(typeof group.get === 'function'); }); - it('should expose `visit`', function () { - assert(typeof group.visit ==='function'); + it('should expose `visit`', function() { + assert(typeof group.visit === 'function'); }); - it('should expose `define`', function () { - assert(typeof group.define ==='function'); + it('should expose `define`', function() { + assert(typeof group.define === 'function'); }); }); - describe('instance', function () { + describe('instance', function() { beforeEach(function() { group = new Group(); }); - it('should expose options:', function () { + it('should expose options:', function() { assert(typeof group.options === 'object'); }); - it('should set a value on the instance:', function () { + it('should set a value on the instance:', function() { group.set('a', 'b'); - assert(group.a ==='b'); + assert(group.a === 'b'); }); - it('should get a value from the instance:', function () { + it('should get a value from the instance:', function() { group.set('a', 'b'); - assert(group.get('a') ==='b'); + assert(group.get('a') === 'b'); }); }); describe('get', function() { - it('should get a normal value when not an array', function () { - var group = new Group({'foo': {items: [1,2,3]}}); - assert.deepEqual(group.get('foo'), {items: [1,2,3]}); + it('should get a normal value when not an array', function() { + var group = new Group({'foo': {items: [1, 2, 3]}}); + assert.deepEqual(group.get('foo'), {items: [1, 2, 3]}); }); - it('should get an instance of List when value is an array', function () { - var group = new Group({'foo': {items: [{path: 'one.hbs'},{path: 'two.hbs'}, {path: 'three.hbs'}]}}); + it('should get an instance of List when value is an array', function() { + var group = new Group({'foo': {items: [{path: 'one.hbs'}, {path: 'two.hbs'}, {path: 'three.hbs'}]}}); var list = group.get('foo.items'); assert(list instanceof List); assert.deepEqual(list.items.length, 3); }); - it('should throw an error when trying to use a List method on a non List value', function () { - (function () { - var group = new Group({'foo': {items: [1,2,3]}}); + it('should throw an error when trying to use a List method on a non List value', function() { + (function() { + var group = new Group({'foo': {items: [1, 2, 3]}}); var foo = group.get('foo'); foo.paginate(); }).should.throw('paginate can only be used with an array of `List` items.'); }); - it('should not override properties already existing on non List values', function (done) { - var group = new Group({'foo': {items: [1,2,3], paginate: function () { + it('should not override properties already existing on non List values', function(done) { + var group = new Group({'foo': {items: [1, 2, 3], paginate: function() { assert(true); done(); }}}); @@ -117,18 +121,18 @@ describe('group', function () { group = new Group(); }); - it('should use middleware on a group:', function () { + it('should use middleware on a group:', function() { group.set('one', {contents: new Buffer('aaa')}); group.set('two', {contents: new Buffer('zzz')}); group - .use(function (group) { + .use(function(group) { group.options = {}; }) - .use(function (group) { + .use(function(group) { group.options.foo = 'bar'; }) - .use(function () { + .use(function() { this.set('one', 'two'); }); diff --git a/test/handlers.js b/test/handlers.js index 4c07276..96c5c53 100644 --- a/test/handlers.js +++ b/test/handlers.js @@ -5,30 +5,30 @@ var support = require('./support'); var App = support.resolve(); var app; -describe('handlers', function () { - describe('custom handlers', function () { - beforeEach(function () { +describe('handlers', function() { + describe('custom handlers', function() { + beforeEach(function() { app = new App(); app.create('page'); }); - it('should add custom middleware handlers:', function () { + it('should add custom middleware handlers:', function() { app.handler('foo'); app.handler('bar'); - app.pages.use(function () { - return function (view) { + app.pages.use(function() { + return function(view) { app.handle('foo', view); app.handle('bar', view); }; }); - app.foo(/a/, function (view, next) { + app.foo(/a/, function(view, next) { view.one = 'aaa'; next(); }); - app.bar(/z/, function (view, next) { + app.bar(/z/, function(view, next) { view.two = 'zzz'; next(); }); diff --git a/test/helpers.js b/test/helpers.js index dddc13c..eb5633d 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var path = require('path'); @@ -15,28 +17,28 @@ var helpers = App._.proto.helpers; var init = App._.proto.init; var app; -describe('helpers', function () { - describe('constructor', function () { - it('should create an instance of Helpers:', function () { +describe('helpers', function() { + describe('constructor', function() { + it('should create an instance of Helpers:', function() { app = new App(); assert(app instanceof App); }); }); - describe('prototype methods', function () { + describe('prototype methods', function() { beforeEach(function() { app = new App(); }); - it('should expose `helper`', function () { - assert(typeof app.helper ==='function'); + it('should expose `helper`', function() { + assert(typeof app.helper === 'function'); }); - it('should expose `asyncHelper`', function () { - assert(typeof app.asyncHelper ==='function'); + it('should expose `asyncHelper`', function() { + assert(typeof app.asyncHelper === 'function'); }); }); - describe('instance', function () { - it('should prime _', function () { + describe('instance', function() { + it('should prime _', function() { function Foo() { Base.call(this); init(this); @@ -44,7 +46,7 @@ describe('helpers', function () { Base.extend(Foo); var foo = new Foo(); helpers(foo); - assert(typeof foo._ ==='object'); + assert(typeof foo._ === 'object'); }); }); @@ -53,12 +55,12 @@ describe('helpers', function () { app = new App(); }); - it('should add a sync helper to the `sync` object:', function () { - app.helper('one', function () {}); + it('should add a sync helper to the `sync` object:', function() { + app.helper('one', function() {}); assert(typeof app._.helpers.sync.one === 'function'); }); - it('should load a glob of sync helper functions:', function () { + it('should load a glob of sync helper functions:', function() { app.helpers('test/fixtures/helpers/[a-c].js'); assert(typeof app._.helpers.sync.c === 'function'); @@ -66,23 +68,23 @@ describe('helpers', function () { assert(typeof app._.helpers.sync.a === 'function'); }); - it('should fail gracefully on bad globs:', function (done) { + it('should fail gracefully on bad globs:', function(cb) { try { app.helpers('test/fixtures/helpers/*.foo'); - done(); - } catch(err) { - done(new Error('should not throw an error.')); + cb(); + } catch (err) { + cb(new Error('should not throw an error.')); } }); - it('should add a glob of sync helper objects:', function () { + it('should add a glob of sync helper objects:', function() { app.helpers('test/fixtures/helpers/!([a-c]).js'); assert(typeof app._.helpers.sync.one === 'function'); assert(typeof app._.helpers.sync.two === 'function'); assert(typeof app._.helpers.sync.three === 'function'); }); - it('should add a glob with mixed helper objects and functions:', function () { + it('should add a glob with mixed helper objects and functions:', function() { app.helpers('test/fixtures/helpers/*.js'); assert(typeof app._.helpers.sync.a === 'function'); assert(typeof app._.helpers.sync.b === 'function'); @@ -92,11 +94,11 @@ describe('helpers', function () { assert(typeof app._.helpers.sync.three === 'function'); }); - it('should add an object of sync helpers to the `sync` object:', function () { + it('should add an object of sync helpers to the `sync` object:', function() { app.helpers({ - x: function () {}, - y: function () {}, - z: function () {} + x: function() {}, + y: function() {}, + z: function() {} }); assert(typeof app._.helpers.sync.x === 'function'); @@ -104,11 +106,11 @@ describe('helpers', function () { assert(typeof app._.helpers.sync.z === 'function'); }); - it('should add a helper "group":', function () { + it('should add a helper "group":', function() { app.helperGroup('foo', { - x: function () {}, - y: function () {}, - z: function () {} + x: function() {}, + y: function() {}, + z: function() {} }); assert(typeof app._.helpers.sync.foo.x === 'function'); @@ -122,35 +124,35 @@ describe('helpers', function () { app = new App(); }); - it('should add an async helper to the `async` object:', function () { - app.asyncHelper('two', function () {}); + it('should add an async helper to the `async` object:', function() { + app.asyncHelper('two', function() {}); assert(typeof app._.helpers.async.two === 'function'); }); - it('should load a glob of async helper functions:', function () { + it('should load a glob of async helper functions:', function() { app.asyncHelpers('test/fixtures/helpers/[a-c].js'); assert(typeof app._.helpers.async.a === 'function'); assert(typeof app._.helpers.async.b === 'function'); assert(typeof app._.helpers.async.c === 'function'); }); - it('should add a glob of async helper objects:', function () { + it('should add a glob of async helper objects:', function() { app.asyncHelpers('test/fixtures/helpers/!([a-c]).js'); assert(typeof app._.helpers.async.one === 'function'); assert(typeof app._.helpers.async.two === 'function'); assert(typeof app._.helpers.async.three === 'function'); }); - it('should fail gracefully on bad globs:', function (done) { + it('should fail gracefully on bad globs:', function(cb) { try { app.asyncHelpers('test/fixtures/helpers/*.foo'); - done(); - } catch(err) { - done(new Error('should not throw an error.')); + cb(); + } catch (err) { + cb(new Error('should not throw an error.')); } }); - it('should add a glob with mixed helper objects and functions:', function () { + it('should add a glob with mixed helper objects and functions:', function() { app.asyncHelpers('test/fixtures/helpers/*.js'); assert(typeof app._.helpers.async.a === 'function'); assert(typeof app._.helpers.async.b === 'function'); @@ -160,11 +162,11 @@ describe('helpers', function () { assert(typeof app._.helpers.async.three === 'function'); }); - it('should add an object of async helpers to the `async` object:', function () { + it('should add an object of async helpers to the `async` object:', function() { app.asyncHelpers({ - x: function () {}, - y: function () {}, - z: function () {} + x: function() {}, + y: function() {}, + z: function() {} }); assert(typeof app._.helpers.async.x === 'function'); @@ -172,11 +174,11 @@ describe('helpers', function () { assert(typeof app._.helpers.async.z === 'function'); }); - it('should add an async helper "group":', function () { + it('should add an async helper "group":', function() { app.helperGroup('foo', { - x: function () {}, - y: function () {}, - z: function () {} + x: function() {}, + y: function() {}, + z: function() {} }, true); assert(typeof app._.helpers.async.foo.x === 'function'); @@ -186,42 +188,42 @@ describe('helpers', function () { }); }); -describe('sync helpers', function () { - beforeEach(function () { +describe('sync helpers', function() { + beforeEach(function() { app = new App(); app.engine('tmpl', require('engine-base')); app.create('page'); }); - it('should register a helper:', function () { - app.helper('a', function () {}); - app.helper('b', function () {}); + it('should register a helper:', function() { + app.helper('a', function() {}); + app.helper('b', function() {}); assert(app._.helpers.sync.hasOwnProperty('a')); assert(app._.helpers.sync.hasOwnProperty('b')); }); - it('should use a helper:', function (done) { + it('should use a helper:', function(cb) { app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= upper(a) %>', locals: {a: 'bbb'}}); - app.helper('upper', function (str) { + app.helper('upper', function(str) { return str.toUpperCase(); }); var page = app.pages.getView('a.tmpl'); - app.render(page, function (err, view) { - if (err) return done(err); + app.render(page, function(err, view) { + if (err) return cb(err); assert.equal(typeof view.contents.toString(), 'string'); assert.equal(view.contents.toString(), 'BBB'); - done(); + cb(); }); }); - it('should use a namespaced helper:', function (done) { + it('should use a namespaced helper:', function(cb) { app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= foo.upper(a) %>', locals: {a: 'bbb'}}); app.helperGroup('foo', { - upper: function (str) { + upper: function(str) { return str.toUpperCase(); } }); @@ -229,50 +231,50 @@ describe('sync helpers', function () { // console.log(app._.helpers) var page = app.pages.getView('a.tmpl'); - app.render(page, function (err, view) { - if (err) return done(err); + app.render(page, function(err, view) { + if (err) return cb(err); assert.equal(typeof view.contents.toString(), 'string'); assert.equal(view.contents.toString(), 'BBB'); - done(); + cb(); }); }); }); -describe('async helpers', function () { - beforeEach(function () { +describe('async helpers', function() { + beforeEach(function() { app = new App(); app.engine('tmpl', require('engine-base')); app.create('page'); }); - it('should register an async helper:', function () { - app.asyncHelper('a', function () {}); - app.asyncHelper('b', function () {}); + it('should register an async helper:', function() { + app.asyncHelper('a', function() {}); + app.asyncHelper('b', function() {}); app._.helpers.async.should.have.property('a'); app._.helpers.async.should.have.property('b'); }); - it('should use an async helper:', function (done) { + it('should use an async helper:', function(cb) { app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= lower(a) %>', locals: {a: 'BBB'}}); - app.asyncHelper('lower', function (str, next) { + app.asyncHelper('lower', function(str, next) { if (typeof next !== 'function') return str; next(null, str.toLowerCase()); }); var page = app.pages.getView('a.tmpl'); - app.render(page, function (err, view) { - if (err) return done(err); + app.render(page, function(err, view) { + if (err) return cb(err); assert.equal(typeof view.content, 'string'); assert.equal(view.content, 'bbb'); - done(); + cb(); }); }); }); -describe('built-in helpers:', function () { - describe('automatically generated helpers for default view types:', function () { - beforeEach(function () { +describe('built-in helpers:', function() { + describe('automatically generated helpers for default view types:', function() { + beforeEach(function() { app = new App({rethrow: false}); app.engine('md', require('engine-base')); app.engine('tmpl', require('engine-base')); @@ -280,243 +282,333 @@ describe('built-in helpers:', function () { app.create('pages'); // parse front matter - app.onLoad(/./, function (view, next) { + app.onLoad(/./, function(view, next) { matter.parse(view, next); }); }); - it('should expose front matter to the `partial` helper.', function (done) { + it('should expose front matter to the `partial` helper.', function(cb) { app.partial('a.md', {content: '---\nname: "AAA"\n---\n<%= name %>', locals: {name: 'BBB'}}); app.page('b.md', {path: 'b.md', content: 'foo <%= partial("a.md") %> bar'}); - app.render('b.md', function (err, res) { - if (err) return done(err); + app.render('b.md', function(err, res) { + if (err) return cb(err); res.content.should.equal('foo AAA bar'); - done(); + cb(); }); }); - it('should use helper locals.', function (done) { - app.partial('abc.md', {content: '---\nname: "AAA"\n---\n<%= name %>', locals: {name: 'BBB'}}); + it('should use helper locals.', function(cb) { + app.partial('abc.md', {content: '<%= name %>', locals: {name: 'BBB'}}); app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md", { name: "CCC" }) %> bar'}); - app.render('xyz.md', {name: 'DDD'}, function (err, res) { - if (err) return done(err); + app.render('xyz.md', {name: 'DDD'}, function(err, res) { + if (err) return cb(err); res.content.should.equal('foo CCC bar'); - done(); + cb(); }); }); - it('should use front matter data.', function (done) { + it('should use front matter data.', function(cb) { app.partial('abc.md', {content: '---\nname: "AAA"\n---\n<%= name %>'}); app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}); - app.render('xyz.md', {name: 'DDD'}, function (err, res) { - if (err) return done(err); + app.render('xyz.md', {name: 'DDD'}, function(err, res) { + if (err) return cb(err); res.content.should.equal('foo AAA bar'); - done(); + cb(); + }); + }); + + it('should prefer helper locals over front-matter', function(cb) { + app.partial('abc.md', {content: '---\nname: "AAA"\n---\n<%= name %>', locals: {name: 'BBB'}}); + app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md", { name: "CCC" }) %> bar'}); + + app.render('xyz.md', {name: 'DDD'}, function(err, res) { + if (err) return cb(err); + res.content.should.equal('foo CCC bar'); + cb(); }); }); - it('should use partial locals:', function (done) { + it('should use partial locals:', function(cb) { app.partial('abc.md', {content: '<%= name %>', locals: {name: 'EEE'}}); app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}) - .render({name: 'DDD'}, function (err, res) { - if (err) return done(err); + .render({name: 'DDD'}, function(err, res) { + if (err) return cb(err); res.content.should.equal('foo EEE bar'); - done(); + cb(); }); }); - it('should use locals from the `view.render` method:', function (done) { + it('should use locals from the `view.render` method:', function(cb) { app.partial('abc.md', {content: '<%= name %>', locals: {name: 'EEE'}}); app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}) - .render({name: 'DDD'}, function (err, res) { - if (err) return done(err); + .render({name: 'DDD'}, function(err, res) { + if (err) return cb(err); res.content.should.equal('foo EEE bar'); - done(); + cb(); }); }); - it('should use locals from the `app.render` method:', function (done) { + it('should use locals from the `app.render` method:', function(cb) { app.partial('abc.md', {content: '<%= name %>'}); app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}); - app.render('xyz.md', {name: 'DDD'}, function (err, res) { - if (err) return done(err); + app.render('xyz.md', {name: 'DDD'}, function(err, res) { + if (err) return cb(err); res.content.should.equal('foo DDD bar'); - done(); + cb(); }); }); - it('should return an empty string when the partial is missing.', function (done) { + it('should use a `helperContext` function from app.options', function(cb) { + app.option('helperContext', function(view, locals) { + return { name: 'blah' }; + }); + + app.partial('abc.md', {content: '<%= name %>'}); + app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}); + + app.render('xyz.md', {name: 'DDD'}, function(err, res) { + if (err) return cb(err); + res.content.should.equal('foo blah bar'); + cb(); + }); + }); + + it('should return an empty string when the partial is missing.', function(cb) { app.partial('abc.md', {content: '---\nname: "AAA"\n---\n<%= name %>', locals: {name: 'BBB'}}); app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("def.md", { name: "CCC" }) %> bar'}); - app.render('xyz.md', {name: 'DDD'}, function (err, res) { - if (err) return done(err); + app.render('xyz.md', {name: 'DDD'}, function(err, res) { + if (err) return cb(err); res.content.should.equal('foo bar'); - done(); + cb(); }); }); }); - describe('helper context:', function () { - beforeEach(function () { + describe('helper context:', function() { + beforeEach(function() { app = new App({rethrow: false}); app.engine(['tmpl', 'md'], require('engine-base')); app.create('partial', { viewType: 'partial' }); app.create('page'); // parse front matter - app.onLoad(/./, function (view, next) { + app.onLoad(/./, function(view, next) { matter.parse(view, next); }); }); - it('should prefer helper locals over view locals.', function (done) { + it('should prefer helper locals over view locals.', function(cb) { app.partial('abc.md', {content: '<%= name %>', name: 'BBB'}); app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md", { name: "CCC" }) %> bar'}); - app.render('xyz.md', {name: 'DDD'}, function (err, res) { - if (err) return done(err); + app.render('xyz.md', {name: 'DDD'}, function(err, res) { + if (err) return cb(err); res.content.should.equal('foo CCC bar'); - done(); + cb(); }); }); - it('should give preference to view locals over render locals.', function (done) { + it('should give preference to view locals over render locals.', function(cb) { app.partial('abc.md', {content: '<%= name %>', locals: {name: 'BBB'}}); app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}); var page = app.pages.getView('xyz.md'); - app.render(page, {name: 'DDD'}, function (err, res) { - if (err) return done(err); + app.render(page, {name: 'DDD'}, function(err, res) { + if (err) return cb(err); res.content.should.equal('foo BBB bar'); - done(); + cb(); }); }); - it('should use render locals when other locals are not defined.', function (done) { + it('should use render locals when other locals are not defined.', function(cb) { app.partial('abc.md', {content: '<%= name %>'}); app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}); - app.render('xyz.md', {name: 'DDD'}, function (err, res) { - if (err) return done(err); + app.render('xyz.md', {name: 'DDD'}, function(err, res) { + if (err) return cb(err); res.content.should.equal('foo DDD bar'); - done(); + cb(); }); }); }); - describe('user-defined engines:', function () { - beforeEach(function () { + describe('user-defined engines:', function() { + beforeEach(function() { app = new App({rethrow: false}); app.create('partial', { viewType: 'partial' }); app.create('page'); // parse front matter - app.onLoad(/./, function (view, next) { + app.onLoad(/./, function(view, next) { matter.parse(view, next); }); }); - it('should use the `partial` helper with handlebars.', function (done) { + it('should use the `partial` helper with handlebars.', function(cb) { app.engine(['tmpl', 'md'], require('engine-base')); app.engine('hbs', handlebars); app.partial('title.hbs', {content: '{{name}}', locals: {name: 'BBB'}}); app.page('a.hbs', {path: 'a.hbs', content: 'foo {{{partial "title.hbs" this}}} bar'}); - app.render('a.hbs', {name: 'Halle Nicole'}, function (err, res) { - if (err) return done(err); + app.render('a.hbs', {name: 'Halle Nicole'}, function(err, res) { + if (err) return cb(err); res.content.should.equal('foo Halle Nicole bar'); - done(); + cb(); }); }); - it('should use the `partial` helper with any engine.', function (done) { + it('should use the `partial` helper with any engine.', function(cb) { app.engine('hbs', handlebars); app.engine('md', handlebars); app.engine('swig', swig); app.engine('tmpl', require('engine-base')); - app.partial('a.hbs', {content: '---\nname: "AAA"\n---\n{{name}}', locals: {name: 'BBB'}}); - app.page('a.hbs', {path: 'a.hbs', content: '{{author}}', locals: {author: 'Halle Nicole'}}); - app.page('b.tmpl', {path: 'b.tmpl', content: '<%= author %>', locals: {author: 'Halle Nicole'}}); - app.page('d.swig', {path: 'd.swig', content: '{{author}}', locals: {author: 'Halle Nicole'}}); - app.page('e.swig', {path: 'e.swig', content: '{{author}}', locals: {author: 'Halle Nicole'}}); - app.page('f.hbs', {content: '{{author}}', locals: {author: 'Halle Nicole'}}); - app.page('g.md', {content: '---\nauthor: Brian Woodward\n---\n{{author}}', locals: {author: 'Halle Nicole'}}); - app.page('with-partial.hbs', {path: 'with-partial.hbs', content: '{{{partial "a.hbs" custom.locals}}}'}); + /** + * Partial + */ + + app.partial('a.hbs', { + content: '---\nname: "AAA"\n---\n{{name}}', + locals: { + name: 'BBB' + } + }); + + /** + * Pages + */ + + app.page('a.hbs', { + path: 'a.hbs', + content: '{{author}}', + locals: { + author: 'Halle Nicole' + } + }); + app.page('b.tmpl', { + path: 'b.tmpl', + content: '<%= author %>', + locals: { + author: 'Halle Nicole' + } + }); + app.page('d.swig', { + path: 'd.swig', + content: '{{author}}', + locals: { + author: 'Halle Nicole' + } + }); + app.page('e.swig', { + path: 'e.swig', + content: '{{author}}', + locals: { + author: 'Halle Nicole' + } + }); + app.page('f.hbs', { + content: '{{author}}', + locals: { + author: 'Halle Nicole' + } + }); + app.page('g.md', { + content: '---\nauthor: Brian Woodward\n---\n{{author}}', + locals: { + author: 'Halle Nicole' + } + }); + app.page('with-partial.hbs', { + path: 'with-partial.hbs', + content: '{{{partial "a.hbs" custom.locals}}}' + }); + + app.on('error', function(err) { + cb(err); + }); var locals = {custom: {locals: {name: 'Halle Nicole' }}}; - app.render('a.hbs', locals, function (err, res) { - if (err) return console.log(err); + app.render('a.hbs', locals, function(err, res) { + if (err) { + app.emit('error', err); + return; + } res.content.should.equal('Halle Nicole'); }); - app.render('with-partial.hbs', locals, function (err, res) { - if (err) return console.log(err); + app.render('with-partial.hbs', locals, function(err, res) { + if (err) { + app.emit('error', err); + return; + } res.content.should.equal('Halle Nicole'); }); var page = app.pages.getView('g.md'); locals.author = page.data.author || locals.author; - page.render(locals, function (err, res) { - if (err) return done(err); + page.render(locals, function(err, res) { + if (err) { + app.emit('error', err); + return; + } res.content.should.equal('Brian Woodward'); - done(null, res.content); + cb(null, res.content); }); }); }); }); -describe('helpers integration', function () { - beforeEach(function () { +describe('helpers integration', function() { + beforeEach(function() { app = new App(); app.create('pages'); app.engine('md', require('engine-base')); }); - describe('.helpers()', function () { - it('should add helpers and use them in templates.', function (done) { + describe('.helpers()', function() { + it('should add helpers and use them in templates.', function(cb) { app.helpers({ - upper: function (str) { + upper: function(str) { return str.toUpperCase(); } }); app.page('doc.md', {content: 'a <%= upper(name) %> b'}) - .render({name: 'Halle'}, function (err, res) { - if (err) return done(err); + .render({name: 'Halle'}, function(err, res) { + if (err) return cb(err); assert(res.content === 'a HALLE b'); - done(); + cb(); }); }); }); - describe('helper options:', function () { - it('should expose `this.options` to helpers:', function (done) { - app.helper('cwd', function (fp) { + describe('helper options:', function() { + it('should expose `this.options` to helpers:', function(cb) { + app.helper('cwd', function(fp) { return path.join(this.options.cwd, fp); }); app.option('one', 'two'); app.option('cwd', 'foo/bar'); app.page('doc.md', {content: 'a <%= cwd("baz") %> b'}) - .render(function (err, res) { - if (err) return done(err); + .render(function(err, res) { + if (err) return cb(err); assert(res.content === 'a foo/bar/baz b'); - done(); + cb(); }); }); - it('should pass helper options to helpers:', function (done) { - app.helper('cwd', function (fp) { + it('should pass helper options to helpers:', function(cb) { + app.helper('cwd', function(fp) { return path.join(this.options.cwd, fp); }); @@ -524,72 +616,72 @@ describe('helpers integration', function () { app.option('helper.whatever', '...'); app.page('doc.md', {content: 'a <%= cwd("baz") %> b'}) - .render(function (err, res) { - if (err) return done(err); + .render(function(err, res) { + if (err) return cb(err); assert(res.content === 'a foo/bar/baz b'); - done(); + cb(); }); }); }); - describe('options.helpers', function () { - it('should register helpers passed on the options:', function (done) { + describe('options.helpers', function() { + it('should register helpers passed on the options:', function(cb) { app.option({ helpers: { - upper: function (str) { + upper: function(str) { return str.toUpperCase(); }, - foo: function (str) { + foo: function(str) { return 'foo' + str; } } }); app.page('doc.md', {content: 'a <%= upper(name) %> <%= foo("bar") %> b'}) - .render({name: 'Halle'}, function (err, res) { - if (err) return done(err); + .render({name: 'Halle'}, function(err, res) { + if (err) return cb(err); assert(res.content === 'a HALLE foobar b'); - done(); + cb(); }); }); }); - describe('options.helpers', function () { - it('should add helpers and use them in templates.', function (done) { + describe('options.helpers', function() { + it('should add helpers and use them in templates.', function(cb) { app.options.helpers = { - upper: function (str) { + upper: function(str) { return str.toUpperCase(); }, - foo: function (str) { + foo: function(str) { return 'foo' + str; } }; app.page('doc.md', {content: 'a <%= upper(name) %> b'}) - .render({name: 'Halle'}, function (err, res) { - if (err) return done(err); + .render({name: 'Halle'}, function(err, res) { + if (err) return cb(err); assert(res.content === 'a HALLE b'); - done(); + cb(); }); }); }); }); -describe('collection helpers', function () { - beforeEach(function () { +describe('collection helpers', function() { + beforeEach(function() { app = new App(); app.create('posts'); app.create('pages', {engine: 'hbs'}); app.create('partials', {viewType: 'partial', engine: 'hbs'}); app.create('snippet', {viewType: 'partial'}); app.engine('hbs', require('engine-handlebars')); - app.helper('log', function (ctx) { + app.helper('log', function(ctx) { console.log(ctx); }); }); - describe('plural', function () { - it('should get the given collection', function (done) { + describe('plural', function() { + it('should get the given collection', function(cb) { app.post('a.hbs', {content: 'foo'}); app.post('b.hbs', {content: 'bar'}); app.post('c.hbs', {content: 'baz'}); @@ -601,16 +693,16 @@ describe('collection helpers', function () { app.page('index.hbs', { content: '{{> list.hbs }}' }) - .render(function (err, res) { - if (err) return done(err); + .render(function(err, res) { + if (err) return cb(err); assert(res.content === 'foobarbaz'); - done(); + cb(); }); }); }); - describe('single', function () { - it('should get a view from an unspecified collection', function (done) { + describe('single', function() { + it('should get a view from an unspecified collection', function(cb) { app.post('a.hbs', {content: 'post-a'}); app.post('b.hbs', {content: 'post-b'}); @@ -624,30 +716,30 @@ describe('collection helpers', function () { assert(one === 'post-a'); assert(two === 'post-b'); - done(); + cb(); }); - it('should return an empty string if not found', function (done) { + it('should return an empty string if not found', function(cb) { var one = app.page('one', {content: '{{view "foo.hbs"}}'}) .compile() .fn(); assert(one === ''); - done(); + cb(); }); - it('should handle engine errors', function (done) { + it('should handle engine errors', function(cb) { app.post('foo.hbs', {content: '{{one "two"}}'}); app.page('one', {content: '{{posts "foo.hbs"}}'}) - .render(function (err) { + .render(function(err) { assert(err); assert(typeof err === 'object'); assert(typeof err.message === 'string'); assert(/Missing helper: "one"/.test(err.message)); - done(); + cb(); }); }); - it('should handle engine errors2', function(done) { + it('should handle engine errors2', function(cb) { app.engine('tmpl', require('engine-base')); app.create('foo', {engine: 'tmpl'}); app.create('bar', {engine: 'tmpl'}); @@ -655,15 +747,15 @@ describe('collection helpers', function () { app.create('foo', {viewType: 'partial'}); app.foo('foo.tmpl', {path: 'foo.tmpl', content: '<%= blah.bar %>'}); app.bar('one.tmpl', {content: '<%= foo("foo.tmpl") %>'}) - .render(function (err) { + .render(function(err) { assert(err); assert(typeof err === 'object'); assert(/blah is not defined/.test(err.message)); - done(); + cb(); }); }); - it('should work with non-handlebars engine', function (done) { + it('should work with non-handlebars engine', function(cb) { app.engine('tmpl', require('engine-base')); app.create('foo', {engine: 'tmpl'}); app.create('bar', {engine: 'tmpl'}); @@ -681,10 +773,10 @@ describe('collection helpers', function () { assert(one === 'foo-a'); assert(two === 'foo-b'); - done(); + cb(); }); - it('should get a specific view from the given collection', function (done) { + it('should get a specific view from the given collection', function(cb) { app.post('a.hbs', {content: 'post-a'}); app.post('b.hbs', {content: 'post-b'}); app.post('c.hbs', {content: 'post-c'}); @@ -702,7 +794,7 @@ describe('collection helpers', function () { assert(one === 'post-a'); assert(two === 'page-b'); - done(); + cb(); }); }); }); diff --git a/test/item.js b/test/item.js index c44ea4d..ca0f435 100644 --- a/test/item.js +++ b/test/item.js @@ -2,7 +2,6 @@ require('mocha'); var should = require('should'); var fs = require('fs'); var path = require('path'); -var util = require('util'); var assert = require('assert'); var es = require('event-stream'); var Stream = require('stream'); @@ -11,107 +10,107 @@ var App = support.resolve(); var Item = App.Item; var item; -describe('Item', function () { - describe('instance', function () { - it('should create an instance of Item:', function () { +describe('Item', function() { + describe('instance', function() { + it('should create an instance of Item:', function() { item = new Item(); assert(item instanceof Item); }); - it('should instantiate without new:', function () { + it('should instantiate without new:', function() { item = Item(); assert(item instanceof Item); }); it('inspect should not double name `Stream` when ctor is `Stream`', function(done) { var val = new Stream(); - var item = new Item({contents: val}); + item = new Item({contents: val}); done(); }); }); - describe('static methods', function () { - it('should expose `extend`:', function () { + describe('static methods', function() { + it('should expose `extend`:', function() { assert(typeof Item.extend === 'function'); }); }); - describe('prototype methods', function () { - beforeEach(function () { + describe('prototype methods', function() { + beforeEach(function() { item = new Item(); }); - it('should expose `set`:', function () { + it('should expose `set`:', function() { assert(typeof item.set === 'function'); }); - it('should expose `get`:', function () { + it('should expose `get`:', function() { assert(typeof item.get === 'function'); }); - it('should expose `del`:', function () { + it('should expose `del`:', function() { assert(typeof item.del === 'function'); }); - it('should expose `define`:', function () { + it('should expose `define`:', function() { assert(typeof item.define === 'function'); }); - it('should expose `visit`:', function () { + it('should expose `visit`:', function() { assert(typeof item.visit === 'function'); }); }); - describe('properties', function () { - it('should expose an `options` property', function () { + describe('properties', function() { + it('should expose an `options` property', function() { item = new Item({}); assert.deepEqual(item.options, {}); assert(item.hasOwnProperty('options')); }); - it('should add `options` when passed on the constructor', function () { + it('should add `options` when passed on the constructor', function() { item = new Item({options: {foo: 'bar'}}); assert(item.options.foo === 'bar'); }); - it('should expose a `data` property', function () { + it('should expose a `data` property', function() { item = new Item({app: {}}); assert.deepEqual(item.data, {}); assert(item.hasOwnProperty('data')); }); - it('should add `data` when passed on the constructor', function () { + it('should add `data` when passed on the constructor', function() { item = new Item({data: {foo: 'bar'}}); assert(item.data.foo === 'bar'); }); - it('should add `locals` when passed on the constructor', function () { + it('should add `locals` when passed on the constructor', function() { item = new Item({locals: {foo: 'bar'}}); assert(item.locals.foo === 'bar'); }); }); - describe('set', function () { - it('should set properties on the object', function () { + describe('set', function() { + it('should set properties on the object', function() { item = new Item(); item.set('foo', 'bar'); assert.equal(item.foo, 'bar'); }); }); - describe('get', function () { - it('should get properties from the object', function () { + describe('get', function() { + it('should get properties from the object', function() { item = new Item(); item.set('foo', 'bar'); assert.equal(item.get('foo'), 'bar'); }); }); - describe('cwd', function () { - it('should get properties from the object', function () { + describe('cwd', function() { + it('should get properties from the object', function() { item = new Item({cwd: 'test/fixtures'}); assert(item.cwd === 'test/fixtures'); }); }); - describe('clone', function () { - it('should clone the item:', function () { + describe('clone', function() { + it('should clone the item:', function() { item = new Item({content: 'foo'}); item.set({path: 'foo/bar'}); item.set('options.one', 'two'); @@ -127,7 +126,7 @@ describe('Item', function () { assert(item.get('options.three') === 'four'); }); - it('should deep clone the entire object', function () { + it('should deep clone the entire object', function() { item = new Item({content: 'foo'}); item.set({path: 'foo/bar'}); item.set('options.one', 'two'); @@ -140,8 +139,8 @@ describe('Item', function () { }); }); - describe('visit', function () { - it('should visit all properties on an object and call the specified method', function () { + describe('visit', function() { + it('should visit all properties on an object and call the specified method', function() { item = new Item(); var obj = { foo: 'bar', @@ -154,7 +153,7 @@ describe('Item', function () { assert.equal(item.get('baz'), 'bang'); }); - it('should visit all properties on all objects in an array and call the specified method', function () { + it('should visit all properties on all objects in an array and call the specified method', function() { item = new Item(); var arr = [{foo: 'bar', bar: 'baz', baz: 'bang'}]; item.visit('set', arr); @@ -174,7 +173,7 @@ describe('Item', function () { describe('Item', function() { describe('isVinyl()', function() { it('should return true on a vinyl object', function(done) { - var item = new Item(); + item = new Item(); assert(Item.isVinyl(item) === true); done(); }); @@ -190,65 +189,65 @@ describe('Item', function() { describe('constructor()', function() { it('should default cwd to process.cwd', function(done) { - var item = new Item(); + item = new Item(); item.cwd.should.equal(process.cwd()); done(); }); it('should default base to cwd', function(done) { var cwd = '/'; - var item = new Item({cwd: cwd}); + item = new Item({cwd: cwd}); item.base.should.equal(cwd); done(); }); it('should default base to cwd even when none is given', function(done) { - var item = new Item(); + item = new Item(); item.base.should.equal(process.cwd()); done(); }); it('should default path to null', function(done) { - var item = new Item(); + item = new Item(); should.not.exist(item.path); done(); }); it('should default history to []', function(done) { - var item = new Item(); + item = new Item(); item.history.should.eql([]); done(); }); it('should default stat to null', function(done) { - var item = new Item(); + item = new Item(); should.not.exist(item.stat); done(); }); it('should default contents to null', function(done) { - var item = new Item(); + item = new Item(); should.not.exist(item.contents); done(); }); it('should set base to given value', function(done) { var val = '/'; - var item = new Item({base: val}); + item = new Item({base: val}); item.base.should.equal(val); done(); }); it('should set cwd to given value', function(done) { var val = '/'; - var item = new Item({cwd: val}); + item = new Item({cwd: val}); item.cwd.should.equal(val); done(); }); it('should set path to given value', function(done) { var val = '/test.coffee'; - var item = new Item({path: val}); + item = new Item({path: val}); item.path.should.equal(val); item.history.should.eql([val]); done(); @@ -256,7 +255,7 @@ describe('Item', function() { it('should set history to given value', function(done) { var val = '/test.coffee'; - var item = new Item({history: [val]}); + item = new Item({history: [val]}); item.path.should.equal(val); item.history.should.eql([val]); done(); @@ -264,14 +263,14 @@ describe('Item', function() { it('should set stat to given value', function(done) { var val = {}; - var item = new Item({stat: val}); + item = new Item({stat: val}); item.stat.should.equal(val); done(); }); it('should set contents to given value', function(done) { var val = new Buffer('test'); - var item = new Item({contents: val}); + item = new Item({contents: val}); item.contents.should.equal(val); done(); }); @@ -280,7 +279,7 @@ describe('Item', function() { describe('isBuffer()', function() { it('should return true when the contents are a Buffer', function(done) { var val = new Buffer('test'); - var item = new Item({contents: val}); + item = new Item({contents: val}); item.isBuffer().should.equal(true); done(); }); @@ -437,7 +436,9 @@ describe('Item', function() { item2.path.should.equal(item.path); item2.contents.should.not.equal(item.contents, 'stream ref should not be the same'); item.contents.pipe(es.wait(function(err, data) { + if (err) return done(err); item2.contents.pipe(es.wait(function(err, data2) { + if (err) return done(err); data2.should.not.equal(data, 'stream contents ref should not be the same'); data2.should.eql(data, 'stream contents should be the same'); })); @@ -477,10 +478,6 @@ describe('Item', function() { assert(copy.stat.isFile()); assert(!copy.stat.isDirectory()); - - assert(item.stat.hasOwnProperty('birthtime')); - assert(copy.stat.hasOwnProperty('birthtime')); - assert.deepEqual(item.stat, copy.stat); done(); }); @@ -827,11 +824,10 @@ describe('Item', function() { }); it('should error on get when no base', function(done) { - var a; var item = new Item(); delete item.base; try { - a = item.relative; + item.relative; } catch (err) { should.exist(err); done(); @@ -839,10 +835,9 @@ describe('Item', function() { }); it('should error on get when no path', function(done) { - var a; var item = new Item(); try { - a = item.relative; + item.relative; } catch (err) { should.exist(err); done(); @@ -864,17 +859,16 @@ describe('Item', function() { cwd: '/', path: '/test/test.coffee' }); - item.relative.should.equal(path.join('test','test.coffee')); + item.relative.should.equal(path.join('test', 'test.coffee')); done(); }); }); describe('dirname get/set', function() { it('should error on get when no path', function(done) { - var a; var item = new Item(); try { - a = item.dirname; + item.dirname; } catch (err) { should.exist(err); done(); @@ -915,10 +909,9 @@ describe('Item', function() { describe('basename get/set', function() { it('should error on get when no path', function(done) { - var a; - var item = new Item(); + item = new Item(); try { - a = item.basename; + item.basename; } catch (err) { should.exist(err); done(); @@ -926,7 +919,7 @@ describe('Item', function() { }); it('should return the basename of the path', function(done) { - var item = new Item({ + item = new Item({ cwd: '/', base: '/test/', path: '/test/test.coffee' @@ -936,7 +929,7 @@ describe('Item', function() { }); it('should error on set when no path', function(done) { - var item = new Item(); + item = new Item(); try { item.basename = 'test.coffee'; } catch (err) { @@ -946,7 +939,7 @@ describe('Item', function() { }); it('should set the basename of the path', function(done) { - var item = new Item({ + item = new Item({ cwd: '/', base: '/test/', path: '/test/test.coffee' @@ -959,10 +952,9 @@ describe('Item', function() { describe('extname get/set', function() { it('should error on get when no path', function(done) { - var a; - var item = new Item(); + item = new Item(); try { - a = item.extname; + item.extname; } catch (err) { should.exist(err); done(); @@ -970,7 +962,7 @@ describe('Item', function() { }); it('should return the extname of the path', function(done) { - var item = new Item({ + item = new Item({ cwd: '/', base: '/test/', path: '/test/test.coffee' @@ -980,7 +972,7 @@ describe('Item', function() { }); it('should error on set when no path', function(done) { - var item = new Item(); + item = new Item(); try { item.extname = '.coffee'; } catch (err) { @@ -990,7 +982,7 @@ describe('Item', function() { }); it('should set the extname of the path', function(done) { - var item = new Item({ + item = new Item({ cwd: '/', base: '/test/', path: '/test/test.coffee' @@ -1047,7 +1039,7 @@ describe('Item', function() { it('should throw when set path null in constructor', function() { (function() { - new Item({ + Item({ cwd: '/', path: null }); @@ -1055,7 +1047,7 @@ describe('Item', function() { }); it('should throw when set path null', function() { - var item = new Item({ + item = new Item({ cwd: '/', path: 'foo' }); diff --git a/test/layouts.js b/test/layouts.js index f079d32..76f65bc 100644 --- a/test/layouts.js +++ b/test/layouts.js @@ -5,20 +5,20 @@ var support = require('./support'); var App = support.resolve(); var app; -describe('layouts', function () { - beforeEach(function () { +describe('layouts', function() { + beforeEach(function() { app = new App(); app.engine('tmpl', require('engine-base')); app.create('layout', { viewType: 'layout' }); app.create('page'); }); - it('should apply a layout to a view:', function (done) { + it('should apply a layout to a view:', function(done) { app.layout('base', {path: 'base.tmpl', content: 'a {% body %} c'}); app.pages('a.tmpl', {path: 'a.tmpl', content: 'b', layout: 'base'}); var page = app.pages.getView('a.tmpl'); - app.render(page, function (err, view) { + app.render(page, function(err, view) { if (err) return done(err); assert.equal(typeof view.content, 'string'); assert.equal(view.content, 'a b c'); @@ -26,13 +26,13 @@ describe('layouts', function () { }); }); - it('should not apply a layout when `layoutApplied` is set:', function (done) { + it('should not apply a layout when `layoutApplied` is set:', function(done) { app.layout('base', {path: 'base.tmpl', content: 'a {% body %} c'}); app.pages('a.tmpl', {path: 'a.tmpl', content: 'b', layout: 'base'}); var page = app.pages.getView('a.tmpl'); page.option('layoutApplied', true); - app.render(page, function (err, view) { + app.render(page, function(err, view) { if (err) return done(err); assert.equal(typeof view.content, 'string'); assert.equal(view.content, 'b'); @@ -40,12 +40,12 @@ describe('layouts', function () { }); }); - it('should not apply a layout to itself:', function (done) { + it('should not apply a layout to itself:', function(done) { app.layout('base', {path: 'base.tmpl', content: 'a {% body %} c', layout: 'base'}); app.pages('a.tmpl', {path: 'a.tmpl', content: 'b', layout: 'base'}); var page = app.pages.getView('a.tmpl'); - app.render(page, function (err, view) { + app.render(page, function(err, view) { if (err) return done(err); assert.equal(typeof view.content, 'string'); assert.equal(view.content, 'a b c'); @@ -53,7 +53,7 @@ describe('layouts', function () { }); }); - it('should apply nested layouts to a view:', function (done) { + it('should apply nested layouts to a view:', function(done) { app.layout('a', {path: 'a.tmpl', content: 'a {% body %} a', layout: 'b'}); app.layout('b', {path: 'b.tmpl', content: 'b {% body %} b', layout: 'c'}); app.layout('c', {path: 'c.tmpl', content: 'c {% body %} c', layout: 'base'}); @@ -62,7 +62,7 @@ describe('layouts', function () { app.pages('z.tmpl', {path: 'a.tmpl', content: 'inner', layout: 'a'}); var page = app.pages.getView('z.tmpl'); - app.render(page, function (err, view) { + app.render(page, function(err, view) { if (err) return done(err); assert.equal(typeof view.content, 'string'); assert.equal(view.content, 'outter c b a inner a b c outter'); @@ -70,7 +70,7 @@ describe('layouts', function () { }); }); - it('should track layout stack history on `layoutStack`:', function (done) { + it('should track layout stack history on `layoutStack`:', function(done) { app.layout('a', {path: 'a.tmpl', content: 'a {% body %} a', layout: 'b'}); app.layout('b', {path: 'b.tmpl', content: 'b {% body %} b', layout: 'c'}); app.layout('c', {path: 'c.tmpl', content: 'c {% body %} c', layout: 'base'}); @@ -79,7 +79,7 @@ describe('layouts', function () { app.pages('z.tmpl', {path: 'a.tmpl', content: 'inner', layout: 'a'}); var page = app.pages.getView('z.tmpl'); - app.render(page, function (err, view) { + app.render(page, function(err, view) { if (err) return done(err); assert(view.layoutStack.length === 4); assert(typeof view.layoutStack[0] === 'object'); @@ -88,7 +88,7 @@ describe('layouts', function () { }); }); - it('should track layout stack history on `layoutStack`:', function (done) { + it('should track layout stack history on `layoutStack`:', function(done) { app.layout('a', {path: 'a.tmpl', content: 'a {% body %} a', layout: 'b'}); app.layout('b', {path: 'b.tmpl', content: 'b {% body %} b', layout: 'c'}); app.layout('c', {path: 'c.tmpl', content: 'c {% body %} c', layout: 'base'}); @@ -97,7 +97,7 @@ describe('layouts', function () { app.pages('z.tmpl', {path: 'a.tmpl', content: 'inner', layout: 'a'}); var page = app.pages.getView('z.tmpl'); - app.render(page, function (err, view) { + app.render(page, function(err, view) { if (err) return done(err); assert.equal(typeof view.content, 'string'); assert.equal(view.content, 'outter c b a inner a b c outter'); @@ -105,7 +105,7 @@ describe('layouts', function () { }); }); - it('should get layouts from `layout` viewTypes:', function (done) { + it('should get layouts from `layout` viewTypes:', function(done) { app.create('section', { viewType: 'layout' }); app.create('block', { viewType: 'layout' }); @@ -117,7 +117,7 @@ describe('layouts', function () { app.pages('z.tmpl', {path: 'a.tmpl', content: 'inner', layout: 'a'}); var page = app.pages.getView('z.tmpl'); - app.render(page, function (err, view) { + app.render(page, function(err, view) { if (err) return done(err); assert.equal(typeof view.content, 'string'); assert.equal(view.content, 'outter c b a inner a b c outter'); diff --git a/test/list.js b/test/list.js index 0dc23ca..8f9cf5d 100644 --- a/test/list.js +++ b/test/list.js @@ -2,38 +2,36 @@ require('mocha'); require('should'); var path = require('path'); var get = require('get-value'); -var isBuffer = require('is-buffer'); var assert = require('assert'); var typeOf = require('kind-of'); var support = require('./support/'); +var isBuffer = require('is-buffer'); assert.containEql = support.containEql; - -var support = require('./support'); var App = support.resolve(); var List = App.List; var Views = App.Views; var list, views; -describe('list', function () { - describe('constructor', function () { - it('should create an instance of List', function () { +describe('list', function() { + describe('constructor', function() { + it('should create an instance of List', function() { var list = new List(); assert(list instanceof List); }); - it('should instaniate without `new`', function () { + it('should instaniate without `new`', function() { var list = List(); assert(list instanceof List); }); }); - describe('static methods', function () { - it('should expose `extend`', function () { - assert(typeof List.extend ==='function'); + describe('static methods', function() { + it('should expose `extend`', function() { + assert(typeof List.extend === 'function'); }); }); - describe('prototype methods', function () { + describe('prototype methods', function() { beforeEach(function() { list = new List(); }); @@ -59,67 +57,67 @@ describe('list', function () { 'hasListeners' ]; - methods.forEach(function (method) { - it('should expose the ' + method + ' method', function () { + methods.forEach(function(method) { + it('should expose the ' + method + ' method', function() { assert(typeof list[method] === 'function'); }); }); - it('should expose the isList property', function () { + it('should expose the isList property', function() { assert(typeof list.isList === 'boolean'); }); - it('should expose the keys property', function () { + it('should expose the keys property', function() { assert(Array.isArray(list.keys)); }); - it('should expose the queue property', function () { + it('should expose the queue property', function() { assert(Array.isArray(list.queue)); }); - it('should expose the items property', function () { + it('should expose the items property', function() { assert(Array.isArray(list.items)); }); - it('should expose the options property', function () { + it('should expose the options property', function() { assert(typeOf(list.options) === 'object'); }); }); - describe('instance', function () { + describe('instance', function() { beforeEach(function() { list = new List(); }); - it('should set a value on the instance', function () { + it('should set a value on the instance', function() { list.set('a', 'b'); - assert(list.a ==='b'); + assert(list.a === 'b'); }); - it('should get a value from the instance', function () { + it('should get a value from the instance', function() { list.set('a', 'b'); - assert(list.get('a') ==='b'); + assert(list.get('a') === 'b'); }); }); - describe('use', function () { + describe('use', function() { beforeEach(function() { list = new List(); }); - it('should expose the instance to plugins', function () { + it('should expose the instance to plugins', function() { list - .use(function (inst) { + .use(function(inst) { inst.foo = 'bar'; }); assert(list.foo === 'bar'); }); - it('should expose `item` when the plugin returns a function', function () { + it('should expose `item` when the plugin returns a function', function() { list - .use(function () { - return function (item) { + .use(function() { + return function(item) { item.foo = 'bar'; }; }); @@ -135,11 +133,11 @@ describe('list', function () { }); describe('addItem', function() { - beforeEach(function() { + beforeEach(function() { list = new List(); }); - - it('should add items to a list', function () { + + it('should add items to a list', function() { list.addItem('a', {content: '...'}); list.addItem('b', {content: '...'}); list.addItem('c', {content: '...'}); @@ -152,7 +150,7 @@ describe('list', function () { list = new List(); }); - it('should remove an item from `items`', function () { + it('should remove an item from `items`', function() { list.addItem('a', {content: '...'}); list.addItem('b', {content: '...'}); list.addItem('c', {content: '...'}); @@ -165,7 +163,7 @@ describe('list', function () { assert(list.items[0].key === 'b'); }); - it('should remove an item from `items` by key', function () { + it('should remove an item from `items` by key', function() { list.addItem('a', {content: '...'}); list.addItem('b', {content: '...'}); list.addItem('c', {content: '...'}); @@ -182,7 +180,7 @@ describe('list', function () { list = new List(); }); - it('should add an object with multiple items', function () { + it('should add an object with multiple items', function() { list.addItems({ one: {content: 'foo'}, two: {content: 'bar'} @@ -191,8 +189,8 @@ describe('list', function () { assert(isBuffer(list.items[1].contents)); }); - it('should signal `loaded` when finished (addItems)', function () { - list.on('addItems', function (items) { + it('should signal `loaded` when finished (addItems)', function() { + list.on('addItems', function(items) { for (var key in items) { if (key === 'c') { list.loaded = true; @@ -214,12 +212,12 @@ describe('list', function () { }); }); - describe('addList', function () { + describe('addList', function() { beforeEach(function() { list = new List(); }); - it('should add an array with multiple items', function () { + it('should add an array with multiple items', function() { list.addList([ {path: 'one', content: 'foo'}, {path: 'two', content: 'bar'} @@ -228,7 +226,7 @@ describe('list', function () { assert(isBuffer(list.items[1].contents)); }); - it('should take a callback on `addList`', function () { + it('should take a callback on `addList`', function() { function addContents(item) { item.contents = new Buffer(item.path.charAt(0)); } @@ -236,7 +234,7 @@ describe('list', function () { list.addList([ { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, - { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, + { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } } ], addContents); assert(isBuffer(list.items[0].contents)); @@ -244,16 +242,16 @@ describe('list', function () { assert(isBuffer(list.items[2].contents)); }); - it('should throw an error when the list is not an array', function () { + it('should throw an error when the list is not an array', function() { function addContents(item) { item.contents = new Buffer(item.path.charAt(0)); } - (function () { + (function() { list.addList({ 'a.md': {locals: { date: '2014-01-01', foo: 'zzz', bar: 1 }}, 'f.md': {locals: { date: '2014-01-01', foo: 'mmm', bar: 2 }}, - 'd.md': {locals: { date: '2014-01-01', foo: 'xxx', bar: 3 }}, + 'd.md': {locals: { date: '2014-01-01', foo: 'xxx', bar: 3 }} }, addContents); assert(isBuffer(list.items[0].contents)); @@ -262,9 +260,11 @@ describe('list', function () { }).should.throw('expected list to be an array.'); }); - it('should signal `loaded` when finished (addList)', function () { - list.on('addList', function (items) { - var len = items.length, i = -1; + it('should signal `loaded` when finished (addList)', function() { + list.on('addList', function(items) { + var len = items.length; + var i = -1; + while (++i < len) { if (items[i].path === 'd.md') { list.loaded = true; @@ -277,7 +277,7 @@ describe('list', function () { list.addList([ { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, - { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, + { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } } ]); assert.equal(list.items.length, 2); @@ -285,13 +285,13 @@ describe('list', function () { }); }); - describe('queue', function () { - beforeEach(function () { + describe('queue', function() { + beforeEach(function() { list = new List(); }); - it('should emit arguments on addItem', function (done) { - list.on('addItem', function (args) { + it('should emit arguments on addItem', function(done) { + list.on('addItem', function(args) { assert(args[0] === 'a'); assert(args[1] === 'b'); assert(args[2] === 'c'); @@ -303,7 +303,7 @@ describe('list', function () { list.addItem('a', 'b', 'c', 'd', 'e'); }); - it('should expose the `queue` property for loading items', function () { + it('should expose the `queue` property for loading items', function() { list.queue.push(list.item('b', {path: 'b'})); list.addItem('a', {path: 'a'}); @@ -311,8 +311,8 @@ describe('list', function () { assert(list.items[1].key === 'b'); }); - it('should load all items on the queue when addItem is called', function () { - list.on('addItem', function (args) { + it('should load all items on the queue when addItem is called', function() { + list.on('addItem', function(args) { var len = args.length; var last = args[len - 1]; if (typeof last === 'string') { @@ -347,15 +347,15 @@ describe('list', function () { { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, - { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, + { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } } ]; - it('should sort a list', function () { + it('should sort a list', function() { list = new List(); list.addList(items); var compare = function(prop) { - return function (a, b, fn) { + return function(a, b, fn) { var valA = get(a, prop); var valB = get(b, prop); return fn(valA, valB); @@ -384,12 +384,12 @@ describe('list', function () { ]); }); - it('should not sort the (original) instance list `items`', function () { + it('should not sort the (original) instance list `items`', function() { list = new List(); list.addList(items); var compare = function(prop) { - return function (a, b, fn) { + return function(a, b, fn) { var valA = get(a, prop); var valB = get(b, prop); return fn(valA, valB); @@ -422,12 +422,12 @@ describe('list', function () { ]); }); - it('should pass options to array-sort from the constructor', function () { + it('should pass options to array-sort from the constructor', function() { list = new List({sort: {reverse: true}}); list.addList(items); var compare = function(prop) { - return function (a, b, fn) { + return function(a, b, fn) { var valA = get(a, prop); var valB = get(b, prop); return fn(valA, valB); @@ -456,12 +456,12 @@ describe('list', function () { ]); }); - it('should pass options to array-sort from the sortBy method', function () { + it('should pass options to array-sort from the sortBy method', function() { list = new List(); list.addList(items); var compare = function(prop) { - return function (a, b, fn) { + return function(a, b, fn) { var valA = get(a, prop); var valB = get(b, prop); return fn(valA, valB); @@ -505,10 +505,10 @@ describe('list', function () { { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, - { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, + { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } } ]; - it('should group a list by a property', function () { + it('should group a list by a property', function() { list = new List(); list.addList(items); @@ -536,15 +536,15 @@ describe('list', function () { { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, - { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, + { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } } ]; - it('should group a list by a property', function () { + it('should group a list by a property', function() { list = new List(items); var context = list .sortBy('locals.date') - .groupBy(function (view) { + .groupBy(function(view) { var date = view.locals.date; view.locals.year = date.slice(0, 4); view.locals.month = date.slice(5, 7); @@ -574,10 +574,10 @@ describe('list', function () { { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, - { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, + { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } } ]; - it('should paginate a list', function () { + it('should paginate a list', function() { list = new List(items); var res = list.paginate(); @@ -586,15 +586,15 @@ describe('list', function () { assert.containEql(res[1].items, items.slice(10)); }); - it('should add pager properties', function () { + it('should add pager properties', function() { list = new List({pager: true}); list.addList(items); - list.items.forEach(function (item, i) { + list.items.forEach(function(item, i) { assert.equal(item.data.pager.index, i); }); }); - it('should paginate a list with given options', function () { + it('should paginate a list with given options', function() { list = new List(items); var res = list.paginate({limit: 5}); @@ -610,7 +610,7 @@ describe('list', function () { views = new Views(); }); - it('should add views from an instance of Views', function () { + it('should add views from an instance of Views', function() { views.addViews({ one: {content: 'foo'}, two: {content: 'bar'} @@ -626,16 +626,16 @@ describe('list', function () { beforeEach(function() { list = new List(); }); - it('should get the index of a key when key is not renamed', function () { + it('should get the index of a key when key is not renamed', function() { list.addItem('a/b/c/ddd.hbs', {content: 'ddd'}); list.addItem('a/b/c/eee.hbs', {content: 'eee'}); assert(list.getIndex('a/b/c/ddd.hbs') === 0); assert(list.getIndex('a/b/c/eee.hbs') === 1); }); - it('should get the index of a key when key is renamed', function () { + it('should get the index of a key when key is renamed', function() { list = new List({ - renameKey: function (key) { + renameKey: function(key) { return path.basename(key); } }); @@ -653,7 +653,7 @@ describe('list', function () { list = new List(); }); - it('should get an view from `views`', function () { + it('should get an view from `views`', function() { list.addItem('one', {content: 'aaa'}); list.addItem('two', {content: 'zzz'}); assert(list.items.length === 2); @@ -669,15 +669,15 @@ describe('list', function () { list = new List(); }); - it('should use middleware on a list', function () { + it('should use middleware on a list', function() { list.addItem('one', {content: 'aaa'}); list.addItem('two', {content: 'zzz'}); list - .use(function () { + .use(function() { this.set('foo', 'bar'); }) - .use(function () { + .use(function() { this.set('one', 'two'); }); diff --git a/test/list.render.js b/test/list.render.js index b660836..beffeea 100644 --- a/test/list.render.js +++ b/test/list.render.js @@ -7,40 +7,40 @@ var App = support.resolve(); var List = App.List; var pages; -describe('render', function () { - describe('rendering', function () { - beforeEach(function () { +describe('render', function() { + describe('rendering', function() { + beforeEach(function() { pages = new List(); pages.engine('tmpl', require('engine-base')); }); - it('should throw an error when no callback is given:', function () { + it('should throw an error when no callback is given:', function() { (function() { pages.render({}); }).should.throw('List#render is async and expects a callback function'); }); - it('should throw an error when an engine is not defined:', function (done) { + it('should throw an error when an engine is not defined:', function(done) { pages.addItem('foo.bar', {content: '<%= name %>'}); var page = pages.getItem('foo.bar'); pages.render(page, function(err) { - assert(err.message === 'List#render cannot find an engine for: .bar'); + assert(err.message === 'List#render cannot find engine: .bar'); done(); }); }); - it('should use helpers to render a item:', function (done) { + it('should use helpers to render a item:', function(done) { var locals = {name: 'Halle'}; - pages.helper('upper', function (str) { + pages.helper('upper', function(str) { return str.toUpperCase(str); }); pages.addItem('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); var page = pages.getItem('a.tmpl'); - pages.render(page, function (err, res) { + pages.render(page, function(err, res) { if (err) return done(err); assert(res.content === 'a HALLE b'); @@ -48,55 +48,55 @@ describe('render', function () { }); }); - it('should use helpers when rendering a item:', function (done) { + it('should use helpers when rendering a item:', function(done) { var locals = {name: 'Halle'}; - pages.helper('upper', function (str) { + pages.helper('upper', function(str) { return str.toUpperCase(str); }); pages.addItem('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); var page = pages.getItem('a.tmpl'); - pages.render(page, function (err, res) { + pages.render(page, function(err, res) { if (err) return done(err); assert(res.content === 'a HALLE b'); done(); }); }); - it('should render a template when contents is a buffer:', function (done) { + it('should render a template when contents is a buffer:', function(done) { pages.addItem('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); var item = pages.getItem('a.tmpl'); - pages.render(item, function (err, item) { + pages.render(item, function(err, item) { if (err) return done(err); assert(item.contents.toString() === 'b'); done(); }); }); - it('should render a template when content is a string:', function (done) { + it('should render a template when content is a string:', function(done) { pages.addItem('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); var item = pages.getItem('a.tmpl'); - pages.render(item, function (err, item) { + pages.render(item, function(err, item) { if (err) return done(err); assert(item.contents.toString() === 'b'); done(); }); }); - it('should render a item from its path:', function (done) { + it('should render a item from its path:', function(done) { pages.addItem('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - pages.render('a.tmpl', function (err, item) { + pages.render('a.tmpl', function(err, item) { if (err) return done(err); assert(item.content === 'b'); done(); }); }); - it('should use a plugin for rendering:', function (done) { + it('should use a plugin for rendering:', function(done) { pages.engine('tmpl', require('engine-base')); pages.option('engine', 'tmpl'); @@ -110,22 +110,22 @@ describe('render', function () { 'g': {content: '<%= title %>', locals: {title: 'ggg'}}, 'h': {content: '<%= title %>', locals: {title: 'hhh'}}, 'i': {content: '<%= title %>', locals: {title: 'iii'}}, - 'j': {content: '<%= title %>', locals: {title: 'jjj'}}, + 'j': {content: '<%= title %>', locals: {title: 'jjj'}} }); - pages.use(function (collection) { + pages.use(function(collection) { collection.option('pager', false); - collection.renderEach = function (cb) { + collection.renderEach = function(cb) { var list = new List(collection); - async.map(list.items, function (item, next) { + async.map(list.items, function(item, next) { collection.render(item, next); }, cb); }; }); - pages.renderEach(function (err, items) { + pages.renderEach(function(err, items) { if (err) return done(err); assert(items[0].content === 'aaa'); assert(items[9].content === 'jjj'); diff --git a/test/list.use.js b/test/list.use.js index c192a00..5ac2c1b 100644 --- a/test/list.use.js +++ b/test/list.use.js @@ -7,33 +7,33 @@ var List = App.List; var Item = App.Item; var list; -describe('list.use', function () { - beforeEach(function () { +describe('list.use', function() { + beforeEach(function() { list = new List(); }); - it('should expose the instance to `use`:', function (done) { - list.use(function (inst) { + it('should expose the instance to `use`:', function(done) { + list.use(function(inst) { assert(inst instanceof List); done(); }); }); - it('should be chainable:', function (done) { - list.use(function (inst) { - assert(inst instanceof List); - }) - .use(function (inst) { + it('should be chainable:', function(done) { + list.use(function(inst) { + assert(inst instanceof List); + }) + .use(function(inst) { assert(inst instanceof List); }) - .use(function (inst) { + .use(function(inst) { assert(inst instanceof List); done(); }); }); - it('should expose the list to a plugin:', function () { - list.use(function (items) { + it('should expose the list to a plugin:', function() { + list.use(function(items) { assert(items instanceof List); items.foo = items.addItem.bind(items); }); @@ -42,17 +42,17 @@ describe('list.use', function () { assert(list.hasItem('a')); }); - it('should expose list when chained:', function () { + it('should expose list when chained:', function() { list - .use(function (items) { + .use(function(items) { assert(items instanceof List); items.foo = items.addItem.bind(items); }) - .use(function (items) { + .use(function(items) { assert(items instanceof List); items.bar = items.addItem.bind(items); }) - .use(function (items) { + .use(function(items) { assert(items instanceof List); items.baz = items.addItem.bind(items); }); @@ -68,18 +68,18 @@ describe('list.use', function () { assert(list.hasItem('c')); }); - it('should work when a custom `Item` constructor is passed:', function () { + it('should work when a custom `Item` constructor is passed:', function() { list = new List({Item: require('vinyl')}); list - .use(function (items) { + .use(function(items) { assert(items instanceof List); items.foo = items.addItem.bind(items); }) - .use(function (items) { + .use(function(items) { assert(items instanceof List); items.bar = items.addItem.bind(items); }) - .use(function (items) { + .use(function(items) { assert(items instanceof List); items.baz = items.addItem.bind(items); }); @@ -95,11 +95,11 @@ describe('list.use', function () { assert(list.hasItem('c')); }); - it('should pass to item `use` if a function is returned:', function () { - list.use(function (items) { + it('should pass to item `use` if a function is returned:', function() { + list.use(function(items) { assert(items instanceof List); - return function (item) { + return function(item) { item.foo = items.addItem.bind(items); assert(item.isItem || item.isView); }; @@ -116,28 +116,28 @@ describe('list.use', function () { assert(list.hasItem('d')); }); - it('should be chainable when a item function is returned:', function () { + it('should be chainable when a item function is returned:', function() { list - .use(function (items) { + .use(function(items) { assert(items instanceof List); - return function (item) { + return function(item) { item.foo = items.addItem.bind(items); assert(item instanceof Item); }; }) - .use(function (items) { + .use(function(items) { assert(items instanceof List); - return function (item) { + return function(item) { item.bar = items.addItem.bind(items); assert(item instanceof Item); }; }) - .use(function (items) { + .use(function(items) { assert(items instanceof List); - return function (item) { + return function(item) { item.baz = items.addItem.bind(items); assert(item instanceof Item); }; diff --git a/test/mergePartials.js b/test/mergePartials.js index 6b0835f..38f23be 100644 --- a/test/mergePartials.js +++ b/test/mergePartials.js @@ -3,14 +3,12 @@ var support = require('./support'); var App = support.resolve(); var app; -describe('mergePartials', function () { - beforeEach(function () { +describe('mergePartials', function() { + beforeEach(function() { app = new App(); - // reset views - app.views = {}; }); - it('should merge multiple partials collections onto one collection:', function () { + it('should merge multiple partials collections onto one collection:', function() { var opts = { viewType: 'partial' }; app.create('foo', opts); app.create('bar', opts); @@ -25,7 +23,7 @@ describe('mergePartials', function () { actual.partials.should.have.properties(['a', 'b', 'c']); }); - it('should keep partials collections on separaet collections:', function () { + it('should keep partials collections on separaet collections:', function() { var opts = { viewType: 'partial' }; app.create('foo', opts); app.create('bar', opts); @@ -40,14 +38,14 @@ describe('mergePartials', function () { actual.should.eql({ foos: { a: 'aaa' }, bars: { b: 'bbb' }, bazs: { c: 'ccc' } }); }); - it('should emit `mergePartials`:', function () { + it('should emit `mergePartials`:', function() { var opts = { viewType: 'partial' }; app.create('foo', opts); app.create('bar', opts); app.create('baz', opts); var arr = []; - app.on('onMerge', function (view) { + app.on('onMerge', function(view) { arr.push(view.content); }); @@ -61,13 +59,13 @@ describe('mergePartials', function () { arr.should.eql(['aaa', 'bbb', 'ccc']); }); - it('should handle `onMerge` middleware:', function () { + it('should handle `onMerge` middleware:', function() { var opts = { viewType: 'partial' }; app.create('foo', opts); app.create('bar', opts); app.create('baz', opts); - app.onMerge(/./, function (view, next) { + app.onMerge(/./, function(view, next) { view.content += ' onMerge'; next(); }); @@ -84,14 +82,14 @@ describe('mergePartials', function () { }); }); - it('should skip views with `nomerge=true`:', function () { + it('should skip views with `nomerge=true`:', function() { var opts = { viewType: 'partial' }; app.create('foo', opts); app.create('bar', opts); app.create('baz', opts); - app.onMerge(/[ab]/, function (view, next) { + app.onMerge(/[ab]/, function(view, next) { view.options.nomerge = true; next(); }); @@ -104,5 +102,3 @@ describe('mergePartials', function () { actual.should.eql({ bazs: { c: 'ccc' } }); }); }); - - diff --git a/test/partials.js b/test/partials.js index 575e7b5..b5fbb94 100644 --- a/test/partials.js +++ b/test/partials.js @@ -5,8 +5,8 @@ var support = require('./support'); var App = support.resolve(); var app, pages; -describe('partials', function () { - beforeEach(function () { +describe('partials', function() { + beforeEach(function() { app = new App(); app.engine('tmpl', require('engine-base')); app.engine('hbs', require('engine-handlebars')); @@ -17,12 +17,12 @@ describe('partials', function () { pages = app.create('page'); }); - it('should inject a partial with a helper:', function (done) { + it('should inject a partial with a helper:', function(done) { app.include('base', {path: 'base.tmpl', content: 'xyz'}); app.pages('a.tmpl', {path: 'a.tmpl', content: 'a <%= include("base") %> c'}); var page = app.pages.getView('a.tmpl'); - app.render(page, function (err, view) { + app.render(page, function(err, view) { if (err) return done(err); assert.equal(typeof view.content, 'string'); assert.equal(view.content, 'a xyz c'); @@ -30,7 +30,7 @@ describe('partials', function () { }); }); - it('should inject a partial with a helper on a collection:', function (done) { + it('should inject a partial with a helper on a collection:', function(done) { app.include('base', {path: 'base.tmpl', content: 'xyz'}); pages.engine('.tmpl', require('engine-handlebars')); pages.helpers(app._.helpers.sync); @@ -38,7 +38,7 @@ describe('partials', function () { pages.addView('a.tmpl', {path: 'a.tmpl', content: 'a {{include "base" }} c'}); var page = pages.getView('a.tmpl'); - pages.render(page, function (err, view) { + pages.render(page, function(err, view) { if (err) return done(err); assert.equal(typeof view.content, 'string'); assert.equal(view.content, 'a xyz c'); @@ -46,7 +46,7 @@ describe('partials', function () { }); }); - it('should use handlebars partial with a helper on a collection:', function (done) { + it('should use handlebars partial with a helper on a collection:', function(done) { app.include('base', {path: 'base.tmpl', content: 'xyz'}); pages.engine('.tmpl', require('engine-handlebars')); pages.helpers(app._.helpers.sync); @@ -56,7 +56,7 @@ describe('partials', function () { var page = pages.getView('a.tmpl'); var locals = app.mergePartials(this.options); - pages.render(page, locals, function (err, view) { + pages.render(page, locals, function(err, view) { if (err) return done(err); assert.equal(typeof view.content, 'string'); assert.equal(view.content, 'a xyz c'); @@ -64,13 +64,13 @@ describe('partials', function () { }); }); - it('should use layouts with partials:', function (done) { + it('should use layouts with partials:', function(done) { app.layout('default', {path: 'a.tmpl', content: 'a {% body %} c'}); app.include('b', {path: 'b.tmpl', content: 'b', layout: 'default'}); app.pages('a.tmpl', {path: 'c.tmpl', content: '<%= include("b") %>'}); var page = app.pages.getView('a.tmpl'); - app.render(page, function (err, view) { + app.render(page, function(err, view) { if (err) return done(err); assert.equal(typeof view.content, 'string'); assert.equal(view.content, 'a b c'); @@ -78,13 +78,13 @@ describe('partials', function () { }); }); - it('should add `layoutApplied` after layout is applied:', function (done) { + it('should add `layoutApplied` after layout is applied:', function(done) { app.layout('default', {path: 'a.tmpl', content: 'a {% body %} c'}); app.include('b', {path: 'b.tmpl', content: 'b', layout: 'default'}); app.pages('a.tmpl', {path: 'c.tmpl', content: '<%= include("b") %>'}); var page = app.pages.getView('a.tmpl'); - app.render(page, function (err, view) { + app.render(page, function(err, view) { if (err) return done(err); assert.equal(typeof view.content, 'string'); assert.equal(app.layouts.getView('default').options.layoutApplied); @@ -93,8 +93,8 @@ describe('partials', function () { }); }); - it('should pass partials to handlebars:', function (done) { - app.onMerge(/\.hbs$/, function (view, next) { + it('should pass partials to handlebars:', function(done) { + app.onMerge(/\.hbs$/, function(view, next) { app.applyLayout(view); next(); }); @@ -104,7 +104,7 @@ describe('partials', function () { app.pages('a.hbs', {path: 'c.hbs', content: '{{> foo }}'}); var page = app.pages.getView('a.hbs'); - app.render(page, function (err, view) { + app.render(page, function(err, view) { if (err) return done(err); assert.equal(typeof view.content, 'string'); assert.equal(view.content, 'a foo c'); @@ -112,8 +112,8 @@ describe('partials', function () { }); }); - it('should only merge in the specified viewTypes:', function (done) { - app.onMerge(/\.hbs$/, function (view, next) { + it('should only merge in the specified viewTypes:', function(done) { + app.onMerge(/\.hbs$/, function(view, next) { app.applyLayout(view); next(); }); @@ -126,7 +126,7 @@ describe('partials', function () { app.pages('a.hbs', {path: 'c.hbs', content: '{{> foo }}'}); app.pages.getView('a.hbs') - .render(function (err, view) { + .render(function(err, view) { if (err) return done(err); assert.equal(typeof view.content, 'string'); assert.equal(view.content, 'a foo c'); @@ -135,8 +135,8 @@ describe('partials', function () { }); - it('should merge the specified viewTypes in the order defined:', function (done) { - app.onMerge(/\.hbs$/, function (view, next) { + it('should merge the specified viewTypes in the order defined:', function(done) { + app.onMerge(/\.hbs$/, function(view, next) { app.applyLayout(view); next(); }); @@ -149,7 +149,7 @@ describe('partials', function () { app.pages('a.hbs', {path: 'c.hbs', content: '{{> foo }}'}); app.pages.getView('a.hbs') - .render(function (err, view) { + .render(function(err, view) { if (err) return done(err); assert.equal(typeof view.content, 'string'); assert.equal(view.content, 'a bar c'); @@ -157,8 +157,8 @@ describe('partials', function () { }); }); - it('should not merge in partials with `options.nomerge` defined:', function (done) { - app.onMerge(/\.hbs$/, function (view, next) { + it('should not merge in partials with `options.nomerge` defined:', function(done) { + app.onMerge(/\.hbs$/, function(view, next) { app.applyLayout(view); next(); }); @@ -171,7 +171,7 @@ describe('partials', function () { app.pages('a.hbs', {path: 'c.hbs', content: '{{> foo }}'}); app.pages.getView('a.hbs') - .render(function (err, view) { + .render(function(err, view) { if (err) return done(err); assert.equal(typeof view.content, 'string'); assert.equal(view.content, 'a foo c'); @@ -179,8 +179,8 @@ describe('partials', function () { }); }); - it('should emit an `onMerge` event:', function (done) { - app.on('onMerge', function (view) { + it('should emit an `onMerge` event:', function(done) { + app.on('onMerge', function(view) { app.applyLayout(view); }); @@ -192,7 +192,7 @@ describe('partials', function () { app.pages('a.hbs', {path: 'c.hbs', content: '{{> foo }}'}); app.pages.getView('a.hbs') - .render(function (err, view) { + .render(function(err, view) { if (err) return done(err); assert.equal(typeof view.content, 'string'); assert.equal(view.content, 'a bar c'); diff --git a/test/questions.js b/test/questions.js index 5ae3bad..4318e64 100644 --- a/test/questions.js +++ b/test/questions.js @@ -6,12 +6,12 @@ var support = require('./support'); var App = support.resolve(); var app; -describe('content', function () { - beforeEach(function () { +describe.skip('content', function() { + beforeEach(function() { app = new App(); }); - it('should store a question:', function () { + it('should store a question:', function() { app.question('a', 'b'); assert(app.questions); assert(app.questions.cache); @@ -20,11 +20,11 @@ describe('content', function () { assert(app.questions.cache.a.message === 'b'); }); - it('should ask a question and use data value to answer:', function (done) { + it('should ask a question and use data value to answer:', function(done) { app.question('a', 'b'); app.data('a', 'b'); - app.ask('a', function (err, answer) { + app.ask('a', function(err, answer) { assert(!err); assert(answer); assert(answer === 'b'); @@ -32,11 +32,11 @@ describe('content', function () { }) }); - it('should ask a question and use store value to answer:', function (done) { + it('should ask a question and use store value to answer:', function(done) { app.question('a', 'b'); app.store.set('a', 'c'); - app.ask('a', function (err, answer) { + app.ask('a', function(err, answer) { assert(!err); assert(answer); assert(answer === 'c'); @@ -44,11 +44,11 @@ describe('content', function () { }) }); - it('should ask a question and use config value to answer:', function (done) { + it('should ask a question and use config value to answer:', function(done) { app.question('a', 'b'); app.store.set('a', 'c'); - app.ask('a', function (err, answer) { + app.ask('a', function(err, answer) { assert(!err); assert(answer); assert(answer === 'c'); diff --git a/test/renameKey.js b/test/renameKey.js index 9662f03..94b3ab2 100644 --- a/test/renameKey.js +++ b/test/renameKey.js @@ -7,16 +7,16 @@ function renameKey(key) { return path.basename(key, path.extname(key)); } -describe('renameKey', function () { - beforeEach(function () { +describe('renameKey', function() { + beforeEach(function() { app = new App(); app.engine('tmpl', require('engine-base')); app.create('pages'); app.create('posts'); }); - describe('global options:', function () { - it('should use `renameKey` function defined on global opts:', function () { + describe('global options:', function() { + it('should use `renameKey` function defined on global opts:', function() { app.option('renameKey', renameKey); app.posts('a/b/c/a.txt', {content: '...'}); @@ -32,7 +32,7 @@ describe('renameKey', function () { app.views.posts.should.have.property('e'); }); - it('should not have conflicts when view name is the collection name:', function () { + it('should not have conflicts when view name is the collection name:', function() { app.option('renameKey', renameKey); app.post('a/b/c/post.txt', {content: 'this is contents'}); @@ -43,10 +43,10 @@ describe('renameKey', function () { }); }); - describe('create method:', function () { - it('should use `renameKey` option chained from the `create` method:', function () { + describe('create method:', function() { + it('should use `renameKey` option chained from the `create` method:', function() { app.create('post') - .option('renameKey', function (key) { + .option('renameKey', function(key) { return 'posts/' + path.basename(key); }); @@ -64,10 +64,10 @@ describe('renameKey', function () { }); }); - describe('create method:', function () { - it('should use `renameKey` defined on the `create` method:', function () { + describe('create method:', function() { + it('should use `renameKey` defined on the `create` method:', function() { app.create('post', { - renameKey: function (key) { + renameKey: function(key) { return 'posts/' + path.basename(key); } }); @@ -86,10 +86,10 @@ describe('renameKey', function () { }); }); - describe('collections:', function () { - describe('setting:', function () { - it('should get a view with the `renameKey` defined on app.options:', function () { - app.option('renameKey', function (key) { + describe('collections:', function() { + describe('setting:', function() { + it('should get a view with the `renameKey` defined on app.options:', function() { + app.option('renameKey', function(key) { return 'foo/' + path.basename(key); }); @@ -102,12 +102,12 @@ describe('renameKey', function () { app.views.posts.should.have.property('foo/c.txt'); }); - it('should use `renameKey` defined on collection.options:', function () { - app.pages.option('renameKey', function (key) { + it('should use `renameKey` defined on collection.options:', function() { + app.pages.option('renameKey', function(key) { return 'page/' + path.basename(key); }); - app.posts.option('renameKey', function (key) { + app.posts.option('renameKey', function(key) { return 'post/' + path.basename(key); }); @@ -136,8 +136,8 @@ describe('renameKey', function () { app.views.posts.should.have.property('post/e.txt'); }); - it('should use the `collection.renameKey()` method:', function () { - app.pages.renameKey(function (key) { + it('should use the `collection.renameKey()` method:', function() { + app.pages.renameKey(function(key) { return 'baz/' + path.basename(key); }); @@ -154,8 +154,8 @@ describe('renameKey', function () { app.views.pages.should.have.property('baz/e.txt'); }); - it('should use the `app.renameKey()` method:', function () { - app.renameKey(function (key) { + it('should use the `app.renameKey()` method:', function() { + app.renameKey(function(key) { return 'app/' + path.basename(key); }); @@ -172,7 +172,7 @@ describe('renameKey', function () { app.views.pages.should.have.property('app/e.txt'); }); - it('should prefer collection method over app.options:', function () { + it('should prefer collection method over app.options:', function() { // this works when you switch the order around... app.pages.renameKey(function pagesRenameKey(key) { return 'aaa/' + path.basename(key); @@ -194,11 +194,11 @@ describe('renameKey', function () { app.views.pages.should.have.property('aaa/e.txt'); }); - it('should prefer collection method over app method:', function () { - app.pages.renameKey(function (key) { + it('should prefer collection method over app method:', function() { + app.pages.renameKey(function(key) { return 'aaa/' + path.basename(key); }); - app.renameKey(function (key) { + app.renameKey(function(key) { return 'zzz/' + path.basename(key); }); @@ -215,11 +215,11 @@ describe('renameKey', function () { app.views.pages.should.have.property('aaa/e.txt'); }); - it('should prefer collection options over app.options:', function () { - app.pages.option('renameKey', function (key) { + it('should prefer collection options over app.options:', function() { + app.pages.option('renameKey', function(key) { return 'collection/' + path.basename(key); }); - app.option('renameKey', function (key) { + app.option('renameKey', function(key) { return 'app/' + path.basename(key); }); @@ -236,11 +236,11 @@ describe('renameKey', function () { app.views.pages.should.have.property('collection/e.txt'); }); - it('should prefer collection options over app method:', function () { - app.pages.option('renameKey', function (key) { + it('should prefer collection options over app method:', function() { + app.pages.option('renameKey', function(key) { return 'collection/' + path.basename(key); }); - app.renameKey(function (key) { + app.renameKey(function(key) { return 'app/' + path.basename(key); }); @@ -257,7 +257,7 @@ describe('renameKey', function () { app.views.pages.should.have.property('collection/e.txt'); }); - it('should use renameKey on chained methods:', function () { + it('should use renameKey on chained methods:', function() { app.page('test/fixtures/pages/a.txt', { options: { renameKey: function foo(key) { @@ -281,15 +281,15 @@ describe('renameKey', function () { }); }); - describe('getting', function () { - beforeEach(function () { + describe('getting', function() { + beforeEach(function() { app = new App(); app.engine('tmpl', require('engine-base')); app.create('post'); app.create('page'); }); - it('should get a view with the `renameKey` defined on the `create` method:', function () { + it('should get a view with the `renameKey` defined on the `create` method:', function() { app.create('post', { renameKey: function createRenameKey(key) { return 'posts/' + path.basename(key); @@ -304,8 +304,8 @@ describe('renameKey', function () { app.posts.getView('posts/a.txt').should.have.property('path', 'a/b/c/a.txt'); }); - it('should get a view with `renameKey` on collection.options:', function () { - app.pages.option('renameKey', function (key) { + it('should get a view with `renameKey` on collection.options:', function() { + app.pages.option('renameKey', function(key) { return 'bar/' + path.basename(key); }); @@ -318,8 +318,8 @@ describe('renameKey', function () { app.views.pages.should.have.property('bar/c.txt'); }); - it('should get a view with the the `app.renameKey()` method:', function () { - app.renameKey(function (key) { + it('should get a view with the the `app.renameKey()` method:', function() { + app.renameKey(function(key) { return 'baz/' + path.basename(key); }); @@ -332,8 +332,8 @@ describe('renameKey', function () { app.views.pages.should.have.property('baz/c.txt'); }); - it('should get a view with the the `collection.renameKey()` method:', function () { - app.pages.renameKey(function (key) { + it('should get a view with the the `collection.renameKey()` method:', function() { + app.pages.renameKey(function(key) { return 'baz/' + path.basename(key); }); diff --git a/test/render.js b/test/render.js index e097fcd..04540b6 100644 --- a/test/render.js +++ b/test/render.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var assert = require('assert'); @@ -5,65 +7,65 @@ var support = require('./support'); var App = support.resolve(); var app; -describe('render', function () { - describe('engine', function () { +describe.skip('render', function() { + describe('engine', function() { var view; - beforeEach(function () { + beforeEach(function() { app = new App({silent: true}); app.engine('tmpl', require('engine-base')); app.create('page'); view = {contents: new Buffer('a <%= name %> b'), locals: {name: 'Halle'}}; }); - it('should render a view from an object:', function (done) { + it('should render a view from an object:', function(done) { app.page('a.tmpl', view) - .render(function (err, res) { + .render(function(err, res) { if (err) return done(err); assert(res.contents.toString() === 'a Halle b'); done(); }); }); - it('should throw an error when a variable is undefined:', function (done) { - view = {contents: new Buffer('a <%= foo %> b')}; + it('should throw an error when a variable is undefined:', function(done) { + delete view.locals.name; app.page('a.tmpl', view) - .render(function (err) { - assert(err.message === 'foo is not defined'); + .render(function(err) { + assert(err.message === 'name is not defined'); done(); }); }); - it('should re-throw an error when rethrow is true:', function (done) { - view = {contents: new Buffer('a <%= foo %> b')}; + it('should re-throw an error when rethrow is true:', function(done) { + delete view.locals.name; app = new App({rethrow: true, silent: true}); app.engine('tmpl', require('engine-base')); app.create('page'); app.page('a.tmpl', view) - .render(function (err) { - assert(err.message === 'foo is not defined'); + .render(function(err) { + assert(err.message === 'name is not defined'); done(); }); }); - it('should emit a re-thrown error when rethrow is true:', function (done) { - view = {contents: new Buffer('a <%= foo %> b')}; + it('should emit a re-thrown error when rethrow is true:', function(done) { + delete view.locals.name; app = new App({rethrow: true, silent: false}); app.engine('tmpl', require('engine-base')); app.create('page'); app.on('error', function(err) { - assert(err.message === 'foo is not defined'); + assert(err.message === 'name is not defined'); done(); }); app.page('a.tmpl', view) - .render(function (err) { - assert(err.message === 'foo is not defined'); + .render(function(err) { + assert(err.message === 'name is not defined'); }); }); }); diff --git a/test/routes.js b/test/routes.js index fed137f..af66b93 100644 --- a/test/routes.js +++ b/test/routes.js @@ -19,14 +19,14 @@ function prepend(str) { }; } -describe('routes', function () { - beforeEach(function () { +describe('routes', function() { + beforeEach(function() { app = new App(); app.engine('tmpl', require('engine-base')); app.create('page'); }); - describe('params', function () { + describe('params', function() { it('should call param function when routing', function(done) { app.param('id', function(view, next, id) { assert.equal(id, '123'); @@ -42,8 +42,8 @@ describe('routes', function () { }); }); - describe('onLoad middleware', function () { - it('should run when templates are loaded:', function () { + describe('onLoad middleware', function() { + it('should run when templates are loaded:', function() { app.onLoad(/\.tmpl/, prepend('onLoad')); app.pages('a.tmpl', { path: 'a.tmpl', content: '<%= name %>'}); @@ -52,27 +52,27 @@ describe('routes', function () { }); }); - describe('preCompile middleware', function () { - it('should run before templates are compiled:', function () { + describe('preCompile middleware', function() { + it('should run before templates are compiled:', function() { }); }); - describe('postCompile middleware', function () { - it('should run after templates are compiled:', function () { + describe('postCompile middleware', function() { + it('should run after templates are compiled:', function() { }); }); - describe('preRender middleware', function () { - it('should run before templates are rendered:', function (done) { + describe('preRender middleware', function() { + it('should run before templates are rendered:', function(done) { app.preRender(/\.tmpl/, prepend('preRender')); app.pages('a.tmpl', { path: 'a.tmpl', content: '<%= name %>', locals: {name: 'aaa'} }); var page = app.pages.getView('a.tmpl'); page.contents.toString().should.equal('<%= name %>'); - page.render({}, function (err, res) { + page.render({}, function(err, res) { if (err) return done(err); res.contents.toString().should.equal('preRender aaa'); done(); @@ -80,15 +80,15 @@ describe('routes', function () { }); }); - describe('postRender middleware', function () { - it('should run after templates are rendered:', function (done) { + describe('postRender middleware', function() { + it('should run after templates are rendered:', function(done) { app.postRender(/\.tmpl/, append('postRender')); app.pages('a.tmpl', { path: 'a.tmpl', content: '<%= name %>', locals: {name: 'aaa' }}); var page = app.pages.getView('a.tmpl'); page.contents.toString().should.equal('<%= name %>'); - page.render({}, function (err, res) { + page.render({}, function(err, res) { if (err) return done(err); res.contents.toString().should.equal('aaa postRender'); done(); diff --git a/test/store.js b/test/store.js index de4295f..e75ec6e 100644 --- a/test/store.js +++ b/test/store.js @@ -4,67 +4,60 @@ require('mocha'); require('should'); var fs = require('fs'); var path = require('path'); +var Store = require('data-store'); var assert = require('assert'); -var store = require('base-store'); -var support = require('./support'); -var update = support.resolve(); +var App = require('../'); var app; -describe('store', function () { - beforeEach(function () { - app = update(); - app.use(store('update-tests')); +describe('store', function() { + beforeEach(function() { + app = new App(); }); - afterEach(function () { - app.store.data = {}; + afterEach(function(cb) { app.store.del({force: true}); + app.store.data = {}; + cb(); }); - it('should create a store with the given `name`', function () { - app.use(store('foo-bar-baz')); - assert(app.store.name === 'foo-bar-baz'); - }); - - it('should create a store at the given `cwd`', function () { - var cwd = path.resolve(__dirname, 'actual'); - app.use(store('abc', {cwd: cwd})); + it('should create a store at the given `cwd`', function() { + app = new App({store: {cwd: __dirname + '/actual'}}); app.store.set('foo', 'bar'); - path.basename(app.store.path).should.equal('abc.json'); - app.store.data.should.have.property('foo', 'bar'); - assert.equal(fs.existsSync(path.join(cwd, 'abc.json')), true); + assert(path.basename(app.store.path) === 'update.json'); + assert(app.store.data.hasOwnProperty('foo')); + assert(app.store.data.foo === 'bar'); + assert(fs.existsSync(path.join(__dirname, 'actual', 'update.json'))); }); - it('should create a store using the given `indent` value', function () { - var cwd = path.resolve(__dirname, 'actual'); - app.use(store('abc', {cwd: cwd, indent: 0})); + it('should create a store using the given `indent` value', function() { + app = new App({store: {cwd: __dirname + '/actual', indent: 0}}); app.store.set('foo', 'bar'); - var contents = fs.readFileSync(path.resolve(cwd, 'abc.json'), 'utf8'); - assert.equal(contents, '{"foo":"bar"}'); + var contents = fs.readFileSync(path.join(__dirname, 'actual', 'update.json'), 'utf8'); + assert(contents === '{"foo":"bar"}'); }); - it('should `.set()` a value on the store', function () { + it('should set a value on the store', function() { app.store.set('one', 'two'); app.store.data.one.should.equal('two'); }); - it('should `.set()` an object', function () { + it('should set an object', function() { app.store.set({four: 'five', six: 'seven'}); app.store.data.four.should.equal('five'); app.store.data.six.should.equal('seven'); }); - it('should `.set()` a nested value', function () { + it('should set a nested value', function() { app.store.set('a.b.c.d', {e: 'f'}); app.store.data.a.b.c.d.e.should.equal('f'); }); - it('should `.union()` a value on the store', function () { + it('should union a value onto an array on the store', function() { app.store.union('one', 'two'); app.store.data.one.should.eql(['two']); }); - it('should not union duplicate values', function () { + it('should not union duplicate values', function() { app.store.union('one', 'two'); app.store.data.one.should.eql(['two']); @@ -72,7 +65,7 @@ describe('store', function () { app.store.data.one.should.eql(['two']); }); - it('should concat an existing array:', function () { + it('should concat an existing array:', function() { app.store.union('one', 'a'); app.store.data.one.should.eql(['a']); @@ -83,85 +76,85 @@ describe('store', function () { app.store.data.one.should.eql(['a', 'b', 'c', 'd']); }); - it('should return true if a key `.has()` on the store', function () { + it('should return true if a key exists on the store', function() { app.store.set('foo', 'bar'); + assert(app.store.has('foo')); + }); + + it('should return true when the value is null', function() { app.store.set('baz', null); - app.store.set('qux', undefined); + assert(app.store.has('baz')); + }); - app.store.has('foo').should.eql(true); - app.store.has('bar').should.eql(false); - app.store.has('baz').should.eql(false); - app.store.has('qux').should.eql(false); + it('should return false when the value is undefined', function() { + app.store.set('qux', undefined); + assert(!app.store.has('qux')); }); - it('should return true if a nested key `.has()` on the store', function () { + it('should return true if a nested key exists on the store', function() { app.store.set('a.b.c.d', {x: 'zzz'}); app.store.set('a.b.c.e', {f: null}); app.store.set('a.b.g.j', {k: undefined}); - app.store.has('a.b.bar').should.eql(false); - app.store.has('a.b.c.d').should.eql(true); - app.store.has('a.b.c.d.x').should.eql(true); - app.store.has('a.b.c.d.z').should.eql(false); - app.store.has('a.b.c.e').should.eql(true); - app.store.has('a.b.c.e.f').should.eql(false); - app.store.has('a.b.c.e.z').should.eql(false); - app.store.has('a.b.g.j').should.eql(true); - app.store.has('a.b.g.j.k').should.eql(false); - app.store.has('a.b.g.j.z').should.eql(false); + assert(!app.store.has('a.b.bar')); + assert(app.store.has('a.b.c.d')); + assert(app.store.has('a.b.c.d.x')); + assert(!app.store.has('a.b.c.d.z')); + assert(app.store.has('a.b.c.e')); + assert(app.store.has('a.b.c.e.f')); + assert(!app.store.has('a.b.c.e.z')); + assert(app.store.has('a.b.g.j')); + assert(!app.store.has('a.b.g.j.k')); + assert(!app.store.has('a.b.g.j.z')); }); - it('should return true if a key exists `.hasOwn()` on the store', function () { + it('should return true if a key exists `.hasOwn()` on the store', function() { app.store.set('foo', 'bar'); app.store.set('baz', null); app.store.set('qux', undefined); - app.store.hasOwn('foo').should.eql(true); - app.store.hasOwn('bar').should.eql(false); - app.store.hasOwn('baz').should.eql(true); - app.store.hasOwn('qux').should.eql(true); + assert(app.store.hasOwn('foo')); + assert(!app.store.hasOwn('bar')); + assert(app.store.hasOwn('baz')); + assert(app.store.hasOwn('qux')); }); - it('should return true if a nested key exists `.hasOwn()` on the store', function () { + it('should return true if a nested key exists `.hasOwn()` on the store', function() { app.store.set('a.b.c.d', {x: 'zzz'}); app.store.set('a.b.c.e', {f: null}); app.store.set('a.b.g.j', {k: undefined}); - app.store.hasOwn('a.b.bar').should.eql(false); - app.store.hasOwn('a.b.c.d').should.eql(true); - app.store.hasOwn('a.b.c.d.x').should.eql(true); - app.store.hasOwn('a.b.c.d.z').should.eql(false); - app.store.has('a.b.c.e.f').should.eql(false); - app.store.hasOwn('a.b.c.e.f').should.eql(true); - app.store.hasOwn('a.b.c.e.bar').should.eql(false); - app.store.has('a.b.g.j.k').should.eql(false); - app.store.hasOwn('a.b.g.j.k').should.eql(true); - app.store.hasOwn('a.b.g.j.foo').should.eql(false); + assert(app.store.hasOwn('a.b.c.d')); + assert(app.store.hasOwn('a.b.c.d.x')); + assert(app.store.has('a.b.c.e.f')); + assert(app.store.hasOwn('a.b.c.e.f')); + assert(app.store.hasOwn('a.b.g.j.k')); + + assert(!app.store.hasOwn('a.b.bar')); + assert(!app.store.hasOwn('a.b.c.d.z')); + assert(!app.store.hasOwn('a.b.c.e.bar')); + assert(!app.store.has('a.b.g.j.k')); + assert(!app.store.hasOwn('a.b.g.j.foo')); }); - it('should `.get()` a stored value', function () { + it('should `.get()` a stored value', function() { app.store.set('three', 'four'); app.store.get('three').should.equal('four'); }); - it('should `.get()` a nested value', function () { + it('should `.get()` a nested value', function() { app.store.set({a: {b: {c: 'd'}}}); app.store.get('a.b.c').should.equal('d'); }); - it('should `.del()` a stored value', function () { + it('should `.del()` a stored value', function() { app.store.set('a', 'b'); app.store.set('c', 'd'); - app.store.data.should.have.property('a'); - app.store.data.should.have.property('c'); - app.store.del('a'); - app.store.del('c'); - app.store.data.should.not.have.property('a'); - app.store.data.should.not.have.property('c'); + assert(!app.store.hasOwnProperty('a')); }); - it('should `.del()` multiple stored values', function () { + it('should `.del()` multiple stored values', function() { app.store.set('a', 'b'); app.store.set('c', 'd'); app.store.set('e', 'f'); @@ -170,76 +163,61 @@ describe('store', function () { }); }); -describe('events', function () { - beforeEach(function () { - app.use(store('abc')); +describe('events', function() { + beforeEach(function() { + app = new App(); + app.store = new Store('update-tests'); }); - afterEach(function () { - app.store.data = {}; + afterEach(function(cb) { app.store.del({force: true}); + cb(); }); - it('should emit `set` when an object is set:', function () { + it('should emit `set` when an object is set:', function() { var keys = []; - app.store.on('set', function (key) { + app.store.on('set', function(key) { keys.push(key); }); app.store.set({a: {b: {c: 'd'}}}); - keys.should.eql(['a']); + assert(keys[0] === 'a'); }); - it('should emit `set` when a key/value pair is set:', function () { + it('should emit `set` when a key/value pair is set:', function() { var keys = []; - - app.store.on('set', function (key) { + app.store.on('set', function(key) { keys.push(key); }); app.store.set('a', 'b'); - keys.should.eql(['a']); + assert(keys[0] === 'a'); }); - it('should emit `set` when an object value is set:', function () { + it('should emit `set` when an object value is set:', function() { var keys = []; - - app.store.on('set', function (key) { + app.store.on('set', function(key) { keys.push(key); }); app.store.set('a', {b: 'c'}); - keys.should.eql(['a']); + assert(keys[0] === 'a'); }); - it('should emit `set` when an array of objects is passed:', function (cb) { + it('should emit `set` when an array of objects is passed:', function() { var keys = []; - - app.store.on('set', function (key) { + app.store.on('set', function(key) { keys.push(key); }); app.store.set([{a: 'b'}, {c: 'd'}]); - keys.should.eql(['a', 'c']); - cb(); + assert(keys[0] === 'a'); + assert(keys[1] === 'c'); }); - it('should emit `has`:', function (cb) { - var keys = []; - - app.store.on('has', function (val) { - assert(val); - cb(); - }); - - app.store.set('a', 'b'); - app.store.has('a'); - }); - - it('should emit `del` when a value is delted:', function (cb) { - app.store.on('del', function (keys) { - keys.should.eql('a'); - assert(typeof app.store.get('a') === 'undefined'); + it('should emit `del` when a value is deleted:', function(cb) { + app.store.on('del', function(key) { + assert(key === 'a'); cb(); }); @@ -248,30 +226,18 @@ describe('events', function () { app.store.del('a'); }); - it('should emit deleted keys on `del`:', function (cb) { - var arr = []; - - app.store.on('del', function (key) { - arr.push(key); - assert(Object.keys(app.store.data).length === 0); + it('should emit deleted keys on `del`:', function(cb) { + app.store.once('del', function(key) { + console.log(key) + assert(key === 'a'); + cb(); }); app.store.set('a', 'b'); app.store.set('c', 'd'); app.store.set('e', 'f'); - + assert.deepEqual(Object.keys(app.store.data), ['a', 'c', 'e']); app.store.del({force: true}); - arr.should.eql(['a', 'c', 'e']); - cb(); - }); - - it('should throw an error if force is not passed', function () { - app.store.set('a', 'b'); - app.store.set('c', 'd'); - app.store.set('e', 'f'); - - (function () { - app.store.del(); - }).should.throw('options.force is required to delete the entire cache.'); + assert.deepEqual(Object.keys(app.store.data), []); }); }); diff --git a/test/support/ignore.js b/test/support/ignore.js index ef59903..7dcbb75 100644 --- a/test/support/ignore.js +++ b/test/support/ignore.js @@ -3,4 +3,4 @@ module.exports = [ 'removeEventListener', 'removeAllListeners', 'removeListener' -]; \ No newline at end of file +]; diff --git a/test/support/index.js b/test/support/index.js index 899c846..e2194a5 100644 --- a/test/support/index.js +++ b/test/support/index.js @@ -56,9 +56,9 @@ exports.resolve = function(filepath) { function tryResolve(name) { try { return require.resolve(name); - } catch(err) {} + } catch (err) {} try { return require.resolve(path.resolve(name)); - } catch(err) {} + } catch (err) {} } diff --git a/test/view.content.js b/test/view.content.js index ccd21f7..80ea113 100644 --- a/test/view.content.js +++ b/test/view.content.js @@ -6,16 +6,16 @@ var support = require('./support'); var App = support.resolve(); var app; -describe('content', function () { - beforeEach(function () { +describe('content', function() { + beforeEach(function() { app = new App(); app.create('page'); app.engine('tmpl', require('engine-base')); }); - it('should normalize the `content` property on a view to a string:', function (done) { + it('should normalize the `content` property on a view to a string:', function(done) { app.page('abc', {path: 'test/fixtures/templates/a.tmpl'}) - .set('read', function () { + .set('read', function() { this.contents = fs.readFileSync(this.path); return this; }); diff --git a/test/view.events.js b/test/view.events.js index eef1dcd..3956379 100644 --- a/test/view.events.js +++ b/test/view.events.js @@ -3,18 +3,18 @@ var support = require('./support'); var App = support.resolve(); var app; -describe('view.option()', function () { - beforeEach(function () { +describe('view.option()', function() { + beforeEach(function() { app = new App(); app.create('page'); }); - it('should emit events:', function () { + it('should emit events:', function() { app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}); var page = app.pages.getView('a.tmpl'); var events = []; - page.on('option', function (key) { + page.on('option', function(key) { events.push(key); }); diff --git a/test/view.js b/test/view.js index c83b651..92ddf3c 100644 --- a/test/view.js +++ b/test/view.js @@ -10,102 +10,105 @@ var App = support.resolve(); var View = App.View; var view; -describe('View', function () { - describe('instance', function () { - it('should create an instance of View:', function () { +describe('View', function() { + describe('instance', function() { + it('should create an instance of View:', function() { view = new View(); assert(view instanceof View); }); }); - describe('static methods', function () { - it('should expose `extend`:', function () { + describe('static methods', function() { + it('should expose `extend`:', function() { assert(typeof View.extend === 'function'); }); }); - describe('prototype methods', function () { - beforeEach(function () { + describe('prototype methods', function() { + beforeEach(function() { view = new View(); }); - it('should expose `set`:', function () { + it('should expose `set`:', function() { assert(typeof view.set === 'function'); }); - it('should expose `get`:', function () { + it('should expose `get`:', function() { assert(typeof view.get === 'function'); }); - it('should expose `del`:', function () { + it('should expose `del`:', function() { assert(typeof view.del === 'function'); }); - it('should expose `define`:', function () { + it('should expose `define`:', function() { assert(typeof view.define === 'function'); }); - it('should expose `visit`:', function () { + it('should expose `visit`:', function() { assert(typeof view.visit === 'function'); }); - it('should expose `compile`:', function () { + it('should expose `compile`:', function() { assert(typeof view.compile === 'function'); }); - it('should expose `render`:', function () { + it('should expose `render`:', function() { assert(typeof view.render === 'function'); }); + it('should expose `isType`:', function() { + assert(typeof view.isType === 'function'); + }); }); - describe('properties', function () { - it('should expose an `options` property', function () { + describe('properties', function() { + it('should expose an `options` property', function() { view = new View({}); assert.deepEqual(view.options, {}); assert(view.hasOwnProperty('options')); }); - it('should add `options` when passed on the constructor', function () { + it('should add `options` when passed on the constructor', function() { view = new View({options: {foo: 'bar'}}); assert(view.options.foo === 'bar'); }); - it('should expose a `data` property', function () { + it('should expose a `data` property', function() { view = new View({app: {}}); assert.deepEqual(view.data, {}); assert(view.hasOwnProperty('data')); }); - it('should add `data` when passed on the constructor', function () { + it('should add `data` when passed on the constructor', function() { view = new View({data: {foo: 'bar'}}); assert(view.data.foo === 'bar'); }); - it('should add `locals` when passed on the constructor', function () { + it('should add `locals` when passed on the constructor', function() { view = new View({locals: {foo: 'bar'}}); assert(view.locals.foo === 'bar'); }); }); - describe('set', function () { - it('should set properties on the object', function () { + describe('set', function() { + it('should set properties on the object', function() { view = new View(); view.set('foo', 'bar'); assert.equal(view.foo, 'bar'); }); }); - describe('get', function () { - it('should get properties from the object', function () { + describe('get', function() { + it('should get properties from the object', function() { view = new View(); view.set('foo', 'bar'); assert.equal(view.get('foo'), 'bar'); }); }); - describe('cwd', function () { - it('should get properties from the object', function () { + describe('cwd', function() { + it('should get properties from the object', function() { view = new View({cwd: 'test/fixtures'}); assert(view.cwd === 'test/fixtures'); }); }); - describe('clone', function () { - it('should clone the view:', function () { + describe('clone', function() { + it('should clone the view:', function() { view = new View({content: 'foo'}); view.set({path: 'foo/bar'}); view.set('options.one', 'two'); @@ -121,7 +124,7 @@ describe('View', function () { assert(view.get('options.three') === 'four'); }); - it('should deep clone the entire object', function () { + it('should deep clone the entire object', function() { view = new View({content: 'foo'}); view.set({path: 'foo/bar'}); view.set('options.one', 'two'); @@ -134,8 +137,8 @@ describe('View', function () { }); }); - describe('visit', function () { - it('should visit all properties on an object and call the specified method', function () { + describe('visit', function() { + it('should visit all properties on an object and call the specified method', function() { view = new View(); var obj = { foo: 'bar', @@ -148,7 +151,7 @@ describe('View', function () { assert.equal(view.get('baz'), 'bang'); }); - it('should visit all properties on all objects in an array and call the specified method', function () { + it('should visit all properties on all objects in an array and call the specified method', function() { view = new View(); var arr = [{foo: 'bar', bar: 'baz', baz: 'bang'}]; view.visit('set', arr); @@ -158,41 +161,41 @@ describe('View', function () { }); }); - describe('compile', function () { - it('should get view.layout from view.data.layout', function () { + describe('compile', function() { + it('should get view.layout from view.data.layout', function() { view = new View({path: 'foo', contents: 'a b c', data: {layout: 'default'}}); assert(view.layout === 'default'); }); - it('should get view.layout from view.options.layout', function () { + it('should get view.layout from view.options.layout', function() { view = new View({path: 'foo', contents: 'a b c', options: {layout: 'default'}}); assert(view.layout === 'default'); }); - it('should get view.layout from view.locals.layout', function () { + it('should get view.layout from view.locals.layout', function() { view = new View({path: 'foo', contents: 'a b c', locals: {layout: 'default'}}); assert(view.layout === 'default'); }); - it('should get view.layout from the view', function () { + it('should get view.layout from the view', function() { view = new View({path: 'foo', contents: 'a b c', layout: 'default'}); assert(view.layout === 'default'); }); - it('should add a compiled function to `view.fn`', function () { + it('should add a compiled function to `view.fn`', function() { view = new View({path: 'foo', contents: 'a <%= name %> z'}); view.compile(); assert(typeof view.fn === 'function'); }); - it('should render a compiled template', function (done) { + it('should render a compiled template', function(done) { view = new View({path: 'foo', contents: 'a <%= name %> z'}); view.compile(); - view.render({name: 'Halle'}, function (err, res) { + view.render({name: 'Halle'}, function(err, res) { if (err) return done(err); assert(res.contents.toString() === 'a Halle z'); done(); }); }); - it('should render `fn` using data passed on the constructor', function (done) { + it('should render `fn` using data passed on the constructor', function(done) { view = new View({ path: 'foo', contents: 'a <%= name %> z', @@ -202,7 +205,7 @@ describe('View', function () { }); view.compile(); - view.render(function (err, res) { + view.render(function(err, res) { if (err) return done(err); assert(res.contents.toString() === 'a Brooke z'); done(); @@ -210,17 +213,17 @@ describe('View', function () { }); }); - describe('render', function () { - it('should render a template', function (done) { + describe('render', function() { + it('should render a template', function(done) { view = new View({path: 'foo', contents: 'a <%= name %> z'}); - view.render({name: 'Halle'}, function (err, res) { + view.render({name: 'Halle'}, function(err, res) { if (err) return done(err); assert(res.contents.toString() === 'a Halle z'); done(); }); }); - it('should render fn using data passed on the constructor', function (done) { + it('should render fn using data passed on the constructor', function(done) { view = new View({ path: 'foo', contents: 'a <%= name %> z', @@ -229,20 +232,20 @@ describe('View', function () { } }); - view.render(function (err, res) { + view.render(function(err, res) { if (err) return done(err); assert(res.contents.toString() === 'a Brooke z'); done(); }); }); - it('should pass errors in the callback.', function (done) { + it('should pass errors in the callback.', function(done) { view = new View({ path: 'foo', contents: 'a <%= name %> z' }); - view.render(function (err) { + view.render(function(err) { assert(err.message === 'name is not defined'); done(); }); @@ -373,13 +376,13 @@ describe('View', function() { it('should return false when the contents are a Stream', function(done) { var val = new Stream(); var view = new View({contents: val}); - view.isBuffer().should.equal(false); + assert(!view.isBuffer()); done(); }); it('should return false when the contents are a null', function(done) { var view = new View({contents: null}); - view.isBuffer().should.equal(false); + assert(!view.isBuffer()); done(); }); }); @@ -388,7 +391,7 @@ describe('View', function() { it('should return false when the contents are a Buffer', function(done) { var val = new Buffer('test'); var view = new View({contents: val}); - view.isStream().should.equal(false); + assert(!view.isStream()); done(); }); @@ -401,7 +404,7 @@ describe('View', function() { it('should return false when the contents are a null', function(done) { var view = new View({contents: null}); - view.isStream().should.equal(false); + assert(!view.isStream()); done(); }); }); @@ -410,14 +413,14 @@ describe('View', function() { it('should return false when the contents are a Buffer', function(done) { var val = new Buffer('test'); var view = new View({contents: val}); - view.isNull().should.equal(false); + assert(!view.isNull()); done(); }); it('should return false when the contents are a Stream', function(done) { var val = new Stream(); var view = new View({contents: val}); - view.isNull().should.equal(false); + assert(!view.isNull()); done(); }); @@ -438,14 +441,14 @@ describe('View', function() { it('should return false when the contents are a Buffer', function(done) { var val = new Buffer('test'); var view = new View({contents: val, stat: fakeStat}); - view.isDirectory().should.equal(false); + assert(!view.isDirectory()); done(); }); it('should return false when the contents are a Stream', function(done) { var val = new Stream(); var view = new View({contents: val, stat: fakeStat}); - view.isDirectory().should.equal(false); + assert(!view.isDirectory()); done(); }); @@ -522,7 +525,9 @@ describe('View', function() { view2.path.should.equal(view.path); view2.contents.should.not.equal(view.contents, 'stream ref should not be the same'); view.contents.pipe(es.wait(function(err, data) { + if (err) return done(err); view2.contents.pipe(es.wait(function(err, data2) { + if (err) return done(err); data2.should.not.equal(data, 'stream contents ref should not be the same'); data2.should.eql(data, 'stream contents should be the same'); })); @@ -562,10 +567,6 @@ describe('View', function() { assert(copy.stat.isFile()); assert(!copy.stat.isDirectory()); - - assert(view.stat.hasOwnProperty('birthtime')); - assert(copy.stat.hasOwnProperty('birthtime')); - assert.deepEqual(view.stat, copy.stat); done(); }); @@ -912,11 +913,10 @@ describe('View', function() { }); it('should error on get when no base', function(done) { - var a; var view = new View(); delete view.base; try { - a = view.relative; + view.relative; } catch (err) { should.exist(err); done(); @@ -924,10 +924,9 @@ describe('View', function() { }); it('should error on get when no path', function(done) { - var a; var view = new View(); try { - a = view.relative; + view.relative; } catch (err) { should.exist(err); done(); @@ -949,17 +948,16 @@ describe('View', function() { cwd: '/', path: '/test/test.coffee' }); - view.relative.should.equal(path.join('test','test.coffee')); + view.relative.should.equal(path.join('test', 'test.coffee')); done(); }); }); describe('dirname get/set', function() { it('should error on get when no path', function(done) { - var a; var view = new View(); try { - a = view.dirname; + view.dirname; } catch (err) { should.exist(err); done(); @@ -1000,10 +998,9 @@ describe('View', function() { describe('basename get/set', function() { it('should error on get when no path', function(done) { - var a; var view = new View(); try { - a = view.basename; + view.basename; } catch (err) { should.exist(err); done(); @@ -1044,10 +1041,9 @@ describe('View', function() { describe('extname get/set', function() { it('should error on get when no path', function(done) { - var a; var view = new View(); try { - a = view.extname; + view.extname; } catch (err) { should.exist(err); done(); @@ -1087,7 +1083,6 @@ describe('View', function() { }); describe('path get/set', function() { - it('should record history when instantiation', function() { var view = new View({ cwd: '/', @@ -1132,7 +1127,7 @@ describe('View', function() { it('should throw when set path null in constructor', function() { (function() { - new View({ + View({ cwd: '/', path: null }); diff --git a/test/view.methods.js b/test/view.methods.js index bb120c6..08f064e 100644 --- a/test/view.methods.js +++ b/test/view.methods.js @@ -3,21 +3,20 @@ var support = require('./support'); var App = support.resolve(); var app; -describe('view.option()', function () { - beforeEach(function () { +describe('view.option()', function() { + beforeEach(function() { app = new App(); app.engine('tmpl', require('engine-base')); app.create('page'); }); - - describe('.use', function () { - it('should expose `.use` for running plugins on a view:', function () { + describe('.use', function() { + it('should expose `.use` for running plugins on a view:', function() { app.page('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}) - .use(function () { + .use(function() { this.options.foo = 'bar'; }) - .use(function () { + .use(function() { this.options.bar = 'baz'; }); @@ -27,10 +26,10 @@ describe('view.option()', function () { }); }); - describe('.render:', function () { - it('should expose `.render` for rendering a view:', function (done) { + describe('.render:', function() { + it('should expose `.render` for rendering a view:', function(done) { app.page('a.tmpl', {path: 'a.tmpl', content: '<%= a %>', locals: {a: 'bbb'}}) - .render({}, function (err, res) { + .render({}, function(err, res) { if (err) return done(err); res.contents.toString().should.equal('bbb'); done(); diff --git a/test/view.option.js b/test/view.option.js index 4190be7..087399c 100644 --- a/test/view.option.js +++ b/test/view.option.js @@ -3,13 +3,13 @@ var support = require('./support'); var App = support.resolve(); var app; -describe('view.option()', function () { - beforeEach(function () { +describe('view.option()', function() { + beforeEach(function() { app = new App(); app.create('page'); }); - it('should set an option:', function () { + it('should set an option:', function() { app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}); var page = app.pages.getView('a.tmpl'); @@ -18,7 +18,7 @@ describe('view.option()', function () { page.options.should.have.property('foo'); }); - it('should extend options:', function () { + it('should extend options:', function() { app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}); var page = app.pages.getView('a.tmpl'); page.option('a', 'b'); diff --git a/test/view.render.js b/test/view.render.js index 5fe5c7d..a7d6d71 100644 --- a/test/view.render.js +++ b/test/view.render.js @@ -2,48 +2,46 @@ require('mocha'); require('should'); var support = require('./support'); var App = support.resolve(); -var View = App.View; -var view, app; +var app; -describe('helpers', function () { - describe('rendering', function () { - beforeEach(function () { +describe('helpers', function() { + describe('rendering', function() { + beforeEach(function() { app = new App(); - view = new View(); app.engine('tmpl', require('engine-base')); app.create('layouts', {viewType: 'layout'}); app.create('pages'); }); - it('should expose `.render` for rendering a view:', function (done) { + it('should expose `.render` for rendering a view:', function(done) { app.page('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}) - .render({a: 'bbb'}, function (err, res) { + .render({a: 'bbb'}, function(err, res) { if (err) return done(err); res.content.should.equal('bbb'); done(); }); }); - it('should render a view with a layout', function (done) { + it('should render a view with a layout', function(done) { app.layout('default.tmpl', {content: 'a {% body %} b'}); app.page('a.tmpl', {content: '<%= title %>', layout: 'default.tmpl'}) - .render({title: 'zzz'}, function (err, res) { + .render({title: 'zzz'}, function(err, res) { if (err) return done(err); res.content.should.equal('a zzz b'); done(); }); }); - it('should render a view with a layout', function (done) { + it('should render a view with a layout', function(done) { app.layout('foo.tmpl', {content: 'a {% body %} a'}); app.layout('bar.tmpl', {content: 'b {% body %} b'}); app.pages('a.tmpl', {content: '<%= title %>'}); app.pages.getView('a.tmpl') - .option('resolveLayout', function () { + .option('resolveLayout', function() { return 'bar.tmpl'; }) - .render({title: 'zzz'}, function (err, res) { + .render({title: 'zzz'}, function(err, res) { if (err) return done(err); res.content.should.equal('b zzz b'); done(); diff --git a/test/view.set.js b/test/view.set.js index 180bcdc..018df6e 100644 --- a/test/view.set.js +++ b/test/view.set.js @@ -6,18 +6,16 @@ var support = require('./support'); var App = support.resolve(); var app; -describe('set', function () { - beforeEach(function () { +describe('set', function() { + beforeEach(function() { app = new App(); app.create('page'); app.engine('tmpl', require('engine-base')); - - app.cache.data = {}; }); - it('should set a property on a view:', function (done) { + it('should set a property on a view:', function(done) { app.page('abc', {path: 'test/fixtures/templates/a.tmpl'}) - .set('read', function () { + .set('read', function() { this.contents = fs.readFileSync(this.path); return this; }); @@ -26,8 +24,9 @@ describe('set', function () { app.views.pages.abc .read() .set('data.name', 'Brooke') - .render(function (err, res) { + .render(function(err, res) { if (err) return done(err); + assert(res.content === 'Brooke'); done(); }); diff --git a/test/view.use.js b/test/view.use.js index 3706c0b..2883424 100644 --- a/test/view.use.js +++ b/test/view.use.js @@ -6,50 +6,50 @@ var App = support.resolve(); var View = App.View; var view; -describe('view.use', function () { - beforeEach(function () { +describe('view.use', function() { + beforeEach(function() { view = new View(); }); - it('should expose the instance to `use`:', function (done) { - view.use(function (inst) { + it('should expose the instance to `use`:', function(done) { + view.use(function(inst) { assert(inst instanceof View); done(); }); }); - it('should be chainable:', function (done) { - view.use(function (inst) { - assert(inst instanceof View); - }) - .use(function (inst) { + it('should be chainable:', function(done) { + view.use(function(inst) { + assert(inst instanceof View); + }) + .use(function(inst) { assert(inst instanceof View); }) - .use(function (inst) { + .use(function(inst) { assert(inst instanceof View); done(); }); }); - it('should expose the view to a plugin:', function () { - view.use(function (view) { + it('should expose the view to a plugin:', function() { + view.use(function(view) { assert(view instanceof View); - view.foo = function (str) { + view.foo = function(str) { return str + ' ' + 'bar'; }; }); assert(view.foo('foo') === 'foo bar'); }); - it('should be chainable:', function () { + it('should be chainable:', function() { view - .use(function (view) { + .use(function(view) { view.a = 'aaa'; }) - .use(function (view) { + .use(function(view) { view.b = 'bbb'; }) - .use(function (view) { + .use(function(view) { view.c = 'ccc'; }); diff --git a/test/viewTypes.js b/test/viewTypes.js index 47da0a9..b96115e 100644 --- a/test/viewTypes.js +++ b/test/viewTypes.js @@ -3,14 +3,14 @@ var support = require('./support'); var App = support.resolve(); var app; -describe('viewType', function () { - describe('view types', function () { - beforeEach(function () { +describe('viewType', function() { + describe('view types', function() { + beforeEach(function() { app = new App(); app.engine('tmpl', require('engine-base')); }); - it('should add collection (plural) to the `viewTypes` object', function () { + it('should add collection (plural) to the `viewTypes` object', function() { app.viewTypes = []; // reset app.create('foo', {viewType: 'layout'}); app.create('bar', {viewType: 'layout'}); @@ -20,14 +20,33 @@ describe('viewType', function () { assert.deepEqual(app.viewTypes.renderable, [ 'bazs' ]); }); - it('should add collection to the given viewType', function () { + it('should add collection to the given viewType', function() { app.create('layout', {viewType: 'layout'}); assert(app.layouts.options.viewType[0] === 'layout'); }); - it('should add a collection to multiple viewTypes', function () { + it('should return true if a collection has the given viewType', function() { + app.create('layout', {viewType: 'layout'}); + assert(app.layouts.isType('layout')); + assert(!app.layouts.isType('partial')); + }); + + it('should add types to the collection', function() { + app.create('layout', {viewType: 'layout'}); + app.layouts.viewType('partial'); + assert(app.layouts.options.viewType[0] === 'layout'); + assert(app.layouts.options.viewType[1] === 'partial'); + }); + + it('should add a collection to multiple viewTypes', function() { app.create('foo', {viewType: ['layout', 'renderable']}); assert.deepEqual(app.foos.options.viewType, ['layout', 'renderable']); }); + + it('should expose viewType on `view`', function() { + app.create('foo', {viewType: ['layout', 'renderable']}); + var a = app.foo('a', {content: ''}); + assert.deepEqual(app.foos.options.viewType, a.options.viewType); + }); }); }); diff --git a/test/views.js b/test/views.js index 0a25513..f3f9c21 100644 --- a/test/views.js +++ b/test/views.js @@ -3,34 +3,34 @@ require('should'); var path = require('path'); var assert = require('assert'); var typeOf = require('kind-of'); -var isBuffer = require('is-buffer'); var support = require('./support'); +var isBuffer = require('is-buffer'); var App = support.resolve(); var List = App.List; var View = App.View; var Views = App.Views; var collection; -describe('views', function () { - describe('constructor', function () { - it('should create an instance of Views:', function () { +describe('views', function() { + describe('constructor', function() { + it('should create an instance of Views:', function() { var collection = new Views(); assert(collection instanceof Views); }); - it('should instantiate without `new`:', function () { + it('should instantiate without `new`:', function() { var collection = Views(); assert(collection instanceof Views); }); }); - describe('static methods', function () { - it('should expose `extend`:', function () { - assert(typeof Views.extend ==='function'); + describe('static methods', function() { + it('should expose `extend`:', function() { + assert(typeof Views.extend === 'function'); }); }); - describe('prototype methods', function () { + describe('prototype methods', function() { beforeEach(function() { collection = new Views(); }); @@ -56,42 +56,42 @@ describe('views', function () { 'hasListeners' ]; - methods.forEach(function (method) { - it('should expose ' + method + ' method', function () { + methods.forEach(function(method) { + it('should expose ' + method + ' method', function() { assert(typeof collection[method] === 'function'); }); }); - it('should expose isCollection property', function () { + it('should expose isCollection property', function() { assert(typeof collection.isCollection === 'boolean'); }); - it('should expose queue property', function () { + it('should expose queue property', function() { assert(Array.isArray(collection.queue)); }); - it('should expose views property', function () { + it('should expose views property', function() { assert(typeOf(collection.views) === 'object'); }); - it('should expose options property', function () { + it('should expose options property', function() { assert(typeOf(collection.options) === 'object'); }); }); - describe('instance', function () { + describe('instance', function() { beforeEach(function() { collection = new Views(); }); - it('should set a value on the instance:', function () { + it('should set a value on the instance:', function() { collection.set('a', 'b'); - assert(collection.a ==='b'); + assert(collection.a === 'b'); }); - it('should get a value from the instance:', function () { + it('should get a value from the instance:', function() { collection.set('a', 'b'); - assert(collection.get('a') ==='b'); + assert(collection.get('a') === 'b'); }); }); @@ -100,17 +100,17 @@ describe('views', function () { collection = new Views(); }); - it('should set a key/value pair on options:', function () { + it('should set a key/value pair on options:', function() { collection.option('a', 'b'); assert(collection.options.a === 'b'); }); - it('should set an object on options:', function () { + it('should set an object on options:', function() { collection.option({c: 'd'}); assert(collection.options.c === 'd'); }); - it('should get an option:', function () { + it('should get an option:', function() { collection.option({c: 'd'}); var c = collection.option('c'); assert(c === 'd'); @@ -122,13 +122,13 @@ describe('views', function () { collection = new Views(); }); - it('should throw an error when args are invalid:', function () { - (function () { + it('should throw an error when args are invalid:', function() { + (function() { collection.addView(function() {}); }).should.throw('expected value to be an object.'); }); - it('should add a view to `views`:', function () { + it('should add a view to `views`:', function() { collection.addView('foo'); collection.views.should.have.property('foo'); @@ -137,12 +137,12 @@ describe('views', function () { assert(isBuffer(collection.views.one.contents)); }); - it('should create an instance of `View`:', function () { + it('should create an instance of `View`:', function() { collection.addView('one', {content: '...'}); assert(collection.views.one instanceof collection.View); }); - it('should allow an `View` constructor to be passed:', function () { + it('should allow an `View` constructor to be passed:', function() { View.prototype.foo = function(key, value) { this[key] = value; }; @@ -152,7 +152,7 @@ describe('views', function () { assert(collection.views.one.bar === 'baz'); }); - it('should allow an instance of `View` to be passed:', function () { + it('should allow an instance of `View` to be passed:', function() { var collection = new Views({View: View}); var view = new View({content: '...'}); collection.addView('one', view); @@ -161,6 +161,27 @@ describe('views', function () { assert(isBuffer(collection.views.one.contents)); assert(collection.views.one.abc === 'xyz'); }); + + it('should expose the `isType` method on items', function() { + var collection = new Views({View: View}); + var view = new View({content: '...'}); + collection.setView('one', view); + + var one = collection.getView('one'); + assert(one.isType('renderable')); + }); + + it('should set viewTypes on a collection', function() { + var collection = new Views({View: View}); + collection.viewType(['partial']); + + var view = new View({content: '...'}); + collection.setView('one', view); + + var one = collection.getView('one'); + assert(!one.isType('renderable')); + assert(one.isType('partial')); + }); }); describe('addViews', function() { @@ -168,11 +189,11 @@ describe('views', function () { collection = new Views(); }); - it('should emit an error if a string glob pattern is passed', function (done) { + it('should emit an error if a string glob pattern is passed', function(done) { try { collection.addViews('*.js'); done(new Error('expected an error')); - } catch(err) { + } catch (err) { assert(err); assert(err.message); assert(/glob/.test(err.message)); @@ -180,11 +201,11 @@ describe('views', function () { } }); - it('should emit an error if an array glob pattern is passed', function (done) { + it('should emit an error if an array glob pattern is passed', function(done) { try { collection.addViews(['*.js']); done(new Error('expected an error')); - } catch(err) { + } catch (err) { assert(err); assert(err.message); assert(/glob/.test(err.message)); @@ -192,7 +213,7 @@ describe('views', function () { } }); - it('should add multiple views:', function () { + it('should add multiple views:', function() { collection.addViews({ one: {content: 'foo'}, two: {content: 'bar'} @@ -201,7 +222,7 @@ describe('views', function () { assert(isBuffer(collection.views.two.contents)); }); - it('should return the collection instance for chaining:', function () { + it('should return the collection instance for chaining:', function() { var views = collection.addViews({ one: {content: 'foo'}, two: {content: 'bar'} @@ -213,7 +234,7 @@ describe('views', function () { assert(view.content === 'foo'); }); - it('should create views from an instance of Views', function () { + it('should create views from an instance of Views', function() { collection.addViews({ one: {content: 'foo'}, two: {content: 'bar'} @@ -223,7 +244,7 @@ describe('views', function () { assert(isBuffer(pages.views.two.contents)); }); - it('should add an array of views:', function () { + it('should add an array of views:', function() { collection.addViews([ {path: 'one', content: 'foo'}, {path: 'two', content: 'bar'} @@ -238,7 +259,7 @@ describe('views', function () { collection = new Views(); }); - it('should return a single collection view from a key-value pair', function () { + it('should return a single collection view from a key-value pair', function() { var one = collection.view('one', {content: 'foo'}); var two = collection.view('two', {content: 'bar'}); @@ -248,7 +269,7 @@ describe('views', function () { assert(two.path === 'two'); }); - it('should return a single collection view from an object', function () { + it('should return a single collection view from an object', function() { var one = collection.view({path: 'one', content: 'foo'}); var two = collection.view({path: 'two', content: 'bar'}); @@ -264,11 +285,11 @@ describe('views', function () { collection = new Views(); }); - it('should emit an error if a string glob pattern is passed', function (done) { + it('should emit an error if a string glob pattern is passed', function(done) { try { collection.addList('*.js'); done(new Error('expected an error')); - } catch(err) { + } catch (err) { assert(err); assert(err.message); assert(/glob/.test(err.message)); @@ -276,11 +297,11 @@ describe('views', function () { } }); - it('should emit an error if an array glob pattern is passed', function (done) { + it('should emit an error if an array glob pattern is passed', function(done) { try { collection.addList(['*.js']); done(new Error('expected an error')); - } catch(err) { + } catch (err) { assert(err); assert(err.message); assert(/glob/.test(err.message)); @@ -288,7 +309,7 @@ describe('views', function () { } }); - it('should add a list of views:', function () { + it('should add a list of views:', function() { collection.addList([ {path: 'one', content: 'foo'}, {path: 'two', content: 'bar'} @@ -297,7 +318,7 @@ describe('views', function () { assert(isBuffer(collection.views.two.contents)); }); - it('should add a list from the constructor:', function () { + it('should add a list from the constructor:', function() { var list = new List([ {path: 'one', content: 'foo'}, {path: 'two', content: 'bar'} @@ -308,7 +329,7 @@ describe('views', function () { assert(isBuffer(collection.views.two.contents)); }); - it('should add list items from the constructor:', function () { + it('should add list items from the constructor:', function() { var list = new List([ {path: 'one', content: 'foo'}, {path: 'two', content: 'bar'} @@ -319,50 +340,50 @@ describe('views', function () { assert(isBuffer(collection.views.two.contents)); }); - it('should throw an error when list is not an array:', function () { + it('should throw an error when list is not an array:', function() { var views = new Views(); - (function () { + (function() { views.addList(); }).should.throw('expected list to be an array.'); - (function () { + (function() { views.addList({}); }).should.throw('expected list to be an array.'); - (function () { + (function() { views.addList('foo'); }).should.throw('expected list to be an array.'); }); - it('should load an array of items from an event:', function () { - var collection = new Views(); + it('should load an array of items from an event:', function() { + var pages = new Views(); - collection.on('addList', function(list) { + pages.on('addList', function(list) { while (list.length) { - collection.addView({path: list.pop()}); + pages.addView({path: list.pop()}); } this.loaded = true; }); - collection.addList(['a.txt', 'b.txt', 'c.txt']); - assert(collection.views.hasOwnProperty('a.txt')); - assert(collection.views['a.txt'].path === 'a.txt'); + pages.addList(['a.txt', 'b.txt', 'c.txt']); + assert(pages.views.hasOwnProperty('a.txt')); + assert(pages.views['a.txt'].path === 'a.txt'); }); - it('should load an array of items from the addList callback:', function () { + it('should load an array of items from the addList callback:', function() { var collection = new Views(); - collection.addList(['a.txt', 'b.txt', 'c.txt'], function (fp) { + collection.addList(['a.txt', 'b.txt', 'c.txt'], function(fp) { return {path: fp}; }); assert(collection.views.hasOwnProperty('a.txt')); assert(collection.views['a.txt'].path === 'a.txt'); }); - it('should load an object of views from an event:', function () { + it('should load an object of views from an event:', function() { var collection = new Views(); - collection.on('addViews', function (views) { + collection.on('addViews', function(views) { for (var key in views) { collection.addView('foo/' + key, views[key]); delete views[key]; @@ -379,10 +400,10 @@ describe('views', function () { assert(collection.views['foo/a'].path === 'a.txt'); }); - it('should signal `loaded` when finished:', function () { + it('should signal `loaded` when finished:', function() { var collection = new Views(); - collection.on('addViews', function (views) { + collection.on('addViews', function(views) { for (var key in views) { if (key === 'c') break; collection.addView('foo/' + key, views[key]); @@ -405,7 +426,7 @@ describe('views', function () { beforeEach(function() { collection = new Views(); }); - it('should get a view from `views`:', function () { + it('should get a view from `views`:', function() { collection.addView('one', {content: 'aaa'}); collection.addView('two', {content: 'zzz'}); assert(isBuffer(collection.views.one.contents)); @@ -420,7 +441,7 @@ describe('views', function () { collection = new Views(); }); - it('should get the number of views:', function () { + it('should get the number of views:', function() { collection.addView('one', {content: 'aaa'}); collection.addView('two', {content: 'zzz'}); assert(Object.keys(collection.views).length === 2); @@ -432,7 +453,7 @@ describe('options', function() { describe('options.renameKey', function() { beforeEach(function() { collection = new Views({ - renameKey: function (key) { + renameKey: function(key) { return path.basename(key); } }); @@ -443,26 +464,25 @@ describe('options', function() { assert(collection.views['d.hbs'].contents.toString() === 'foo bar baz'); }); - it('should get a view with the renamed key:', function () { + it('should get a view with the renamed key:', function() { collection.addView('a/b/c/d.hbs', {content: 'foo bar baz'}); assert(collection.getView('d.hbs').contents.toString() === 'foo bar baz'); }); - it('should get a view with the original key:', function () { + it('should get a view with the original key:', function() { collection.addView('a/b/c/d.hbs', {content: 'foo bar baz'}); assert(collection.getView('a/b/c/d.hbs').contents.toString() === 'foo bar baz'); }); }); }); - -describe('queue', function () { - beforeEach(function () { +describe('queue', function() { + beforeEach(function() { collection = new Views(); }); - it('should emit arguments on addView', function (done) { - collection.on('addView', function (args) { + it('should emit arguments on addView', function(done) { + collection.on('addView', function(args) { assert(args[0] === 'a'); assert(args[1] === 'b'); assert(args[2] === 'c'); @@ -474,7 +494,7 @@ describe('queue', function () { collection.addView('a', 'b', 'c', 'd', 'e'); }); - it('should expose the `queue` property for loading views', function () { + it('should expose the `queue` property for loading views', function() { collection.queue.push(collection.view('b', {path: 'b'})); collection.addView('a', {path: 'a'}); @@ -482,8 +502,8 @@ describe('queue', function () { assert(collection.views.hasOwnProperty('b')); }); - it('should load all views on the queue when addView is called', function () { - collection.on('addView', function (args) { + it('should load all views on the queue when addView is called', function() { + collection.on('addView', function(args) { var len = args.length; var last = args[len - 1]; if (typeof last === 'string') { @@ -502,4 +522,4 @@ describe('queue', function () { assert(collection.views.hasOwnProperty('c.html')); assert(collection.getView('c.html').content === 'ccc'); }); -}); \ No newline at end of file +}); diff --git a/test/views.use.js b/test/views.use.js index 09d47df..4329b31 100644 --- a/test/views.use.js +++ b/test/views.use.js @@ -7,33 +7,33 @@ var Views = App.Views; var View = App.View; var collection; -describe('views.use', function () { - beforeEach(function () { +describe('views.use', function() { + beforeEach(function() { collection = new Views(); }); - it('should expose the instance to `use`:', function (done) { - collection.use(function (inst) { + it('should expose the instance to `use`:', function(done) { + collection.use(function(inst) { assert(inst instanceof Views); done(); }); }); - it('should be chainable:', function (done) { - collection.use(function (inst) { - assert(inst instanceof Views); - }) - .use(function (inst) { + it('should be chainable:', function(done) { + collection.use(function(inst) { + assert(inst instanceof Views); + }) + .use(function(inst) { assert(inst instanceof Views); }) - .use(function (inst) { + .use(function(inst) { assert(inst instanceof Views); done(); }); }); - it('should expose the collection to a plugin:', function () { - collection.use(function (views) { + it('should expose the collection to a plugin:', function() { + collection.use(function(views) { assert(views instanceof Views); views.foo = views.addView.bind(views); }); @@ -42,17 +42,17 @@ describe('views.use', function () { assert(collection.views.hasOwnProperty('a')); }); - it('should expose collection when chained:', function () { + it('should expose collection when chained:', function() { collection - .use(function (views) { + .use(function(views) { assert(views instanceof Views); views.foo = views.addView.bind(views); }) - .use(function (views) { + .use(function(views) { assert(views instanceof Views); views.bar = views.addView.bind(views); }) - .use(function (views) { + .use(function(views) { assert(views instanceof Views); views.baz = views.addView.bind(views); }); @@ -68,18 +68,18 @@ describe('views.use', function () { assert(collection.views.hasOwnProperty('c')); }); - it('should work when a custom `View` constructor is passed:', function () { + it('should work when a custom `View` constructor is passed:', function() { collection = new Views({View: require('vinyl')}); collection - .use(function (views) { + .use(function(views) { assert(views instanceof Views); views.foo = views.addView.bind(views); }) - .use(function (views) { + .use(function(views) { assert(views instanceof Views); views.bar = views.addView.bind(views); }) - .use(function (views) { + .use(function(views) { assert(views instanceof Views); views.baz = views.addView.bind(views); }); @@ -95,11 +95,11 @@ describe('views.use', function () { assert(collection.views.hasOwnProperty('c')); }); - it('should pass to view `use` if a function is returned:', function () { - collection.use(function (views) { + it('should pass to view `use` if a function is returned:', function() { + collection.use(function(views) { assert(views instanceof Views); - return function (view) { + return function(view) { view.foo = views.addView.bind(views); assert(view instanceof View); }; @@ -116,28 +116,28 @@ describe('views.use', function () { assert(collection.views.hasOwnProperty('d')); }); - it('should be chainable when a view function is returned:', function () { + it('should be chainable when a view function is returned:', function() { collection - .use(function (views) { + .use(function(views) { assert(views instanceof Views); - return function (view) { + return function(view) { view.foo = views.addView.bind(views); assert(view instanceof View); }; }) - .use(function (views) { + .use(function(views) { assert(views instanceof Views); - return function (view) { + return function(view) { view.bar = views.addView.bind(views); assert(view instanceof View); }; }) - .use(function (views) { + .use(function(views) { assert(views instanceof Views); - return function (view) { + return function(view) { view.baz = views.addView.bind(views); assert(view instanceof View); }; From 10a18795f627d7f5850e26c165b68c8894c8aef0 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 4 Jan 2016 08:28:14 -0500 Subject: [PATCH 152/274] fix urls --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index bb3afd9..18da5c8 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "update", "description": "Easily keep anything in your project up-to-date by installing the updaters you want to use and running `update` in the command line! Update the copyright date, licence type, ensure that a project uses your latest eslint or jshint configuration, remove deprecated package.json fields, or anything you can think of!", "version": "0.4.1", - "homepage": "https://github.com/jonschlinkert/update", + "homepage": "https://github.com/update/update", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", "authors": [ "Brian Woodward (https://github.com/doowb)", @@ -12,9 +12,9 @@ "Brian Woodward (https://github.com/doowb)", "Jon Schlinkert (https://github.com/jonschlinkert)" ], - "repository": "jonschlinkert/update", + "repository": "update/update", "bugs": { - "url": "https://github.com/jonschlinkert/update/issues" + "url": "https://github.com/update/update/issues" }, "license": "MIT", "files": [ From f266f08c1edc5fd797d3c62898f252722c31329d Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Mon, 4 Jan 2016 08:28:34 -0500 Subject: [PATCH 153/274] clean up, start adding code comments and docs --- .verb.md | 25 ++++++++++- bin/update.js | 3 +- examples.js | 59 ------------------------- index.js | 88 ++++++++++++++------------------------ lib/_updaters/copyright.js | 9 ---- lib/_updaters/license.js | 46 -------------------- lib/context.js | 74 -------------------------------- lib/utils.js | 69 ++++++++---------------------- package.json | 9 +--- updatefile.js | 6 +-- 10 files changed, 80 insertions(+), 308 deletions(-) delete mode 100644 examples.js delete mode 100644 lib/_updaters/copyright.js delete mode 100644 lib/_updaters/license.js delete mode 100644 lib/context.js diff --git a/.verb.md b/.verb.md index c414fa3..fd5a913 100644 --- a/.verb.md +++ b/.verb.md @@ -8,10 +8,33 @@ {%= include("install-global") %} + +### Commands + +```sh +$ update [options] +``` + +**List updaters** + +Choose from a list of updaters and tasks to run: + ```sh -$ update +$ update list ``` +**Run a specific updater** + +The following would run updater `foo`: + +```sh +$ update foo + +# run updater "foo" with options +$ update foo --bar=baz +``` + + ### tasks ### plugins #### pipeline plugins diff --git a/bin/update.js b/bin/update.js index b59fb41..2160867 100755 --- a/bin/update.js +++ b/bin/update.js @@ -1,6 +1,7 @@ #!/usr/bin/env node var path = require('path'); +var gm = require('global-modules'); var Runner = require('../lib/runner/runner')(); var utils = require('../lib/utils'); var argv = require('minimist')(process.argv.slice(2), { @@ -25,7 +26,7 @@ if (argv.verbose) { }); } -runner.registerEach('update-*', {cwd: utils.gm}); +runner.registerEach('update-*', {cwd: gm}); runner.base.task('run', function(cb) { runner.run(cb); diff --git a/examples.js b/examples.js deleted file mode 100644 index 8c5de7b..0000000 --- a/examples.js +++ /dev/null @@ -1,59 +0,0 @@ -'use strict'; - -var path = require('path'); -var del = require('rimraf'); -var through = require('through2'); -var update = require('./'); -var app = update(); - -// app.config.get = function () { - -// }; -// console.log(app) - - -// app.config({ -// plugins: function (obj) { -// for (var key in obj) { -// var name = path.basename(key, path.extname(key)); -// var fn = require(path.resolve(key)); -// app.plugin(name, obj[key], fn); -// } -// } -// }); - - -// app.config('plugins', function (obj) { -// for (var key in obj) { -// var name = path.basename(key, path.extname(key)); -// var fn = require(path.resolve(key)); -// app.plugin(name, obj[key], fn); -// } -// }); - -// app.config.process(); - -// app.plugin('a', require('./lib/pipeline/a')()); -// app.plugin('b', require('./lib/pipeline/b')); -// app.plugin('c', require('./lib/pipeline/c')()); -// app.plugin(/foo/, require('./lib/pipeline/d')()); - - -// app.on('error', function(err) { -// console.log('Error in plugin:', err.plugin, err.message) -// }); - -// app.task('default', function (cb) { -// app.src('LICENSE') -// .pipe(through.obj(function (file, enc, next) { -// if (file.isNull()) return next(); -// next(null, file); -// })) -// .pipe(app.pipeline()) -// .pipe(app.dest('actual')) -// .on('finish', cb); -// }); - -// app.build('default', function(err) { -// if (err) return console.log(err); -// }); diff --git a/index.js b/index.js index 9c519c3..98c826d 100644 --- a/index.js +++ b/index.js @@ -7,19 +7,10 @@ 'use strict'; -/** - * module dependencies - */ - var path = require('path'); var minimist = require('minimist'); var expand = require('expand-args'); -var cli = require('base-cli'); -var store = require('base-store'); -var pipeline = require('base-pipeline'); -var loader = require('assemble-loader'); var Base = require('assemble-core'); - var config = require('./lib/config'); var locals = require('./lib/locals'); var utils = require('./lib/utils'); @@ -82,32 +73,42 @@ Update.prototype.initUpdate = function(base) { this.data(utils.pkg.sync(this.options.path)); config(this); - this.use(utils.runtimes({ - displayName: function (key) { - return base.name === key ? key : (base.name + ':' + key); - } - })) - this.use(locals('update')) - .use(store()) - .use(pipeline()) - .use(loader()) - .use(cli()) - + .use(utils.runtimes({ + displayName: function(key) { + return base.name === key ? key : (base.name + ':' + key); + } + })) + .use(utils.store()) + .use(utils.pipeline()) + .use(utils.loader()) + .use(utils.cli()) .use(utils.defaults()) .use(utils.opts()) var data = utils.get(this.cache.data, 'update'); - data = utils.extend({}, data, argv); - - this.config.process(data); + this.config.process(utils.extend({}, data, argv)); this.engine(['md', 'tmpl'], require('engine-base')); - this.onLoad(/\.(md|tmpl)$/, function (view, next) { + this.onLoad(/\.(md|tmpl)$/, function(view, next) { utils.matter.parse(view, next); }); }; +/** + * Returns a function for resolving filepaths from the given `directory` + * or from the user's current working directory if no directory + * is passed. + * + * ```js + * var cwd = update.cwd('foo'); + * var a = cwd('bar'); + * var b = cwd('baz'); + * ``` + * @param {String} `dir` + * @return {Function} + */ + Update.prototype.cwd = function(dir) { var cwd = dir || process.cwd(); return function() { @@ -117,38 +118,18 @@ Update.prototype.cwd = function(dir) { }; }; +/** + * Temporary logger method. + * TODO: add event logger + */ + Update.prototype.log = function() { + this.emit.bind(this, 'log').apply(this, arguments); if (this.enabled('verbose')) { console.log.apply(console, arguments); } }; -Update.prototype.flag = function(key) { - return this.get('argv.' + key); -}; - -Update.prototype.cmd = function(key) { - return utils.commands(this.argv)[key] || false; -}; - -Update.prototype.extendFile = function(file, config, opts) { - var parsed = utils.tryParse(file.content); - var obj = utils.extend({}, parsed, config); - var res = {}; - if (opts && opts.sort === true) { - var keys = Object.keys(obj).sort(); - var len = keys.length, i = -1; - while (++i < len) { - var key = keys[i]; - res[key] = obj[key]; - } - } else { - res = obj; - } - file.content = JSON.stringify(res, null, 2); - if (opts.newline) file.content += '\n'; -}; - /** * Register updater `name` with the given `update` * instance. @@ -181,13 +162,8 @@ Update.prototype.updater = function(name, app) { module.exports = Update; /** - * Expose `utils` + * Expose `utils` and package.json metadata */ module.exports.utils = utils; - -/** - * Expose package.json metadata - */ - module.exports.pkg = require('./package'); diff --git a/lib/_updaters/copyright.js b/lib/_updaters/copyright.js deleted file mode 100644 index 5768c9f..0000000 --- a/lib/_updaters/copyright.js +++ /dev/null @@ -1,9 +0,0 @@ - -var fs = require('fs'); -var pkg = require('load-pkg')(); -var copyright = require('update-copyright'); - -var str = fs.readFileSync('LICENSE', 'utf8'); -var updated = copyright(str, pkg); - -console.log(updated); diff --git a/lib/_updaters/license.js b/lib/_updaters/license.js deleted file mode 100644 index 56ce211..0000000 --- a/lib/_updaters/license.js +++ /dev/null @@ -1,46 +0,0 @@ - -var fs = require('fs'); -var del = require('delete'); -var writeFile = require('write'); -var green = require('ansi-green'); -var success = require('success-symbol'); -var cwd = require('cwd'); - -var banner = 'The MIT License (MIT)\n\n'; - -function addBanner(str) { - if (!/^The MIT License \(MIT\)/i.test(str)) { - str = banner + str; - } - return str; -} - -function update(filepath, cb) { - var fp = cwd(filepath); - - fs.readFile(fp, 'utf8', function(err, str) { - if (err) return cb(err); - - - del(filepath, function(err) { - if (err) return cb(err); - - writeFile('LICENSE', str, function(err) { - if (err) return cb(err); - - return cb(null, 'updated'); - }); - }); - }); -} - -update('LICENSE', function(err, res) { - if (err) { - return console.error(err); - } - var msg = ' LICENSE is already up to date.'; - if (res === 'updated') { - msg = ' updated LICENSE'; - } - console.log(green(success), msg); -}); diff --git a/lib/context.js b/lib/context.js deleted file mode 100644 index f50064c..0000000 --- a/lib/context.js +++ /dev/null @@ -1,74 +0,0 @@ -'use strict'; - -var utils = require('./utils'); - -/** - * Create an instance of `Settings`. - * - * @class `Settings` - * @api public - */ - -function Settings() { - this.settings = []; -} - -/** - * Add a setting to merge. - * - * ```js - * setting - * .addSettings({a: 'b', c: 'd'}) - * .addSettings({a: 'z'}) - * .merge(); - * - * //=> {a: 'z', c: 'd'} - * ``` - * - * @param {Object} `object` The object to merge into the setting - * @api public - */ - -Settings.prototype.addSettings = function(obj) { - if (Array.isArray(obj) || arguments.length > 1) { - var args = [].concat.apply([], arguments); - args.forEach(function(arg) { - this.settings.push(arg); - }.bind(this)); - } else { - this.settings.push(obj); - } - return this; -}; - -/** - * Calculate the setting, optionally passing a callback `fn` for sorting. - * _(Note that sorting must be done on levels, not on the setting names)_. - * - * ```js - * app.calculate(['a', 'b'], function(a, b) { - * return app.lvl[a] - app.lvl[a]; - * }); - * ``` - * - * @param {String|Array} `keys` Key, or array of keys for setting levels to include. - * @param {Function} `fn` Sort function for determining the order of merging. - * @api public - */ - -Settings.prototype.merge = function(obj) { - if (obj) this.addSettings.apply(this, arguments); - var setting = {}; - this.settings.forEach(function(ctx) { - utils.merge(setting, ctx); - }); - return setting; -}; - -/** - * Export `Settings` - * - * @type {Object} - */ - -module.exports = Settings; diff --git a/lib/utils.js b/lib/utils.js index c288293..122ee60 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -5,36 +5,36 @@ var path = require('path'); var pkg = require(path.resolve(__dirname, '../package')); /** - * Lazily required module dependencies + * Module dependencies */ var utils = require('lazy-cache')(require); var fn = require; require = utils; -require('for-own'); +require('assemble-loader', 'loader'); require('async'); +require('base-cli', 'cli'); +require('base-pipeline', 'pipeline'); +require('base-store', 'store'); +require('composer-runtimes', 'runtimes'); require('expand-args'); -require('load-pkg', 'pkg'); require('expand-object', 'expand'); -require('global-modules', 'gm'); -require('look-up', 'lookup'); +require('extend-shallow', 'extend'); +require('for-own'); +require('get-value', 'get'); +require('load-pkg', 'pkg'); +require('matched', 'glob'); +require('micromatch', 'mm'); require('object.omit', 'omit'); require('object.pick', 'pick'); -require('micromatch', 'mm'); -require('map-config', 'config'); -require('matched', 'glob'); -require('composer-runtimes', 'runtimes'); require('parser-front-matter', 'matter'); -require('question-cache', 'questions'); -require('stream-exhaust', 'exhaust'); -require('extend-shallow', 'extend'); -require('resolve-dir', 'resolve'); require('project-name', 'project'); -require('union-value', 'union'); +require('question-cache', 'questions'); require('set-value', 'set'); -require('get-value', 'get'); +require('stream-exhaust', 'exhaust'); require('through2', 'through'); +require('union-value', 'union'); require('success-symbol'); require('ansi-yellow', 'yellow'); @@ -43,16 +43,17 @@ require('ansi-gray', 'gray'); require('ansi-cyan', 'cyan'); require('ansi-red', 'red'); require('time-stamp', 'stamp'); - require = fn; +/** + * Logging utils + */ utils.timestamp = function(msg) { var time = '[' + utils.gray(utils.stamp('HH:mm:ss', new Date())) + ']'; return console.log(time, msg); }; - function Status(status) { status = status || {}; this.err = status.err || null; @@ -124,30 +125,6 @@ utils.exists = function(fp) { return fs.existsSync(fp); }; -/** - * Create a global path for the given value - */ - -utils.toGlobalPath = function(fp) { - return '@/' + path.basename(fp, path.extname(fp)); -}; - -/** - * Create a global path for the given value - */ - -utils.findGlobal = function(pattern) { - return utils.lookup(pattern, { cwd: utils.gm }); -}; - -/** - * Get the resolved path to an "updatefile.js" - */ - -utils.updatefile = function(dir) { - return path.join(dir, 'updatefile.js'); -}; - /** * Rename a filepath to the "nickname" of the project. * @@ -176,7 +153,7 @@ utils.matchFiles = function(pattern, options) { var res = []; while (++i < len) { var name = files[i]; - if (name === 'update-next') continue; + if (name === 'update') continue; var fp = path.join(options.cwd, name); if (isMatch(fp) || isMatch(name)) { res.push(fp); @@ -185,14 +162,6 @@ utils.matchFiles = function(pattern, options) { return res; }; -/** - * Create a global path for the given value - */ - -utils.matchGlobal = function(pattern, filename) { - return utils.matchFiles(pattern, {cwd: utils.gm}); -}; - /** * Resolve the correct updater module to instantiate. * If `update` exists in `node_modules` of the cwd, diff --git a/package.json b/package.json index 18da5c8..2771318 100644 --- a/package.json +++ b/package.json @@ -49,9 +49,7 @@ "base-pipeline": "^0.1.4", "base-store": "^0.3.2", "composer-runtimes": "^0.7.0", - "cwd": "^0.9.1", "define-property": "^0.2.5", - "delete": "^0.3.0", "engine-base": "^0.1.2", "expand-args": "^0.3.1", "expand-object": "^0.4.1", @@ -62,8 +60,6 @@ "global-modules": "^0.2.0", "lazy-cache": "^1.0.3", "load-pkg": "^3.0.1", - "look-up": "^0.8.2", - "map-config": "^0.3.0", "matched": "^0.4.1", "micromatch": "^2.3.7", "minimist": "^1.2.0", @@ -72,7 +68,6 @@ "parser-front-matter": "^1.3.0", "project-name": "^0.2.3", "question-cache": "^0.3.5", - "resolve-dir": "^0.1.0", "rimraf": "^2.5.0", "set-value": "^0.3.2", "sort-object-arrays": "^0.1.1", @@ -81,9 +76,7 @@ "through2": "^2.0.0", "time-stamp": "^0.1.3", "union-value": "^0.2.1", - "update-copyright": "^0.1.0", - "use": "^1.1.2", - "write": "^0.2.1" + "use": "^1.1.2" }, "devDependencies": { "buffer-equal": "^1.0.0", diff --git a/updatefile.js b/updatefile.js index 28fa62b..2126403 100644 --- a/updatefile.js +++ b/updatefile.js @@ -1,9 +1,7 @@ 'use strict'; /** - * temporary test code + * temporary noop */ -module.exports = function (app) { - app.task('lint', require('./lib/tasks/lint')); -}; +module.exports = function() {}; From cbcb054ad445a17202e5ec5c0d2684f24445e78f Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 9 Jan 2016 15:39:54 -0500 Subject: [PATCH 154/274] update engine error messages in tests --- test/app.collection.compile.js | 2 +- test/app.collection.render.js | 2 +- test/app.compile.js | 2 +- test/app.render.js | 2 +- test/collection.render.js | 2 +- test/helpers.js | 2 +- test/list.render.js | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/app.collection.compile.js b/test/app.collection.compile.js index 4d23e93..1ceed4e 100644 --- a/test/app.collection.compile.js +++ b/test/app.collection.compile.js @@ -15,7 +15,7 @@ describe('compile', function() { var page = views.getView('foo.bar'); (function() { views.compile(page); - }).should.throw('Views#compile cannot find engine: .bar'); + }).should.throw('Views#compile cannot find an engine for: .bar'); }); it('should compile a template:', function() { diff --git a/test/app.collection.render.js b/test/app.collection.render.js index 12f06f7..2d23152 100644 --- a/test/app.collection.render.js +++ b/test/app.collection.render.js @@ -29,7 +29,7 @@ describe('render', function() { var page = pages.getView('foo.bar'); app.pages.render(page, function(err) { - assert(err.message === 'Views#render cannot find engine: .bar'); + assert(err.message === 'Views#render cannot find an engine for: .bar'); done(); }); }); diff --git a/test/app.compile.js b/test/app.compile.js index d2ffe99..090676e 100644 --- a/test/app.compile.js +++ b/test/app.compile.js @@ -15,7 +15,7 @@ describe('compile', function() { var page = app.pages.getView('foo.bar'); (function() { app.compile(page); - }).should.throw('Templates#compile cannot find engine: .bar'); + }).should.throw('Templates#compile cannot find an engine for: .bar'); }); it('should compile a template:', function() { diff --git a/test/app.render.js b/test/app.render.js index ddcb352..98c194c 100644 --- a/test/app.render.js +++ b/test/app.render.js @@ -24,7 +24,7 @@ describe('render', function() { var page = app.pages.getView('foo.bar'); app.render(page, function(err) { - assert(err.message === 'Templates#render cannot find engine: .bar'); + assert(err.message === 'Templates#render cannot find an engine for: .bar'); done(); }); }); diff --git a/test/collection.render.js b/test/collection.render.js index 39b5895..896c805 100644 --- a/test/collection.render.js +++ b/test/collection.render.js @@ -26,7 +26,7 @@ describe('render', function() { var page = pages.getView('foo.bar'); pages.render(page, function(err) { - assert(err.message === 'Views#render cannot find engine: .bar'); + assert(err.message === 'Views#render cannot find an engine for: .bar'); done(); }); }); diff --git a/test/helpers.js b/test/helpers.js index eb5633d..b842ef2 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -3,7 +3,7 @@ require('mocha'); require('should'); var path = require('path'); -var Base = require('base-methods'); +var Base = require('base'); var assert = require('assert'); var consolidate = require('consolidate'); var handlebars = require('engine-handlebars'); diff --git a/test/list.render.js b/test/list.render.js index beffeea..7b226a8 100644 --- a/test/list.render.js +++ b/test/list.render.js @@ -25,7 +25,7 @@ describe('render', function() { var page = pages.getItem('foo.bar'); pages.render(page, function(err) { - assert(err.message === 'List#render cannot find engine: .bar'); + assert(err.message === 'List#render cannot find an engine for: .bar'); done(); }); }); From cda1e9d796d8263eca5792818b7247aa3bf64822 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 9 Jan 2016 15:40:09 -0500 Subject: [PATCH 155/274] rename `base-methods` to `base`, update deps --- index.js | 4 ++-- lib/runner/runner.js | 2 +- package.json | 17 +++++++++-------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/index.js b/index.js index 98c826d..1b941e0 100644 --- a/index.js +++ b/index.js @@ -8,9 +8,9 @@ 'use strict'; var path = require('path'); -var minimist = require('minimist'); -var expand = require('expand-args'); var Base = require('assemble-core'); +var expand = require('expand-args'); +var minimist = require('minimist'); var config = require('./lib/config'); var locals = require('./lib/locals'); var utils = require('./lib/utils'); diff --git a/lib/runner/runner.js b/lib/runner/runner.js index 2ae1405..7d458e7 100644 --- a/lib/runner/runner.js +++ b/lib/runner/runner.js @@ -1,9 +1,9 @@ 'use strict'; var path = require('path'); +var Base = require('base'); var option = require('base-options'); var store = require('base-store'); -var Base = require('base-methods'); var fns = require('../middleware'); var Updater = require('./updater'); var tasks = require('../tasks'); diff --git a/package.json b/package.json index 2771318..e3752be 100644 --- a/package.json +++ b/package.json @@ -41,10 +41,10 @@ "ansi-yellow": "^0.1.1", "assemble-core": "^0.8.0", "assemble-loader": "^0.2.6", - "async": "^1.5.1", + "async": "^1.5.2", + "base": "^0.6.3", "base-cli": "^0.4.0", "base-config": "^0.3.3", - "base-methods": "^0.6.2", "base-options": "^0.5.4", "base-pipeline": "^0.1.4", "base-store": "^0.3.2", @@ -58,6 +58,7 @@ "for-own": "^0.1.3", "get-value": "^2.0.2", "global-modules": "^0.2.0", + "gulp-eslint": "^1.1.1", "lazy-cache": "^1.0.3", "load-pkg": "^3.0.1", "matched": "^0.4.1", @@ -87,16 +88,15 @@ "event-stream": "^3.3.2", "graceful-fs": "^4.1.2", "gulp": "^3.9.0", - "gulp-eslint": "^1.1.1", - "gulp-format-md": "^0.1.4", + "gulp-format-md": "^0.1.5", "gulp-istanbul": "^0.10.3", "gulp-mocha": "^2.2.0", "is-buffer": "^1.1.1", "istanbul": "^0.4.1", "kind-of": "^3.0.2", - "mocha": "*", - "resolve-glob": "^0.1.7", - "should": "*", + "mocha": "^2.3.4", + "resolve-glob": "^0.1.8", + "should": "^8.0.2", "sinon": "^1.17.2", "swig": "^1.4.2", "vinyl": "^1.1.0" @@ -125,7 +125,8 @@ "boilerplate", "scaffold", "template", - "verb" + "verb", + "vinyl" ], "plugins": [ "gulp-format-md" From de0e1344b048505ed5e49c1b1618a86f98f999e4 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 9 Jan 2016 15:42:37 -0500 Subject: [PATCH 156/274] generate docs with verb --- .verb.md | 36 +++++++++++++++++++++++++----- README.md | 65 +++++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 81 insertions(+), 20 deletions(-) diff --git a/.verb.md b/.verb.md index fd5a913..fad0eb2 100644 --- a/.verb.md +++ b/.verb.md @@ -36,24 +36,39 @@ $ update foo --bar=baz ### tasks + +_(TODO)_ + ### plugins + +_(TODO)_ + #### pipeline plugins + +_(TODO)_ + #### instance plugins + +_(TODO)_ + ### middleware -A middleware function takes `file` and `next`. +A middleware is a function that exposes the following parameters: + +- `file`: **{Object}** [vinyl][] file object +- `next`: **{Function}** must be called to continue on to the next file. ```js function rename(file, next) { - file.path = 'foo/' + path.basename(file.path); + file.path = 'foo/' + file.path; next(); } -``` +// example usage: prefix all `.js` file paths with `foo/` +app.onLoad(/\.js/, rename); +``` -**Example** - -The `onStream` method is a custom [middleware](docs/middleware.md) handler that the `update` i +The `onStream` method is a custom [middleware](docs/middleware.md) handler that the `update` ```js app.onStream(/lib\//, rename); @@ -79,8 +94,17 @@ var update = require('{%= name %}'); ## Authoring ### Updaters + +_(TODO)_ + #### Tasks + +_(TODO)_ + #### Middleware + +_(TODO)_ + #### Plugins > Updater plugins follow the same signature as gulp plugins diff --git a/README.md b/README.md index 6dba885..9718a4c 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ -# update [![NPM version](https://img.shields.io/npm/v/update.svg)](https://www.npmjs.com/package/update) +# update [![NPM version](https://img.shields.io/npm/v/update.svg)](https://www.npmjs.com/package/update) [![Build Status](https://img.shields.io/travis/update/update.svg)](https://travis-ci.org/update/update) -[![Build Status](https://img.shields.io/travis/jonschlinkert/update.svg)](https://travis-ci.org/jonschlinkert/update) - -> Update +> Easily keep anything in your project up-to-date by installing the updaters you want to use and running `update` in the command line! Update the copyright date, licence type, ensure that a project uses your latest eslint or jshint configuration, remove deprecated package.json fields, or anything you can think of! ## CLI @@ -14,32 +12,65 @@ Install globally with [npm](https://www.npmjs.com/) $ npm i -g update ``` +### Commands + +```sh +$ update [options] +``` + +**List updaters** + +Choose from a list of updaters and tasks to run: + +```sh +$ update list +``` + +**Run a specific updater** + +The following would run updater `foo`: + ```sh -$ update +$ update foo + +# run updater "foo" with options +$ update foo --bar=baz ``` ### tasks +_(TODO)_ + ### plugins +_(TODO)_ + #### pipeline plugins +_(TODO)_ + #### instance plugins +_(TODO)_ + ### middleware -A middleware function takes `file` and `next`. +A middleware is a function that exposes the following parameters: + +* `file`: **{Object}** [vinyl](http://github.com/gulpjs/vinyl) file object +* `next`: **{Function}** must be called to continue on to the next file. ```js function rename(file, next) { - file.path = 'foo/' + path.basename(file.path); + file.path = 'foo/' + file.path; next(); } -``` -**Example** +// example usage: prefix all `.js` file paths with `foo/` +app.onLoad(/\.js/, rename); +``` -The `onStream` method is a custom [middleware](docs/middleware.md) handler that the `update` i +The `onStream` method is a custom [middleware](docs/middleware.md) handler that the `update` ```js app.onStream(/lib\//, rename); @@ -61,7 +92,7 @@ var update = require('update'); ## API -### [Update](index.js#L40) +### [Update](index.js#L30) Create an `update` application. This is the main function exported by the update module. @@ -78,10 +109,10 @@ var update = new Update(); ## Related projects -* [assemble](https://www.npmjs.com/package/assemble): Static site generator for Grunt.js, Yeoman and Node.js. Used by Zurb Foundation, Zurb Ink, H5BP/Effeckt,… [more](https://www.npmjs.com/package/assemble) | [homepage](http://assemble.io) +* [assemble](https://www.npmjs.com/package/assemble): Assemble is a powerful, extendable and easy to use static site generator for node.js. Used… [more](https://www.npmjs.com/package/assemble) | [homepage](https://github.com/assemble/assemble) * [boilerplate](https://www.npmjs.com/package/boilerplate): Tools and conventions for authoring and publishing boilerplates that can be generated by any build… [more](https://www.npmjs.com/package/boilerplate) | [homepage](http://boilerplates.io) * [composer](https://www.npmjs.com/package/composer): API-first task runner with three methods: task, run and watch. | [homepage](https://github.com/jonschlinkert/composer) -* [generate](https://www.npmjs.com/package/generate): Fast, composable, highly extendable project generator for node.js | [homepage](https://github.com/jonschlinkert/generate) +* [generate](https://www.npmjs.com/package/generate): Fast, composable, highly extendable project generator with a user-friendly and expressive API. | [homepage](https://github.com/generate/generate) * [scaffold](https://www.npmjs.com/package/scaffold): Conventions and API for creating declarative configuration objects for project scaffolds - similar in format… [more](https://www.npmjs.com/package/scaffold) | [homepage](https://github.com/jonschlinkert/scaffold) * [templates](https://www.npmjs.com/package/templates): System for creating and managing template collections, and rendering templates with any node.js template engine.… [more](https://www.npmjs.com/package/templates) | [homepage](https://github.com/jonschlinkert/templates) * [update](https://www.npmjs.com/package/update): Update | [homepage](https://github.com/jonschlinkert/update) @@ -91,10 +122,16 @@ var update = new Update(); ### Updaters +_(TODO)_ + #### Tasks +_(TODO)_ + #### Middleware +_(TODO)_ + #### Plugins > Updater plugins follow the same signature as gulp plugins @@ -145,4 +182,4 @@ Released under the MIT license. *** -_This file was generated by [verb](https://github.com/verbose/verb) on January 04, 2016._ \ No newline at end of file +_This file was generated by [verb](https://github.com/verbose/verb) on January 09, 2016._ \ No newline at end of file From cf423446393d68666c9f591b8eee7458191cea45 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 9 Jan 2016 15:42:56 -0500 Subject: [PATCH 157/274] 0.4.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e3752be..c7db06e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "update", "description": "Easily keep anything in your project up-to-date by installing the updaters you want to use and running `update` in the command line! Update the copyright date, licence type, ensure that a project uses your latest eslint or jshint configuration, remove deprecated package.json fields, or anything you can think of!", - "version": "0.4.1", + "version": "0.4.2", "homepage": "https://github.com/update/update", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", "authors": [ From e0d48c8d6d6a30b9866cc326cbbbc627522fa23b Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 9 Jan 2016 17:44:43 -0500 Subject: [PATCH 158/274] start refactoring --- _index.js | 169 ++++++++ bin/cli.js | 168 ++++++++ bin/errors.js | 5 + bin/update.js | 38 -- index.js | 198 ++++----- lib/cli.js | 78 ++++ lib/commands/ask.js | 23 ++ lib/commands/help.js | 106 +++++ lib/commands/index.js | 1 + lib/commands/open.js | 26 ++ lib/commands/show.js | 32 ++ lib/config.js | 30 -- lib/generator.js | 5 + lib/locals.js | 26 -- lib/utils.js | 356 +++++------------ test/app.collection.js | 13 +- test/app.create.js | 6 +- test/app.list.js | 7 +- test/fixtures/a.txt | 1 + test/fixtures/b.txt | 1 + test/fixtures/c.txt | 1 + test/fixtures/copy/a.txt | 1 - test/fixtures/copy/b.txt | 1 - test/fixtures/copy/c.txt | 1 - test/fixtures/one/generator.js | 19 + test/fixtures/one/package.json | 30 ++ test/fixtures/one/templates/a.txt | 1 + test/fixtures/one/templates/x.txt | 1 + test/fixtures/one/templates/y.txt | 1 + test/fixtures/one/templates/z.txt | 1 + test/fixtures/three/four/five/generator.js | 14 + test/fixtures/three/four/five/package.json | 30 ++ test/fixtures/three/four/five/templates/a.txt | 0 test/fixtures/three/four/five/templates/b.txt | 0 test/fixtures/three/four/five/templates/c.txt | 0 test/fixtures/three/four/generator.js | 14 + test/fixtures/three/four/package.json | 30 ++ test/fixtures/three/four/templates/a.txt | 0 test/fixtures/three/four/templates/b.txt | 0 test/fixtures/three/four/templates/c.txt | 0 test/fixtures/three/generator.js | 14 + test/fixtures/three/package.json | 30 ++ test/fixtures/three/templates/a.txt | 0 test/fixtures/three/templates/b.txt | 0 test/fixtures/three/templates/c.txt | 0 test/fixtures/two/generate.js | 21 + test/fixtures/two/package.json | 30 ++ test/fixtures/two/templates/a.txt | 0 test/fixtures/two/templates/b.txt | 0 test/fixtures/two/templates/c.txt | 0 test/generate.compose.js | 75 ++++ test/generate.extendGenerator.js | 46 +++ test/generate.generator.js | 35 ++ test/generate.getGenerator.js | 45 +++ test/generate.js | 133 ++++++ test/generate.logger.js | 95 +++++ test/generate.process.js | 245 ++++++++++++ test/generate.register.js | 50 +++ test/generate.registerPath.js | 28 ++ test/generate.scaffold.js | 234 +++++++++++ test/generate.to-tasks.js | 13 + test/generate.utils.js | 239 +++++++++++ test/generator.task.js | 155 +++++++ test/generators.js | 20 + test/helpers.js | 2 +- test/render.js | 4 +- test/store.js | 9 +- test/support/ignore.js | 2 +- test/support/index.js | 19 +- test/to-tasks.js | 378 ++++++++++++++++++ 70 files changed, 2852 insertions(+), 504 deletions(-) create mode 100644 _index.js create mode 100755 bin/cli.js create mode 100644 bin/errors.js delete mode 100755 bin/update.js create mode 100644 lib/cli.js create mode 100644 lib/commands/ask.js create mode 100644 lib/commands/help.js create mode 100644 lib/commands/index.js create mode 100644 lib/commands/open.js create mode 100644 lib/commands/show.js delete mode 100644 lib/config.js create mode 100644 lib/generator.js delete mode 100644 lib/locals.js create mode 100644 test/fixtures/a.txt create mode 100644 test/fixtures/b.txt create mode 100644 test/fixtures/c.txt delete mode 100644 test/fixtures/copy/a.txt delete mode 100644 test/fixtures/copy/b.txt delete mode 100644 test/fixtures/copy/c.txt create mode 100644 test/fixtures/one/generator.js create mode 100644 test/fixtures/one/package.json create mode 100644 test/fixtures/one/templates/a.txt create mode 100644 test/fixtures/one/templates/x.txt create mode 100644 test/fixtures/one/templates/y.txt create mode 100644 test/fixtures/one/templates/z.txt create mode 100644 test/fixtures/three/four/five/generator.js create mode 100644 test/fixtures/three/four/five/package.json create mode 100644 test/fixtures/three/four/five/templates/a.txt create mode 100644 test/fixtures/three/four/five/templates/b.txt create mode 100644 test/fixtures/three/four/five/templates/c.txt create mode 100644 test/fixtures/three/four/generator.js create mode 100644 test/fixtures/three/four/package.json create mode 100644 test/fixtures/three/four/templates/a.txt create mode 100644 test/fixtures/three/four/templates/b.txt create mode 100644 test/fixtures/three/four/templates/c.txt create mode 100644 test/fixtures/three/generator.js create mode 100644 test/fixtures/three/package.json create mode 100644 test/fixtures/three/templates/a.txt create mode 100644 test/fixtures/three/templates/b.txt create mode 100644 test/fixtures/three/templates/c.txt create mode 100644 test/fixtures/two/generate.js create mode 100644 test/fixtures/two/package.json create mode 100644 test/fixtures/two/templates/a.txt create mode 100644 test/fixtures/two/templates/b.txt create mode 100644 test/fixtures/two/templates/c.txt create mode 100644 test/generate.compose.js create mode 100644 test/generate.extendGenerator.js create mode 100644 test/generate.generator.js create mode 100644 test/generate.getGenerator.js create mode 100644 test/generate.js create mode 100644 test/generate.logger.js create mode 100644 test/generate.process.js create mode 100644 test/generate.register.js create mode 100644 test/generate.registerPath.js create mode 100644 test/generate.scaffold.js create mode 100644 test/generate.to-tasks.js create mode 100644 test/generate.utils.js create mode 100644 test/generator.task.js create mode 100644 test/generators.js create mode 100644 test/to-tasks.js diff --git a/_index.js b/_index.js new file mode 100644 index 0000000..44ef9fb --- /dev/null +++ b/_index.js @@ -0,0 +1,169 @@ +/*! + * update + * + * Copyright (c) 2015, Jon Schlinkert. + * Licensed under the MIT License. + */ + +'use strict'; + +var path = require('path'); +var Generate = require('generate'); +var expand = require('expand-args'); +var minimist = require('minimist'); +var config = require('./lib/config'); +var locals = require('./lib/locals'); +var utils = require('./lib/utils'); + +/** + * Create an `update` application. This is the main function exported + * by the update module. + * + * ```js + * var Update = require('update'); + * var update = new Update(); + * ``` + * @param {Object} `options` + * @api public + */ + +function Update(options) { + if (!(this instanceof Update)) { + return new Update(options); + } + Base.call(this, options); + this.name = this.options.name || 'update'; + this.isUpdate = true; + this.initUpdate(this); +} + +/** + * Inherit assemble-core + */ + +Base.extend(Update); + +/** + * Initialize Updater defaults + */ + +Update.prototype.initUpdate = function(base) { + this.set('updaters', {}); + + // custom middleware handlers + this.handler('onStream'); + this.handler('preWrite'); + this.handler('postWrite'); + + // parse command line arguments + var argv = expand(minimist(process.argv.slice(2)), { + alias: {v: 'verbose'} + }); + + this.option('argv', argv); + + // expose `argv` on the instance + this.mixin('argv', function(prop) { + var args = [].slice.call(arguments); + args.unshift(argv); + return utils.get.apply(null, args); + }); + + // load the package.json for the updater + this.data(utils.pkg.sync(this.options.path)); + config(this); + + this.use(locals('update')) + .use(utils.runtimes({ + displayName: function(key) { + return base.name === key ? key : (base.name + ':' + key); + } + })) + .use(utils.store()) + .use(utils.pipeline()) + .use(utils.loader()) + .use(utils.cli()) + .use(utils.defaults()) + .use(utils.opts()) + + var data = utils.get(this.cache.data, 'update'); + this.config.process(utils.extend({}, data, argv)); + + this.engine(['md', 'tmpl'], require('engine-base')); + this.onLoad(/\.(md|tmpl)$/, function(view, next) { + utils.matter.parse(view, next); + }); +}; + +/** + * Returns a function for resolving filepaths from the given `directory` + * or from the user's current working directory if no directory + * is passed. + * + * ```js + * var cwd = update.cwd('foo'); + * var a = cwd('bar'); + * var b = cwd('baz'); + * ``` + * @param {String} `dir` + * @return {Function} + */ + +Update.prototype.cwd = function(dir) { + var cwd = dir || process.cwd(); + return function() { + var args = [].slice.call(arguments); + args.unshift(cwd); + return path.resolve.apply(null, args); + }; +}; + +/** + * Temporary logger method. + * TODO: add event logger + */ + +Update.prototype.log = function() { + this.emit.bind(this, 'log').apply(this, arguments); + if (this.enabled('verbose')) { + console.log.apply(console, arguments); + } +}; + +/** + * Register updater `name` with the given `update` + * instance. + * + * @param {String} `name` + * @param {Object} `update` Instance of update + * @return {Object} Returns the instance for chaining + */ + +Update.prototype.updater = function(name, app) { + if (arguments.length === 1 && typeof name === 'string') { + return this.updaters[name]; + } + + app.use(utils.runtimes({ + displayName: function(key) { + return app.name === key ? key : (app.name + ':' + key); + } + })); + + this.emit('updater', name, app); + this.updaters[name] = app; + return app; +}; + +/** + * Expose `Update` + */ + +module.exports = Update; + +/** + * Expose `utils` and package.json metadata + */ + +module.exports.utils = utils; +module.exports.pkg = require('./package'); diff --git a/bin/cli.js b/bin/cli.js new file mode 100755 index 0000000..48ed85a --- /dev/null +++ b/bin/cli.js @@ -0,0 +1,168 @@ +#!/usr/bin/env node + +var path = require('path'); +var utils = require('../lib/utils'); +var errors = require('./errors'); +var update = require('..'); +var Env = update.Env; + +var argv = require('minimist')(process.argv.slice(2), { + alias: { + help: 'h', + verbose: 'v' + } +}); + +function run(cb) { + var cwd = process.cwd(); + var root = cwd; + + /** + * Set the working directory + */ + + if (argv.cwd && cwd !== path.resolve(argv.cwd)) { + process.chdir(argv.cwd); + utils.timestamp('cwd changed to ' + utils.colors.yellow('~/' + argv.cwd)); + } + + /** + * Get the updatefile.js to use + */ + + var updatefile = path.resolve(process.cwd(), 'updatefile.js'); + + /** + * Notify the user if updatefile.js is not found + */ + + if (!utils.exists(updatefile)) { + cb('updatefile'); + return; + } + + /** + * Get the `update` instance to use + */ + + var app = require(updatefile); + if (typeof app === 'function') { + var fn = app; + app = update(); + app.option(argv); + app.fn = fn; + fn(app); + } + + app.generator('base', require('../lib/generator')); + + /** + * Create enviroment + */ + + app.env = createEnv('updatefile.js', process.cwd()); + + /** + * Process command line arguments + */ + + var args = utils.processArgv(app, argv); + app.set('argv', args); + + /** + * Show path to updatefile + */ + + var fp = utils.homeRelative(root, updatefile); + utils.timestamp('using updatefile ' + fp); + + /** + * Support `--emit` for debugging + * + * Example: + * $ --emit data + */ + + if (argv.emit && typeof argv.emit === 'string') { + app.on(argv.emit, console.error.bind(console)); + } + + /** + * Listen for generator configs, and register them + * as they're emitted + */ + + app.env.on('config', function(name, env) { + app.register(name, env.config.fn, env); + }); + + /** + * Resolve update generators + */ + + app.env + .resolve('updater-*/updatefile.js', { + configfile: 'updatefile.js', + cwd: utils.gm + }) + .resolve('generate-*/generator.js', { + configfile: 'generator.js', + cwd: utils.gm + }); + + /** + * Process command line arguments + */ + + app.cli.process(args); + cb(null, app); +} + +/** + * Run + */ + +run(function(err, app) { + if (err) handleError(err); + + /** + * Listen for errors + */ + + app.on('error', function(err) { + console.log(err); + }); + + /** + * Run tasks + */ + + app.build(argv, function(err) { + if (err) throw err; + utils.timestamp('finished ' + utils.success()); + process.exit(0); + }); +}); + +/** + * Creat a new `env` object + */ + +function createEnv(configfile, cwd) { + var env = new Env(configfile, 'update', cwd);; + env.module.path = utils.tryResolve('update'); + return env; +} + +/** + * Handle CLI errors + */ + +function handleError(err) { + if (typeof err === 'string' && errors[err]) { + console.error(errors[err]); + } else { + console.error(err); + } + process.exit(1); +} diff --git a/bin/errors.js b/bin/errors.js new file mode 100644 index 0000000..bae4786 --- /dev/null +++ b/bin/errors.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = { + updatefile: 'Cannot find updatefile.js in the current working directory. Use `--cwd` to specify a working directory.' +}; diff --git a/bin/update.js b/bin/update.js deleted file mode 100755 index 2160867..0000000 --- a/bin/update.js +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env node - -var path = require('path'); -var gm = require('global-modules'); -var Runner = require('../lib/runner/runner')(); -var utils = require('../lib/utils'); -var argv = require('minimist')(process.argv.slice(2), { - alias: {verbose: 'v'} -}); - -var cmd = utils.commands(argv); -var runner = new Runner(argv); - -runner.base.option(argv); -runner.option(argv); - -var task = cmd.list ? ['list', 'default'] : ['default']; - -runner.on('*', function(method, key, val) { - console.log(method + ':', key, val); -}); - -if (argv.verbose) { - runner.on('register', function(key) { - utils.ok(utils.gray('registered'), 'updater', utils.cyan(key)); - }); -} - -runner.registerEach('update-*', {cwd: gm}); - -runner.base.task('run', function(cb) { - runner.run(cb); -}); - -runner.base.build(task, function(err) { - if (err) return console.error(err); - utils.timestamp('finished ' + utils.green(utils.successSymbol)); -}); diff --git a/index.js b/index.js index 1b941e0..d9ffae3 100644 --- a/index.js +++ b/index.js @@ -1,29 +1,23 @@ -/*! - * update - * - * Copyright (c) 2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - 'use strict'; +/** + * module dependencies + */ + var path = require('path'); -var Base = require('assemble-core'); -var expand = require('expand-args'); -var minimist = require('minimist'); -var config = require('./lib/config'); -var locals = require('./lib/locals'); +var Generate = require('generate'); var utils = require('./lib/utils'); +var cli = require('./lib/cli'); /** - * Create an `update` application. This is the main function exported + * Create an `update` instance. This is the main function exported * by the update module. * * ```js - * var Update = require('update'); - * var update = new Update(); + * var update = require('update'); + * var app = update(); * ``` - * @param {Object} `options` + * @param {Object} `options` Optionally pass default options to use. * @api public */ @@ -31,139 +25,101 @@ function Update(options) { if (!(this instanceof Update)) { return new Update(options); } - Base.call(this, options); - this.name = this.options.name || 'update'; - this.isUpdate = true; - this.initUpdate(this); -} -/** - * Inherit assemble-core - */ + Generate.apply(this, arguments); + this.isUpdate = true; -Base.extend(Update); + this.initDefaults(this); + this.initPlugins(this); + this.initCollections(this); +} /** - * Initialize Updater defaults + * Inherit Generate */ -Update.prototype.initUpdate = function(base) { - this.set('updaters', {}); - - // custom middleware handlers - this.handler('onStream'); - this.handler('preWrite'); - this.handler('postWrite'); - - // parse command line arguments - var argv = expand(minimist(process.argv.slice(2)), { - alias: {v: 'verbose'} - }); - - this.option('argv', argv); - - // expose `argv` on the instance - this.mixin('argv', function(prop) { - var args = [].slice.call(arguments); - args.unshift(argv); - return utils.get.apply(null, args); - }); - - // load the package.json for the updater - this.data(utils.pkg.sync(this.options.path)); - config(this); - - this.use(locals('update')) - .use(utils.runtimes({ - displayName: function(key) { - return base.name === key ? key : (base.name + ':' + key); - } - })) - .use(utils.store()) - .use(utils.pipeline()) - .use(utils.loader()) - .use(utils.cli()) - .use(utils.defaults()) - .use(utils.opts()) - - var data = utils.get(this.cache.data, 'update'); - this.config.process(utils.extend({}, data, argv)); - - this.engine(['md', 'tmpl'], require('engine-base')); - this.onLoad(/\.(md|tmpl)$/, function(view, next) { - utils.matter.parse(view, next); - }); -}; +Generate.extend(Update); /** - * Returns a function for resolving filepaths from the given `directory` - * or from the user's current working directory if no directory - * is passed. + * Load default plugins. Built-in plugins can be disabled + * on the `update` options. * * ```js - * var cwd = update.cwd('foo'); - * var a = cwd('bar'); - * var b = cwd('baz'); + * var app = update({ + * plugins: { + * loader: false, + * store: false + * } + * }); * ``` - * @param {String} `dir` - * @return {Function} - */ - -Update.prototype.cwd = function(dir) { - var cwd = dir || process.cwd(); - return function() { - var args = [].slice.call(arguments); - args.unshift(cwd); - return path.resolve.apply(null, args); - }; -}; - -/** - * Temporary logger method. - * TODO: add event logger */ -Update.prototype.log = function() { - this.emit.bind(this, 'log').apply(this, arguments); - if (this.enabled('verbose')) { - console.log.apply(console, arguments); +Update.prototype.initPlugins = function(app) { + enable('middleware', utils.middleware); + enable('loader', utils.loader); + enable('config', utils.config); + enable('argv', utils.argv); + enable('cli', cli); + + function enable(name, fn) { + if (app.option('plugins') === false) return; + if (app.option('plugins.' + name) !== false) { + app.use(fn(app.options)); + } } }; /** - * Register updater `name` with the given `update` - * instance. - * - * @param {String} `name` - * @param {Object} `update` Instance of update - * @return {Object} Returns the instance for chaining + * Built-in view collections + * | partials + * | layouts + * | pages */ -Update.prototype.updater = function(name, app) { - if (arguments.length === 1 && typeof name === 'string') { - return this.updaters[name]; - } +Update.prototype.initCollections = function(app) { + if (this.option('collections') === false) return; + + var engine = this.options.defaultEngine || 'hbs'; + this.create('partials', { + engine: engine, + viewType: 'partial', + renameKey: function(fp) { + return path.basename(fp, path.extname(fp)); + } + }); - app.use(utils.runtimes({ - displayName: function(key) { - return app.name === key ? key : (app.name + ':' + key); + this.create('layouts', { + engine: engine, + viewType: 'layout', + renameKey: function(fp) { + return path.basename(fp, path.extname(fp)); } - })); + }); - this.emit('updater', name, app); - this.updaters[name] = app; - return app; + this.create('pages', { + engine: engine, + renameKey: function(fp) { + return fp; + } + }); }; /** - * Expose `Update` + * Ensure `name` is set on the instance for lookups. */ -module.exports = Update; +Object.defineProperty(Update.prototype, 'name', { + configurable: true, + set: function(name) { + this.options.name = name; + }, + get: function() { + return this.options.name || 'base'; + } +}); /** - * Expose `utils` and package.json metadata + * Expose `Update` */ -module.exports.utils = utils; -module.exports.pkg = require('./package'); +module.exports = Update; diff --git a/lib/cli.js b/lib/cli.js new file mode 100644 index 0000000..94ee1f2 --- /dev/null +++ b/lib/cli.js @@ -0,0 +1,78 @@ +'use strict'; + +var commands = require('./commands'); +var utils = require('./utils'); + +/** + * Assemble CLI + * + * Custom extensions to the built-in mappings + * provided by the `base-cli` plugin. + */ + +module.exports = function(options) { + return function(app) { + if (!app.cli) { + app.use(utils.cli()); + } + + /** + * Help and information-related + */ + + app.cli + .map('init', function(fp) { + console.log('cli > init (implement me!)'); + app.set('questions.options.forceAll', true); + }) + .map('help', commands.help(app)) + .map('show', commands.show(app)) + .map('open', commands.open(app)) + .map('diff', function(val) { + app.option('diff', val); + }) + + /** + * Options, settings and context related + */ + + app.cli + .map('ask', commands.ask(app)) + .map('cwd', function(val) { + app.option('cwd', val); + }) + .map('save', function(val) { + app.store.config.set(val); + val = utils.tableize(val); + console.log('saved > "%j" %s', val, 'in global config store.'); + }) + .map('data', function(val) { + app.data(val); + }) + .map('option', function(val) { + app.option(val); + }) + .map('config', function(val) { + app.config.process({ + update: val + }); + }); + + /** + * Task-related + */ + + app.cli + .map('choose', function(key) { + if (key === true) { + app.enable('tasks.choose'); + } + }) + .map('tasks', function(key) { + if (key === true) { + app.enable('tasks.display'); + } + }); + + }; +}; diff --git a/lib/commands/ask.js b/lib/commands/ask.js new file mode 100644 index 0000000..6e72d32 --- /dev/null +++ b/lib/commands/ask.js @@ -0,0 +1,23 @@ +'use strict'; + +var utils = require('../utils'); + +module.exports = function(app) { + return function(val) { + if (val === true) { + app.enable('questions.init'); + return; + } + + if (utils.isObject(val)) { + var keys = Object.keys(utils.tableize(val)); + app.questions.enqueue(keys); + app.option('questions.init', keys); + return; + } + + var keys = utils.arrayify(val); + app.questions.enqueue(keys); + app.option('questions.init', keys); + } +}; diff --git a/lib/commands/help.js b/lib/commands/help.js new file mode 100644 index 0000000..de64933 --- /dev/null +++ b/lib/commands/help.js @@ -0,0 +1,106 @@ +'use strict'; + +var utils = require('../utils'); + +module.exports = function(app) { + return function(key) { + // var commands = help(); + // console.log(commands.options[key]); + // process.exit(0); + }; +}; + +/** + * Create `help` documentation + */ + +function help() { + return { + heading: '', + options: { + init: { + description: 'Force initialization questions to be re-asked.', + example: '', + short: 'i' + }, + help: { + description: '', + example: '', + short: 'h' + }, + show: { + description: '', + example: '', + short: null + }, + ask: { + description: '', + example: '', + short: null + }, + open: { + description: '', + example: '', + short: 'o' + }, + config: { + description: '', + example: '', + short: 'c' + }, + diff: { + description: '', + example: '', + short: null + }, + cwd: { + description: '', + example: '', + short: null + }, + data: { + description: '', + example: '', + short: 'd' + }, + choose: { + description: '', + example: '', + short: null + }, + tasks: { + description: '', + example: '', + short: null + } + }, + footer: '' + }; +} + +function format(obj) { + var heading = obj.heading || ''; + var optsList = ''; + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + var val = obj[key]; + + optsList += toFlag(key, val.short); + optsList += utils.wrap(val.description); + } + } + + return heading + '\n' + + optsList + '\n' + + obj.footer || ''; +} + +function toFlag(key, short) { + return shortKey(short) + '--' + key + ' '; +} + +function shortKey(sh) { + return sh ? ('-' + sh + ', ') : ' '; +} + +// console.log(format(help())) diff --git a/lib/commands/index.js b/lib/commands/index.js new file mode 100644 index 0000000..23b2930 --- /dev/null +++ b/lib/commands/index.js @@ -0,0 +1 @@ +module.exports = require('export-files')(__dirname); diff --git a/lib/commands/open.js b/lib/commands/open.js new file mode 100644 index 0000000..cf940c0 --- /dev/null +++ b/lib/commands/open.js @@ -0,0 +1,26 @@ +'use strict'; + +var path = require('path'); +var utils = require('../utils'); + +module.exports = function(app) { + return function(val) { + if (val === 'answers') { + var dest = app.get('questions.dest'); + if (dest) { + console.log('opening answers data directory >', '"' + dest + '"'); + utils.opn(dest); + process.exit(0); + } + } + + if (val === 'store') { + var dir = path.dirname(app.get('store.path')); + if (dir) { + console.log('opening store data directory >', '"' + dir + '"'); + utils.opn(dir); + process.exit(0); + } + } + }; +}; diff --git a/lib/commands/show.js b/lib/commands/show.js new file mode 100644 index 0000000..b9ccb5c --- /dev/null +++ b/lib/commands/show.js @@ -0,0 +1,32 @@ +'use strict'; + +var utils = require('../utils'); + +module.exports = function(app) { + return function(key) { + if (typeof key === 'string') { + var val = this.get(key) + || this.get(['cache', key]) + || this.get(['cache.data', key]) + || this.get(['options', key]) + + if (val) { + console.log('showing >', key, val); + } + } + + if (utils.isObject(key)) { + key = utils.tableize(key); + } + + if (key === 'answers') { + app.on('answers', console.log); + return; + } + + if (key === 'commands') { + console.log(app.commands.sort()); + return; + } + }; +}; diff --git a/lib/config.js b/lib/config.js deleted file mode 100644 index c829c9b..0000000 --- a/lib/config.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict'; - - -module.exports = function(app) { - if (!app.isUpdate) return; - - var config = require('base-config'); - app.use(config()); - - app.config - .map('addViews') - .map('addView') - .map('helpers') - .map('asyncHelpers') - .map('plugins', function(val) { - app.visit('plugin', val); - }) - .map('data', function(val) { - app.visit('data', val); - }) - .map('collections', function(val) { - app.visit('create', val); - }) - .map('reflinks', function(val) { - app.data({reflinks: val}); - }) - .map('related', function(val) { - app.data({related: val}); - }); -}; diff --git a/lib/generator.js b/lib/generator.js new file mode 100644 index 0000000..5b57802 --- /dev/null +++ b/lib/generator.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = function(app, base, env) { + +}; diff --git a/lib/locals.js b/lib/locals.js deleted file mode 100644 index 4d0097b..0000000 --- a/lib/locals.js +++ /dev/null @@ -1,26 +0,0 @@ -'use strict'; - -var get = require('get-value'); -var set = require('set-value'); -var utils = require('./utils'); - -module.exports = function(name) { - name = name || utils.project(process.cwd()); - - return function(app) { - app.define('locals', new Locals(name, this)); - }; -}; - -function Locals(name, app) { - this.cache = get(app, ['cache.data', name]) || {}; -} - -Locals.prototype.get = function(key) { - return get(this.cache, key); -}; - -Locals.prototype.set = function(key, value) { - set(this.cache, key, value); - return this; -}; diff --git a/lib/utils.js b/lib/utils.js index 122ee60..eb6c5a1 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,329 +1,185 @@ 'use strict'; -var fs = require('fs'); var path = require('path'); -var pkg = require(path.resolve(__dirname, '../package')); - -/** - * Module dependencies - */ - var utils = require('lazy-cache')(require); var fn = require; require = utils; +/** + * Lazily required module dependencies + */ + +// plugins, middleware or helpers require('assemble-loader', 'loader'); -require('async'); +require('common-middleware', 'middleware'); +require('base-config', 'config'); +require('base-argv', 'argv'); require('base-cli', 'cli'); -require('base-pipeline', 'pipeline'); -require('base-store', 'store'); -require('composer-runtimes', 'runtimes'); -require('expand-args'); -require('expand-object', 'expand'); -require('extend-shallow', 'extend'); -require('for-own'); -require('get-value', 'get'); -require('load-pkg', 'pkg'); + +// other +require('find-pkg'); +require('global-modules', 'gm'); +require('mixin-deep', 'merge'); +require('isobject', 'isObject'); +require('word-wrap', 'wrap'); require('matched', 'glob'); -require('micromatch', 'mm'); -require('object.omit', 'omit'); -require('object.pick', 'pick'); -require('parser-front-matter', 'matter'); -require('project-name', 'project'); -require('question-cache', 'questions'); -require('set-value', 'set'); -require('stream-exhaust', 'exhaust'); -require('through2', 'through'); -require('union-value', 'union'); +require('resolve-dir'); +require('try-open'); +require('opn'); +// CLI require('success-symbol'); -require('ansi-yellow', 'yellow'); -require('ansi-green', 'green'); -require('ansi-gray', 'gray'); -require('ansi-cyan', 'cyan'); -require('ansi-red', 'red'); +require('ansi-colors', 'colors'); require('time-stamp', 'stamp'); +require('expand-args'); require = fn; /** - * Logging utils + * Green checkmark + * + * @return {String} */ -utils.timestamp = function(msg) { - var time = '[' + utils.gray(utils.stamp('HH:mm:ss', new Date())) + ']'; - return console.log(time, msg); -}; - -function Status(status) { - status = status || {}; - this.err = status.err || null; - this.code = status.code || null; - this.name = status.name || ''; - this.msg = status.msg || ''; -} - -utils.ok = function() { - var args = utils.toArray(arguments) || []; - args.unshift(' ' + utils.green(utils.successSymbol)); - console.log.apply(console, args); -}; -utils.success = function() { - var args = utils.toArray(arguments) || []; - args[0] = utils.green(args[0] || ''); - console.log.apply(console, args); -}; -utils.error = function() { - var args = utils.toArray(arguments); - args.unshift(utils.red('Error:')); - console.error.apply(console, args); +utils.processArgv = function(app, argv) { + var args = app.processArgv(argv); + var opts = utils.merge({}, args, args.options, args.commands); + app.option(opts); + return opts; }; /** - * CLI utils + * Get a home-relative filepath */ -utils.commands = function(argv) { - argv._ = argv._ || []; - var commands = {}; - - argv._.forEach(function(key) { - commands[key] = true; - }); - return commands; -}; - -utils.identity = function(val) { - return val; -}; - -utils.arrayify = function(val) { - return Array.isArray(val) ? val : [val]; -}; - -utils.toArgv = function(args) { - var argv = args.flags; - argv._ = args.commands; - return argv; -}; - -utils.toArray = function(val) { - if (Array.isArray(val)) return val; - if (val && val.length) { - return [].slice.call(val); - } -}; - -utils.contains = function(arr, key) { - return arr.indexOf(key) > -1; -}; - -utils.npm = function(name) { - return utils.tryRequire(name) || utils.tryRequire(path.resolve(name)); -}; - -utils.exists = function(fp) { - return fs.existsSync(fp); +utils.homeRelative = function(root, fp) { + var dir = path.resolve(utils.resolveDir(fp)); + var fp = path.relative(root, dir); + return utils.colors.green('~/' + fp); }; /** - * Rename a filepath to the "nickname" of the project. + * Green checkmark * - * ```js - * renameFn('updater-foo'); - * //=> 'foo' - * ``` + * @return {String} */ -utils.renameFn = function(filename, options) { - if (options && typeof options.renameFn === 'function') { - return options.renameFn(filename); - } - return filename.slice(filename.indexOf('-') + 1); +utils.success = function() { + return utils.colors.green(utils.successSymbol); }; /** - * Return a glob of file paths + * Create a formatted timestamp + * + * @param {String} msg + * @return {String} */ -utils.matchFiles = function(pattern, options) { - options = options || {}; - var isMatch = utils.mm.matcher(pattern); - var files = fs.readdirSync(options.cwd); - var len = files.length, i = -1; - var res = []; - while (++i < len) { - var name = files[i]; - if (name === 'update') continue; - var fp = path.join(options.cwd, name); - if (isMatch(fp) || isMatch(name)) { - res.push(fp); - } - } - return res; +utils.timestamp = function(msg, stamp) { + var time = '[' + utils.colors.gray(utils.stamp('HH:mm:ss', new Date())) + ']'; + console.log(time, msg); }; /** - * Resolve the correct updater module to instantiate. - * If `update` exists in `node_modules` of the cwd, - * then that will be used to create the instance, - * otherwise this module will be used. + * Log out a custom, time-stamped message to indicate that a task is starting. */ -utils.resolveModule = function(dir) { - dir = path.join(dir, 'node_modules/', pkg.name); - if (utils.exists(dir)) { - return require(path.resolve(dir)); - } - return null; +utils.logTask = function(appname, taskname) { + utils.timestamp('starting ' + utils.colors.cyan(appname + taskname)); }; /** - * Print a tree of "updaters" and their tasks - * - * ```js - * utils.tree(updaters); - * ``` + * Utils */ -utils.tree = function(updaters) { - var res = ''; - for (var key in updaters) { - res += utils.cyan(key) + '\n'; - for (var task in updaters[key].tasks) { - res += ' - ' + task + '\n'; - } - } - return res; +utils.exists = function exists(fp) { + return utils.tryOpen(fp, 'r'); }; -/** - * Return a list of "updaters" and their tasks - * - * ```js - * utils.list(updaters); - * ``` - */ - -utils.list = function(updaters) { - var list = []; - for (var key in updaters) { - var updater = updaters[key]; - if (!Object.keys(updater.tasks).length) { - continue; - } +utils.arrayify = function arrayify(val) { + if (!val) return val; + return Array.isArray(val) ? val : [val]; +}; - var hasDefault = updater.tasks['default']; - var name = updater.option('name'); - var item = { - name: name + (hasDefault ? ' (default)' : ''), - value: key, - short: name + (hasDefault ? ':default' : '') - }; - list.push(item); - for (var task in updater.tasks) { - if (task === 'default') continue; - list.push({ - name: ' - ' + task, - value: key + ':' + task, - short: key + ':' + task - }); - } - } - return list; +utils.extRegex = function extRegex(exts) { + return new RegExp('\\.(' + utils.arrayify(exts).join('|') + ')$'); }; /** * Try to require a file */ -utils.tryRequire = function(name) { +utils.tryRequire = function tryRequire(name) { try { return require(name); - } catch (err) { - console.log(err); - } - return null; -}; - -/** - * Try to read a file - */ - -utils.tryRead = function(fp) { - try { - return fs.readFileSync(fp); } catch (err) {} - return null; -}; -utils.tryParse = function(str) { try { - return JSON.parse(str); + return require(path.resolve(name)); } catch (err) {} return {}; }; -utils.register = function(pattern, base, update, options) { - utils.matchFiles(pattern, options).forEach(function(fp) { - var name = utils.project(fp); - var mod = utils.resolveModule(fp) || update; - var app = mod(base.options) - .option('name', name) - .set('path', fp); +utils.tryResolve = function tryResolve(name) { + try { + return require.resolve(name); + } catch (err) {} - require(utils.updatefile(fp))(app, base); - base.updater(name, app); - }); -}; + try { + return require.resolve(path.resolve(name)); + } catch (err) {} -utils.opts = function(key) { - key = key || 'opts'; - return function(app) { - var name = this.options.name || 'base'; - this.define(key, function() { - var config = this.defaults.apply(this, arguments); - return function(key, opts) { - var args = [].concat.apply([], [].slice.call(arguments)); - var prop = typeof key === 'string' ? args.shift() : null; - var val; - - if (prop && !args.length) { - val = utils.get(config, prop); - if (val) return val; - } - - var options = utils.extend.apply(utils.extend, [config].concat(args)); - return prop ? utils.get(options, prop) : options; - }; - }); - }; -}; + try { + return require.resolve(path.resolve(process.cwd(), name)); + } catch (err) {} -utils.defaults = function(key) { - key = key || 'defaults'; - return function(app) { - this.define(key, function() { - var args = [].concat.apply([], [].slice.call(arguments)); - args.unshift({}, this.options); - return utils.extend.apply(utils.extend, args); - }); - }; + try { + return require.resolve(path.resolve(utils.gm, name)); + } catch (err) {} }; /** - * Restore `require` + * Modified from the `tableize` lib, which replaces + * dashes with underscores, and we don't want that behavior. + * Tableize `obj` by flattening and normalizing the keys. + * + * @param {Object} obj + * @return {Object} + * @api public */ -require = fn; +utils.tableize = function tableize(obj, opts) { + var ret = {}; + opts = opts || {}; + type(ret, obj, '', opts); + return ret; +}; /** - * Expose `utils` + * Type `obj` recursively. + * + * @param {Object} schema + * @param {Object} obj + * @param {String} prefix + * @api private */ -module.exports = utils; +function type(schema, obj, prefix, opts) { + Object.keys(obj).forEach(function(key) { + var val = obj[key]; + + key = prefix + key; + if (opts.lowercase) key = key.toLowerCase(); + + if (utils.isObject(val)) { + type(schema, val, key + '.', opts); + } else { + schema[key] = val; + } + }); +} /** - * Expose utils + * Expose `utils` */ module.exports = utils; diff --git a/test/app.collection.js b/test/app.collection.js index ef8c548..db5c65d 100644 --- a/test/app.collection.js +++ b/test/app.collection.js @@ -3,7 +3,6 @@ require('mocha'); require('should'); var fs = require('fs'); -var path = require('path'); var assert = require('assert'); var define = require('define-property'); var support = require('./support'); @@ -49,11 +48,7 @@ describe('collection', function() { }); app.engine('tmpl', require('engine-base')); - app.create('pages', { - renameKey: function(fp) { - return path.relative(process.cwd(), fp); - } - }); + app.create('pages'); }); it('should load a view onto the respective collection:', function() { @@ -166,11 +161,7 @@ describe('collection singular method', function() { beforeEach(function() { app = new App(); app.engine('tmpl', require('engine-base')); - app.create('page', { - renameKey: function(fp) { - return path.relative(process.cwd(), fp); - } - }); + app.create('page'); }); it('should add a view to the created collection:', function() { diff --git a/test/app.create.js b/test/app.create.js index 5486eb2..99b6ed7 100644 --- a/test/app.create.js +++ b/test/app.create.js @@ -137,11 +137,7 @@ describe('create', function() { beforeEach(function() { app = new App(); app.engine('tmpl', require('engine-base')); - app.create('page', { - renameKey: function(fp) { - return path.relative(process.cwd(), fp); - } - }); + app.create('page'); }); it('should create views from key-value pairs:', function() { diff --git a/test/app.list.js b/test/app.list.js index 84527ec..22337b8 100644 --- a/test/app.list.js +++ b/test/app.list.js @@ -3,7 +3,6 @@ require('mocha'); require('should'); var fs = require('fs'); -var path = require('path'); var assert = require('assert'); var support = require('./support'); var App = support.resolve(); @@ -35,11 +34,7 @@ describe('list', function() { beforeEach(function() { app = new App(); app.engine('tmpl', require('engine-base')); - app.create('pages', { - renameKey: function(fp) { - return path.relative(process.cwd(), fp); - } - }); + app.create('pages'); }); it('should add an item to a list:', function() { diff --git a/test/fixtures/a.txt b/test/fixtures/a.txt new file mode 100644 index 0000000..a8a9406 --- /dev/null +++ b/test/fixtures/a.txt @@ -0,0 +1 @@ +this is a test \ No newline at end of file diff --git a/test/fixtures/b.txt b/test/fixtures/b.txt new file mode 100644 index 0000000..a8a9406 --- /dev/null +++ b/test/fixtures/b.txt @@ -0,0 +1 @@ +this is a test \ No newline at end of file diff --git a/test/fixtures/c.txt b/test/fixtures/c.txt new file mode 100644 index 0000000..a8a9406 --- /dev/null +++ b/test/fixtures/c.txt @@ -0,0 +1 @@ +this is a test \ No newline at end of file diff --git a/test/fixtures/copy/a.txt b/test/fixtures/copy/a.txt deleted file mode 100644 index 7c4a013..0000000 --- a/test/fixtures/copy/a.txt +++ /dev/null @@ -1 +0,0 @@ -aaa \ No newline at end of file diff --git a/test/fixtures/copy/b.txt b/test/fixtures/copy/b.txt deleted file mode 100644 index 01f02e3..0000000 --- a/test/fixtures/copy/b.txt +++ /dev/null @@ -1 +0,0 @@ -bbb \ No newline at end of file diff --git a/test/fixtures/copy/c.txt b/test/fixtures/copy/c.txt deleted file mode 100644 index 2383bd5..0000000 --- a/test/fixtures/copy/c.txt +++ /dev/null @@ -1 +0,0 @@ -ccc \ No newline at end of file diff --git a/test/fixtures/one/generator.js b/test/fixtures/one/generator.js new file mode 100644 index 0000000..546d004 --- /dev/null +++ b/test/fixtures/one/generator.js @@ -0,0 +1,19 @@ +'use strict'; + + +module.exports = function(app, base, env) { + app.task('default', function(cb) { + console.log('one > default'); + cb(); + }); + + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); + + app.register('foo', function(app) { + app.task('x', function() {}); + app.task('y', function() {}); + app.task('z', function() {}); + }); +}; \ No newline at end of file diff --git a/test/fixtures/one/package.json b/test/fixtures/one/package.json new file mode 100644 index 0000000..d1d4676 --- /dev/null +++ b/test/fixtures/one/package.json @@ -0,0 +1,30 @@ +{ + "name": "one", + "description": "The most interesting project in the world > Verb", + "version": "0.1.0", + "homepage": "https://github.com/jonschlinkert/one", + "author": "Jon Schlinkert (https://github.com/jonschlinkert)", + "repository": "jonschlinkert/one", + "bugs": { + "url": "https://github.com/jonschlinkert/one/issues" + }, + "license": "MIT", + "files": [ + "index.js" + ], + "main": "index.js", + "engines": { + "node": ">=0.10.0" + }, + "scripts": { + "test": "mocha" + }, + "dependencies": { + "resolve-modules": "^0.1.2" + }, + "devDependencies": { + "mocha": "*", + "should": "*" + }, + "keywords": [] +} diff --git a/test/fixtures/one/templates/a.txt b/test/fixtures/one/templates/a.txt new file mode 100644 index 0000000..2ff8250 --- /dev/null +++ b/test/fixtures/one/templates/a.txt @@ -0,0 +1 @@ +one: aaa \ No newline at end of file diff --git a/test/fixtures/one/templates/x.txt b/test/fixtures/one/templates/x.txt new file mode 100644 index 0000000..139e1d8 --- /dev/null +++ b/test/fixtures/one/templates/x.txt @@ -0,0 +1 @@ +one: xxx \ No newline at end of file diff --git a/test/fixtures/one/templates/y.txt b/test/fixtures/one/templates/y.txt new file mode 100644 index 0000000..7308ea9 --- /dev/null +++ b/test/fixtures/one/templates/y.txt @@ -0,0 +1 @@ +one: yyy \ No newline at end of file diff --git a/test/fixtures/one/templates/z.txt b/test/fixtures/one/templates/z.txt new file mode 100644 index 0000000..04c378a --- /dev/null +++ b/test/fixtures/one/templates/z.txt @@ -0,0 +1 @@ +one: zzz \ No newline at end of file diff --git a/test/fixtures/three/four/five/generator.js b/test/fixtures/three/four/five/generator.js new file mode 100644 index 0000000..61f8bf6 --- /dev/null +++ b/test/fixtures/three/four/five/generator.js @@ -0,0 +1,14 @@ +'use strict'; + +module.exports = function(generate, base) { + generate.task('default', function() {}); + generate.task('a', function() {}); + generate.task('b', function() {}); + generate.task('c', function() {}); + + generate.register('foo', function(app) { + app.task('x', function() {}); + app.task('y', function() {}); + app.task('z', function() {}); + }); +}; \ No newline at end of file diff --git a/test/fixtures/three/four/five/package.json b/test/fixtures/three/four/five/package.json new file mode 100644 index 0000000..984f0f6 --- /dev/null +++ b/test/fixtures/three/four/five/package.json @@ -0,0 +1,30 @@ +{ + "name": "five", + "description": "The most interesting project in the world > Verb", + "version": "0.1.0", + "homepage": "https://github.com/jonschlinkert/five", + "author": "Jon Schlinkert (https://github.com/jonschlinkert)", + "repository": "jonschlinkert/five", + "bugs": { + "url": "https://github.com/jonschlinkert/five/issues" + }, + "license": "MIT", + "files": [ + "index.js" + ], + "main": "index.js", + "engines": { + "node": ">=0.10.0" + }, + "scripts": { + "test": "mocha" + }, + "dependencies": { + "resolve-modules": "^0.1.2" + }, + "devDependencies": { + "mocha": "*", + "should": "*" + }, + "keywords": [] +} diff --git a/test/fixtures/three/four/five/templates/a.txt b/test/fixtures/three/four/five/templates/a.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/three/four/five/templates/b.txt b/test/fixtures/three/four/five/templates/b.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/three/four/five/templates/c.txt b/test/fixtures/three/four/five/templates/c.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/three/four/generator.js b/test/fixtures/three/four/generator.js new file mode 100644 index 0000000..61f8bf6 --- /dev/null +++ b/test/fixtures/three/four/generator.js @@ -0,0 +1,14 @@ +'use strict'; + +module.exports = function(generate, base) { + generate.task('default', function() {}); + generate.task('a', function() {}); + generate.task('b', function() {}); + generate.task('c', function() {}); + + generate.register('foo', function(app) { + app.task('x', function() {}); + app.task('y', function() {}); + app.task('z', function() {}); + }); +}; \ No newline at end of file diff --git a/test/fixtures/three/four/package.json b/test/fixtures/three/four/package.json new file mode 100644 index 0000000..3f25912 --- /dev/null +++ b/test/fixtures/three/four/package.json @@ -0,0 +1,30 @@ +{ + "name": "four", + "description": "The most interesting project in the world > Verb", + "version": "0.1.0", + "homepage": "https://github.com/jonschlinkert/four", + "author": "Jon Schlinkert (https://github.com/jonschlinkert)", + "repository": "jonschlinkert/four", + "bugs": { + "url": "https://github.com/jonschlinkert/four/issues" + }, + "license": "MIT", + "files": [ + "index.js" + ], + "main": "index.js", + "engines": { + "node": ">=0.10.0" + }, + "scripts": { + "test": "mocha" + }, + "dependencies": { + "resolve-modules": "^0.1.2" + }, + "devDependencies": { + "mocha": "*", + "should": "*" + }, + "keywords": [] +} diff --git a/test/fixtures/three/four/templates/a.txt b/test/fixtures/three/four/templates/a.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/three/four/templates/b.txt b/test/fixtures/three/four/templates/b.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/three/four/templates/c.txt b/test/fixtures/three/four/templates/c.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/three/generator.js b/test/fixtures/three/generator.js new file mode 100644 index 0000000..61f8bf6 --- /dev/null +++ b/test/fixtures/three/generator.js @@ -0,0 +1,14 @@ +'use strict'; + +module.exports = function(generate, base) { + generate.task('default', function() {}); + generate.task('a', function() {}); + generate.task('b', function() {}); + generate.task('c', function() {}); + + generate.register('foo', function(app) { + app.task('x', function() {}); + app.task('y', function() {}); + app.task('z', function() {}); + }); +}; \ No newline at end of file diff --git a/test/fixtures/three/package.json b/test/fixtures/three/package.json new file mode 100644 index 0000000..12b5fac --- /dev/null +++ b/test/fixtures/three/package.json @@ -0,0 +1,30 @@ +{ + "name": "three", + "description": "The most interesting project in the world > Verb", + "version": "0.1.0", + "homepage": "https://github.com/jonschlinkert/three", + "author": "Jon Schlinkert (https://github.com/jonschlinkert)", + "repository": "jonschlinkert/three", + "bugs": { + "url": "https://github.com/jonschlinkert/three/issues" + }, + "license": "MIT", + "files": [ + "index.js" + ], + "main": "index.js", + "engines": { + "node": ">=0.10.0" + }, + "scripts": { + "test": "mocha" + }, + "dependencies": { + "resolve-modules": "^0.1.2" + }, + "devDependencies": { + "mocha": "*", + "should": "*" + }, + "keywords": [] +} diff --git a/test/fixtures/three/templates/a.txt b/test/fixtures/three/templates/a.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/three/templates/b.txt b/test/fixtures/three/templates/b.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/three/templates/c.txt b/test/fixtures/three/templates/c.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/two/generate.js b/test/fixtures/two/generate.js new file mode 100644 index 0000000..04c031f --- /dev/null +++ b/test/fixtures/two/generate.js @@ -0,0 +1,21 @@ +'use strict'; + +var Generate = require('../../..'); +var generate = new Generate(); + +generate.task('default', function() {}); +generate.task('a', function() {}); +generate.task('b', function() {}); +generate.task('c', function() {}); + +generate.register('foo', function(app) { + app.task('x', function() {}); + app.task('y', function() {}); + app.task('z', function() {}); +}); + +/** + * Expose this instance of `Generate` + */ + +module.exports = generate; diff --git a/test/fixtures/two/package.json b/test/fixtures/two/package.json new file mode 100644 index 0000000..8a02df0 --- /dev/null +++ b/test/fixtures/two/package.json @@ -0,0 +1,30 @@ +{ + "name": "two", + "description": "The most interesting project in the world > Verb", + "version": "0.1.0", + "homepage": "https://github.com/jonschlinkert/two", + "author": "Jon Schlinkert (https://github.com/jonschlinkert)", + "repository": "jonschlinkert/two", + "bugs": { + "url": "https://github.com/jonschlinkert/two/issues" + }, + "license": "MIT", + "files": [ + "index.js" + ], + "main": "index.js", + "engines": { + "node": ">=0.10.0" + }, + "scripts": { + "test": "mocha" + }, + "dependencies": { + "resolve-modules": "^0.1.2" + }, + "devDependencies": { + "mocha": "*", + "should": "*" + }, + "keywords": [] +} diff --git a/test/fixtures/two/templates/a.txt b/test/fixtures/two/templates/a.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/two/templates/b.txt b/test/fixtures/two/templates/b.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/two/templates/c.txt b/test/fixtures/two/templates/c.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/generate.compose.js b/test/generate.compose.js new file mode 100644 index 0000000..8ffe1d8 --- /dev/null +++ b/test/generate.compose.js @@ -0,0 +1,75 @@ +/* deps: coveralls istanbul */ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var Generate = support.resolve(); +var Base = Generate.Base; +var generate; + +describe('generate.compose', function() { + beforeEach(function() { + generate = new Generate(); + }); + + it('should throw an error when trying to compose an instance', function(cb) { + var foo = new Generate({name: 'foo'}); + try { + generate.compose(foo); + cb(new Error('Expected an error.')); + } catch (err) { + assert.equal(err.message, 'generators must export a function to extend other generators'); + cb(); + } + }); + + it('should compose a generator', function() { + var foo = generate.generator('foo', function(app) { + app.task('foo', function(cb) { + cb(); + }); + }); + + var bar = generate.generator('bar', function(app) { + app.task('bar', function(cb) { + cb(); + }); + }); + + foo.tasks.should.have.property('foo'); + bar.tasks.should.have.property('bar'); + + bar.tasks.should.not.have.property('foo'); + foo.tasks.should.not.have.property('bar'); + + foo.compose(bar); + bar.tasks.should.have.property('foo'); + bar.compose(foo); + foo.tasks.should.have.property('bar'); + }); + + it('should compose a generator by name', function() { + var foo = generate.generator('foo', function(app) { + app.task('foo', function(cb) { + cb(); + }); + }); + + var bar = generate.generator('bar', function(app) { + app.task('bar', function(cb) { + cb(); + }); + }); + + foo.tasks.should.have.property('foo'); + bar.tasks.should.have.property('bar'); + + bar.tasks.should.not.have.property('foo'); + foo.tasks.should.not.have.property('bar'); + + generate.compose('foo', bar); + bar.tasks.should.have.property('foo'); + generate.compose('bar', foo); + foo.tasks.should.have.property('bar'); + }); +}); diff --git a/test/generate.extendGenerator.js b/test/generate.extendGenerator.js new file mode 100644 index 0000000..fc4f3f8 --- /dev/null +++ b/test/generate.extendGenerator.js @@ -0,0 +1,46 @@ +/* deps: coveralls istanbul */ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var Generate = support.resolve(); +var Base = Generate.Base; +var generate; + +describe('generate.extendGenerator', function() { + beforeEach(function() { + generate = new Generate(); + }); + + it('should throw an error when trying to extend an instance', function(done) { + var foo = new Generate({name: 'foo'}); + try { + generate.extendGenerator(foo); + done(new Error('Expected an error.')); + } catch (err) { + err.message.should.equal('generators must export a function to extend other generators'); + done(); + } + }); + + it('should extend a generator', function() { + var foo = generate.generator('foo', function(app) { + app.task('foo', function(cb) { cb(); }); + }); + + var bar = generate.generator('bar', function(app) { + app.task('bar', function(cb) { cb(); }); + }); + + foo.tasks.should.have.property('foo'); + bar.tasks.should.have.property('bar'); + + bar.tasks.should.not.have.property('foo'); + foo.tasks.should.not.have.property('bar'); + + foo.extendGenerator(bar); + bar.tasks.should.have.property('foo'); + bar.extendGenerator(foo); + foo.tasks.should.have.property('bar'); + }); +}); diff --git a/test/generate.generator.js b/test/generate.generator.js new file mode 100644 index 0000000..caee290 --- /dev/null +++ b/test/generate.generator.js @@ -0,0 +1,35 @@ +/* deps: coveralls istanbul */ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var Generate = support.resolve(); +var Base = Generate.Base; +var generate; +var one; +var two; + +describe('generate.generator', function() { + before(function() { + generate = new Generate(); + }); + + it('should register a generator function from a file path', function() { + one = generate.generator('one', './test/fixtures/one/generator.js'); + generate.generators.should.have.property('one'); + assert(typeof generate.generators.one === 'object'); + generate.generators.one.should.deepEqual(one); + }); + + it('should register a Generate instance from a file path', function() { + two = generate.generator('two', './test/fixtures/two/generate.js'); + generate.generators.should.have.property('two'); + assert(typeof generate.generators.two === 'object'); + generate.generators.two.should.deepEqual(two); + }); + + it('should get a registered generator by name', function() { + generate.generator('one').should.deepEqual(one); + generate.generator('two').should.deepEqual(two); + }); +}); diff --git a/test/generate.getGenerator.js b/test/generate.getGenerator.js new file mode 100644 index 0000000..4d81cbe --- /dev/null +++ b/test/generate.getGenerator.js @@ -0,0 +1,45 @@ +var assert = require('assert'); +var Generate = require('..'); +var generate; + +describe('.getGenerator', function() { + beforeEach(function() { + generate = new Generate(); + }); + + it('should get a generator from the base instance', function() { + generate.register('abc', function() {}); + var generator = generate.getGenerator('abc'); + assert(generator); + assert(typeof generator === 'object'); + assert(generator.name === 'abc'); + }); + + it('should get nested generator', function() { + generate.register('abc', function(abc) { + abc.register('def', function() {}); + }); + + var generator = generate.getGenerator('abc.def'); + assert(generator); + assert(typeof generator === 'object'); + assert(generator.name === 'def'); + }); + + it('should get a deeply nested generator', function() { + generate.register('abc', function(abc) { + abc.register('def', function(def) { + def.register('ghi', function(ghi) { + ghi.register('jkl', function(jkl) { + jkl.register('mno', function() {}); + }); + }); + }); + }); + + var generator = generate.getGenerator('abc.def.ghi.jkl.mno'); + assert(generator); + assert(typeof generator === 'object'); + assert(generator.name === 'mno'); + }); +}); diff --git a/test/generate.js b/test/generate.js new file mode 100644 index 0000000..b9d7825 --- /dev/null +++ b/test/generate.js @@ -0,0 +1,133 @@ +/* deps: coveralls istanbul */ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var Generate = support.resolve(); +var generate; + +describe('generate', function() { + describe('constructor', function() { + it('should create an instance of Generate:', function() { + generate = new Generate(); + assert(generate instanceof Generate); + }); + + it('should new up without new:', function() { + generate = Generate(); + assert(generate instanceof Generate); + }); + }); + + describe('prototype methods', function() { + beforeEach(function() { + generate = new Generate(); + }); + + it('should expose `addLeaf`', function() { + assert(typeof generate.addLeaf === 'function'); + }); + + it('should expose `compose`', function() { + assert(typeof generate.compose === 'function'); + }); + + it('should expose `generator`', function() { + assert(typeof generate.generator === 'function'); + }); + + it('should expose `getGenerator`', function() { + assert(typeof generate.getGenerator === 'function'); + }); + + it('should expose `registerPath`', function() { + assert(typeof generate.registerPath === 'function'); + }); + + it('should expose `register`', function() { + assert(typeof generate.register === 'function'); + }); + + it('should expose `extendGenerator`', function() { + assert(typeof generate.extendGenerator === 'function'); + }); + + it('should expose `process`', function() { + assert(typeof generate.process === 'function'); + }); + + it('should expose `each`', function() { + assert(typeof generate.each === 'function'); + }); + + it('should expose `eachSeries`', function() { + assert(typeof generate.eachSeries === 'function'); + }); + + it('should expose `scaffold`', function() { + assert(typeof generate.scaffold === 'function'); + }); + }); + + describe('prototype properties', function() { + beforeEach(function() { + generate = new Generate(); + }); + + it('should expose `name`', function() { + assert(typeof generate.name === 'string'); + }); + + it('should expose `base`', function() { + assert(typeof generate.base === 'object'); + }); + }); + + describe('instance', function() { + beforeEach(function() { + generate = new Generate(); + }); + + it('should default `name` to `generate`', function() { + assert.equal(generate.name, 'generate'); + }); + + it('should default `name` to `assemble`', function() { + delete generate._name; + assert.equal(generate.name, 'assemble'); + }); + + it('should default `name` to `base`', function() { + delete generate._name; + delete generate._appname; + assert.equal(generate.name, 'base'); + }); + + it('should set `name` to `base`', function() { + generate.name = 'base'; + assert.equal(generate.name, 'base'); + }); + + it('should use `options.name` for `name`', function() { + generate = new Generate({name: 'generate'}); + delete generate._name; + assert.equal(generate.name, 'generate'); + }); + + it('should return `this` as `base`', function() { + generate.base.should.deepEqual(generate); + }); + + it('should return generator "base" as `base`', function() { + var base = new Generate(); + generate.register('base', base); + generate.base.should.deepEqual(base); + }); + + it('should return generate as `base`', function() { + var child = new Generate(); + generate.register('child', child); + child.base.should.deepEqual(generate); + }); + }); +}); diff --git a/test/generate.logger.js b/test/generate.logger.js new file mode 100644 index 0000000..7d458dc --- /dev/null +++ b/test/generate.logger.js @@ -0,0 +1,95 @@ +var capture = require('capture-stream'); +var assert = require('assert'); + +var Logger = require('../lib/logger'); + +describe('logger', function() { + it('should create a Logger instance', function() { + var logger = new Logger(); + assert(logger instanceof Logger); + }); + + it('should create a Logger instance with provided options', function() { + var logger = new Logger({foo: 'bar'}); + assert.deepEqual(logger.options, {foo: 'bar'}); + }); + + it('should write a message out to the standard output', function() { + var restore = capture(process.stdout); + var logger = new Logger(); + logger.write('this is a test'); + var output = restore(true); + assert.equal(output, 'this is a test'); + }); + + describe('events', function() { + it('should emit `log` when `.log` is called', function() { + var output = []; + var logger = new Logger(); + logger.on('log', function(msg) { + output.push(msg); + }); + logger.log('this is a log message'); + assert.deepEqual(output, ['this is a log message']); + }); + + it('should emit `info` when `.info` is called', function() { + var output = []; + var logger = new Logger(); + logger.on('info', function(msg) { + output.push(msg); + }); + logger.info('this is an info message'); + assert.deepEqual(output, ['this is an info message']); + }); + + it('should emit `error` when `.error` is called', function() { + var output = []; + var logger = new Logger(); + logger.on('error', function(msg) { + output.push(msg); + }); + logger.error('this is an error message'); + assert.deepEqual(output, ['this is an error message']); + }); + + it('should emit `warn` when `.warn` is called', function() { + var output = []; + var logger = new Logger(); + logger.on('warn', function(msg) { + output.push(msg); + }); + logger.warn('this is a warning message'); + assert.deepEqual(output, ['this is a warning message']); + }); + + it('should chain methods together when emitting methods are called', function() { + var output = []; + var logger = new Logger(); + function handler(event) { + return function(msg) { + output.push(event + ': ' + msg); + }; + } + logger.on('log', handler('log')); + logger.on('info', handler('info')); + logger.on('error', handler('error')); + logger.on('warn', handler('warn')); + + logger + .log('this is a log message') + .info('this is an info message') + .error('this is an error message') + .warn('this is a warning message'); + + var expected = [ + 'log: this is a log message', + 'info: this is an info message', + 'error: this is an error message', + 'warn: this is a warning message' + ]; + + assert.deepEqual(output, expected); + }); + }); +}); diff --git a/test/generate.process.js b/test/generate.process.js new file mode 100644 index 0000000..cf9a4bd --- /dev/null +++ b/test/generate.process.js @@ -0,0 +1,245 @@ +'use strict'; + +require('mocha'); +var fs = require('fs'); +var path = require('path'); +var assert = require('assert'); +var rimraf = require('rimraf'); +var through = require('through2'); +var files = require('expand-files'); +var Generate = require('..'); +var app, files, config; + +var fixtures = path.join(__dirname, 'fixtures'); +var actual = path.join(__dirname, 'actual'); + +function expand(options) { + var config = files(); + config.expand(options); + return config.files[0]; +} + +function output(name) { + return path.join(actual, name); +} + +function fixture(name) { + return path.join(fixtures, name); +} + +function exists(name) { + try { + fs.statSync(output(name)); + return true; + } catch(err) {} + return false; +} + +function base(cb) { + return through.obj(function(file, enc, next) { + var str = file.contents.toString(); + cb(file, str, next); + }); +} + +describe('process plugins', function() { + beforeEach(function(cb) { + app = new Generate(); + rimraf(actual, cb); + }); + + afterEach(function(cb) { + rimraf(actual, cb); + }); + + describe('plugin', function() { + it('should use a plugin to modify file contents', function(cb) { + app.plugin('append', function(opts) { + opts = opts || {}; + return base(function(file, str, next) { + file.contents = new Buffer(str + opts.suffix); + next(null, file); + }); + }); + + config = expand({ + cwd: fixtures, + src: '*.txt', + dest: actual + }); + + app.process(config, {suffix: 'zzz'}) + .on('error', cb) + .on('data', function(data) { + var str = data.contents.toString(); + var end = str.slice(-3); + assert(end === 'zzz'); + }) + .on('end', function() { + assert(exists('example.txt')); + cb(); + }); + }); + + it('should run plugins defined on config.options', function(cb) { + function appendString(suffix) { + return base(function(file, str, next) { + file.contents = new Buffer(str + suffix); + next(null, file); + }); + } + + app.plugin('a', appendString('aaa')); + app.plugin('b', appendString('bbb')); + app.plugin('c', appendString('ccc')); + + config = expand({ + options: {pipeline: ['a', 'c']}, + cwd: fixtures, + src: 'a.txt', + dest: actual + }); + + app.process(config, {suffix: 'zzz'}) + .on('error', cb) + .on('data', function(data) { + var str = data.contents.toString(); + assert(str.indexOf('bbb') === -1); + var end = str.slice(-6); + assert(end === 'aaaccc'); + }) + .on('end', function() { + assert(exists('a.txt')); + cb(); + }); + }); + + it('should run plugins defined on process.options', function(cb) { + function appendString(suffix) { + return base(function(file, str, next) { + file.contents = new Buffer(str + suffix); + next(null, file); + }); + } + + app.plugin('a', appendString('aaa')); + app.plugin('b', appendString('bbb')); + app.plugin('c', appendString('ccc')); + + config = expand({ + cwd: fixtures, + src: 'a.txt', + dest: actual + }); + + app.process(config, {pipeline: ['a', 'c'], suffix: 'zzz'}) + .on('error', cb) + .on('data', function(data) { + var str = data.contents.toString(); + assert(str.indexOf('bbb') === -1); + var end = str.slice(-6); + assert(end === 'aaaccc'); + }) + .on('end', function() { + assert(exists('a.txt')); + cb(); + }); + }); + }); +}); + +describe('process()', function() { + beforeEach(function(cb) { + app = new Generate(); + rimraf(actual, cb); + }); + + afterEach(function(cb) { + rimraf(actual, cb); + }); + + describe('setup', function() { + it('should clean out all test fixtures', function(cb) { + assert(!exists(actual)); + cb(); + }); + }); + + describe('streams', function() { + it('should process files from the process options.cwd', function(cb) { + config = expand({src: 'b.txt', dest: actual, cwd: fixtures}); + + app.process(config, {cwd: fixtures}) + .on('error', cb) + .on('end', function() { + assert(exists('b.txt')); + cb(); + }); + }); + + it('should use the cwd passed on the config.options.cwd', function(cb) { + assert(!exists('b.txt')); + + config = expand({ + cwd: fixtures, + src: 'b.txt', + dest: actual + }); + + app.process(config) + .on('error', cb) + .on('end', function() { + assert(exists('b.txt')); + cb(); + }); + }); + + it('should work with no options:', function(cb) { + config = expand({src: 'b.txt', dest: actual, cwd: fixtures}); + app.process(config) + .on('error', cb) + .on('end', function() { + assert(exists('b.txt')); + cb(); + }); + }); + + it('should process a single file', function(cb) { + assert(!exists('a.txt')); + + config = expand({ + cwd: fixtures, + src: 'a.txt', + dest: actual + }); + + app.process(config) + .on('error', cb) + .on('end', function() { + assert(exists('a.txt')); + cb(); + }); + }); + + it('should process a glob of files', function(cb) { + assert(!exists('a.txt')); + assert(!exists('b.txt')); + assert(!exists('c.txt')); + + config = expand({ + cwd: fixtures, + src: '*.txt', + dest: actual + }); + + app.process(config) + .on('error', cb) + .on('end', function() { + assert(exists('a.txt')); + assert(exists('b.txt')); + assert(exists('c.txt')); + cb(); + }); + }); + }); +}); diff --git a/test/generate.register.js b/test/generate.register.js new file mode 100644 index 0000000..162ac69 --- /dev/null +++ b/test/generate.register.js @@ -0,0 +1,50 @@ +/* deps: coveralls istanbul */ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var Generate = support.resolve(); +var Base = Generate.Base; +var generate; + +describe('generate.register', function() { + beforeEach(function() { + generate = new Generate(); + }); + + it('should register a Generate instance', function() { + var child = new Generate(); + generate.register('child', child); + generate.generators.should.have.property('child'); + assert(typeof generate.generators.child === 'object'); + generate.generators.child.should.deepEqual(child); + }); + + it('should register a generator function', function() { + var registered = false; + var child = generate.register('child', function(app, base, env) { + registered = true; + assert(typeof app === 'object'); + assert(app.isGenerate === true); + }); + assert(registered); + generate.generators.should.have.property('child'); + assert(typeof generate.generators.child === 'object'); + generate.generators.child.should.deepEqual(child); + }); + + it('should register a non-generate instance', function() { + var child = new Base(); + generate.register('child', child); + generate.generators.should.have.property('child'); + assert(typeof generate.generators.child === 'object'); + generate.generators.child.should.deepEqual(child); + }); + + it('should register a generator from a string', function() { + var one = generate.register('one', './test/fixtures/one/generator.js'); + generate.generators.should.have.property('one'); + assert(typeof generate.generators.one === 'object'); + generate.generators.one.should.deepEqual(one); + }); +}); diff --git a/test/generate.registerPath.js b/test/generate.registerPath.js new file mode 100644 index 0000000..9e64767 --- /dev/null +++ b/test/generate.registerPath.js @@ -0,0 +1,28 @@ +/* deps: coveralls istanbul */ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var Generate = support.resolve(); +var Base = Generate.Base; +var generate; + +describe('generate.registerPath', function() { + beforeEach(function() { + generate = new Generate(); + }); + + it('should register a generator function from a file path', function() { + var one = generate.registerPath('one', './test/fixtures/one/generator.js'); + generate.generators.should.have.property('one'); + assert(typeof generate.generators.one === 'object'); + generate.generators.one.should.deepEqual(one); + }); + + it('should register a Generate instance from a file path', function() { + var two = generate.registerPath('two', './test/fixtures/two/generate.js'); + generate.generators.should.have.property('two'); + assert(typeof generate.generators.two === 'object'); + generate.generators.two.should.deepEqual(two); + }); +}); diff --git a/test/generate.scaffold.js b/test/generate.scaffold.js new file mode 100644 index 0000000..f9de9c4 --- /dev/null +++ b/test/generate.scaffold.js @@ -0,0 +1,234 @@ +'use strict'; + +require('mocha'); +var fs = require('fs'); +var path = require('path'); +var assert = require('assert'); +var rimraf = require('rimraf'); +var through = require('through2'); +var Scaffold = require('scaffold'); +var Generate = require('..'); +var app, scaffold, config; + +var fixtures = path.join(__dirname, 'fixtures'); +var actual = path.join(__dirname, 'actual'); + +function output(name) { + return path.join(actual, name); +} + +function fixture(name) { + return path.join(fixtures, name); +} + +function exists(name) { + try { + fs.statSync(output(name)); + return true; + } catch(err) {} + return false; +} + +function base(cb) { + return through.obj(function(file, enc, next) { + var str = file.contents.toString(); + cb(file, str, next); + }); +} + +describe('scaffolds', function() { + beforeEach(function(cb) { + app = new Generate(); + rimraf(actual, cb); + }); + + afterEach(function(cb) { + rimraf(actual, cb); + }); + + describe('setup', function() { + it('should clean out all test fixtures', function(cb) { + assert(!exists(actual)); + cb(); + }); + }); + + describe('targets', function() { + it.only('should process files from the process options.cwd', function(cb) { + scaffold = new Scaffold({ + docs: { + src: 'b.txt', + dest: actual, + cwd: fixtures + } + }); + + app.scaffold(scaffold, {cwd: fixtures}, function(err) { + if (err) return cb(err); + assert(exists('b.txt')); + cb(); + }); + }); + + it('should use the cwd passed on the config.options.cwd', function(cb) { + assert(!exists('b.txt')); + + scaffold = new Scaffold({ + cwd: fixtures, + src: 'b.txt', + dest: actual + }); + + app.scaffold(scaffold) + .on('error', cb) + .on('end', function() { + assert(exists('b.txt')); + cb(); + }); + }); + + it('should work with no options:', function(cb) { + scaffold = new Scaffold({src: 'b.txt', dest: actual, cwd: fixtures}); + app.scaffold(scaffold) + .on('error', cb) + .on('end', function() { + assert(exists('b.txt')); + cb(); + }); + }); + + it('should process a single file', function(cb) { + assert(!exists('a.txt')); + + scaffold = new Scaffold({ + cwd: fixtures, + src: 'a.txt', + dest: actual + }); + + app.scaffold(scaffold) + .on('error', cb) + .on('end', function() { + assert(exists('a.txt')); + cb(); + }); + }); + + it('should process a glob of files', function(cb) { + assert(!exists('a.txt')); + assert(!exists('b.txt')); + assert(!exists('c.txt')); + + scaffold = new Scaffold({ + cwd: fixtures, + src: '*.txt', + dest: actual + }); + + app.scaffold(scaffold) + .on('error', cb) + .on('end', function() { + assert(exists('a.txt')); + assert(exists('b.txt')); + assert(exists('c.txt')); + cb(); + }); + }); + }); + + describe('plugin', function() { + it('should use a plugin to modify file contents', function(cb) { + app.plugin('append', function(opts) { + opts = opts || {}; + return base(function(file, str, next) { + file.contents = new Buffer(str + opts.suffix); + next(null, file); + }); + }); + + scaffold = new Scaffold({ + cwd: fixtures, + src: '*.txt', + dest: actual + }); + + app.scaffold(scaffold, {suffix: 'zzz'}) + .on('error', cb) + .on('data', function(data) { + var str = data.contents.toString(); + var end = str.slice(-3); + assert(end === 'zzz'); + }) + .on('end', function() { + assert(exists('example.txt')); + cb(); + }); + }); + + it('should run plugins defined on config.options', function(cb) { + function appendString(suffix) { + return base(function(file, str, next) { + file.contents = new Buffer(str + suffix); + next(null, file); + }); + } + + app.plugin('a', appendString('aaa')); + app.plugin('b', appendString('bbb')); + app.plugin('c', appendString('ccc')); + + scaffold = new Scaffold({ + options: {pipeline: ['a', 'c']}, + cwd: fixtures, + src: 'a.txt', + dest: actual + }); + + app.scaffold(scaffold, {suffix: 'zzz'}) + .on('error', cb) + .on('data', function(data) { + var str = data.contents.toString(); + assert(str.indexOf('bbb') === -1); + var end = str.slice(-6); + assert(end === 'aaaccc'); + }) + .on('end', function() { + assert(exists('a.txt')); + cb(); + }); + }); + + it('should run plugins defined on process.options', function(cb) { + function appendString(suffix) { + return base(function(file, str, next) { + file.contents = new Buffer(str + suffix); + next(null, file); + }); + } + + app.plugin('a', appendString('aaa')); + app.plugin('b', appendString('bbb')); + app.plugin('c', appendString('ccc')); + + scaffold = new Scaffold({ + cwd: fixtures, + src: 'a.txt', + dest: actual + }); + + app.scaffold(scaffold, {pipeline: ['a', 'c'], suffix: 'zzz'}) + .on('error', cb) + .on('data', function(data) { + var str = data.contents.toString(); + assert(str.indexOf('bbb') === -1); + var end = str.slice(-6); + assert(end === 'aaaccc'); + }) + .on('end', function() { + assert(exists('a.txt')); + cb(); + }); + }); + }); +}); + diff --git a/test/generate.to-tasks.js b/test/generate.to-tasks.js new file mode 100644 index 0000000..f8e5c43 --- /dev/null +++ b/test/generate.to-tasks.js @@ -0,0 +1,13 @@ +'use strict'; + +require('mocha'); +require('should'); +var assert = require('assert'); +var toTasks = require('../lib/to-tasks'); + +describe('to-tasks', function() { + it('should create a task', function() { + console.log(toTasks({_: ['foo', 'bar']}, {}, {})) + }); +}); + diff --git a/test/generate.utils.js b/test/generate.utils.js new file mode 100644 index 0000000..ed3e647 --- /dev/null +++ b/test/generate.utils.js @@ -0,0 +1,239 @@ +var assert = require('assert'); +var utils = require('../lib/utils'); +var mkdirp = require('mkdirp'); +var rimraf = require('rimraf'); + +describe('utils', function() { + describe('alias', function() { + it('should throw an error when pkgName is not a string', function(done) { + try { + utils.alias(null, {}); + done(new Error('expected an error')); + } catch (err) { + assert.equal(err.message, 'expected name to be a string'); + done(); + } + }); + + it('should use the cwd when filepath and pkgName are undefined', function() { + assert.equal(utils.alias(), 'generate'); + }); + + it('should create an alias from a given pkgName', function() { + assert.equal(utils.alias(null, 'generator-foo'), 'foo'); + }); + + it.skip('should create an alias from a given file filepath', function() { + assert.equal(utils.alias(__filename), 'test'); + }); + + it.skip('should create an alias from a given directory filepath', function() { + assert.equal(utils.alias(__dirname), 'test'); + }); + }); + + describe('arrayify', function() { + it('should return original array', function() { + assert.deepEqual(utils.arrayify(['foo', 'bar']), ['foo', 'bar']); + }); + + it('should return array when given a string', function() { + assert.deepEqual(utils.arrayify('foo'), ['foo']); + }); + + it('should return array when given an object', function() { + assert.deepEqual(utils.arrayify({foo: 'bar'}), [{foo: 'bar'}]); + }); + + it('should return array when given a function', function() { + var foo = function() {}; + assert.deepEqual(utils.arrayify(foo), [foo]); + }); + + it('should return an empty array when given null', function() { + assert.deepEqual(utils.arrayify(null), []); + }); + + it('should return an empty array when given undefined', function() { + assert.deepEqual(utils.arrayify(), []); + }); + + it('should return an empty array when given false', function() { + assert.deepEqual(utils.arrayify(false), []); + }); + }); + + describe('createAlias', function() { + it('should throw an error when name is undefined', function(done) { + try { + utils.createAlias(); + done(new Error('expected an error')); + } catch (err) { + assert.equal(err.message, 'expected name to be a string'); + done(); + } + }); + + it('should throw an error when name is not a string', function(done) { + try { + utils.createAlias({}); + done(new Error('expected an error')); + } catch (err) { + assert.equal(err.message, 'expected name to be a string'); + done(); + } + }); + + it('should return original name when name does not contain a -', function() { + assert.equal(utils.createAlias('foo'), 'foo'); + }); + + it('should return an alias when name contains a -', function() { + assert.equal(utils.createAlias('generator-foo'), 'foo'); + }); + + it('should return an alias when name contains multiple -', function() { + assert.equal(utils.createAlias('generator-foo-bar'), 'foo-bar'); + }); + }); + + describe('exists', function() { + it('should return true for ' + __filename, function() { + assert(utils.exists(__filename)); + }); + + it('should return false for something-that-does-not-exist.txt', function() { + assert(utils.exists('something-that-does-not-exist.txt') === false); + }); + }); + + describe('flatten', function() { + + }); + + describe('globFiles', function() { + it('should return files from process.cwd', function() { + assert.deepEqual(utils.globFiles(['index.js']), [process.cwd() + '/index.js']); + }); + + it('should return files from custom cwd', function() { + assert.deepEqual(utils.globFiles(['*.js'], {cwd: __dirname + '/fixtures/one'}), [__dirname + '/fixtures/one/generator.js']); + }); + }); + + describe('isDirectory', function() { + it('should return true for ' + __dirname, function() { + assert(utils.isDirectory(__dirname)); + }); + + it('should return false for ' + __filename, function() { + assert(utils.isDirectory(__filename) === false); + }); + + it('should return false for something-that-does-not-exist', function() { + assert(utils.isDirectory(__dirname + '/something-that-does-not-exist') === false); + }); + }); + + describe('isEmpty', function() { + before(function(done) { + mkdirp(__dirname + '/fixtures/empty', done); + }); + + after(function(done) { + rimraf(__dirname + '/fixtures/empty', done); + }); + + it('should return false when the directory does not exist', function() { + assert(utils.isEmpty(__dirname + '/something-that-does-not-exist/') === false); + }); + + it('should return false when the directory is not empty', function() { + assert(utils.isEmpty(__dirname) === false); + }); + + it('should return true when an empty directory exists', function() { + assert(utils.isEmpty(__dirname + '/fixtures/empty')); + }); + + it('should return true using custom filter function', function() { + assert(utils.isEmpty(__dirname, function() { return false; })); + }); + + it('should return false using custom filter function', function() { + assert(utils.isEmpty(__dirname, function() { return true; }) === false); + }); + }); + + describe('isObject', function() { + it('should return true for an object', function() { + assert(utils.isObject({})); + }); + + it('should return false for a function', function() { + assert(utils.isObject(function() {}) === false); + }); + + it('should return false for a string', function() { + assert(utils.isObject('foo') === false); + }); + + it('should return false for an array', function() { + assert(utils.isObject([]) === false); + }); + + it('should return false for undefined', function() { + assert(utils.isObject() === false); + }); + + it('should return false for null', function() { + assert(utils.isObject(null) === false); + }); + + it('should return false for a number', function() { + assert(utils.isObject(5) === false); + }); + + it('should return false for a boolean', function() { + assert(utils.isObject(true) === false); + }); + }); + + describe('logger', function() { + + }); + + describe('runtimes', function() { + + }); + + describe('tableize', function() { + + }); + + describe('timestamp', function() { + + }); + + describe('toKey', function() { + it('should make a key from a namespace and prop', function() { + assert.equal(utils.toKey('generators', 'foo.bar'), 'generators.foo.generators.bar'); + }); + + it('should make a key from a namespace and prop starting with the namespace', function() { + assert.equal(utils.toKey('generators', 'generators.foo.bar'), 'generators.foo.generators.bar'); + }); + + it('should make a key from a namespace and prop with multiple namespace segments', function() { + assert.equal(utils.toKey('generators', 'generators.foo.generators.bar'), 'generators.foo.generators.bar'); + }); + }); + + describe('tryRequire', function() { + + }); + + describe('tryResolve', function() { + + }); +}); diff --git a/test/generator.task.js b/test/generator.task.js new file mode 100644 index 0000000..474222f --- /dev/null +++ b/test/generator.task.js @@ -0,0 +1,155 @@ +var assert = require('assert'); +var App = require('..'); +var app; + +describe('task()', function() { + beforeEach(function() { + app = new App(); + }); + + it('should register a task', function() { + var fn = function(cb) { + cb(); + }; + app.task('default', fn); + assert.equal(typeof app.tasks.default, 'object'); + assert.equal(app.tasks.default.fn, fn); + }); + + it('should register a task with an array of dependencies', function() { + app.task('default', ['foo', 'bar'], function(cb) { + cb(); + }); + assert.equal(typeof app.tasks.default, 'object'); + assert.deepEqual(app.tasks.default.deps, ['foo', 'bar']); + }); + + it('should register a task with a list of strings as dependencies', function() { + app.task('default', 'foo', 'bar', function(cb) { + cb(); + }); + assert.equal(typeof app.tasks.default, 'object'); + assert.deepEqual(app.tasks.default.deps, ['foo', 'bar']); + }); + + it('should run a task', function(cb) { + var count = 0; + app.task('default', function(cb) { + count++; + cb(); + }); + + app.build('default', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should throw an error when a task with unregistered dependencies is run', function(cb) { + var count = 0; + app.task('default', ['foo', 'bar'], function(cb) { + count++; + cb(); + }); + + app.build('default', function(err) { + if (!err) return cb(new Error('Expected an error to be thrown.')); + assert.equal(count, 0); + cb(); + }); + }); + + it('should throw an error when `.build` is called without a callback function.', function() { + try { + app.build('default'); + throw new Error('Expected an error to be thrown.'); + } catch (err) { + } + }); + + it('should emit task events', function(cb) { + var events = []; + app.on('task:starting', function(task) { + events.push('starting.' + task.name); + }); + app.on('task:finished', function(task) { + events.push('finished.' + task.name); + }); + app.on('task:error', function(err, task) { + events.push('error.' + task.name); + }); + app.task('foo', function(cb) { + cb(); + }); + app.task('bar', ['foo'], function(cb) { + cb(); + }); + app.task('default', ['bar']); + app.build('default', function(err) { + if (err) return cb(err); + assert.deepEqual(events, [ + 'starting.default', + 'starting.bar', + 'starting.foo', + 'finished.foo', + 'finished.bar', + 'finished.default' + ]); + cb(); + }); + }); + + it('should emit an error event when an error is passed back in a task', function(cb) { + app.on('error', function(err) { + assert(err); + assert.equal(err.message, 'This is an error'); + }); + app.task('default', function(cb) { + return cb(new Error('This is an error')); + }); + app.build('default', function(err) { + if (err) return cb(); + cb(new Error('Expected an error')); + }); + }); + + it('should emit an error event when an error is thrown in a task', function(cb) { + var errors = 0; + app.on('error', function(err) { + errors++; + assert(err); + assert.equal(err.message, 'This is an error'); + }); + app.task('default', function(cb) { + cb(new Error('This is an error')); + }); + app.build('default', function(err) { + assert.equal(errors, 1); + if (err) return cb(); + cb(new Error('Expected an error')); + }); + }); + + it('should run dependencies before running the dependent task.', function(cb) { + var seq = []; + app.task('foo', function(cb) { + seq.push('foo'); + cb(); + }); + app.task('bar', function(cb) { + seq.push('bar'); + cb(); + }); + app.task('default', ['foo', 'bar'], function(cb) { + seq.push('default'); + cb(); + }); + + app.build('default', function(err) { + if (err) return cb(err); + assert.deepEqual(seq, ['foo', 'bar', 'default']); + cb(); + }); + }); +}); diff --git a/test/generators.js b/test/generators.js new file mode 100644 index 0000000..2157ec9 --- /dev/null +++ b/test/generators.js @@ -0,0 +1,20 @@ +var assert = require('assert'); +var Generate = require('..'); +var app; + +describe('generators', function() { + beforeEach(function() { + app = new Generate(); + }); + + it('should add a generator to app.generators', function() { + app.register('abc', function() {}); + assert(app.generators.abc); + assert(typeof app.generators.abc === 'object'); + }); + + it('should be an instance of generate', function() { + app.register('foo', function() {}); + assert(app.generators.foo instanceof Generate); + }); +}); diff --git a/test/helpers.js b/test/helpers.js index b842ef2..eb5633d 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -3,7 +3,7 @@ require('mocha'); require('should'); var path = require('path'); -var Base = require('base'); +var Base = require('base-methods'); var assert = require('assert'); var consolidate = require('consolidate'); var handlebars = require('engine-handlebars'); diff --git a/test/render.js b/test/render.js index 04540b6..2ab9280 100644 --- a/test/render.js +++ b/test/render.js @@ -1,5 +1,3 @@ -'use strict'; - require('mocha'); require('should'); var assert = require('assert'); @@ -7,7 +5,7 @@ var support = require('./support'); var App = support.resolve(); var app; -describe.skip('render', function() { +describe('render', function() { describe('engine', function() { var view; diff --git a/test/store.js b/test/store.js index e75ec6e..773d904 100644 --- a/test/store.js +++ b/test/store.js @@ -23,16 +23,16 @@ describe('store', function() { it('should create a store at the given `cwd`', function() { app = new App({store: {cwd: __dirname + '/actual'}}); app.store.set('foo', 'bar'); - assert(path.basename(app.store.path) === 'update.json'); + assert(path.basename(app.store.path) === 'generate.json'); assert(app.store.data.hasOwnProperty('foo')); assert(app.store.data.foo === 'bar'); - assert(fs.existsSync(path.join(__dirname, 'actual', 'update.json'))); + assert(fs.existsSync(path.join(__dirname, 'actual', 'generate.json'))); }); it('should create a store using the given `indent` value', function() { app = new App({store: {cwd: __dirname + '/actual', indent: 0}}); app.store.set('foo', 'bar'); - var contents = fs.readFileSync(path.join(__dirname, 'actual', 'update.json'), 'utf8'); + var contents = fs.readFileSync(path.join(__dirname, 'actual', 'generate.json'), 'utf8'); assert(contents === '{"foo":"bar"}'); }); @@ -166,7 +166,7 @@ describe('store', function() { describe('events', function() { beforeEach(function() { app = new App(); - app.store = new Store('update-tests'); + app.store = new Store('generate-tests'); }); afterEach(function(cb) { @@ -228,7 +228,6 @@ describe('events', function() { it('should emit deleted keys on `del`:', function(cb) { app.store.once('del', function(key) { - console.log(key) assert(key === 'a'); cb(); }); diff --git a/test/support/ignore.js b/test/support/ignore.js index 7dcbb75..ef59903 100644 --- a/test/support/ignore.js +++ b/test/support/ignore.js @@ -3,4 +3,4 @@ module.exports = [ 'removeEventListener', 'removeAllListeners', 'removeListener' -]; +]; \ No newline at end of file diff --git a/test/support/index.js b/test/support/index.js index e2194a5..fc91929 100644 --- a/test/support/index.js +++ b/test/support/index.js @@ -1,7 +1,7 @@ 'use strict'; var path = require('path'); -var loadpkg = require('load-pkg'); +var loadPkg = require('load-pkg'); var assert = require('assert'); var ignore = require('./ignore'); var cache = {}; @@ -36,7 +36,7 @@ exports.resolve = function(filepath) { return cache[key]; } - var pkg = loadpkg.sync(process.cwd()); + var pkg = loadPkg.sync(process.cwd()); var prefix = pkg.name !== 'templates' ? 'templates' : ''; @@ -56,9 +56,20 @@ exports.resolve = function(filepath) { function tryResolve(name) { try { return require.resolve(name); - } catch (err) {} + } catch(err) {} try { return require.resolve(path.resolve(name)); - } catch (err) {} + } catch(err) {} } + +function tryRequire(name) { + try { + return require(name); + } catch(err) {} + + try { + return require(path.resolve(name)); + } catch(err) {} +} + diff --git a/test/to-tasks.js b/test/to-tasks.js new file mode 100644 index 0000000..ed6c688 --- /dev/null +++ b/test/to-tasks.js @@ -0,0 +1,378 @@ +var assert = require('assert'); +var minimist = require('minimist'); +var support = require('./support'); +var Generate = support.resolve(); +var generate; + +var toTasks = require('../lib/to-tasks'); + +describe('to-tasks', function() { + beforeEach(function() { + generate = new Generate(); + }); + + it('should return default empty objects when only passing in argv as an empty array', function() { + var tasks = toTasks([]); + var expected = { + _: [], + unknown: ['default'], + commands: {}, + options: {}, + tasks: [] + }; + assert.deepEqual(tasks, expected); + }); + + it('should return default empty objects with blank unknown when only passing in argv as an empty string', function() { + var tasks = toTasks(''); + var expected = { + _: [], + unknown: [''], + commands: {}, + options: {}, + tasks: [] + }; + assert.deepEqual(tasks, expected); + }); + + it('should return default empty objects with string in unknown when only passing in argv as a string', function() { + var tasks = toTasks('foo'); + var expected = { + _: [], + unknown: ['foo'], + commands: {}, + options: {}, + tasks: [] + }; + assert.deepEqual(tasks, expected); + }); + + it('should process an argv object and return all unknowns', function() { + var argv = minimist(['foo', 'bar', 'baz']); + var tasks = toTasks(argv); + var expected = { + _: [], + unknown: ['foo', 'bar', 'baz'], + commands: {}, + options: {}, + tasks: [] + }; + assert.deepEqual(tasks, expected); + }); + + it('should process an argv object with options', function() { + var argv = minimist(['foo', '--bar', '--baz']); + var tasks = toTasks(argv); + var expected = { + _: [], + unknown: ['foo'], + commands: {}, + options: { + bar: true, + baz: true + }, + tasks: [] + }; + assert.deepEqual(tasks, expected); + }); + + it('should process an argv object with commands', function() { + var argv = minimist(['--tasks']); + var tasks = toTasks(argv, generate); + var expected = { + _: [], + unknown: ['default'], + commands: { + tasks: true + }, + options: {}, + tasks: [] + }; + assert.deepEqual(tasks, expected); + }); + + it('should process an argv object with commands without using --', function() { + var argv = minimist(['tasks']); + var tasks = toTasks(argv, generate); + var expected = { + _: [], + unknown: ['tasks'], + commands: {}, + options: {}, + tasks: [] + }; + assert.deepEqual(tasks, expected); + }); + + it('should process an argv object with a generator specified', function() { + var argv = minimist(['foo:bar']); + generate.register('foo', function(app) { + app.task('bar', function(){}); + }); + + var tasks = toTasks(argv, generate); + var expected = { + _: [], + unknown: [], + commands: {}, + options: {}, + tasks: ['generators.foo:bar'] + }; + assert.deepEqual(tasks, expected); + }); + + it('should process an argv object with many generators specified', function() { + var argv = minimist(['foo:bar', 'beep:boop']); + generate.register('foo', function(app) { + app.task('bar', function(){}); + }); + + generate.register('beep', function(app) { + app.task('boop', function(){}); + }); + + var tasks = toTasks(argv, generate); + var expected = { + _: [], + unknown: [], + commands: {}, + options: {}, + tasks: [ + 'generators.foo:bar', + 'generators.beep:boop' + ] + }; + assert.deepEqual(tasks, expected); + }); + + it('should process an argv object with deeply nested generators specified', function() { + var argv = minimist(['foo.beep:boop']); + generate.register('foo', function(app) { + app.task('bar', function(){}); + app.register('beep', function(app) { + app.task('boop', function(){}); + }); + }); + + + var tasks = toTasks(argv, generate); + var expected = { + _: [], + unknown: [], + commands: {}, + options: {}, + tasks: ['generators.foo.generators.beep:boop'] + }; + assert.deepEqual(tasks, expected); + }); + + it('should process an argv object with deeply nested generators and many tasks specified', function() { + var argv = minimist(['foo.beep:boop,bop', 'foo:bar,baz']); + generate.register('foo', function(app) { + app.task('bar', function(){}); + app.task('baz', function(){}); + app.register('beep', function(app) { + app.task('boop', function(){}); + app.task('bop', function(){}); + }); + }); + + + var tasks = toTasks(argv, generate); + var expected = { + _: [], + unknown: [], + commands: {}, + options: {}, + tasks: [ + 'generators.foo.generators.beep:boop,bop', + 'generators.foo:bar,baz' + ] + }; + assert.deepEqual(tasks, expected); + }); + + it('should process an argv object with only a task specified', function() { + var argv = minimist(['bar']); + generate.task('bar', function(){}); + generate.task('baz', function(){}); + + var tasks = toTasks(argv, generate); + var expected = { + _: [], + unknown: [], + commands: {}, + options: {}, + tasks: ['bar'] + }; + assert.deepEqual(tasks, expected); + }); + + it('should process an argv object with many tasks specified', function() { + var argv = minimist(['bar', 'baz']); + generate.task('bar', function(){}); + generate.task('baz', function(){}); + + var tasks = toTasks(argv, generate); + var expected = { + _: [], + unknown: [], + commands: {}, + options: {}, + tasks: ['bar', 'baz'] + }; + assert.deepEqual(tasks, expected); + }); + + it('should process an argv object with many comma separated tasks specified', function() { + var argv = minimist(['bar,baz']); + generate.task('bar', function(){}); + generate.task('baz', function(){}); + + var tasks = toTasks(argv, generate); + var expected = { + _: [], + unknown: [], + commands: {}, + options: {}, + tasks: ['bar,baz'] + }; + assert.deepEqual(tasks, expected); + }); + + it('should fallback to a "base" task', function() { + var argv = minimist(['default']); + generate.register('base', function(app) { + app.task('default', function() {}); + }); + generate.task('bar', function(){}); + generate.task('baz', function(){}); + + var tasks = toTasks(argv, generate); + var expected = { + _: [], + unknown: [], + commands: {}, + options: {}, + tasks: ['generators.base:default'] + }; + assert.deepEqual(tasks, expected); + }); + + it('should fallback to a nested generator "default" task', function() { + var argv = minimist(['foo']); + generate.register('base', function(app) { + app.task('default', function() {}); + }); + generate.register('foo', function(app) { + app.task('default', function() {}); + }); + generate.task('bar', function(){}); + generate.task('baz', function(){}); + + var tasks = toTasks(argv, generate); + var expected = { + _: [], + unknown: [], + commands: {}, + options: {}, + tasks: ['generators.foo:default'] + }; + assert.deepEqual(tasks, expected); + }); + + it('should fallback to a nested generator "default" task', function() { + var argv = minimist(['foo']); + generate.register('base', function(app) { + app.task('default', function() {}); + }); + generate.register('foo', function(app) { + app.task('default', function() {}); + }); + generate.task('bar', function(){}); + generate.task('baz', function(){}); + + var tasks = toTasks(argv, generate); + var expected = { + _: [], + unknown: [], + commands: {}, + options: {}, + tasks: ['generators.foo:default'] + }; + assert.deepEqual(tasks, expected); + }); + + it('should fallback to root generator when task has a dot', function() { + var argv = minimist(['foo:bar.2']); + generate.register('base', function(app) { + app.task('default', function() {}); + }); + generate.register('foo', function(app) { + app.task('default', function() {}); + }); + generate.task('bar', function(){}); + generate.task('baz', function(){}); + generate.task('bar.2', function(){}); + + var tasks = toTasks(argv, generate); + var expected = { + _: [], + unknown: [], + commands: {}, + options: {}, + tasks: ['bar.2'] + }; + assert.deepEqual(tasks, expected); + }); + + it('should throw an error when a task is not found', function(done) { + var argv = minimist(['bar,nothing']); + generate.task('bar', function(){}); + generate.task('baz', function(){}); + + try { + var tasks = toTasks(argv, generate); + done(new Error('expected an error when a task is not found.')); + } catch(err) { + assert.equal(err.message, 'task "nothing" is not registered'); + done(); + } + }); + + it('should throw an error when a task on a sub generator is not found', function(done) { + var argv = minimist(['beep:boop,nothing']); + generate.task('bar', function(){}); + generate.task('baz', function(){}); + generate.register('beep', function(app) { + app.task('boop', function(){}); + }); + + try { + var tasks = toTasks(argv, generate); + console.log(tasks); + done(new Error('expected an error when a task is not found.')); + } catch(err) { + assert.equal(err.message, 'task "beep:nothing" is not registered on generator "beep"'); + done(); + } + }); + + it('should throw an error when a sub generator is not found', function(done) { + var argv = minimist(['bep:boop,nothing']); + generate.task('bar', function(){}); + generate.task('baz', function(){}); + generate.register('beep', function(app) { + app.task('boop', function(){}); + }); + + try { + var tasks = toTasks(argv, generate); + console.log(tasks); + done(new Error('expected an error when a task is not found.')); + } catch(err) { + assert.equal(err.message, 'task "bep:boop" is not registered on generator "bep"'); + done(); + } + }); +}); From 1fba32ede0d6a8ebb65580cc48b9850c52115d15 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 9 Jan 2016 17:52:01 -0500 Subject: [PATCH 159/274] rename files --- test/{generate.compose.js => update.compose.js} | 0 test/{generate.extendGenerator.js => update.extendGenerator.js} | 0 test/{generate.generator.js => update.generator.js} | 0 test/{generate.getGenerator.js => update.getGenerator.js} | 0 test/{generate.js => update.js} | 0 test/{generate.logger.js => update.logger.js} | 0 test/{generate.process.js => update.process.js} | 0 test/{generate.register.js => update.register.js} | 0 test/{generate.registerPath.js => update.registerPath.js} | 0 test/{generate.scaffold.js => update.scaffold.js} | 0 test/{generate.to-tasks.js => update.to-tasks.js} | 0 test/{generate.utils.js => update.utils.js} | 0 12 files changed, 0 insertions(+), 0 deletions(-) rename test/{generate.compose.js => update.compose.js} (100%) rename test/{generate.extendGenerator.js => update.extendGenerator.js} (100%) rename test/{generate.generator.js => update.generator.js} (100%) rename test/{generate.getGenerator.js => update.getGenerator.js} (100%) rename test/{generate.js => update.js} (100%) rename test/{generate.logger.js => update.logger.js} (100%) rename test/{generate.process.js => update.process.js} (100%) rename test/{generate.register.js => update.register.js} (100%) rename test/{generate.registerPath.js => update.registerPath.js} (100%) rename test/{generate.scaffold.js => update.scaffold.js} (100%) rename test/{generate.to-tasks.js => update.to-tasks.js} (100%) rename test/{generate.utils.js => update.utils.js} (100%) diff --git a/test/generate.compose.js b/test/update.compose.js similarity index 100% rename from test/generate.compose.js rename to test/update.compose.js diff --git a/test/generate.extendGenerator.js b/test/update.extendGenerator.js similarity index 100% rename from test/generate.extendGenerator.js rename to test/update.extendGenerator.js diff --git a/test/generate.generator.js b/test/update.generator.js similarity index 100% rename from test/generate.generator.js rename to test/update.generator.js diff --git a/test/generate.getGenerator.js b/test/update.getGenerator.js similarity index 100% rename from test/generate.getGenerator.js rename to test/update.getGenerator.js diff --git a/test/generate.js b/test/update.js similarity index 100% rename from test/generate.js rename to test/update.js diff --git a/test/generate.logger.js b/test/update.logger.js similarity index 100% rename from test/generate.logger.js rename to test/update.logger.js diff --git a/test/generate.process.js b/test/update.process.js similarity index 100% rename from test/generate.process.js rename to test/update.process.js diff --git a/test/generate.register.js b/test/update.register.js similarity index 100% rename from test/generate.register.js rename to test/update.register.js diff --git a/test/generate.registerPath.js b/test/update.registerPath.js similarity index 100% rename from test/generate.registerPath.js rename to test/update.registerPath.js diff --git a/test/generate.scaffold.js b/test/update.scaffold.js similarity index 100% rename from test/generate.scaffold.js rename to test/update.scaffold.js diff --git a/test/generate.to-tasks.js b/test/update.to-tasks.js similarity index 100% rename from test/generate.to-tasks.js rename to test/update.to-tasks.js diff --git a/test/generate.utils.js b/test/update.utils.js similarity index 100% rename from test/generate.utils.js rename to test/update.utils.js From af800870d9a537ba1c88ab92bac0031ae92bdbae Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sun, 10 Jan 2016 05:16:59 -0500 Subject: [PATCH 160/274] clean up --- .eslintrc | 125 ++ .gitignore | 22 +- .travis.yml | 2 - .verb.md | 122 +- bin/cli.js | 76 +- bin2/update.js | 38 + index.js | 42 +- lib/utils.js | 60 +- lib2/config.js | 30 + lib2/locals.js | 26 + {lib => lib2}/middleware/index.js | 0 {lib => lib2}/middleware/json.js | 0 {lib => lib2}/runner/argv.js | 0 {lib => lib2}/runner/decorate.js | 0 {lib => lib2}/runner/env.js | 0 {lib => lib2}/runner/list.js | 0 {lib => lib2}/runner/listen.js | 0 {lib => lib2}/runner/run.js | 0 {lib => lib2}/runner/runner.js | 0 {lib => lib2}/runner/updater.js | 0 {lib => lib2}/tasks/default.js | 0 {lib => lib2}/tasks/del.js | 0 {lib => lib2}/tasks/dest.js | 0 {lib => lib2}/tasks/files.js | 0 {lib => lib2}/tasks/index.js | 0 {lib => lib2}/tasks/lint.js | 0 {lib => lib2}/tasks/list.js | 0 {lib => lib2}/tasks/noop.js | 0 {lib => lib2}/tasks/rename.js | 0 {lib => lib2}/tasks/tree.js | 0 lib2/utils.js | 329 +++++ package.json | 46 +- test/app.renderFile.js | 4 +- test/app.symlink.js | 4 +- test/app.toStream.js | 4 +- test/fixtures/three/four/five/generator.js | 12 +- test/fixtures/three/four/generator.js | 12 +- test/fixtures/three/generator.js | 14 - test/fixtures/three/updatefile.js | 14 + test/fixtures/two/generate.js | 21 - test/fixtures/two/updatefile.js | 21 + test/generators.js | 8 +- test/helpers.js | 6 +- test/store.js | 8 +- test/to-tasks.js | 378 ------ test/update.compose.js | 20 +- test/update.extendGenerator.js | 12 +- test/update.generator.js | 28 +- test/update.getGenerator.js | 16 +- test/update.js | 88 +- test/update.logger.js | 95 -- test/update.register.js | 40 +- test/update.registerPath.js | 26 +- test/update.scaffold.js | 234 ---- test/update.to-tasks.js | 13 - test/update.utils.js | 239 ---- test2/app.applyLayout.js | 87 ++ test2/app.collection.compile.js | 50 + test2/app.collection.js | 186 +++ test2/app.collection.render.js | 157 +++ test2/app.compile.js | 52 + test2/app.copy.js | 31 + test2/app.create.js | 244 ++++ test2/app.data.js | 94 ++ test2/app.dest.js | 1103 ++++++++++++++++ test2/app.engines.js | 160 +++ test2/app.events.js | 113 ++ test2/app.get-set.js | 72 ++ test2/app.handle.js | 37 + test2/app.handlers.js | 72 ++ test2/app.js | 130 ++ test2/app.list.compile.js | 44 + test2/app.list.js | 111 ++ test2/app.lookups.js | 112 ++ test2/app.middleware.js | 60 + test2/app.onLoad.js | 48 + test2/app.option.js | 98 ++ test2/app.render.js | 88 ++ test2/app.renderFile.js | 135 ++ test2/app.route.js | 93 ++ test2/app.src.js | 295 +++++ test2/app.symlink.js | 396 ++++++ test2/app.task.js | 159 +++ test2/app.toStream.js | 64 + test2/app.use.js | 281 ++++ test2/app.view.compile.js | 38 + test2/app.view.render.js | 92 ++ test2/app.watch.js | 42 + test2/collection.engines.js | 176 +++ test2/collection.events.js | 27 + test2/collection.getView.js | 34 + test2/collection.js | 540 ++++++++ test2/collection.options.js | 25 + test2/collection.render.js | 138 ++ test2/collection.use.js | 156 +++ test2/fixtures/bom-utf16be.txt | Bin 0 -> 244 bytes test2/fixtures/bom-utf16le.txt | Bin 0 -> 244 bytes test2/fixtures/bom-utf8.txt | 1 + test2/fixtures/copy/a.txt | 1 + test2/fixtures/copy/b.txt | 1 + test2/fixtures/copy/c.txt | 1 + test2/fixtures/copy/example.txt | 1 + test2/fixtures/data/a.json | 3 + test2/fixtures/data/alert.json | 7 + test2/fixtures/data/b.json | 3 + test2/fixtures/data/c.json | 3 + test2/fixtures/data/data.json | 3 + test2/fixtures/data/test.json | 4 + test2/fixtures/example.txt | 1 + .../front-matter/autodetect-no-lang.md | 5 + .../fixtures/front-matter/autodetect-yaml.md | 5 + test2/fixtures/front-matter/lang-yaml.md | 5 + test2/fixtures/generic/run.dmc | 1 + test2/fixtures/generic/test.dmc | 1 + test2/fixtures/helpers/a.js | 3 + test2/fixtures/helpers/b.js | 3 + test2/fixtures/helpers/c.js | 3 + test2/fixtures/helpers/obj.js | 9 + test2/fixtures/noext/license | 21 + test2/fixtures/pages/a.hbs | 2 + test2/fixtures/pages/b.hbs | 2 + test2/fixtures/pages/c.hbs | 2 + {test => test2}/fixtures/pipeline/a.js | 0 {test => test2}/fixtures/pipeline/b.js | 0 {test => test2}/fixtures/pipeline/c.js | 0 {test => test2}/fixtures/pipeline/d.js | 0 test2/fixtures/posts/a.txt | 4 + test2/fixtures/posts/b.txt | 4 + test2/fixtures/posts/c.txt | 4 + test2/fixtures/templates/a.tmpl | 1 + test2/fixtures/templates/b.tmpl | 1 + test2/fixtures/templates/c.tmpl | 1 + test2/fixtures/test-symlink | 1 + test2/fixtures/test-symlink-dir/suchempty | 1 + test2/fixtures/test.coffee | 1 + test2/fixtures/updaters/a.txt | 1 + test2/fixtures/updaters/b.txt | 1 + test2/fixtures/updaters/c.txt | 1 + test2/fixtures/vinyl/bom-utf16be.txt | Bin 0 -> 244 bytes test2/fixtures/vinyl/bom-utf16le.txt | Bin 0 -> 244 bytes test2/fixtures/vinyl/bom-utf8.txt | 1 + test2/fixtures/vinyl/test-symlink | 1 + test2/fixtures/vinyl/test-symlink-dir | 1 + test2/fixtures/vinyl/test.coffee | 1 + test2/fixtures/vinyl/wow/suchempty | 1 + test2/fixtures/watch/test.txt | 1 + test2/fixtures/wow/suchempty | 1 + test2/group.js | 144 +++ test2/handlers.js | 46 + test2/helpers.js | 800 ++++++++++++ test2/item.js | 1060 +++++++++++++++ test2/layouts.js | 127 ++ test2/list.js | 689 ++++++++++ test2/list.render.js | 137 ++ test2/list.use.js | 156 +++ test2/mergePartials.js | 104 ++ test2/partials.js | 202 +++ test2/questions.js | 58 + test2/renameKey.js | 350 +++++ test2/render.js | 72 ++ test2/routes.js | 98 ++ test2/store.js | 243 ++++ test2/support/ignore.js | 6 + test2/support/index.js | 64 + test2/support/spy.js | 27 + test2/view.content.js | 29 + test2/view.events.js | 28 + test2/view.js | 1148 +++++++++++++++++ test2/view.methods.js | 39 + test2/view.option.js | 29 + test2/view.render.js | 52 + test2/view.set.js | 34 + test2/view.use.js | 60 + test2/viewTypes.js | 52 + test2/views.js | 525 ++++++++ test2/views.use.js | 156 +++ updatefile.js | 135 +- 177 files changed, 13496 insertions(+), 1368 deletions(-) create mode 100644 .eslintrc create mode 100755 bin2/update.js create mode 100644 lib2/config.js create mode 100644 lib2/locals.js rename {lib => lib2}/middleware/index.js (100%) rename {lib => lib2}/middleware/json.js (100%) rename {lib => lib2}/runner/argv.js (100%) rename {lib => lib2}/runner/decorate.js (100%) rename {lib => lib2}/runner/env.js (100%) rename {lib => lib2}/runner/list.js (100%) rename {lib => lib2}/runner/listen.js (100%) rename {lib => lib2}/runner/run.js (100%) rename {lib => lib2}/runner/runner.js (100%) rename {lib => lib2}/runner/updater.js (100%) rename {lib => lib2}/tasks/default.js (100%) rename {lib => lib2}/tasks/del.js (100%) rename {lib => lib2}/tasks/dest.js (100%) rename {lib => lib2}/tasks/files.js (100%) rename {lib => lib2}/tasks/index.js (100%) rename {lib => lib2}/tasks/lint.js (100%) rename {lib => lib2}/tasks/list.js (100%) rename {lib => lib2}/tasks/noop.js (100%) rename {lib => lib2}/tasks/rename.js (100%) rename {lib => lib2}/tasks/tree.js (100%) create mode 100644 lib2/utils.js delete mode 100644 test/fixtures/three/generator.js create mode 100644 test/fixtures/three/updatefile.js delete mode 100644 test/fixtures/two/generate.js create mode 100644 test/fixtures/two/updatefile.js delete mode 100644 test/to-tasks.js delete mode 100644 test/update.logger.js delete mode 100644 test/update.scaffold.js delete mode 100644 test/update.to-tasks.js delete mode 100644 test/update.utils.js create mode 100644 test2/app.applyLayout.js create mode 100644 test2/app.collection.compile.js create mode 100644 test2/app.collection.js create mode 100644 test2/app.collection.render.js create mode 100644 test2/app.compile.js create mode 100644 test2/app.copy.js create mode 100644 test2/app.create.js create mode 100644 test2/app.data.js create mode 100644 test2/app.dest.js create mode 100644 test2/app.engines.js create mode 100644 test2/app.events.js create mode 100644 test2/app.get-set.js create mode 100644 test2/app.handle.js create mode 100644 test2/app.handlers.js create mode 100644 test2/app.js create mode 100644 test2/app.list.compile.js create mode 100644 test2/app.list.js create mode 100644 test2/app.lookups.js create mode 100644 test2/app.middleware.js create mode 100644 test2/app.onLoad.js create mode 100644 test2/app.option.js create mode 100644 test2/app.render.js create mode 100644 test2/app.renderFile.js create mode 100644 test2/app.route.js create mode 100644 test2/app.src.js create mode 100644 test2/app.symlink.js create mode 100644 test2/app.task.js create mode 100644 test2/app.toStream.js create mode 100644 test2/app.use.js create mode 100644 test2/app.view.compile.js create mode 100644 test2/app.view.render.js create mode 100644 test2/app.watch.js create mode 100644 test2/collection.engines.js create mode 100644 test2/collection.events.js create mode 100644 test2/collection.getView.js create mode 100644 test2/collection.js create mode 100644 test2/collection.options.js create mode 100644 test2/collection.render.js create mode 100644 test2/collection.use.js create mode 100644 test2/fixtures/bom-utf16be.txt create mode 100644 test2/fixtures/bom-utf16le.txt create mode 100644 test2/fixtures/bom-utf8.txt create mode 100644 test2/fixtures/copy/a.txt create mode 100644 test2/fixtures/copy/b.txt create mode 100644 test2/fixtures/copy/c.txt create mode 100644 test2/fixtures/copy/example.txt create mode 100644 test2/fixtures/data/a.json create mode 100644 test2/fixtures/data/alert.json create mode 100644 test2/fixtures/data/b.json create mode 100644 test2/fixtures/data/c.json create mode 100644 test2/fixtures/data/data.json create mode 100644 test2/fixtures/data/test.json create mode 100644 test2/fixtures/example.txt create mode 100644 test2/fixtures/front-matter/autodetect-no-lang.md create mode 100644 test2/fixtures/front-matter/autodetect-yaml.md create mode 100644 test2/fixtures/front-matter/lang-yaml.md create mode 100644 test2/fixtures/generic/run.dmc create mode 100644 test2/fixtures/generic/test.dmc create mode 100644 test2/fixtures/helpers/a.js create mode 100644 test2/fixtures/helpers/b.js create mode 100644 test2/fixtures/helpers/c.js create mode 100644 test2/fixtures/helpers/obj.js create mode 100644 test2/fixtures/noext/license create mode 100644 test2/fixtures/pages/a.hbs create mode 100644 test2/fixtures/pages/b.hbs create mode 100644 test2/fixtures/pages/c.hbs rename {test => test2}/fixtures/pipeline/a.js (100%) rename {test => test2}/fixtures/pipeline/b.js (100%) rename {test => test2}/fixtures/pipeline/c.js (100%) rename {test => test2}/fixtures/pipeline/d.js (100%) create mode 100644 test2/fixtures/posts/a.txt create mode 100644 test2/fixtures/posts/b.txt create mode 100644 test2/fixtures/posts/c.txt create mode 100644 test2/fixtures/templates/a.tmpl create mode 100644 test2/fixtures/templates/b.tmpl create mode 100644 test2/fixtures/templates/c.tmpl create mode 120000 test2/fixtures/test-symlink create mode 100644 test2/fixtures/test-symlink-dir/suchempty create mode 100644 test2/fixtures/test.coffee create mode 100644 test2/fixtures/updaters/a.txt create mode 100644 test2/fixtures/updaters/b.txt create mode 100644 test2/fixtures/updaters/c.txt create mode 100644 test2/fixtures/vinyl/bom-utf16be.txt create mode 100644 test2/fixtures/vinyl/bom-utf16le.txt create mode 100644 test2/fixtures/vinyl/bom-utf8.txt create mode 120000 test2/fixtures/vinyl/test-symlink create mode 120000 test2/fixtures/vinyl/test-symlink-dir create mode 100644 test2/fixtures/vinyl/test.coffee create mode 100644 test2/fixtures/vinyl/wow/suchempty create mode 100644 test2/fixtures/watch/test.txt create mode 100644 test2/fixtures/wow/suchempty create mode 100644 test2/group.js create mode 100644 test2/handlers.js create mode 100644 test2/helpers.js create mode 100644 test2/item.js create mode 100644 test2/layouts.js create mode 100644 test2/list.js create mode 100644 test2/list.render.js create mode 100644 test2/list.use.js create mode 100644 test2/mergePartials.js create mode 100644 test2/partials.js create mode 100644 test2/questions.js create mode 100644 test2/renameKey.js create mode 100644 test2/render.js create mode 100644 test2/routes.js create mode 100644 test2/store.js create mode 100644 test2/support/ignore.js create mode 100644 test2/support/index.js create mode 100644 test2/support/spy.js create mode 100644 test2/view.content.js create mode 100644 test2/view.events.js create mode 100644 test2/view.js create mode 100644 test2/view.methods.js create mode 100644 test2/view.option.js create mode 100644 test2/view.render.js create mode 100644 test2/view.set.js create mode 100644 test2/view.use.js create mode 100644 test2/viewTypes.js create mode 100644 test2/views.js create mode 100644 test2/views.use.js diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..7b5d047 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,125 @@ +{ + "ecmaFeatures": { + "modules": true, + "experimentalObjectRestSpread": true + }, + + "env": { + "browser": false, + "es6": true, + "node": true, + "mocha": true + }, + + "globals": { + "document": false, + "navigator": false, + "window": false + }, + + "rules": { + "accessor-pairs": 2, + "arrow-spacing": [2, { "before": true, "after": true }], + "block-spacing": [2, "always"], + "brace-style": [2, "1tbs", { "allowSingleLine": true }], + "comma-dangle": [2, "never"], + "comma-spacing": [2, { "before": false, "after": true }], + "comma-style": [2, "last"], + "constructor-super": 2, + "curly": [2, "multi-line"], + "dot-location": [2, "property"], + "eol-last": 2, + "eqeqeq": [2, "allow-null"], + "generator-star-spacing": [2, { "before": true, "after": true }], + "handle-callback-err": [2, "^(err|error)$" ], + "indent": [2, 2, { "SwitchCase": 1 }], + "key-spacing": [2, { "beforeColon": false, "afterColon": true }], + "new-cap": [2, { "newIsCap": true, "capIsNew": false }], + "new-parens": 2, + "no-array-constructor": 2, + "no-caller": 2, + "no-class-assign": 2, + "no-cond-assign": 2, + "no-const-assign": 2, + "no-control-regex": 2, + "no-debugger": 2, + "no-delete-var": 2, + "no-dupe-args": 2, + "no-dupe-class-members": 2, + "no-dupe-keys": 2, + "no-duplicate-case": 2, + "no-empty-character-class": 2, + "no-empty-label": 2, + "no-eval": 2, + "no-ex-assign": 2, + "no-extend-native": 2, + "no-extra-bind": 2, + "no-extra-boolean-cast": 2, + "no-extra-parens": [2, "functions"], + "no-fallthrough": 2, + "no-floating-decimal": 2, + "no-func-assign": 2, + "no-implied-eval": 2, + "no-inner-declarations": [2, "functions"], + "no-invalid-regexp": 2, + "no-irregular-whitespace": 2, + "no-iterator": 2, + "no-label-var": 2, + "no-labels": 2, + "no-lone-blocks": 2, + "no-mixed-spaces-and-tabs": 2, + "no-multi-spaces": 2, + "no-multi-str": 2, + "no-multiple-empty-lines": [2, { "max": 1 }], + "no-native-reassign": 2, + "no-negated-in-lhs": 2, + "no-new": 2, + "no-new-func": 2, + "no-new-object": 2, + "no-new-require": 2, + "no-new-wrappers": 2, + "no-obj-calls": 2, + "no-octal": 2, + "no-octal-escape": 2, + "no-proto": 0, + "no-redeclare": 2, + "no-regex-spaces": 2, + "no-return-assign": 2, + "no-self-compare": 2, + "no-sequences": 2, + "no-shadow-restricted-names": 2, + "no-spaced-func": 2, + "no-sparse-arrays": 2, + "no-this-before-super": 2, + "no-throw-literal": 2, + "no-trailing-spaces": 0, + "no-undef": 2, + "no-undef-init": 2, + "no-unexpected-multiline": 2, + "no-unneeded-ternary": [2, { "defaultAssignment": false }], + "no-unreachable": 2, + "no-unused-vars": [2, { "vars": "all", "args": "none" }], + "no-useless-call": 0, + "no-with": 2, + "one-var": [0, { "initialized": "never" }], + "operator-linebreak": [0, "after", { "overrides": { "?": "before", ":": "before" } }], + "padded-blocks": [0, "never"], + "quotes": [2, "single", "avoid-escape"], + "radix": 2, + "semi": [2, "always"], + "semi-spacing": [2, { "before": false, "after": true }], + "space-after-keywords": [2, "always"], + "space-before-blocks": [2, "always"], + "space-before-function-paren": [2, "never"], + "space-before-keywords": [2, "always"], + "space-in-parens": [2, "never"], + "space-infix-ops": 2, + "space-return-throw-case": 2, + "space-unary-ops": [2, { "words": true, "nonwords": false }], + "spaced-comment": [0, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!", ","] }], + "use-isnan": 2, + "valid-typeof": 2, + "wrap-iife": [2, "any"], + "yoda": [2, "never"] + } +} diff --git a/.gitignore b/.gitignore index 80a228c..7988154 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,21 @@ +# always ignore files *.DS_Store *.sublime-* -_gh_pages -bower_components + +# test related, or directories generated by tests +test/actual +actual +coverage + +# npm node_modules npm-debug.log -actual -test/actual + +# misc +_gh_pages +benchmark +bower_components +vendor temp tmp TODO.md -vendor -.idea -benchmark -coverage diff --git a/.travis.yml b/.travis.yml index 09768f0..d6e658e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,6 @@ sudo: false language: node_js node_js: - "stable" - - "5" - - "4" - "0.12" - "0.10" matrix: diff --git a/.verb.md b/.verb.md index fad0eb2..1ffc957 100644 --- a/.verb.md +++ b/.verb.md @@ -1,86 +1,12 @@ -# {%= name %} {%= badge("fury") %} {%= badge("travis") %} +# {%= name %} {%= badge("fury") %} > {%= description %} -## CLI - -### Install - -{%= include("install-global") %} - - -### Commands - -```sh -$ update [options] -``` - -**List updaters** - -Choose from a list of updaters and tasks to run: - -```sh -$ update list -``` - -**Run a specific updater** - -The following would run updater `foo`: - -```sh -$ update foo - -# run updater "foo" with options -$ update foo --bar=baz -``` - - -### tasks - -_(TODO)_ - -### plugins - -_(TODO)_ - -#### pipeline plugins - -_(TODO)_ - -#### instance plugins - -_(TODO)_ - -### middleware - -A middleware is a function that exposes the following parameters: - -- `file`: **{Object}** [vinyl][] file object -- `next`: **{Function}** must be called to continue on to the next file. - -```js -function rename(file, next) { - file.path = 'foo/' + file.path; - next(); -} - -// example usage: prefix all `.js` file paths with `foo/` -app.onLoad(/\.js/, rename); -``` - -The `onStream` method is a custom [middleware](docs/middleware.md) handler that the `update` - -```js -app.onStream(/lib\//, rename); -``` - - -## API - -### Install - +## Install {%= include("install-npm", {save: true}) %} +## Usage + ```js var update = require('{%= name %}'); ``` @@ -91,44 +17,6 @@ var update = require('{%= name %}'); ## Related projects {%= related(verb.related.list) %} -## Authoring - -### Updaters - -_(TODO)_ - -#### Tasks - -_(TODO)_ - -#### Middleware - -_(TODO)_ - -#### Plugins - -> Updater plugins follow the same signature as gulp plugins - -**Example** - -```js -function myPlugin(options) { - return through.obj(function(file, enc, next) { - var str = file.contents.toString(); - // do stuff to `file` - file.contents = new Buffer(file.contents); - next(null, file); - }); -} -``` - -### Publish - -1. Name your project following the convention: `updater-*` -2. Don't use dots in the name (e.g `.js`) -3. Make sure you add `updater` to the keywords in `package.json` -4. Tweet about your updater! - ## Running tests {%= include("tests") %} @@ -140,7 +28,7 @@ function myPlugin(options) { ## License {%= copyright() %} -{%= license %} +{%= license() %} *** diff --git a/bin/cli.js b/bin/cli.js index 48ed85a..fbb0ad7 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -23,44 +23,66 @@ function run(cb) { if (argv.cwd && cwd !== path.resolve(argv.cwd)) { process.chdir(argv.cwd); + cwd = process.cwd(); utils.timestamp('cwd changed to ' + utils.colors.yellow('~/' + argv.cwd)); } /** - * Get the updatefile.js to use + * Create the base "update" instance */ - var updatefile = path.resolve(process.cwd(), 'updatefile.js'); + var baseDir = path.resolve(__dirname, '..'); + var baseEnv = createEnv('updatefile.js', baseDir); + + // instantiate + var base = update(); + base.env = baseEnv; + + // set the updater function on the instance + base.fn = require('../updatefile.js'); /** - * Notify the user if updatefile.js is not found + * Get the updatefile.js to use */ + var updatefile = path.resolve(process.cwd(), 'updatefile.js'); if (!utils.exists(updatefile)) { - cb('updatefile'); - return; + if (utils.isEmpty(process.cwd())) { + argv._.unshift('defaults:init'); + utils.logConfigfile(baseDir, 'updatefile.js'); + base.fn.call(base, base, base, base.env); + cb(null, base); + return; + } + + updatefile = path.resolve(__dirname, '../updatefile.js'); + cwd = path.dirname(updatefile); } /** - * Get the `update` instance to use + * Create the user's "update" instance */ - var app = require(updatefile); - if (typeof app === 'function') { - var fn = app; - app = update(); + var fn = require(updatefile); + var env = createEnv('updatefile.js', cwd); + var app; + + function register(app, env, fn) { app.option(argv); - app.fn = fn; - fn(app); + app.register('base', base.fn, base.env); + app.env = env; + if (fn) app.fn = fn; } - app.generator('base', require('../lib/generator')); - - /** - * Create enviroment - */ + if (typeof fn === 'function') { + app = update(); + register(app, env, fn); + fn.call(app, app, base, env); - app.env = createEnv('updatefile.js', process.cwd()); + } else { + app = fn; + register(app, env); + } /** * Process command line arguments @@ -73,8 +95,7 @@ function run(cb) { * Show path to updatefile */ - var fp = utils.homeRelative(root, updatefile); - utils.timestamp('using updatefile ' + fp); + utils.logConfigfile(root, updatefile); /** * Support `--emit` for debugging @@ -93,6 +114,7 @@ function run(cb) { */ app.env.on('config', function(name, env) { + console.log(name) app.register(name, env.config.fn, env); }); @@ -110,11 +132,6 @@ function run(cb) { cwd: utils.gm }); - /** - * Process command line arguments - */ - - app.cli.process(args); cb(null, app); } @@ -125,6 +142,10 @@ function run(cb) { run(function(err, app) { if (err) handleError(err); + if (!app) { + process.exit(0); + } + /** * Listen for errors */ @@ -138,7 +159,10 @@ run(function(err, app) { */ app.build(argv, function(err) { - if (err) throw err; + if (err) { + console.error(err.stack); + process.exit(1); + } utils.timestamp('finished ' + utils.success()); process.exit(0); }); diff --git a/bin2/update.js b/bin2/update.js new file mode 100755 index 0000000..2160867 --- /dev/null +++ b/bin2/update.js @@ -0,0 +1,38 @@ +#!/usr/bin/env node + +var path = require('path'); +var gm = require('global-modules'); +var Runner = require('../lib/runner/runner')(); +var utils = require('../lib/utils'); +var argv = require('minimist')(process.argv.slice(2), { + alias: {verbose: 'v'} +}); + +var cmd = utils.commands(argv); +var runner = new Runner(argv); + +runner.base.option(argv); +runner.option(argv); + +var task = cmd.list ? ['list', 'default'] : ['default']; + +runner.on('*', function(method, key, val) { + console.log(method + ':', key, val); +}); + +if (argv.verbose) { + runner.on('register', function(key) { + utils.ok(utils.gray('registered'), 'updater', utils.cyan(key)); + }); +} + +runner.registerEach('update-*', {cwd: gm}); + +runner.base.task('run', function(cb) { + runner.run(cb); +}); + +runner.base.build(task, function(err) { + if (err) return console.error(err); + utils.timestamp('finished ' + utils.green(utils.successSymbol)); +}); diff --git a/index.js b/index.js index d9ffae3..526ac57 100644 --- a/index.js +++ b/index.js @@ -27,11 +27,9 @@ function Update(options) { } Generate.apply(this, arguments); + this.updaters = this.generators; this.isUpdate = true; - - this.initDefaults(this); this.initPlugins(this); - this.initCollections(this); } /** @@ -58,7 +56,6 @@ Update.prototype.initPlugins = function(app) { enable('middleware', utils.middleware); enable('loader', utils.loader); enable('config', utils.config); - enable('argv', utils.argv); enable('cli', cli); function enable(name, fn) { @@ -69,41 +66,6 @@ Update.prototype.initPlugins = function(app) { } }; -/** - * Built-in view collections - * | partials - * | layouts - * | pages - */ - -Update.prototype.initCollections = function(app) { - if (this.option('collections') === false) return; - - var engine = this.options.defaultEngine || 'hbs'; - this.create('partials', { - engine: engine, - viewType: 'partial', - renameKey: function(fp) { - return path.basename(fp, path.extname(fp)); - } - }); - - this.create('layouts', { - engine: engine, - viewType: 'layout', - renameKey: function(fp) { - return path.basename(fp, path.extname(fp)); - } - }); - - this.create('pages', { - engine: engine, - renameKey: function(fp) { - return fp; - } - }); -}; - /** * Ensure `name` is set on the instance for lookups. */ @@ -114,7 +76,7 @@ Object.defineProperty(Update.prototype, 'name', { this.options.name = name; }, get: function() { - return this.options.name || 'base'; + return this.options.name || 'update'; } }); diff --git a/lib/utils.js b/lib/utils.js index eb6c5a1..c4eaa14 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,5 +1,6 @@ 'use strict'; +var fs = require('fs'); var path = require('path'); var utils = require('lazy-cache')(require); var fn = require; @@ -11,27 +12,30 @@ require = utils; // plugins, middleware or helpers require('assemble-loader', 'loader'); -require('common-middleware', 'middleware'); -require('base-config', 'config'); require('base-argv', 'argv'); require('base-cli', 'cli'); +require('base-config', 'config'); +require('common-middleware', 'middleware'); // other +require('async'); require('find-pkg'); require('global-modules', 'gm'); -require('mixin-deep', 'merge'); require('isobject', 'isObject'); -require('word-wrap', 'wrap'); require('matched', 'glob'); +require('mixin-deep', 'merge'); +require('namify'); +require('opn'); +require('project-name', 'project'); require('resolve-dir'); require('try-open'); -require('opn'); +require('word-wrap', 'wrap'); // CLI -require('success-symbol'); require('ansi-colors', 'colors'); -require('time-stamp', 'stamp'); require('expand-args'); +require('success-symbol'); +require('time-stamp', 'stamp'); require = fn; /** @@ -51,10 +55,22 @@ utils.processArgv = function(app, argv) { * Get a home-relative filepath */ -utils.homeRelative = function(root, fp) { - var dir = path.resolve(utils.resolveDir(fp)); - var fp = path.relative(root, dir); - return utils.colors.green('~/' + fp); +utils.homeRelative = function(fp) { + var home = utils.resolveDir('~/'); + var res = path.relative(home, fp); + return utils.colors.green('~/' + res); +}; + +/** + * Log out the path to the given config file, + * relative to the given `root` directory + */ + +utils.logConfigfile = function(root, configfile) { + var configPath = path.resolve(root, configfile); + var fp = utils.homeRelative(configPath); + var name = path.basename(configfile, path.extname(configfile)); + utils.timestamp('using ' + name + ' ' + fp); }; /** @@ -88,11 +104,29 @@ utils.logTask = function(appname, taskname) { }; /** - * Utils + * Return true if a directory exists and is empty. + * + * @param {*} val + * @return {Array} */ +utils.isEmpty = function isEmpty(dir) { + var files; + try { + if (!utils.exists(dir)) { + return false; + } + files = fs.readdirSync(dir); + files = files.filter(function(fp) { + return !/\.DS_Store/i.test(fp) + }); + return files.length === 0; + } catch (err) {}; + return true; +}; + utils.exists = function exists(fp) { - return utils.tryOpen(fp, 'r'); + return !!utils.tryOpen(fp, 'r'); }; utils.arrayify = function arrayify(val) { diff --git a/lib2/config.js b/lib2/config.js new file mode 100644 index 0000000..c829c9b --- /dev/null +++ b/lib2/config.js @@ -0,0 +1,30 @@ +'use strict'; + + +module.exports = function(app) { + if (!app.isUpdate) return; + + var config = require('base-config'); + app.use(config()); + + app.config + .map('addViews') + .map('addView') + .map('helpers') + .map('asyncHelpers') + .map('plugins', function(val) { + app.visit('plugin', val); + }) + .map('data', function(val) { + app.visit('data', val); + }) + .map('collections', function(val) { + app.visit('create', val); + }) + .map('reflinks', function(val) { + app.data({reflinks: val}); + }) + .map('related', function(val) { + app.data({related: val}); + }); +}; diff --git a/lib2/locals.js b/lib2/locals.js new file mode 100644 index 0000000..4d0097b --- /dev/null +++ b/lib2/locals.js @@ -0,0 +1,26 @@ +'use strict'; + +var get = require('get-value'); +var set = require('set-value'); +var utils = require('./utils'); + +module.exports = function(name) { + name = name || utils.project(process.cwd()); + + return function(app) { + app.define('locals', new Locals(name, this)); + }; +}; + +function Locals(name, app) { + this.cache = get(app, ['cache.data', name]) || {}; +} + +Locals.prototype.get = function(key) { + return get(this.cache, key); +}; + +Locals.prototype.set = function(key, value) { + set(this.cache, key, value); + return this; +}; diff --git a/lib/middleware/index.js b/lib2/middleware/index.js similarity index 100% rename from lib/middleware/index.js rename to lib2/middleware/index.js diff --git a/lib/middleware/json.js b/lib2/middleware/json.js similarity index 100% rename from lib/middleware/json.js rename to lib2/middleware/json.js diff --git a/lib/runner/argv.js b/lib2/runner/argv.js similarity index 100% rename from lib/runner/argv.js rename to lib2/runner/argv.js diff --git a/lib/runner/decorate.js b/lib2/runner/decorate.js similarity index 100% rename from lib/runner/decorate.js rename to lib2/runner/decorate.js diff --git a/lib/runner/env.js b/lib2/runner/env.js similarity index 100% rename from lib/runner/env.js rename to lib2/runner/env.js diff --git a/lib/runner/list.js b/lib2/runner/list.js similarity index 100% rename from lib/runner/list.js rename to lib2/runner/list.js diff --git a/lib/runner/listen.js b/lib2/runner/listen.js similarity index 100% rename from lib/runner/listen.js rename to lib2/runner/listen.js diff --git a/lib/runner/run.js b/lib2/runner/run.js similarity index 100% rename from lib/runner/run.js rename to lib2/runner/run.js diff --git a/lib/runner/runner.js b/lib2/runner/runner.js similarity index 100% rename from lib/runner/runner.js rename to lib2/runner/runner.js diff --git a/lib/runner/updater.js b/lib2/runner/updater.js similarity index 100% rename from lib/runner/updater.js rename to lib2/runner/updater.js diff --git a/lib/tasks/default.js b/lib2/tasks/default.js similarity index 100% rename from lib/tasks/default.js rename to lib2/tasks/default.js diff --git a/lib/tasks/del.js b/lib2/tasks/del.js similarity index 100% rename from lib/tasks/del.js rename to lib2/tasks/del.js diff --git a/lib/tasks/dest.js b/lib2/tasks/dest.js similarity index 100% rename from lib/tasks/dest.js rename to lib2/tasks/dest.js diff --git a/lib/tasks/files.js b/lib2/tasks/files.js similarity index 100% rename from lib/tasks/files.js rename to lib2/tasks/files.js diff --git a/lib/tasks/index.js b/lib2/tasks/index.js similarity index 100% rename from lib/tasks/index.js rename to lib2/tasks/index.js diff --git a/lib/tasks/lint.js b/lib2/tasks/lint.js similarity index 100% rename from lib/tasks/lint.js rename to lib2/tasks/lint.js diff --git a/lib/tasks/list.js b/lib2/tasks/list.js similarity index 100% rename from lib/tasks/list.js rename to lib2/tasks/list.js diff --git a/lib/tasks/noop.js b/lib2/tasks/noop.js similarity index 100% rename from lib/tasks/noop.js rename to lib2/tasks/noop.js diff --git a/lib/tasks/rename.js b/lib2/tasks/rename.js similarity index 100% rename from lib/tasks/rename.js rename to lib2/tasks/rename.js diff --git a/lib/tasks/tree.js b/lib2/tasks/tree.js similarity index 100% rename from lib/tasks/tree.js rename to lib2/tasks/tree.js diff --git a/lib2/utils.js b/lib2/utils.js new file mode 100644 index 0000000..122ee60 --- /dev/null +++ b/lib2/utils.js @@ -0,0 +1,329 @@ +'use strict'; + +var fs = require('fs'); +var path = require('path'); +var pkg = require(path.resolve(__dirname, '../package')); + +/** + * Module dependencies + */ + +var utils = require('lazy-cache')(require); +var fn = require; +require = utils; + +require('assemble-loader', 'loader'); +require('async'); +require('base-cli', 'cli'); +require('base-pipeline', 'pipeline'); +require('base-store', 'store'); +require('composer-runtimes', 'runtimes'); +require('expand-args'); +require('expand-object', 'expand'); +require('extend-shallow', 'extend'); +require('for-own'); +require('get-value', 'get'); +require('load-pkg', 'pkg'); +require('matched', 'glob'); +require('micromatch', 'mm'); +require('object.omit', 'omit'); +require('object.pick', 'pick'); +require('parser-front-matter', 'matter'); +require('project-name', 'project'); +require('question-cache', 'questions'); +require('set-value', 'set'); +require('stream-exhaust', 'exhaust'); +require('through2', 'through'); +require('union-value', 'union'); + +require('success-symbol'); +require('ansi-yellow', 'yellow'); +require('ansi-green', 'green'); +require('ansi-gray', 'gray'); +require('ansi-cyan', 'cyan'); +require('ansi-red', 'red'); +require('time-stamp', 'stamp'); +require = fn; + +/** + * Logging utils + */ + +utils.timestamp = function(msg) { + var time = '[' + utils.gray(utils.stamp('HH:mm:ss', new Date())) + ']'; + return console.log(time, msg); +}; + +function Status(status) { + status = status || {}; + this.err = status.err || null; + this.code = status.code || null; + this.name = status.name || ''; + this.msg = status.msg || ''; +} + +utils.ok = function() { + var args = utils.toArray(arguments) || []; + args.unshift(' ' + utils.green(utils.successSymbol)); + console.log.apply(console, args); +}; +utils.success = function() { + var args = utils.toArray(arguments) || []; + args[0] = utils.green(args[0] || ''); + console.log.apply(console, args); +}; +utils.error = function() { + var args = utils.toArray(arguments); + args.unshift(utils.red('Error:')); + console.error.apply(console, args); +}; + +/** + * CLI utils + */ + +utils.commands = function(argv) { + argv._ = argv._ || []; + var commands = {}; + + argv._.forEach(function(key) { + commands[key] = true; + }); + return commands; +}; + +utils.identity = function(val) { + return val; +}; + +utils.arrayify = function(val) { + return Array.isArray(val) ? val : [val]; +}; + +utils.toArgv = function(args) { + var argv = args.flags; + argv._ = args.commands; + return argv; +}; + +utils.toArray = function(val) { + if (Array.isArray(val)) return val; + if (val && val.length) { + return [].slice.call(val); + } +}; + +utils.contains = function(arr, key) { + return arr.indexOf(key) > -1; +}; + +utils.npm = function(name) { + return utils.tryRequire(name) || utils.tryRequire(path.resolve(name)); +}; + +utils.exists = function(fp) { + return fs.existsSync(fp); +}; + +/** + * Rename a filepath to the "nickname" of the project. + * + * ```js + * renameFn('updater-foo'); + * //=> 'foo' + * ``` + */ + +utils.renameFn = function(filename, options) { + if (options && typeof options.renameFn === 'function') { + return options.renameFn(filename); + } + return filename.slice(filename.indexOf('-') + 1); +}; + +/** + * Return a glob of file paths + */ + +utils.matchFiles = function(pattern, options) { + options = options || {}; + var isMatch = utils.mm.matcher(pattern); + var files = fs.readdirSync(options.cwd); + var len = files.length, i = -1; + var res = []; + while (++i < len) { + var name = files[i]; + if (name === 'update') continue; + var fp = path.join(options.cwd, name); + if (isMatch(fp) || isMatch(name)) { + res.push(fp); + } + } + return res; +}; + +/** + * Resolve the correct updater module to instantiate. + * If `update` exists in `node_modules` of the cwd, + * then that will be used to create the instance, + * otherwise this module will be used. + */ + +utils.resolveModule = function(dir) { + dir = path.join(dir, 'node_modules/', pkg.name); + if (utils.exists(dir)) { + return require(path.resolve(dir)); + } + return null; +}; + +/** + * Print a tree of "updaters" and their tasks + * + * ```js + * utils.tree(updaters); + * ``` + */ + +utils.tree = function(updaters) { + var res = ''; + for (var key in updaters) { + res += utils.cyan(key) + '\n'; + for (var task in updaters[key].tasks) { + res += ' - ' + task + '\n'; + } + } + return res; +}; + +/** + * Return a list of "updaters" and their tasks + * + * ```js + * utils.list(updaters); + * ``` + */ + +utils.list = function(updaters) { + var list = []; + for (var key in updaters) { + var updater = updaters[key]; + if (!Object.keys(updater.tasks).length) { + continue; + } + + var hasDefault = updater.tasks['default']; + var name = updater.option('name'); + var item = { + name: name + (hasDefault ? ' (default)' : ''), + value: key, + short: name + (hasDefault ? ':default' : '') + }; + list.push(item); + for (var task in updater.tasks) { + if (task === 'default') continue; + list.push({ + name: ' - ' + task, + value: key + ':' + task, + short: key + ':' + task + }); + } + } + return list; +}; + +/** + * Try to require a file + */ + +utils.tryRequire = function(name) { + try { + return require(name); + } catch (err) { + console.log(err); + } + return null; +}; + +/** + * Try to read a file + */ + +utils.tryRead = function(fp) { + try { + return fs.readFileSync(fp); + } catch (err) {} + return null; +}; + +utils.tryParse = function(str) { + try { + return JSON.parse(str); + } catch (err) {} + return {}; +}; + +utils.register = function(pattern, base, update, options) { + utils.matchFiles(pattern, options).forEach(function(fp) { + var name = utils.project(fp); + var mod = utils.resolveModule(fp) || update; + var app = mod(base.options) + .option('name', name) + .set('path', fp); + + require(utils.updatefile(fp))(app, base); + base.updater(name, app); + }); +}; + +utils.opts = function(key) { + key = key || 'opts'; + return function(app) { + var name = this.options.name || 'base'; + this.define(key, function() { + var config = this.defaults.apply(this, arguments); + return function(key, opts) { + var args = [].concat.apply([], [].slice.call(arguments)); + var prop = typeof key === 'string' ? args.shift() : null; + var val; + + if (prop && !args.length) { + val = utils.get(config, prop); + if (val) return val; + } + + var options = utils.extend.apply(utils.extend, [config].concat(args)); + return prop ? utils.get(options, prop) : options; + }; + }); + }; +}; + +utils.defaults = function(key) { + key = key || 'defaults'; + return function(app) { + this.define(key, function() { + var args = [].concat.apply([], [].slice.call(arguments)); + args.unshift({}, this.options); + return utils.extend.apply(utils.extend, args); + }); + }; +}; + +/** + * Restore `require` + */ + +require = fn; + +/** + * Expose `utils` + */ + +module.exports = utils; + +/** + * Expose utils + */ + +module.exports = utils; diff --git a/package.json b/package.json index c7db06e..8aaeaf1 100644 --- a/package.json +++ b/package.json @@ -31,61 +31,52 @@ }, "preferGlobal": true, "bin": { - "update": "bin/update.js" + "update": "bin/cli.js" }, "dependencies": { - "ansi-cyan": "^0.1.1", - "ansi-gray": "^0.1.1", - "ansi-green": "^0.1.1", - "ansi-red": "^0.1.1", - "ansi-yellow": "^0.1.1", - "assemble-core": "^0.8.0", + "ansi-colors": "^0.1.0", "assemble-loader": "^0.2.6", "async": "^1.5.2", - "base": "^0.6.3", + "base-argv": "^0.3.0", "base-cli": "^0.4.0", "base-config": "^0.3.3", - "base-options": "^0.5.4", - "base-pipeline": "^0.1.4", - "base-store": "^0.3.2", - "composer-runtimes": "^0.7.0", + "common-middleware": "^0.2.2", "define-property": "^0.2.5", "engine-base": "^0.1.2", "expand-args": "^0.3.1", - "expand-object": "^0.4.1", "export-files": "^2.1.0", - "extend-shallow": "^2.0.1", - "for-own": "^0.1.3", + "find-pkg": "^0.1.1", + "generate": "^0.3.6", "get-value": "^2.0.2", "global-modules": "^0.2.0", "gulp-eslint": "^1.1.1", + "isobject": "^2.0.0", "lazy-cache": "^1.0.3", "load-pkg": "^3.0.1", "matched": "^0.4.1", - "micromatch": "^2.3.7", "minimist": "^1.2.0", - "object.omit": "^2.0.0", - "object.pick": "^1.1.1", + "mixin-deep": "^1.1.3", + "namify": "^0.1.3", + "opn": "^3.0.3", "parser-front-matter": "^1.3.0", "project-name": "^0.2.3", - "question-cache": "^0.3.5", + "resolve-dir": "^0.1.0", "rimraf": "^2.5.0", - "set-value": "^0.3.2", - "sort-object-arrays": "^0.1.1", - "stream-exhaust": "^1.0.1", "success-symbol": "^0.1.0", "through2": "^2.0.0", "time-stamp": "^0.1.3", - "union-value": "^0.2.1", - "use": "^1.1.2" + "try-open": "^0.1.0", + "word-wrap": "^1.1.0" }, "devDependencies": { + "base-methods": "^0.6.2", "buffer-equal": "^1.0.0", "consolidate": "^0.13.1", "coveralls": "^2.11.6", "data-store": "^0.12.1", "engine-handlebars": "^0.8.0", "event-stream": "^3.3.2", + "expand-files": "^0.7.1", "graceful-fs": "^4.1.2", "gulp": "^3.9.0", "gulp-format-md": "^0.1.5", @@ -107,6 +98,13 @@ "repo", "update" ], + "lint-deps": { + "ignore": [ + "bin2", + "lib2", + "test2" + ] + }, "verb": { "related": { "list": [ diff --git a/test/app.renderFile.js b/test/app.renderFile.js index 4e3041a..95ef60c 100644 --- a/test/app.renderFile.js +++ b/test/app.renderFile.js @@ -1,6 +1,6 @@ 'use strict'; -var assemble = require('..'); +var update = require('..'); var assert = require('assert'); var should = require('should'); var path = require('path'); @@ -8,7 +8,7 @@ var app; describe('app.renderFile()', function() { beforeEach(function() { - app = assemble(); + app = update(); app.engine('hbs', require('engine-handlebars')); app.engine('*', require('engine-base')); diff --git a/test/app.symlink.js b/test/app.symlink.js index 69e8d45..8a63fea 100644 --- a/test/app.symlink.js +++ b/test/app.symlink.js @@ -6,7 +6,7 @@ var rimraf = require('rimraf'); var bufEqual = require('buffer-equal'); var through = require('through2'); var File = require('vinyl'); -var assemble = require('..'); +var update = require('..'); var spies = require('./support/spy'); var chmodSpy = spies.chmodSpy; var statSpy = spies.statSpy; @@ -17,7 +17,7 @@ var wipeOut = function(cb) { spies.setError('false'); statSpy.reset(); chmodSpy.reset(); - app = assemble(); + app = update(); }; var dataWrap = function(fn) { diff --git a/test/app.toStream.js b/test/app.toStream.js index fddfb93..8eefb1a 100644 --- a/test/app.toStream.js +++ b/test/app.toStream.js @@ -1,13 +1,13 @@ 'use strict'; -var assemble = require('..'); +var update = require('..'); var assert = require('assert'); var should = require('should'); var app; describe('toStream()', function() { beforeEach(function() { - app = assemble(); + app = update(); app.create('pages'); app.page('a', {content: 'this is A'}); app.page('b', {content: 'this is B'}); diff --git a/test/fixtures/three/four/five/generator.js b/test/fixtures/three/four/five/generator.js index 61f8bf6..220c400 100644 --- a/test/fixtures/three/four/five/generator.js +++ b/test/fixtures/three/four/five/generator.js @@ -1,12 +1,12 @@ 'use strict'; -module.exports = function(generate, base) { - generate.task('default', function() {}); - generate.task('a', function() {}); - generate.task('b', function() {}); - generate.task('c', function() {}); +module.exports = function(update, base) { + update.task('default', function() {}); + update.task('a', function() {}); + update.task('b', function() {}); + update.task('c', function() {}); - generate.register('foo', function(app) { + update.register('foo', function(app) { app.task('x', function() {}); app.task('y', function() {}); app.task('z', function() {}); diff --git a/test/fixtures/three/four/generator.js b/test/fixtures/three/four/generator.js index 61f8bf6..220c400 100644 --- a/test/fixtures/three/four/generator.js +++ b/test/fixtures/three/four/generator.js @@ -1,12 +1,12 @@ 'use strict'; -module.exports = function(generate, base) { - generate.task('default', function() {}); - generate.task('a', function() {}); - generate.task('b', function() {}); - generate.task('c', function() {}); +module.exports = function(update, base) { + update.task('default', function() {}); + update.task('a', function() {}); + update.task('b', function() {}); + update.task('c', function() {}); - generate.register('foo', function(app) { + update.register('foo', function(app) { app.task('x', function() {}); app.task('y', function() {}); app.task('z', function() {}); diff --git a/test/fixtures/three/generator.js b/test/fixtures/three/generator.js deleted file mode 100644 index 61f8bf6..0000000 --- a/test/fixtures/three/generator.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -module.exports = function(generate, base) { - generate.task('default', function() {}); - generate.task('a', function() {}); - generate.task('b', function() {}); - generate.task('c', function() {}); - - generate.register('foo', function(app) { - app.task('x', function() {}); - app.task('y', function() {}); - app.task('z', function() {}); - }); -}; \ No newline at end of file diff --git a/test/fixtures/three/updatefile.js b/test/fixtures/three/updatefile.js new file mode 100644 index 0000000..220c400 --- /dev/null +++ b/test/fixtures/three/updatefile.js @@ -0,0 +1,14 @@ +'use strict'; + +module.exports = function(update, base) { + update.task('default', function() {}); + update.task('a', function() {}); + update.task('b', function() {}); + update.task('c', function() {}); + + update.register('foo', function(app) { + app.task('x', function() {}); + app.task('y', function() {}); + app.task('z', function() {}); + }); +}; \ No newline at end of file diff --git a/test/fixtures/two/generate.js b/test/fixtures/two/generate.js deleted file mode 100644 index 04c031f..0000000 --- a/test/fixtures/two/generate.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -var Generate = require('../../..'); -var generate = new Generate(); - -generate.task('default', function() {}); -generate.task('a', function() {}); -generate.task('b', function() {}); -generate.task('c', function() {}); - -generate.register('foo', function(app) { - app.task('x', function() {}); - app.task('y', function() {}); - app.task('z', function() {}); -}); - -/** - * Expose this instance of `Generate` - */ - -module.exports = generate; diff --git a/test/fixtures/two/updatefile.js b/test/fixtures/two/updatefile.js new file mode 100644 index 0000000..df28790 --- /dev/null +++ b/test/fixtures/two/updatefile.js @@ -0,0 +1,21 @@ +'use strict'; + +var Generate = require('../../..'); +var update = new Generate(); + +update.task('default', function() {}); +update.task('a', function() {}); +update.task('b', function() {}); +update.task('c', function() {}); + +update.register('foo', function(app) { + app.task('x', function() {}); + app.task('y', function() {}); + app.task('z', function() {}); +}); + +/** + * Expose this instance of `Generate` + */ + +module.exports = update; diff --git a/test/generators.js b/test/generators.js index 2157ec9..5fa8da2 100644 --- a/test/generators.js +++ b/test/generators.js @@ -1,10 +1,10 @@ var assert = require('assert'); -var Generate = require('..'); +var Update = require('..'); var app; describe('generators', function() { beforeEach(function() { - app = new Generate(); + app = new Update(); }); it('should add a generator to app.generators', function() { @@ -13,8 +13,8 @@ describe('generators', function() { assert(typeof app.generators.abc === 'object'); }); - it('should be an instance of generate', function() { + it('should be an instance of update', function() { app.register('foo', function() {}); - assert(app.generators.foo instanceof Generate); + assert(app.generators.foo instanceof Update); }); }); diff --git a/test/helpers.js b/test/helpers.js index eb5633d..f4a3fbd 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -273,7 +273,7 @@ describe('async helpers', function() { }); describe('built-in helpers:', function() { - describe('automatically generated helpers for default view types:', function() { + describe('automatically updated helpers for default view types:', function() { beforeEach(function() { app = new App({rethrow: false}); app.engine('md', require('engine-base')); @@ -475,7 +475,7 @@ describe('built-in helpers:', function() { /** * Partial */ - + app.partial('a.hbs', { content: '---\nname: "AAA"\n---\n{{name}}', locals: { @@ -486,7 +486,7 @@ describe('built-in helpers:', function() { /** * Pages */ - + app.page('a.hbs', { path: 'a.hbs', content: '{{author}}', diff --git a/test/store.js b/test/store.js index 773d904..b955589 100644 --- a/test/store.js +++ b/test/store.js @@ -23,16 +23,16 @@ describe('store', function() { it('should create a store at the given `cwd`', function() { app = new App({store: {cwd: __dirname + '/actual'}}); app.store.set('foo', 'bar'); - assert(path.basename(app.store.path) === 'generate.json'); + assert(path.basename(app.store.path) === 'update.json'); assert(app.store.data.hasOwnProperty('foo')); assert(app.store.data.foo === 'bar'); - assert(fs.existsSync(path.join(__dirname, 'actual', 'generate.json'))); + assert(fs.existsSync(path.join(__dirname, 'actual', 'update.json'))); }); it('should create a store using the given `indent` value', function() { app = new App({store: {cwd: __dirname + '/actual', indent: 0}}); app.store.set('foo', 'bar'); - var contents = fs.readFileSync(path.join(__dirname, 'actual', 'generate.json'), 'utf8'); + var contents = fs.readFileSync(path.join(__dirname, 'actual', 'update.json'), 'utf8'); assert(contents === '{"foo":"bar"}'); }); @@ -166,7 +166,7 @@ describe('store', function() { describe('events', function() { beforeEach(function() { app = new App(); - app.store = new Store('generate-tests'); + app.store = new Store('update-tests'); }); afterEach(function(cb) { diff --git a/test/to-tasks.js b/test/to-tasks.js deleted file mode 100644 index ed6c688..0000000 --- a/test/to-tasks.js +++ /dev/null @@ -1,378 +0,0 @@ -var assert = require('assert'); -var minimist = require('minimist'); -var support = require('./support'); -var Generate = support.resolve(); -var generate; - -var toTasks = require('../lib/to-tasks'); - -describe('to-tasks', function() { - beforeEach(function() { - generate = new Generate(); - }); - - it('should return default empty objects when only passing in argv as an empty array', function() { - var tasks = toTasks([]); - var expected = { - _: [], - unknown: ['default'], - commands: {}, - options: {}, - tasks: [] - }; - assert.deepEqual(tasks, expected); - }); - - it('should return default empty objects with blank unknown when only passing in argv as an empty string', function() { - var tasks = toTasks(''); - var expected = { - _: [], - unknown: [''], - commands: {}, - options: {}, - tasks: [] - }; - assert.deepEqual(tasks, expected); - }); - - it('should return default empty objects with string in unknown when only passing in argv as a string', function() { - var tasks = toTasks('foo'); - var expected = { - _: [], - unknown: ['foo'], - commands: {}, - options: {}, - tasks: [] - }; - assert.deepEqual(tasks, expected); - }); - - it('should process an argv object and return all unknowns', function() { - var argv = minimist(['foo', 'bar', 'baz']); - var tasks = toTasks(argv); - var expected = { - _: [], - unknown: ['foo', 'bar', 'baz'], - commands: {}, - options: {}, - tasks: [] - }; - assert.deepEqual(tasks, expected); - }); - - it('should process an argv object with options', function() { - var argv = minimist(['foo', '--bar', '--baz']); - var tasks = toTasks(argv); - var expected = { - _: [], - unknown: ['foo'], - commands: {}, - options: { - bar: true, - baz: true - }, - tasks: [] - }; - assert.deepEqual(tasks, expected); - }); - - it('should process an argv object with commands', function() { - var argv = minimist(['--tasks']); - var tasks = toTasks(argv, generate); - var expected = { - _: [], - unknown: ['default'], - commands: { - tasks: true - }, - options: {}, - tasks: [] - }; - assert.deepEqual(tasks, expected); - }); - - it('should process an argv object with commands without using --', function() { - var argv = minimist(['tasks']); - var tasks = toTasks(argv, generate); - var expected = { - _: [], - unknown: ['tasks'], - commands: {}, - options: {}, - tasks: [] - }; - assert.deepEqual(tasks, expected); - }); - - it('should process an argv object with a generator specified', function() { - var argv = minimist(['foo:bar']); - generate.register('foo', function(app) { - app.task('bar', function(){}); - }); - - var tasks = toTasks(argv, generate); - var expected = { - _: [], - unknown: [], - commands: {}, - options: {}, - tasks: ['generators.foo:bar'] - }; - assert.deepEqual(tasks, expected); - }); - - it('should process an argv object with many generators specified', function() { - var argv = minimist(['foo:bar', 'beep:boop']); - generate.register('foo', function(app) { - app.task('bar', function(){}); - }); - - generate.register('beep', function(app) { - app.task('boop', function(){}); - }); - - var tasks = toTasks(argv, generate); - var expected = { - _: [], - unknown: [], - commands: {}, - options: {}, - tasks: [ - 'generators.foo:bar', - 'generators.beep:boop' - ] - }; - assert.deepEqual(tasks, expected); - }); - - it('should process an argv object with deeply nested generators specified', function() { - var argv = minimist(['foo.beep:boop']); - generate.register('foo', function(app) { - app.task('bar', function(){}); - app.register('beep', function(app) { - app.task('boop', function(){}); - }); - }); - - - var tasks = toTasks(argv, generate); - var expected = { - _: [], - unknown: [], - commands: {}, - options: {}, - tasks: ['generators.foo.generators.beep:boop'] - }; - assert.deepEqual(tasks, expected); - }); - - it('should process an argv object with deeply nested generators and many tasks specified', function() { - var argv = minimist(['foo.beep:boop,bop', 'foo:bar,baz']); - generate.register('foo', function(app) { - app.task('bar', function(){}); - app.task('baz', function(){}); - app.register('beep', function(app) { - app.task('boop', function(){}); - app.task('bop', function(){}); - }); - }); - - - var tasks = toTasks(argv, generate); - var expected = { - _: [], - unknown: [], - commands: {}, - options: {}, - tasks: [ - 'generators.foo.generators.beep:boop,bop', - 'generators.foo:bar,baz' - ] - }; - assert.deepEqual(tasks, expected); - }); - - it('should process an argv object with only a task specified', function() { - var argv = minimist(['bar']); - generate.task('bar', function(){}); - generate.task('baz', function(){}); - - var tasks = toTasks(argv, generate); - var expected = { - _: [], - unknown: [], - commands: {}, - options: {}, - tasks: ['bar'] - }; - assert.deepEqual(tasks, expected); - }); - - it('should process an argv object with many tasks specified', function() { - var argv = minimist(['bar', 'baz']); - generate.task('bar', function(){}); - generate.task('baz', function(){}); - - var tasks = toTasks(argv, generate); - var expected = { - _: [], - unknown: [], - commands: {}, - options: {}, - tasks: ['bar', 'baz'] - }; - assert.deepEqual(tasks, expected); - }); - - it('should process an argv object with many comma separated tasks specified', function() { - var argv = minimist(['bar,baz']); - generate.task('bar', function(){}); - generate.task('baz', function(){}); - - var tasks = toTasks(argv, generate); - var expected = { - _: [], - unknown: [], - commands: {}, - options: {}, - tasks: ['bar,baz'] - }; - assert.deepEqual(tasks, expected); - }); - - it('should fallback to a "base" task', function() { - var argv = minimist(['default']); - generate.register('base', function(app) { - app.task('default', function() {}); - }); - generate.task('bar', function(){}); - generate.task('baz', function(){}); - - var tasks = toTasks(argv, generate); - var expected = { - _: [], - unknown: [], - commands: {}, - options: {}, - tasks: ['generators.base:default'] - }; - assert.deepEqual(tasks, expected); - }); - - it('should fallback to a nested generator "default" task', function() { - var argv = minimist(['foo']); - generate.register('base', function(app) { - app.task('default', function() {}); - }); - generate.register('foo', function(app) { - app.task('default', function() {}); - }); - generate.task('bar', function(){}); - generate.task('baz', function(){}); - - var tasks = toTasks(argv, generate); - var expected = { - _: [], - unknown: [], - commands: {}, - options: {}, - tasks: ['generators.foo:default'] - }; - assert.deepEqual(tasks, expected); - }); - - it('should fallback to a nested generator "default" task', function() { - var argv = minimist(['foo']); - generate.register('base', function(app) { - app.task('default', function() {}); - }); - generate.register('foo', function(app) { - app.task('default', function() {}); - }); - generate.task('bar', function(){}); - generate.task('baz', function(){}); - - var tasks = toTasks(argv, generate); - var expected = { - _: [], - unknown: [], - commands: {}, - options: {}, - tasks: ['generators.foo:default'] - }; - assert.deepEqual(tasks, expected); - }); - - it('should fallback to root generator when task has a dot', function() { - var argv = minimist(['foo:bar.2']); - generate.register('base', function(app) { - app.task('default', function() {}); - }); - generate.register('foo', function(app) { - app.task('default', function() {}); - }); - generate.task('bar', function(){}); - generate.task('baz', function(){}); - generate.task('bar.2', function(){}); - - var tasks = toTasks(argv, generate); - var expected = { - _: [], - unknown: [], - commands: {}, - options: {}, - tasks: ['bar.2'] - }; - assert.deepEqual(tasks, expected); - }); - - it('should throw an error when a task is not found', function(done) { - var argv = minimist(['bar,nothing']); - generate.task('bar', function(){}); - generate.task('baz', function(){}); - - try { - var tasks = toTasks(argv, generate); - done(new Error('expected an error when a task is not found.')); - } catch(err) { - assert.equal(err.message, 'task "nothing" is not registered'); - done(); - } - }); - - it('should throw an error when a task on a sub generator is not found', function(done) { - var argv = minimist(['beep:boop,nothing']); - generate.task('bar', function(){}); - generate.task('baz', function(){}); - generate.register('beep', function(app) { - app.task('boop', function(){}); - }); - - try { - var tasks = toTasks(argv, generate); - console.log(tasks); - done(new Error('expected an error when a task is not found.')); - } catch(err) { - assert.equal(err.message, 'task "beep:nothing" is not registered on generator "beep"'); - done(); - } - }); - - it('should throw an error when a sub generator is not found', function(done) { - var argv = minimist(['bep:boop,nothing']); - generate.task('bar', function(){}); - generate.task('baz', function(){}); - generate.register('beep', function(app) { - app.task('boop', function(){}); - }); - - try { - var tasks = toTasks(argv, generate); - console.log(tasks); - done(new Error('expected an error when a task is not found.')); - } catch(err) { - assert.equal(err.message, 'task "bep:boop" is not registered on generator "bep"'); - done(); - } - }); -}); diff --git a/test/update.compose.js b/test/update.compose.js index 8ffe1d8..4e09adc 100644 --- a/test/update.compose.js +++ b/test/update.compose.js @@ -5,17 +5,17 @@ var assert = require('assert'); var support = require('./support'); var Generate = support.resolve(); var Base = Generate.Base; -var generate; +var update; -describe('generate.compose', function() { +describe('update.compose', function() { beforeEach(function() { - generate = new Generate(); + update = new Generate(); }); it('should throw an error when trying to compose an instance', function(cb) { var foo = new Generate({name: 'foo'}); try { - generate.compose(foo); + update.compose(foo); cb(new Error('Expected an error.')); } catch (err) { assert.equal(err.message, 'generators must export a function to extend other generators'); @@ -24,13 +24,13 @@ describe('generate.compose', function() { }); it('should compose a generator', function() { - var foo = generate.generator('foo', function(app) { + var foo = update.generator('foo', function(app) { app.task('foo', function(cb) { cb(); }); }); - var bar = generate.generator('bar', function(app) { + var bar = update.generator('bar', function(app) { app.task('bar', function(cb) { cb(); }); @@ -49,13 +49,13 @@ describe('generate.compose', function() { }); it('should compose a generator by name', function() { - var foo = generate.generator('foo', function(app) { + var foo = update.generator('foo', function(app) { app.task('foo', function(cb) { cb(); }); }); - var bar = generate.generator('bar', function(app) { + var bar = update.generator('bar', function(app) { app.task('bar', function(cb) { cb(); }); @@ -67,9 +67,9 @@ describe('generate.compose', function() { bar.tasks.should.not.have.property('foo'); foo.tasks.should.not.have.property('bar'); - generate.compose('foo', bar); + update.compose('foo', bar); bar.tasks.should.have.property('foo'); - generate.compose('bar', foo); + update.compose('bar', foo); foo.tasks.should.have.property('bar'); }); }); diff --git a/test/update.extendGenerator.js b/test/update.extendGenerator.js index fc4f3f8..d55e4c6 100644 --- a/test/update.extendGenerator.js +++ b/test/update.extendGenerator.js @@ -5,17 +5,17 @@ var assert = require('assert'); var support = require('./support'); var Generate = support.resolve(); var Base = Generate.Base; -var generate; +var update; -describe('generate.extendGenerator', function() { +describe('update.extendGenerator', function() { beforeEach(function() { - generate = new Generate(); + update = new Generate(); }); it('should throw an error when trying to extend an instance', function(done) { var foo = new Generate({name: 'foo'}); try { - generate.extendGenerator(foo); + update.extendGenerator(foo); done(new Error('Expected an error.')); } catch (err) { err.message.should.equal('generators must export a function to extend other generators'); @@ -24,11 +24,11 @@ describe('generate.extendGenerator', function() { }); it('should extend a generator', function() { - var foo = generate.generator('foo', function(app) { + var foo = update.generator('foo', function(app) { app.task('foo', function(cb) { cb(); }); }); - var bar = generate.generator('bar', function(app) { + var bar = update.generator('bar', function(app) { app.task('bar', function(cb) { cb(); }); }); diff --git a/test/update.generator.js b/test/update.generator.js index caee290..6cf9c48 100644 --- a/test/update.generator.js +++ b/test/update.generator.js @@ -5,31 +5,33 @@ var assert = require('assert'); var support = require('./support'); var Generate = support.resolve(); var Base = Generate.Base; -var generate; +var update; var one; var two; -describe('generate.generator', function() { +describe('update.generator', function() { before(function() { - generate = new Generate(); + update = new Generate(); }); it('should register a generator function from a file path', function() { - one = generate.generator('one', './test/fixtures/one/generator.js'); - generate.generators.should.have.property('one'); - assert(typeof generate.generators.one === 'object'); - generate.generators.one.should.deepEqual(one); + one = update.generator('one', './test/fixtures/one/generator.js'); + update.generators.should.have.property('one'); + assert(typeof update.generators.one === 'object'); + update.generators.one.should.deepEqual(one); }); it('should register a Generate instance from a file path', function() { - two = generate.generator('two', './test/fixtures/two/generate.js'); - generate.generators.should.have.property('two'); - assert(typeof generate.generators.two === 'object'); - generate.generators.two.should.deepEqual(two); + two = update.generator('two', './test/fixtures/two/updatefile.js'); + update.generators.should.have.property('two'); + assert(typeof update.generators.two === 'object'); + update.generators.two.should.deepEqual(two); }); it('should get a registered generator by name', function() { - generate.generator('one').should.deepEqual(one); - generate.generator('two').should.deepEqual(two); + one = update.generator('one', './test/fixtures/one/generator.js'); + two = update.generator('two', './test/fixtures/two/updatefile.js'); + update.generator('one').should.deepEqual(one); + update.generator('two').should.deepEqual(two); }); }); diff --git a/test/update.getGenerator.js b/test/update.getGenerator.js index 4d81cbe..f711007 100644 --- a/test/update.getGenerator.js +++ b/test/update.getGenerator.js @@ -1,33 +1,33 @@ var assert = require('assert'); var Generate = require('..'); -var generate; +var update; describe('.getGenerator', function() { beforeEach(function() { - generate = new Generate(); + update = new Generate(); }); it('should get a generator from the base instance', function() { - generate.register('abc', function() {}); - var generator = generate.getGenerator('abc'); + update.register('abc', function() {}); + var generator = update.getGenerator('abc'); assert(generator); assert(typeof generator === 'object'); assert(generator.name === 'abc'); }); it('should get nested generator', function() { - generate.register('abc', function(abc) { + update.register('abc', function(abc) { abc.register('def', function() {}); }); - var generator = generate.getGenerator('abc.def'); + var generator = update.getGenerator('abc.def'); assert(generator); assert(typeof generator === 'object'); assert(generator.name === 'def'); }); it('should get a deeply nested generator', function() { - generate.register('abc', function(abc) { + update.register('abc', function(abc) { abc.register('def', function(def) { def.register('ghi', function(ghi) { ghi.register('jkl', function(jkl) { @@ -37,7 +37,7 @@ describe('.getGenerator', function() { }); }); - var generator = generate.getGenerator('abc.def.ghi.jkl.mno'); + var generator = update.getGenerator('abc.def.ghi.jkl.mno'); assert(generator); assert(typeof generator === 'object'); assert(generator.name === 'mno'); diff --git a/test/update.js b/test/update.js index b9d7825..27d461d 100644 --- a/test/update.js +++ b/test/update.js @@ -1,133 +1,135 @@ +'use strict'; + /* deps: coveralls istanbul */ require('mocha'); require('should'); var assert = require('assert'); var support = require('./support'); var Generate = support.resolve(); -var generate; +var update; -describe('generate', function() { +describe('update', function() { describe('constructor', function() { it('should create an instance of Generate:', function() { - generate = new Generate(); - assert(generate instanceof Generate); + update = new Generate(); + assert(update instanceof Generate); }); it('should new up without new:', function() { - generate = Generate(); - assert(generate instanceof Generate); + update = Generate(); + assert(update instanceof Generate); }); }); describe('prototype methods', function() { beforeEach(function() { - generate = new Generate(); + update = new Generate(); }); it('should expose `addLeaf`', function() { - assert(typeof generate.addLeaf === 'function'); + assert(typeof update.addLeaf === 'function'); }); it('should expose `compose`', function() { - assert(typeof generate.compose === 'function'); + assert(typeof update.compose === 'function'); }); it('should expose `generator`', function() { - assert(typeof generate.generator === 'function'); + assert(typeof update.generator === 'function'); }); it('should expose `getGenerator`', function() { - assert(typeof generate.getGenerator === 'function'); + assert(typeof update.getGenerator === 'function'); }); it('should expose `registerPath`', function() { - assert(typeof generate.registerPath === 'function'); + assert(typeof update.registerPath === 'function'); }); it('should expose `register`', function() { - assert(typeof generate.register === 'function'); + assert(typeof update.register === 'function'); }); it('should expose `extendGenerator`', function() { - assert(typeof generate.extendGenerator === 'function'); + assert(typeof update.extendGenerator === 'function'); }); it('should expose `process`', function() { - assert(typeof generate.process === 'function'); + assert(typeof update.process === 'function'); }); it('should expose `each`', function() { - assert(typeof generate.each === 'function'); + assert(typeof update.each === 'function'); }); it('should expose `eachSeries`', function() { - assert(typeof generate.eachSeries === 'function'); + assert(typeof update.eachSeries === 'function'); }); it('should expose `scaffold`', function() { - assert(typeof generate.scaffold === 'function'); + assert(typeof update.scaffold === 'function'); }); }); describe('prototype properties', function() { beforeEach(function() { - generate = new Generate(); + update = new Generate(); }); it('should expose `name`', function() { - assert(typeof generate.name === 'string'); + assert(typeof update.name === 'string'); }); it('should expose `base`', function() { - assert(typeof generate.base === 'object'); + assert(typeof update.base === 'object'); }); }); describe('instance', function() { beforeEach(function() { - generate = new Generate(); + update = new Generate(); }); - it('should default `name` to `generate`', function() { - assert.equal(generate.name, 'generate'); + it('should set `name` to `update` when `_name` is defined', function() { + assert.equal(update.name, 'update'); }); - it('should default `name` to `assemble`', function() { - delete generate._name; - assert.equal(generate.name, 'assemble'); + it('should set `name` to `update` when `_name` is not defined', function() { + delete update._name; + assert.equal(update.name, 'update'); }); - it('should default `name` to `base`', function() { - delete generate._name; - delete generate._appname; - assert.equal(generate.name, 'base'); + it('should set `name` to `update` when `_appname` is not defined', function() { + delete update._name; + delete update._appname; + assert.equal(update.name, 'update'); }); - it('should set `name` to `base`', function() { - generate.name = 'base'; - assert.equal(generate.name, 'base'); + it('should allow name setter to be set (configurable)', function() { + update.name = 'base'; + assert.equal(update.name, 'base'); }); it('should use `options.name` for `name`', function() { - generate = new Generate({name: 'generate'}); - delete generate._name; - assert.equal(generate.name, 'generate'); + update = new Generate({name: 'update'}); + delete update._name; + assert.equal(update.name, 'update'); }); it('should return `this` as `base`', function() { - generate.base.should.deepEqual(generate); + update.base.should.deepEqual(update); }); it('should return generator "base" as `base`', function() { var base = new Generate(); - generate.register('base', base); - generate.base.should.deepEqual(base); + update.register('base', base); + update.base.should.deepEqual(base); }); - it('should return generate as `base`', function() { + it('should return update as `base`', function() { var child = new Generate(); - generate.register('child', child); - child.base.should.deepEqual(generate); + update.register('child', child); + child.base.should.deepEqual(update); }); }); }); diff --git a/test/update.logger.js b/test/update.logger.js deleted file mode 100644 index 7d458dc..0000000 --- a/test/update.logger.js +++ /dev/null @@ -1,95 +0,0 @@ -var capture = require('capture-stream'); -var assert = require('assert'); - -var Logger = require('../lib/logger'); - -describe('logger', function() { - it('should create a Logger instance', function() { - var logger = new Logger(); - assert(logger instanceof Logger); - }); - - it('should create a Logger instance with provided options', function() { - var logger = new Logger({foo: 'bar'}); - assert.deepEqual(logger.options, {foo: 'bar'}); - }); - - it('should write a message out to the standard output', function() { - var restore = capture(process.stdout); - var logger = new Logger(); - logger.write('this is a test'); - var output = restore(true); - assert.equal(output, 'this is a test'); - }); - - describe('events', function() { - it('should emit `log` when `.log` is called', function() { - var output = []; - var logger = new Logger(); - logger.on('log', function(msg) { - output.push(msg); - }); - logger.log('this is a log message'); - assert.deepEqual(output, ['this is a log message']); - }); - - it('should emit `info` when `.info` is called', function() { - var output = []; - var logger = new Logger(); - logger.on('info', function(msg) { - output.push(msg); - }); - logger.info('this is an info message'); - assert.deepEqual(output, ['this is an info message']); - }); - - it('should emit `error` when `.error` is called', function() { - var output = []; - var logger = new Logger(); - logger.on('error', function(msg) { - output.push(msg); - }); - logger.error('this is an error message'); - assert.deepEqual(output, ['this is an error message']); - }); - - it('should emit `warn` when `.warn` is called', function() { - var output = []; - var logger = new Logger(); - logger.on('warn', function(msg) { - output.push(msg); - }); - logger.warn('this is a warning message'); - assert.deepEqual(output, ['this is a warning message']); - }); - - it('should chain methods together when emitting methods are called', function() { - var output = []; - var logger = new Logger(); - function handler(event) { - return function(msg) { - output.push(event + ': ' + msg); - }; - } - logger.on('log', handler('log')); - logger.on('info', handler('info')); - logger.on('error', handler('error')); - logger.on('warn', handler('warn')); - - logger - .log('this is a log message') - .info('this is an info message') - .error('this is an error message') - .warn('this is a warning message'); - - var expected = [ - 'log: this is a log message', - 'info: this is an info message', - 'error: this is an error message', - 'warn: this is a warning message' - ]; - - assert.deepEqual(output, expected); - }); - }); -}); diff --git a/test/update.register.js b/test/update.register.js index 162ac69..30b0cf0 100644 --- a/test/update.register.js +++ b/test/update.register.js @@ -5,46 +5,46 @@ var assert = require('assert'); var support = require('./support'); var Generate = support.resolve(); var Base = Generate.Base; -var generate; +var update; -describe('generate.register', function() { +describe('update.register', function() { beforeEach(function() { - generate = new Generate(); + update = new Generate(); }); it('should register a Generate instance', function() { var child = new Generate(); - generate.register('child', child); - generate.generators.should.have.property('child'); - assert(typeof generate.generators.child === 'object'); - generate.generators.child.should.deepEqual(child); + update.register('child', child); + update.generators.should.have.property('child'); + assert(typeof update.generators.child === 'object'); + update.generators.child.should.deepEqual(child); }); it('should register a generator function', function() { var registered = false; - var child = generate.register('child', function(app, base, env) { + var child = update.register('child', function(app, base, env) { registered = true; assert(typeof app === 'object'); assert(app.isGenerate === true); }); assert(registered); - generate.generators.should.have.property('child'); - assert(typeof generate.generators.child === 'object'); - generate.generators.child.should.deepEqual(child); + update.generators.should.have.property('child'); + assert(typeof update.generators.child === 'object'); + update.generators.child.should.deepEqual(child); }); - it('should register a non-generate instance', function() { + it('should register a non-update instance', function() { var child = new Base(); - generate.register('child', child); - generate.generators.should.have.property('child'); - assert(typeof generate.generators.child === 'object'); - generate.generators.child.should.deepEqual(child); + update.register('child', child); + update.generators.should.have.property('child'); + assert(typeof update.generators.child === 'object'); + update.generators.child.should.deepEqual(child); }); it('should register a generator from a string', function() { - var one = generate.register('one', './test/fixtures/one/generator.js'); - generate.generators.should.have.property('one'); - assert(typeof generate.generators.one === 'object'); - generate.generators.one.should.deepEqual(one); + var one = update.register('one', './test/fixtures/one/generator.js'); + update.generators.should.have.property('one'); + assert(typeof update.generators.one === 'object'); + update.generators.one.should.deepEqual(one); }); }); diff --git a/test/update.registerPath.js b/test/update.registerPath.js index 9e64767..49ac3b8 100644 --- a/test/update.registerPath.js +++ b/test/update.registerPath.js @@ -5,24 +5,24 @@ var assert = require('assert'); var support = require('./support'); var Generate = support.resolve(); var Base = Generate.Base; -var generate; +var update; -describe('generate.registerPath', function() { +describe('update.registerPath', function() { beforeEach(function() { - generate = new Generate(); + update = new Generate(); }); - it('should register a generator function from a file path', function() { - var one = generate.registerPath('one', './test/fixtures/one/generator.js'); - generate.generators.should.have.property('one'); - assert(typeof generate.generators.one === 'object'); - generate.generators.one.should.deepEqual(one); + it('should register a generator function from a filepath', function() { + var one = update.registerPath('one', './test/fixtures/one/generator.js'); + update.generators.should.have.property('one'); + assert(typeof update.generators.one === 'object'); + update.generators.one.should.deepEqual(one); }); - it('should register a Generate instance from a file path', function() { - var two = generate.registerPath('two', './test/fixtures/two/generate.js'); - generate.generators.should.have.property('two'); - assert(typeof generate.generators.two === 'object'); - generate.generators.two.should.deepEqual(two); + it('should register a Generate instance from a filepath', function() { + var two = update.registerPath('two', './test/fixtures/two/updatefile.js'); + update.generators.should.have.property('two'); + assert(typeof update.generators.two === 'object'); + update.generators.two.should.deepEqual(two); }); }); diff --git a/test/update.scaffold.js b/test/update.scaffold.js deleted file mode 100644 index f9de9c4..0000000 --- a/test/update.scaffold.js +++ /dev/null @@ -1,234 +0,0 @@ -'use strict'; - -require('mocha'); -var fs = require('fs'); -var path = require('path'); -var assert = require('assert'); -var rimraf = require('rimraf'); -var through = require('through2'); -var Scaffold = require('scaffold'); -var Generate = require('..'); -var app, scaffold, config; - -var fixtures = path.join(__dirname, 'fixtures'); -var actual = path.join(__dirname, 'actual'); - -function output(name) { - return path.join(actual, name); -} - -function fixture(name) { - return path.join(fixtures, name); -} - -function exists(name) { - try { - fs.statSync(output(name)); - return true; - } catch(err) {} - return false; -} - -function base(cb) { - return through.obj(function(file, enc, next) { - var str = file.contents.toString(); - cb(file, str, next); - }); -} - -describe('scaffolds', function() { - beforeEach(function(cb) { - app = new Generate(); - rimraf(actual, cb); - }); - - afterEach(function(cb) { - rimraf(actual, cb); - }); - - describe('setup', function() { - it('should clean out all test fixtures', function(cb) { - assert(!exists(actual)); - cb(); - }); - }); - - describe('targets', function() { - it.only('should process files from the process options.cwd', function(cb) { - scaffold = new Scaffold({ - docs: { - src: 'b.txt', - dest: actual, - cwd: fixtures - } - }); - - app.scaffold(scaffold, {cwd: fixtures}, function(err) { - if (err) return cb(err); - assert(exists('b.txt')); - cb(); - }); - }); - - it('should use the cwd passed on the config.options.cwd', function(cb) { - assert(!exists('b.txt')); - - scaffold = new Scaffold({ - cwd: fixtures, - src: 'b.txt', - dest: actual - }); - - app.scaffold(scaffold) - .on('error', cb) - .on('end', function() { - assert(exists('b.txt')); - cb(); - }); - }); - - it('should work with no options:', function(cb) { - scaffold = new Scaffold({src: 'b.txt', dest: actual, cwd: fixtures}); - app.scaffold(scaffold) - .on('error', cb) - .on('end', function() { - assert(exists('b.txt')); - cb(); - }); - }); - - it('should process a single file', function(cb) { - assert(!exists('a.txt')); - - scaffold = new Scaffold({ - cwd: fixtures, - src: 'a.txt', - dest: actual - }); - - app.scaffold(scaffold) - .on('error', cb) - .on('end', function() { - assert(exists('a.txt')); - cb(); - }); - }); - - it('should process a glob of files', function(cb) { - assert(!exists('a.txt')); - assert(!exists('b.txt')); - assert(!exists('c.txt')); - - scaffold = new Scaffold({ - cwd: fixtures, - src: '*.txt', - dest: actual - }); - - app.scaffold(scaffold) - .on('error', cb) - .on('end', function() { - assert(exists('a.txt')); - assert(exists('b.txt')); - assert(exists('c.txt')); - cb(); - }); - }); - }); - - describe('plugin', function() { - it('should use a plugin to modify file contents', function(cb) { - app.plugin('append', function(opts) { - opts = opts || {}; - return base(function(file, str, next) { - file.contents = new Buffer(str + opts.suffix); - next(null, file); - }); - }); - - scaffold = new Scaffold({ - cwd: fixtures, - src: '*.txt', - dest: actual - }); - - app.scaffold(scaffold, {suffix: 'zzz'}) - .on('error', cb) - .on('data', function(data) { - var str = data.contents.toString(); - var end = str.slice(-3); - assert(end === 'zzz'); - }) - .on('end', function() { - assert(exists('example.txt')); - cb(); - }); - }); - - it('should run plugins defined on config.options', function(cb) { - function appendString(suffix) { - return base(function(file, str, next) { - file.contents = new Buffer(str + suffix); - next(null, file); - }); - } - - app.plugin('a', appendString('aaa')); - app.plugin('b', appendString('bbb')); - app.plugin('c', appendString('ccc')); - - scaffold = new Scaffold({ - options: {pipeline: ['a', 'c']}, - cwd: fixtures, - src: 'a.txt', - dest: actual - }); - - app.scaffold(scaffold, {suffix: 'zzz'}) - .on('error', cb) - .on('data', function(data) { - var str = data.contents.toString(); - assert(str.indexOf('bbb') === -1); - var end = str.slice(-6); - assert(end === 'aaaccc'); - }) - .on('end', function() { - assert(exists('a.txt')); - cb(); - }); - }); - - it('should run plugins defined on process.options', function(cb) { - function appendString(suffix) { - return base(function(file, str, next) { - file.contents = new Buffer(str + suffix); - next(null, file); - }); - } - - app.plugin('a', appendString('aaa')); - app.plugin('b', appendString('bbb')); - app.plugin('c', appendString('ccc')); - - scaffold = new Scaffold({ - cwd: fixtures, - src: 'a.txt', - dest: actual - }); - - app.scaffold(scaffold, {pipeline: ['a', 'c'], suffix: 'zzz'}) - .on('error', cb) - .on('data', function(data) { - var str = data.contents.toString(); - assert(str.indexOf('bbb') === -1); - var end = str.slice(-6); - assert(end === 'aaaccc'); - }) - .on('end', function() { - assert(exists('a.txt')); - cb(); - }); - }); - }); -}); - diff --git a/test/update.to-tasks.js b/test/update.to-tasks.js deleted file mode 100644 index f8e5c43..0000000 --- a/test/update.to-tasks.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var assert = require('assert'); -var toTasks = require('../lib/to-tasks'); - -describe('to-tasks', function() { - it('should create a task', function() { - console.log(toTasks({_: ['foo', 'bar']}, {}, {})) - }); -}); - diff --git a/test/update.utils.js b/test/update.utils.js deleted file mode 100644 index ed3e647..0000000 --- a/test/update.utils.js +++ /dev/null @@ -1,239 +0,0 @@ -var assert = require('assert'); -var utils = require('../lib/utils'); -var mkdirp = require('mkdirp'); -var rimraf = require('rimraf'); - -describe('utils', function() { - describe('alias', function() { - it('should throw an error when pkgName is not a string', function(done) { - try { - utils.alias(null, {}); - done(new Error('expected an error')); - } catch (err) { - assert.equal(err.message, 'expected name to be a string'); - done(); - } - }); - - it('should use the cwd when filepath and pkgName are undefined', function() { - assert.equal(utils.alias(), 'generate'); - }); - - it('should create an alias from a given pkgName', function() { - assert.equal(utils.alias(null, 'generator-foo'), 'foo'); - }); - - it.skip('should create an alias from a given file filepath', function() { - assert.equal(utils.alias(__filename), 'test'); - }); - - it.skip('should create an alias from a given directory filepath', function() { - assert.equal(utils.alias(__dirname), 'test'); - }); - }); - - describe('arrayify', function() { - it('should return original array', function() { - assert.deepEqual(utils.arrayify(['foo', 'bar']), ['foo', 'bar']); - }); - - it('should return array when given a string', function() { - assert.deepEqual(utils.arrayify('foo'), ['foo']); - }); - - it('should return array when given an object', function() { - assert.deepEqual(utils.arrayify({foo: 'bar'}), [{foo: 'bar'}]); - }); - - it('should return array when given a function', function() { - var foo = function() {}; - assert.deepEqual(utils.arrayify(foo), [foo]); - }); - - it('should return an empty array when given null', function() { - assert.deepEqual(utils.arrayify(null), []); - }); - - it('should return an empty array when given undefined', function() { - assert.deepEqual(utils.arrayify(), []); - }); - - it('should return an empty array when given false', function() { - assert.deepEqual(utils.arrayify(false), []); - }); - }); - - describe('createAlias', function() { - it('should throw an error when name is undefined', function(done) { - try { - utils.createAlias(); - done(new Error('expected an error')); - } catch (err) { - assert.equal(err.message, 'expected name to be a string'); - done(); - } - }); - - it('should throw an error when name is not a string', function(done) { - try { - utils.createAlias({}); - done(new Error('expected an error')); - } catch (err) { - assert.equal(err.message, 'expected name to be a string'); - done(); - } - }); - - it('should return original name when name does not contain a -', function() { - assert.equal(utils.createAlias('foo'), 'foo'); - }); - - it('should return an alias when name contains a -', function() { - assert.equal(utils.createAlias('generator-foo'), 'foo'); - }); - - it('should return an alias when name contains multiple -', function() { - assert.equal(utils.createAlias('generator-foo-bar'), 'foo-bar'); - }); - }); - - describe('exists', function() { - it('should return true for ' + __filename, function() { - assert(utils.exists(__filename)); - }); - - it('should return false for something-that-does-not-exist.txt', function() { - assert(utils.exists('something-that-does-not-exist.txt') === false); - }); - }); - - describe('flatten', function() { - - }); - - describe('globFiles', function() { - it('should return files from process.cwd', function() { - assert.deepEqual(utils.globFiles(['index.js']), [process.cwd() + '/index.js']); - }); - - it('should return files from custom cwd', function() { - assert.deepEqual(utils.globFiles(['*.js'], {cwd: __dirname + '/fixtures/one'}), [__dirname + '/fixtures/one/generator.js']); - }); - }); - - describe('isDirectory', function() { - it('should return true for ' + __dirname, function() { - assert(utils.isDirectory(__dirname)); - }); - - it('should return false for ' + __filename, function() { - assert(utils.isDirectory(__filename) === false); - }); - - it('should return false for something-that-does-not-exist', function() { - assert(utils.isDirectory(__dirname + '/something-that-does-not-exist') === false); - }); - }); - - describe('isEmpty', function() { - before(function(done) { - mkdirp(__dirname + '/fixtures/empty', done); - }); - - after(function(done) { - rimraf(__dirname + '/fixtures/empty', done); - }); - - it('should return false when the directory does not exist', function() { - assert(utils.isEmpty(__dirname + '/something-that-does-not-exist/') === false); - }); - - it('should return false when the directory is not empty', function() { - assert(utils.isEmpty(__dirname) === false); - }); - - it('should return true when an empty directory exists', function() { - assert(utils.isEmpty(__dirname + '/fixtures/empty')); - }); - - it('should return true using custom filter function', function() { - assert(utils.isEmpty(__dirname, function() { return false; })); - }); - - it('should return false using custom filter function', function() { - assert(utils.isEmpty(__dirname, function() { return true; }) === false); - }); - }); - - describe('isObject', function() { - it('should return true for an object', function() { - assert(utils.isObject({})); - }); - - it('should return false for a function', function() { - assert(utils.isObject(function() {}) === false); - }); - - it('should return false for a string', function() { - assert(utils.isObject('foo') === false); - }); - - it('should return false for an array', function() { - assert(utils.isObject([]) === false); - }); - - it('should return false for undefined', function() { - assert(utils.isObject() === false); - }); - - it('should return false for null', function() { - assert(utils.isObject(null) === false); - }); - - it('should return false for a number', function() { - assert(utils.isObject(5) === false); - }); - - it('should return false for a boolean', function() { - assert(utils.isObject(true) === false); - }); - }); - - describe('logger', function() { - - }); - - describe('runtimes', function() { - - }); - - describe('tableize', function() { - - }); - - describe('timestamp', function() { - - }); - - describe('toKey', function() { - it('should make a key from a namespace and prop', function() { - assert.equal(utils.toKey('generators', 'foo.bar'), 'generators.foo.generators.bar'); - }); - - it('should make a key from a namespace and prop starting with the namespace', function() { - assert.equal(utils.toKey('generators', 'generators.foo.bar'), 'generators.foo.generators.bar'); - }); - - it('should make a key from a namespace and prop with multiple namespace segments', function() { - assert.equal(utils.toKey('generators', 'generators.foo.generators.bar'), 'generators.foo.generators.bar'); - }); - }); - - describe('tryRequire', function() { - - }); - - describe('tryResolve', function() { - - }); -}); diff --git a/test2/app.applyLayout.js b/test2/app.applyLayout.js new file mode 100644 index 0000000..7c8d782 --- /dev/null +++ b/test2/app.applyLayout.js @@ -0,0 +1,87 @@ +'use strict'; + +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; +var page = { + content: '<%= name %>', + layout: 'default.tmpl', + locals: { + name: 'Halle' + } +}; + +describe('helpers', function() { + describe('rendering', function() { + beforeEach(function() { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.create('layout', { viewType: 'layout' }); + app.create('page'); + }); + + it('should throw an error when a layout cannot be found:', function(cb) { + app.layout('fofof.tmpl', {content: '..'}); + app.page('a.tmpl', page) + .render(function(err) { + assert.equal(err.message, 'Templates#layouts layout "default.tmpl" was defined on view "a.tmpl"\nbut cannot be not found (common causes are incorrect glob patterns,\nrenameKey function modifying the key, and typos in search pattern)'); + cb(); + }); + }); + + it('should emit an error when a layout cannot be found:', function(cb) { + app.layout('fofof.tmpl', {content: '..'}); + app.on('error', function(err) { + assert.equal(err.message, 'Templates#layouts layout "default.tmpl" was defined on view "a.tmpl"\nbut cannot be not found (common causes are incorrect glob patterns,\nrenameKey function modifying the key, and typos in search pattern)'); + cb(); + }); + + app.page('a.tmpl', page) + .render(function() { + }); + }); + + it('should throw an error - layout defined but no layouts registered:', function(cb) { + app.page('a.tmpl', page) + .render(function(err) { + assert.equal(err.message, 'Templates#layouts layout "default.tmpl" was defined on view "a.tmpl"\nbut cannot be not found (common causes are incorrect glob patterns,\nrenameKey function modifying the key, and typos in search pattern)'); + cb(); + }); + }); + + it('should emit an error - layout defined but no layouts registered:', function(cb) { + app.on('error', function(err) { + assert.equal(err.message, 'Templates#layouts layout "default.tmpl" was defined on view "a.tmpl"\nbut cannot be not found (common causes are incorrect glob patterns,\nrenameKey function modifying the key, and typos in search pattern)'); + cb(); + }); + app.page('a.tmpl', page) + .render(function() { + }); + }); + + it('should wrap a view with a layout (view.render):', function(cb) { + app.layout('default.tmpl', {content: 'before {% body %} after'}); + app.page('a.tmpl', page) + .render(function(err) { + if (err) return cb(err); + cb(); + }); + }); + + it('should wrap a view with a layout (app.render):', function(cb) { + app.layout('default.tmpl', {content: 'before {% body %} after'}); + app.page('a.tmpl', page); + + var view = app.pages.getView('a.tmpl'); + app.render(view, function(err, res) { + if (err) return cb(err); + assert(res.contents.toString() === 'before Halle after'); + cb(); + }); + }); + }); +}); + diff --git a/test2/app.collection.compile.js b/test2/app.collection.compile.js new file mode 100644 index 0000000..1ceed4e --- /dev/null +++ b/test2/app.collection.compile.js @@ -0,0 +1,50 @@ +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var Views = App.Views; +var views; + +describe('compile', function() { + beforeEach(function() { + views = new Views(); + }); + + it('should throw an error when an engine cannot be found:', function() { + views.addView('foo.bar', {content: '<%= name %>'}); + var page = views.getView('foo.bar'); + (function() { + views.compile(page); + }).should.throw('Views#compile cannot find an engine for: .bar'); + }); + + it('should compile a template:', function() { + views.engine('tmpl', require('engine-base')); + views.addView('a.tmpl', {path: 'a.tmpl', content: '<%= a %>', a: 'b'}); + + var page = views.getView('a.tmpl'); + var view = views.compile(page); + assert.equal(typeof view.fn, 'function'); + }); + + it('should compile a template by name:', function() { + views.engine('tmpl', require('engine-base')); + views.addView('a.tmpl', {path: 'a.tmpl', content: '<%= a %>', a: 'b'}); + + var view = views.compile('a.tmpl'); + assert.equal(typeof view.fn, 'function'); + }); + + it('should throw an error when a callback is given:', function() { + views.engine('md', require('engine-base')); + views.addView('foo.md', {content: '<%= name %>'}); + var page = views.getView('foo.md'); + (function() { + views.compile(page, function() {}); + }).should.throw('Views#compile is sync and does not take a callback function'); + + (function() { + views.compile(page, {}, function() {}); + }).should.throw('Views#compile is sync and does not take a callback function'); + }); +}); diff --git a/test2/app.collection.js b/test2/app.collection.js new file mode 100644 index 0000000..ef8c548 --- /dev/null +++ b/test2/app.collection.js @@ -0,0 +1,186 @@ +'use strict'; + +require('mocha'); +require('should'); +var fs = require('fs'); +var path = require('path'); +var assert = require('assert'); +var define = require('define-property'); +var support = require('./support'); +var App = support.resolve(); +var Collection = App.Collection; +var app; + +describe('collection', function() { + describe('method', function() { + beforeEach(function() { + app = new App(); + }); + + it('should expose the collection method', function() { + assert(typeof app.collection === 'function'); + }); + + it('should return a new collection', function() { + var collection = app.collection(); + assert(typeof collection === 'object'); + }); + + it('should have isCollection property', function() { + var collection = app.collection(); + assert(collection.isCollection === true); + }); + }); + + describe('adding views', function() { + beforeEach(function() { + app = new App() + .use(function() { + return function() { + define(this, 'count', { + get: function() { + return Object.keys(this.views).length; + }, + set: function() { + throw new Error('count is a read-only getter and cannot be defined.'); + } + }); + }; + }); + + app.engine('tmpl', require('engine-base')); + app.create('pages', { + renameKey: function(fp) { + return path.relative(process.cwd(), fp); + } + }); + }); + + it('should load a view onto the respective collection:', function() { + app.pages('test/fixtures/pages/a.hbs'); + app.views.pages.should.have.property('test/fixtures/pages/a.hbs'); + }); + + it('should allow collection methods to be chained:', function() { + app + .pages('test/fixtures/pages/a.hbs') + .pages('test/fixtures/pages/b.hbs') + .pages('test/fixtures/pages/c.hbs'); + + app.views.pages.should.have.properties([ + 'test/fixtures/pages/a.hbs', + 'test/fixtures/pages/b.hbs', + 'test/fixtures/pages/c.hbs' + ]); + }); + + it('should expose the `option` method:', function() { + app.pages.option('foo', 'bar') + .pages('test/fixtures/pages/a.hbs') + .pages('test/fixtures/pages/b.hbs') + .pages('test/fixtures/pages/c.hbs'); + + app.pages.options.should.have.property('foo', 'bar'); + app.views.pages.should.have.properties([ + 'test/fixtures/pages/a.hbs', + 'test/fixtures/pages/b.hbs', + 'test/fixtures/pages/c.hbs' + ]); + }); + + it('should expose the `option` method:', function() { + app.pages.option('foo', 'bar') + .pages('test/fixtures/pages/a.hbs') + .pages('test/fixtures/pages/b.hbs') + .pages('test/fixtures/pages/c.hbs'); + + assert(app.pages.count === 3); + }); + }); + + describe('addItem', function() { + beforeEach(function() { + app = new App(); + }); + + it('should add items to a collection', function() { + var pages = app.collection({Collection: Collection}); + pages.addItem('foo'); + pages.addItem('bar'); + pages.addItem('baz'); + + pages.items.hasOwnProperty('foo'); + pages.items.hasOwnProperty('bar'); + pages.items.hasOwnProperty('baz'); + }); + + it('should create a collection from an existing collection:', function() { + var pages = app.collection({Collection: Collection}); + pages.addItem('foo'); + pages.addItem('bar'); + pages.addItem('baz'); + + var posts = app.collection(pages); + posts.items.hasOwnProperty('foo'); + posts.items.hasOwnProperty('bar'); + posts.items.hasOwnProperty('baz'); + }); + }); + + describe('rendering views', function() { + beforeEach(function() { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.create('pages'); + app.cache.data = {}; + }); + + it('should render a view with inherited app.render', function(cb) { + app.page('test/fixtures/templates/a.tmpl') + .use(function(view) { + view.contents = fs.readFileSync(view.path); + }) + .set('data.name', 'Brian') + .render(function(err, res) { + if (err) return cb(err); + assert(res.content === 'Brian'); + cb(); + }); + }); + }); +}); + +describe('collection singular method', function() { + describe('create', function() { + beforeEach(function() { + app = new App(); + }); + + it('should add a pluralized collection from singular name', function() { + app.create('page'); + assert(typeof app.views.pages === 'object'); + }); + }); + + describe('adding views', function() { + beforeEach(function() { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.create('page', { + renameKey: function(fp) { + return path.relative(process.cwd(), fp); + } + }); + }); + + it('should add a view to the created collection:', function() { + app.page('test/fixtures/pages/a.hbs'); + assert(typeof app.views.pages['test/fixtures/pages/a.hbs'] === 'object'); + }); + + it('should expose the `option` method:', function() { + app.pages.option('foo', 'bar'); + app.pages.options.should.have.property('foo', 'bar'); + }); + }); +}); diff --git a/test2/app.collection.render.js b/test2/app.collection.render.js new file mode 100644 index 0000000..2d23152 --- /dev/null +++ b/test2/app.collection.render.js @@ -0,0 +1,157 @@ +'use strict'; + +require('mocha'); +require('should'); +var async = require('async'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var List = App.List; +var pages, app; + +describe('render', function() { + describe('rendering', function() { + beforeEach(function() { + app = App(); + pages = app.create('pages'); + app.engine('tmpl', require('engine-base')); + pages.engine('tmpl', require('engine-base')); + }); + + it('should throw an error when no callback is given:', function() { + (function() { + app.pages.render({}); + }).should.throw('Views#render is async and expects a callback function'); + }); + + it('should throw an error when an engine is not defined:', function(done) { + pages.addView('foo.bar', { content: '<%= name %>' }); + var page = pages.getView('foo.bar'); + + app.pages.render(page, function(err) { + assert(err.message === 'Views#render cannot find an engine for: .bar'); + done(); + }); + }); + + it('should use helpers defined on app to render a view:', function(done) { + var locals = {name: 'Halle'}; + app.helper('upper', function(str) { + return str.toUpperCase(str) + 'app'; + }); + + pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); + var page = pages.getView('a.tmpl'); + + app.render(page, function(err, res) { + if (err) return done(err); + + assert(res.content === 'a HALLEapp b'); + done(); + }); + }); + + it('should use helpers defined on app to render a view with collection.render:', function(done) { + var locals = {name: 'Halle'}; + app.helper('upper', function(str) { + return str.toUpperCase(str) + 'app'; + }); + + pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); + pages.helper('upper', app._.helpers.sync.upper); + var page = pages.getView('a.tmpl'); + + pages.render(page, function(err, res) { + if (err) return done(err); + + assert(res.content === 'a HALLEapp b'); + done(); + }); + }); + + it('should use helpers when rendering a view:', function(done) { + var locals = {name: 'Halle'}; + pages.helper('upper', function(str) { + return str.toUpperCase(str); + }); + + pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); + var page = pages.getView('a.tmpl'); + + pages.render(page, function(err, res) { + if (err) return done(err); + assert(res.content === 'a HALLE b'); + done(); + }); + }); + + it('should render a template when contents is a buffer:', function(done) { + pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); + var view = pages.getView('a.tmpl'); + + pages.render(view, function(err, view) { + if (err) return done(err); + assert(view.contents.toString() === 'b'); + done(); + }); + }); + + it('should render a template when content is a string:', function(done) { + pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); + var view = pages.getView('a.tmpl'); + + pages.render(view, function(err, view) { + if (err) return done(err); + assert(view.contents.toString() === 'b'); + done(); + }); + }); + + it('should render a view from its path:', function(done) { + pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); + + pages.render('a.tmpl', function(err, view) { + if (err) return done(err); + assert(view.content === 'b'); + done(); + }); + }); + + it('should use a plugin for rendering:', function(done) { + pages.engine('tmpl', require('engine-base')); + pages.option('engine', 'tmpl'); + + pages.addViews({ + 'a': {content: '<%= title %>', locals: {title: 'aaa'}}, + 'b': {content: '<%= title %>', locals: {title: 'bbb'}}, + 'c': {content: '<%= title %>', locals: {title: 'ccc'}}, + 'd': {content: '<%= title %>', locals: {title: 'ddd'}}, + 'e': {content: '<%= title %>', locals: {title: 'eee'}}, + 'f': {content: '<%= title %>', locals: {title: 'fff'}}, + 'g': {content: '<%= title %>', locals: {title: 'ggg'}}, + 'h': {content: '<%= title %>', locals: {title: 'hhh'}}, + 'i': {content: '<%= title %>', locals: {title: 'iii'}}, + 'j': {content: '<%= title %>', locals: {title: 'jjj'}} + }); + + pages.use(function(collection) { + collection.option('pager', false); + + collection.renderEach = function(cb) { + var list = new List(collection); + async.map(list.items, function(item, next) { + collection.render(item, next); + }, cb); + }; + }); + + pages.renderEach(function(err, items) { + if (err) return done(err); + assert(items[0].content === 'aaa'); + assert(items[9].content === 'jjj'); + assert(items.length === 10); + done(); + }); + }); + }); +}); diff --git a/test2/app.compile.js b/test2/app.compile.js new file mode 100644 index 0000000..090676e --- /dev/null +++ b/test2/app.compile.js @@ -0,0 +1,52 @@ +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('compile', function() { + beforeEach(function() { + app = new App(); + app.create('page'); + }); + + it('should throw an error when an engine cannot be found:', function() { + app.page('foo.bar', {content: '<%= name %>'}); + var page = app.pages.getView('foo.bar'); + (function() { + app.compile(page); + }).should.throw('Templates#compile cannot find an engine for: .bar'); + }); + + it('should compile a template:', function() { + app.engine('tmpl', require('engine-base')); + app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>', a: 'b'}); + + var page = app.pages.getView('a.tmpl'); + var view = app.compile(page); + assert.equal(typeof view.fn, 'function'); + }); + + it('should compile a template by name:', function() { + app.engine('tmpl', require('engine-base')); + app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>', a: 'b'}); + + var view = app.compile('a.tmpl'); + assert.equal(typeof view.fn, 'function'); + }); + + it('should throw an error when a callback is given:', function() { + app.engine('md', require('engine-base')); + app.page('foo.md', {content: '<%= name %>'}); + var page = app.pages.getView('foo.md'); + (function() { + app.compile(page, function() { + }); + }).should.throw('Templates#compile is sync and does not take a callback function'); + + (function() { + app.compile(page, {}, function() { + }); + }).should.throw('Templates#compile is sync and does not take a callback function'); + }); +}); diff --git a/test2/app.copy.js b/test2/app.copy.js new file mode 100644 index 0000000..cf18b8c --- /dev/null +++ b/test2/app.copy.js @@ -0,0 +1,31 @@ +require('mocha'); +var path = require('path'); +var assert = require('assert'); +var rimraf = require('rimraf'); +var App = require('..'); +var app; + +var fixtures = path.join(__dirname, 'fixtures/copy/*.txt'); +var actual = path.join(__dirname, 'actual'); + +describe('copy()', function() { + beforeEach(function(done) { + rimraf(actual, done); + app = new App(); + }); + + afterEach(function(done) { + rimraf(actual, done); + }); + + describe('streams', function() { + it('should copy files', function(done) { + app.copy(fixtures, path.join(__dirname, 'actual')) + .on('error', done) + .on('data', function(file) { + assert.equal(typeof file, 'object'); + }) + .on('end', done); + }); + }); +}); diff --git a/test2/app.create.js b/test2/app.create.js new file mode 100644 index 0000000..5486eb2 --- /dev/null +++ b/test2/app.create.js @@ -0,0 +1,244 @@ +require('mocha'); +require('should'); +var fs = require('fs'); +var path = require('path'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('create', function() { + describe('inflections', function() { + beforeEach(function() { + app = new App(); + }); + + it('should expose the create method', function() { + assert(typeof app.create === 'function'); + }); + + it('should add a collection to `views`', function() { + app.create('pages'); + assert(typeof app.views.pages === 'object'); + assert(typeof app.pages === 'function'); + }); + + it('should add a pluralized collection to `views`', function() { + app.create('page'); + assert(typeof app.views.pages === 'object'); + assert(typeof app.page === 'function'); + }); + }); + + describe('renderable views', function() { + beforeEach(function() { + app = new App(); + app.create('pages'); + app.create('partials', {viewType: 'partial'}); + app.create('layout', {viewType: 'layout'}); + }); + + it('should add renderable views when no type is defined', function() { + app.pages.addView('foo', {content: 'bar'}); + assert(app.views.pages.hasOwnProperty('foo')); + }); + + it('should add view Ctor names to views', function() { + app.pages.addView('foo', {content: 'bar'}); + assert(app.views.pages.foo._name === 'Page'); + }); + + it('should add partial views when partial type is defined', function() { + app.partials.addView('abc', {content: 'xyz'}); + assert(app.views.partials.hasOwnProperty('abc')); + }); + + it('should add layout views when layout type is defined', function() { + app.layouts.addView('foo', {content: 'bar'}); + assert(app.views.layouts.hasOwnProperty('foo')); + }); + + it('should set viewType on renderable views', function() { + app.pages.addView('foo', {content: 'bar'}); + var view = app.pages.getView('foo'); + assert(view.isType('renderable')); + assert(!view.isType('layout')); + assert(!view.isType('partial')); + }); + + it('should set viewType on partial views', function() { + app.partials.addView('foo', {content: 'bar'}); + var view = app.partials.getView('foo'); + assert(view.isType('partial')); + assert(!view.isType('layout')); + assert(!view.isType('renderable')); + }); + + it('should set viewType on layout views', function() { + app.layouts.addView('foo', {content: 'bar'}); + var view = app.layouts.getView('foo'); + assert(view.isType('layout')); + assert(!view.isType('renderable')); + assert(!view.isType('partial')); + }); + }); + + describe('custom constructors', function() { + beforeEach(function() { + var Vinyl = require('vinyl'); + Vinyl.prototype.custom = function(key) { + this[key] = 'nonsense'; + return this; + }; + app = new App({View: Vinyl}); + app.create('pages'); + }); + + it('should create views from key-value pairs:', function() { + app.page('a.hbs', {path: 'a.hbs', content: 'a'}); + app.page('b.hbs', {path: 'b.hbs', content: 'b'}); + app.page('c.hbs', {path: 'c.hbs', content: 'c'}); + var a = app.pages.getView('a.hbs'); + a.custom('foo'); + a.foo.should.equal('nonsense'); + }); + }); + + describe('custom instances', function() { + it('should create views from custom `View` and `Views` instance/ctor:', function() { + var Vinyl = require('vinyl'); + Vinyl.prototype.read = function(file) { + return fs.readFileSync(file.path); + }; + + var Views = App.Views; + var views = new Views({View: Vinyl}); + + views.addView('a.hbs', {path: 'a.hbs', content: 'a'}); + views.addView('b.hbs', {path: 'b.hbs', content: 'b'}); + views.addView('c.hbs', {path: 'c.hbs', content: 'c'}); + + app = new App(); + app.create('pages', views); + + var a = app.pages.getView('a.hbs'); + assert(a instanceof Vinyl); + assert(Vinyl.isVinyl(a)); + assert(typeof a.read === 'function'); + + views.addView('d.hbs', {path: 'd.hbs', content: 'd'}); + var d = app.pages.getView('d.hbs'); + assert(d instanceof Vinyl); + assert(Vinyl.isVinyl(d)); + }); + }); + + describe('chaining', function() { + beforeEach(function() { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.create('page', { + renameKey: function(fp) { + return path.relative(process.cwd(), fp); + } + }); + }); + + it('should create views from key-value pairs:', function() { + app.page('a.hbs', {content: 'a'}); + app.page('b.hbs', {content: 'b'}); + app.page('c.hbs', {content: 'c'}); + app.views.pages.should.have.properties(['a.hbs', 'b.hbs', 'c.hbs']); + assert(app.views.pages['a.hbs'].contents.toString() === 'a'); + }); + + it('should create views from file paths:', function() { + app.page('test/fixtures/pages/a.hbs'); + app.page('test/fixtures/pages/b.hbs'); + app.page('test/fixtures/pages/c.hbs'); + + app.views.pages.should.have.properties([ + 'test/fixtures/pages/a.hbs', + 'test/fixtures/pages/b.hbs', + 'test/fixtures/pages/c.hbs' + ]); + }); + }); + + describe('instance', function() { + beforeEach(function() { + app = new App(); + app.engine('tmpl', require('engine-base')); + }); + + it('should return the collection instance', function() { + var collection = app.create('pages'); + assert(collection instanceof App.Views); + + collection.option('renameKey', function(key) { + return path.basename(key); + }); + collection + .use(function(views) { + views.read = function(name) { + var view = this.getView(name); + view.contents = fs.readFileSync(view.path); + }; + }); + + collection.addView('test/fixtures/templates/a.tmpl'); + collection.read('a.tmpl'); + assert(collection.getView('a.tmpl').contents.toString() === '<%= name %>'); + }); + }); + + describe('viewType', function() { + beforeEach(function() { + app = new App(); + app.engine('tmpl', require('engine-base')); + }); + + it('should add collection to the given viewType', function() { + app.create('layout', {viewType: 'layout'}); + assert(app.layouts.options.viewType[0] === 'layout'); + }); + + it('should add a collection to multiple viewTypes', function() { + app.create('foo', {viewType: ['layout', 'renderable']}); + assert.deepEqual(app.foos.options.viewType, ['layout', 'renderable']); + }); + }); + + describe('events', function() { + beforeEach(function() { + app = new App(); + app.engine('tmpl', require('engine-base')); + }); + + it('should emit `create` when a collection is created:', function() { + app.on('create', function(collection) { + if (collection.options.plural === 'layouts') { + collection.options.foo = 'bar'; + } + }); + + app.create('layout'); + app.layout('one', {path: 'two', contents: '...'}); + assert(app.layouts.options.foo === 'bar'); + }); + }); + + describe('collection instantiation', function() { + it('should expose collection instance methods that are created after instantiation on the app collection loader', function() { + app.create('pages'); + app.pages.use(function(collection) { + collection.define('foo', function(msg) { + return 'foo ' + msg; + }); + }); + + assert(app.pages.foo); + assert(typeof app.pages.foo === 'function'); + }); + }); +}); diff --git a/test2/app.data.js b/test2/app.data.js new file mode 100644 index 0000000..f3a1992 --- /dev/null +++ b/test2/app.data.js @@ -0,0 +1,94 @@ +require('mocha'); +require('should'); +var path = require('path'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('app.data', function() { + beforeEach(function() { + app = new App(); + }); + + it('should set a key-value pair on cache.data:', function() { + app.data('a', 'b'); + assert(app.cache.data.a === 'b'); + }); + + it('should set an object on cache.data:', function() { + app.data({c: 'd'}); + assert(app.cache.data.c === 'd'); + }); + + it('should load data from a file onto cache.data:', function() { + app.data('test/fixtures/data/a.json'); + assert(app.cache.data.a.one.a === 'aaa'); + }); + + it('should load a glob of data onto cache.data:', function() { + app.data('test/fixtures/data/*.json'); + assert(app.cache.data.a.one.a === 'aaa'); + assert(app.cache.data.b.two.b === 'bbb'); + assert(app.cache.data.c.three.c === 'ccc'); + }); + + it('should use `namespace` defined on global opts:', function() { + app.option('namespace', function(key) { + return 'prefix_' + path.basename(key, path.extname(key)); + }); + app.data('test/fixtures/data/*.json'); + assert(app.cache.data.prefix_a.one.a === 'aaa'); + assert(app.cache.data.prefix_b.two.b === 'bbb'); + assert(app.cache.data.prefix_c.three.c === 'ccc'); + }); + + it('should use `namespace` defined on data opts:', function() { + app.data('test/fixtures/data/*.json', { + namespace: function(key) { + return 'prefix_' + path.basename(key, path.extname(key)); + } + }); + assert(app.cache.data.prefix_a.one.a === 'aaa'); + assert(app.cache.data.prefix_b.two.b === 'bbb'); + assert(app.cache.data.prefix_c.three.c === 'ccc'); + }); + + it('should use `renameKey` defined on data opts:', function() { + app.data('test/fixtures/data/*.json', { + renameKey: function(key) { + return 'prefix_' + path.basename(key, path.extname(key)); + } + }); + assert(app.cache.data.prefix_a.one.a === 'aaa'); + assert(app.cache.data.prefix_b.two.b === 'bbb'); + assert(app.cache.data.prefix_c.three.c === 'ccc'); + }); + + it('should extend `cache.data`', function() { + app.data({a: 'aaa', b: 'bbb', c: 'ccc'}); + app.data({x: 'xxx', y: 'yyy', z: 'zzz'}); + assert(app.cache.data.a === 'aaa'); + assert(app.cache.data.b === 'bbb'); + assert(app.cache.data.c === 'ccc'); + assert(app.cache.data.x === 'xxx'); + assert(app.cache.data.y === 'yyy'); + assert(app.cache.data.z === 'zzz'); + }); + + it('should extend the `cache.data` object when the first param is a string.', function() { + app.data('foo', {x: 'xxx', y: 'yyy', z: 'zzz'}); + app.data('bar', {a: 'aaa', b: 'bbb', c: 'ccc'}); + assert(app.cache.data.foo.x === 'xxx'); + assert(app.cache.data.bar.a === 'aaa'); + }); + + it('should be chainable.', function() { + app + .data({x: 'xxx', y: 'yyy', z: 'zzz'}) + .data({a: 'aaa', b: 'bbb', c: 'ccc'}); + + assert(app.cache.data.x === 'xxx'); + assert(app.cache.data.a === 'aaa'); + }); +}); diff --git a/test2/app.dest.js b/test2/app.dest.js new file mode 100644 index 0000000..ff285e8 --- /dev/null +++ b/test2/app.dest.js @@ -0,0 +1,1103 @@ +var spies = require('./support/spy'); +var chmodSpy = spies.chmodSpy; +var statSpy = spies.statSpy; + +require('mocha'); +var should = require('should'); +var assert = require('assert'); +var App = require('..'); +var app; + +var path = require('path'); +var fs = require('graceful-fs'); +var rimraf = require('rimraf'); + +var bufferStream; +var bufEqual = require('buffer-equal'); +var through = require('through2'); +var File = require('vinyl'); + +var actual = path.join(__dirname, 'actual'); + +var wipeOut = function(cb) { + app = new App(); + rimraf(path.join(__dirname, 'actual/'), cb); + spies.setError('false'); + statSpy.reset(); + chmodSpy.reset(); +}; + +var dataWrap = function(fn) { + return function(data, enc, cb) { + fn(data); + cb(); + }; +}; + +var realMode = function(n) { + return n & 07777; +}; + +describe('dest stream', function() { + beforeEach(wipeOut); + afterEach(wipeOut); + + it('should explode on invalid folder (empty)', function(done) { + var stream; + try { + stream = app.dest(); + } catch (err) { + assert(err && typeof err === 'object'); + should.not.exist(stream); + done(); + } + }); + + it('should explode on invalid folder (empty string)', function(done) { + var stream; + try { + stream = app.dest(''); + } catch (err) { + assert(err && typeof err === 'object'); + should.not.exist(stream); + done(); + } + }); + + it('should pass through writes with cwd', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + + var expectedFile = new File({ + base: __dirname, + cwd: __dirname, + path: inputPath, + contents: null + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + done(); + }; + + var stream = app.dest('./actual/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should pass through writes with default cwd', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + + var expectedFile = new File({ + base: __dirname, + cwd: __dirname, + path: inputPath, + contents: null + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + done(); + }; + + var stream = app.dest(path.join(__dirname, 'actual/')); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should not write null files', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var expectedPath = path.join(__dirname, 'actual/test.coffee'); + var expectedCwd = __dirname; + var expectedBase = path.join(__dirname, 'actual'); + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: null + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); + buffered[0].base.should.equal(expectedBase, 'base should have changed'); + buffered[0].path.should.equal(expectedPath, 'path should have changed'); + fs.existsSync(expectedPath).should.equal(false); + done(); + }; + + var stream = app.dest('./actual/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should write buffer files to the right folder with relative cwd', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var expectedPath = path.join(__dirname, 'actual/test.coffee'); + var expectedCwd = __dirname; + var expectedBase = path.join(__dirname, 'actual'); + var expectedContents = fs.readFileSync(inputPath); + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); + buffered[0].base.should.equal(expectedBase, 'base should have changed'); + buffered[0].path.should.equal(expectedPath, 'path should have changed'); + fs.existsSync(expectedPath).should.equal(true); + bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); + done(); + }; + + var stream = app.dest('./actual/', {cwd: path.relative(process.cwd(), __dirname)}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should write buffer files to the right folder with function and relative cwd', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var expectedPath = path.join(__dirname, 'actual/test.coffee'); + var expectedCwd = __dirname; + var expectedBase = path.join(__dirname, 'actual'); + var expectedContents = fs.readFileSync(inputPath); + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); + buffered[0].base.should.equal(expectedBase, 'base should have changed'); + buffered[0].path.should.equal(expectedPath, 'path should have changed'); + fs.existsSync(expectedPath).should.equal(true); + bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); + done(); + }; + + var stream = app.dest(function(file){ + should.exist(file); + file.should.equal(expectedFile); + return './actual'; + }, {cwd: path.relative(process.cwd(), __dirname)}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should write buffer files to the right folder', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var expectedPath = path.join(__dirname, 'actual/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedCwd = __dirname; + var expectedBase = path.join(__dirname, 'actual'); + var expectedMode = 0655; + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents, + stat: { + mode: expectedMode + } + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); + buffered[0].base.should.equal(expectedBase, 'base should have changed'); + buffered[0].path.should.equal(expectedPath, 'path should have changed'); + fs.existsSync(expectedPath).should.equal(true); + bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); + realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); + done(); + }; + + var stream = app.dest('./actual/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should write streaming files to the right folder', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var expectedPath = path.join(__dirname, 'actual/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedCwd = __dirname; + var expectedBase = path.join(__dirname, 'actual'); + var expectedMode = 0655; + + var contentStream = through.obj(); + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: contentStream, + stat: { + mode: expectedMode + } + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); + buffered[0].base.should.equal(expectedBase, 'base should have changed'); + buffered[0].path.should.equal(expectedPath, 'path should have changed'); + fs.existsSync(expectedPath).should.equal(true); + bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); + realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); + done(); + }; + + var stream = app.dest('./actual/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + setTimeout(function(){ + contentStream.write(expectedContents); + contentStream.end(); + }, 100); + stream.end(); + }); + + it('should write directories to the right folder', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var expectedPath = path.join(__dirname, 'actual/test'); + var expectedCwd = __dirname; + var expectedBase = path.join(__dirname, 'actual'); + var expectedMode = 0655; + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: null, + stat: { + isDirectory: function(){ + return true; + }, + mode: expectedMode + } + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); + buffered[0].base.should.equal(expectedBase, 'base should have changed'); + buffered[0].path.should.equal(expectedPath, 'path should have changed'); + fs.existsSync(expectedPath).should.equal(true); + fs.lstatSync(expectedPath).isDirectory().should.equal(true); + realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); + done(); + }; + + var stream = app.dest('./actual/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should allow piping multiple dests in streaming mode', function(done) { + var inputPath1 = path.join(__dirname, 'actual/multiple-first'); + var inputPath2 = path.join(__dirname, 'actual/multiple-second'); + var inputBase = path.join(__dirname, 'actual/'); + var srcPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var stream1 = app.dest('./actual/', {cwd: __dirname}); + var stream2 = app.dest('./actual/', {cwd: __dirname}); + var content = fs.readFileSync(srcPath); + var rename = through.obj(function(file, _, next) { + file.path = inputPath2; + this.push(file); + next(); + }); + + stream1.on('data', function(file) { + file.path.should.equal(inputPath1); + }); + + stream1.pipe(rename).pipe(stream2); + stream2.on('data', function(file) { + file.path.should.equal(inputPath2); + }).once('end', function() { + fs.readFileSync(inputPath1, 'utf8').should.equal(content.toString()); + fs.readFileSync(inputPath2, 'utf8').should.equal(content.toString()); + done(); + }); + + var file = new File({ + base: inputBase, + path: inputPath1, + cwd: __dirname, + contents: content + }); + + stream1.write(file); + stream1.end(); + }); + + it('should write new files with the default user mode', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var expectedPath = path.join(__dirname, 'actual/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedMode = 0666 & (~process.umask()); + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents, + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + fs.existsSync(expectedPath).should.equal(true); + realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); + done(); + }; + + chmodSpy.reset(); + var stream = app.dest('./actual/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should write new files with the specified mode', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var expectedPath = path.join(__dirname, 'actual/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedMode = 0744; + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents, + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + fs.existsSync(expectedPath).should.equal(true); + realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); + done(); + }; + + chmodSpy.reset(); + var stream = app.dest('./actual/', {cwd: __dirname, mode:expectedMode}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should update file mode to match the vinyl mode', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var expectedPath = path.join(__dirname, 'actual/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedBase = path.join(__dirname, 'actual'); + var startMode = 0655; + var expectedMode = 0722; + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents, + stat: { + mode: expectedMode + } + }); + + var onEnd = function(){ + assert(chmodSpy.called); + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + fs.existsSync(expectedPath).should.equal(true); + realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); + done(); + }; + + fs.mkdirSync(expectedBase); + fs.closeSync(fs.openSync(expectedPath, 'w')); + fs.chmodSync(expectedPath, startMode); + + chmodSpy.reset(); + var stream = app.dest('./actual/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should use different modes for files and directories', function(done) { + var inputBase = path.join(__dirname, 'fixtures/vinyl'); + var inputPath = path.join(__dirname, 'fixtures/vinyl/wow/suchempty'); + var expectedBase = path.join(__dirname, 'actual/wow'); + var expectedDirMode = 0755; + var expectedFileMode = 0655; + + var firstFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + stat: fs.statSync(inputPath) + }); + + var onEnd = function(){ + realMode(fs.lstatSync(expectedBase).mode).should.equal(expectedDirMode); + realMode(buffered[0].stat.mode).should.equal(expectedFileMode); + done(); + }; + + var stream = app.dest('./actual/', { + cwd: __dirname, + mode: expectedFileMode, + dirMode: expectedDirMode + }); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + + stream.pipe(bufferStream); + stream.write(firstFile); + stream.end(); + }); + + it('should change to the specified base as string', function(done) { + var inputBase = path.join(__dirname, 'fixtures/vinyl'); + var inputPath = path.join(__dirname, 'fixtures/vinyl/wow/suchempty'); + + var firstFile = new File({ + cwd: __dirname, + path: inputPath, + stat: fs.statSync(inputPath) + }); + + var onEnd = function(){ + buffered[0].base.should.equal(inputBase); + done(); + }; + + var stream = app.dest('./actual/', { + cwd: __dirname, + base: inputBase + }); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + + stream.pipe(bufferStream); + stream.write(firstFile); + stream.end(); + }); + + it('should change to the specified base as function', function(done) { + var inputBase = path.join(__dirname, 'fixtures/vinyl'); + var inputPath = path.join(__dirname, 'fixtures/vinyl/wow/suchempty'); + + var firstFile = new File({ + cwd: __dirname, + path: inputPath, + stat: fs.statSync(inputPath) + }); + + var onEnd = function() { + buffered[0].base.should.equal(inputBase); + done(); + }; + + var stream = app.dest('./actual/', { + cwd: __dirname, + base: function(file){ + should.exist(file); + file.path.should.equal(inputPath); + return inputBase; + } + }); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + + stream.pipe(bufferStream); + stream.write(firstFile); + stream.end(); + }); + + it('should report IO errors', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var expectedPath = path.join(__dirname, 'actual/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedBase = path.join(__dirname, 'actual'); + var expectedMode = 0722; + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents, + stat: { + mode: expectedMode + } + }); + + fs.mkdirSync(expectedBase); + fs.closeSync(fs.openSync(expectedPath, 'w')); + fs.chmodSync(expectedPath, 0); + + var stream = app.dest('./actual/', {cwd: __dirname}); + stream.on('error', function(err) { + err.code.should.equal('EACCES'); + done(); + }); + stream.write(expectedFile); + }); + + it('should report stat errors', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var expectedPath = path.join(__dirname, 'actual/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedBase = path.join(__dirname, 'actual'); + var expectedMode = 0722; + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents, + stat: { + mode: expectedMode + } + }); + + fs.mkdirSync(expectedBase); + fs.closeSync(fs.openSync(expectedPath, 'w')); + + spies.setError(function(mod, fn) { + if (fn === 'stat' && arguments[2] === expectedPath) { + return new Error('stat error'); + } + }); + + var stream = app.dest('./actual/', {cwd: __dirname}); + stream.on('error', function(err) { + err.message.should.equal('stat error'); + done(); + }); + stream.write(expectedFile); + }); + + it('should report chmod errors', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var expectedPath = path.join(__dirname, 'actual/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedBase = path.join(__dirname, 'actual'); + var expectedMode = 0722; + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents, + stat: { + mode: expectedMode + } + }); + + fs.mkdirSync(expectedBase); + fs.closeSync(fs.openSync(expectedPath, 'w')); + + spies.setError(function(mod, fn) { + if (fn === 'chmod' && arguments[2] === expectedPath) { + return new Error('chmod error'); + } + }); + + var stream = app.dest('./actual/', {cwd: __dirname}); + stream.on('error', function(err) { + err.message.should.equal('chmod error'); + done(); + }); + stream.write(expectedFile); + }); + + it('should not chmod a matching file', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var expectedPath = path.join(__dirname, 'actual/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedBase = path.join(__dirname, 'actual'); + var expectedMode = 0722; + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents, + stat: { + mode: expectedMode + } + }); + + var expectedCount = 0; + spies.setError(function(mod, fn) { + if (fn === 'stat' && arguments[2] === expectedPath) { + expectedCount++; + } + }); + + var onEnd = function(){ + expectedCount.should.equal(1); + assert(!chmodSpy.called); + realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); + done(); + }; + + fs.mkdirSync(expectedBase); + fs.closeSync(fs.openSync(expectedPath, 'w')); + fs.chmodSync(expectedPath, expectedMode); + + statSpy.reset(); + chmodSpy.reset(); + var stream = app.dest('./actual/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should see a file with special chmod (setuid/setgid/sticky) as matching', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var expectedPath = path.join(__dirname, 'actual/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedBase = path.join(__dirname, 'actual'); + var expectedMode = 03722; + var normalMode = 0722; + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents, + stat: { + mode: normalMode + } + }); + + var expectedCount = 0; + spies.setError(function(mod, fn) { + if (fn === 'stat' && arguments[2] === expectedPath) { + expectedCount++; + } + }); + + var onEnd = function(){ + expectedCount.should.equal(1); + assert(!chmodSpy.called); + done(); + }; + + fs.mkdirSync(expectedBase); + fs.closeSync(fs.openSync(expectedPath, 'w')); + fs.chmodSync(expectedPath, expectedMode); + + statSpy.reset(); + chmodSpy.reset(); + var stream = app.dest('./actual/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should not overwrite files with overwrite option set to false', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var inputContents = fs.readFileSync(inputPath); + + var expectedPath = path.join(__dirname, 'actual/test.coffee'); + var expectedBase = path.join(__dirname, 'actual'); + var existingContents = 'Lorem Ipsum'; + + var inputFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: inputContents + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + bufEqual(fs.readFileSync(expectedPath), new Buffer(existingContents)).should.equal(true); + done(); + }; + + // Write expected file which should not be overwritten + fs.mkdirSync(expectedBase); + fs.writeFileSync(expectedPath, existingContents); + + var stream = app.dest('./actual/', {cwd: __dirname, overwrite: false}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(inputFile); + stream.end(); + }); + + it('should overwrite files with overwrite option set to true', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var inputContents = fs.readFileSync(inputPath); + + var expectedPath = path.join(__dirname, 'actual/test.coffee'); + var expectedBase = path.join(__dirname, 'actual'); + var existingContents = 'Lorem Ipsum'; + + var inputFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: inputContents + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + bufEqual(fs.readFileSync(expectedPath), new Buffer(inputContents)).should.equal(true); + done(); + }; + + // This should be overwritten + fs.mkdirSync(expectedBase); + fs.writeFileSync(expectedPath, existingContents); + + var stream = app.dest('./actual/', {cwd: __dirname, overwrite: true}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(inputFile); + stream.end(); + }); + + it('should create symlinks when the `symlink` attribute is set on the file', function(done) { + var inputPath = path.join(__dirname, 'fixtures/vinyl/test-create-dir-symlink'); + var inputBase = path.join(__dirname, 'fixtures/vinyl/'); + var inputRelativeSymlinkPath = 'wow'; + + var expectedPath = path.join(__dirname, 'actual/test-create-dir-symlink'); + + var inputFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: null, //'' + }); + + // `src()` adds this side-effect with `keepSymlinks` option set to false + inputFile.symlink = inputRelativeSymlinkPath; + + var onEnd = function(){ + fs.readlink(buffered[0].path, function() { + buffered[0].symlink.should.equal(inputFile.symlink); + buffered[0].path.should.equal(expectedPath); + done(); + }); + }; + + var stream = app.dest('./actual/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(inputFile); + stream.end(); + }); + + it('should emit finish event', function(done) { + var srcPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); + var stream = app.dest('./actual/', {cwd: __dirname}); + + stream.once('finish', function() { + done(); + }); + + var file = new File({ + path: srcPath, + cwd: __dirname, + contents: new Buffer("1234567890") + }); + + stream.write(file); + stream.end(); + }); +}); + +describe('dest', function() { + beforeEach(function(done) { + rimraf(actual, done); + app = new App(); + }); + + afterEach(function(done) { + rimraf(actual, done); + }); + + describe('streams', function() { + it('should return a stream', function(done) { + var stream = app.dest(path.join(__dirname, 'fixtures/')); + should.exist(stream); + should.exist(stream.on); + done(); + }); + + it('should return an output stream that writes files', function(done) { + var instream = app.src(path.join(__dirname, 'fixtures/copy/e*.txt')); + var outstream = app.dest(actual); + instream.pipe(outstream); + + outstream.on('error', done); + outstream.on('data', function(file) { + // data should be re-emitted correctly + should.exist(file); + should.exist(file.path); + should.exist(file.contents); + path.join(file.path, '').should.equal(path.join(actual, 'example.txt')); + String(file.contents).should.equal('Hello world!'); + }); + outstream.on('end', function() { + fs.readFile(path.join(actual, 'example.txt'), function(err, contents) { + should.not.exist(err); + should.exist(contents); + String(contents).should.equal('Hello world!'); + done(); + }); + }); + }); + + it('should return an output stream that does not write non-read files', function(done) { + var instream = app.src(path.join(__dirname, 'fixtures/copy/e*.txt'), {read: false}); + var outstream = app.dest(actual); + instream.pipe(outstream); + + outstream.on('error', done); + outstream.on('data', function(file) { + // data should be re-emitted correctly + should.exist(file); + should.exist(file.path); + should.not.exist(file.contents); + path.join(file.path, '').should.equal(path.join(actual, 'example.txt')); + }); + + outstream.on('end', function() { + fs.readFile(path.join(actual, 'example.txt'), function(err, contents) { + should.exist(err); + should.not.exist(contents); + done(); + }); + }); + }); + + it('should return an output stream that writes streaming files', function(done) { + var instream = app.src(path.join(__dirname, 'fixtures/copy/e*.txt'), {buffer: false}); + var outstream = instream.pipe(app.dest(actual)); + + outstream.on('error', done); + outstream.on('data', function(file) { + // data should be re-emitted correctly + should.exist(file); + should.exist(file.path); + should.exist(file.contents); + path.join(file.path, '').should.equal(path.join(actual, 'example.txt')); + }); + outstream.on('end', function() { + fs.readFile(path.join(actual, 'example.txt'), function(err, contents) { + should.not.exist(err); + should.exist(contents); + String(contents).should.equal('Hello world!'); + done(); + }); + }); + }); + + it('should return an output stream that writes streaming files to new directories', function(done) { + testWriteDir({}, done); + }); + + it('should return an output stream that writes streaming files to new directories (buffer: false)', function(done) { + testWriteDir({buffer: false}, done); + }); + + it('should return an output stream that writes streaming files to new directories (read: false)', function(done) { + testWriteDir({read: false}, done); + }); + + it('should return an output stream that writes streaming files to new directories (read: false, buffer: false)', function(done) { + testWriteDir({buffer: false, read: false}, done); + }); + + }); + + describe('ext', function() { + beforeEach(function() { + app = new App(); + app.set('ext', '.txt'); + }); + + afterEach(function() { + app.set('ext', '.html'); + }); + + it('should return a stream', function(done) { + var stream = app.dest(path.join(__dirname, 'fixtures/')); + should.exist(stream); + should.exist(stream.on); + done(); + }); + + it('should return an output stream that writes files', function(done) { + var instream = app.src(path.join(__dirname, 'fixtures/copy/e*.txt')); + var outstream = app.dest(actual); + instream.pipe(outstream); + + outstream.on('error', done); + outstream.on('data', function(file) { + // data should be re-emitted correctly + should.exist(file); + should.exist(file.path); + should.exist(file.contents); + path.join(file.path, '').should.equal(path.join(actual, 'example.txt')); + String(file.contents).should.equal('Hello world!'); + }); + outstream.on('end', function() { + fs.readFile(path.join(actual, 'example.txt'), function(err, contents) { + should.not.exist(err); + should.exist(contents); + String(contents).should.equal('Hello world!'); + done(); + }); + }); + }); + + it('should return an output stream that does not write non-read files', function(done) { + var instream = app.src(path.join(__dirname, 'fixtures/dest/*.txt'), {read: false}); + var outstream = app.dest(actual); + instream.pipe(outstream); + + outstream.on('error', done); + outstream.on('data', function(file) { + // data should be re-emitted correctly + should.exist(file); + should.exist(file.path); + should.not.exist(file.contents); + path.join(file.path, '').should.equal(path.join(actual, 'example.txt')); + }); + + outstream.on('end', function() { + fs.readFile(path.join(actual, 'example.txt'), function(err, contents) { + should.exist(err); + should.not.exist(contents); + done(); + }); + }); + }); + }); + + function testWriteDir(srcOptions, done) { + var instream = app.src(path.join(__dirname, 'fixtures/generic'), srcOptions); + var outstream = instream.pipe(app.dest(actual)); + + outstream.on('error', done); + outstream.on('data', function(file) { + // data should be re-emitted correctly + should.exist(file); + should.exist(file.path); + path.join(file.path,'').should.equal(path.join(actual, 'generic')); + }); + + outstream.on('end', function() { + fs.exists(path.join(actual, 'generic'), function(exists) { + /* jshint expr: true */ + should(exists).be.ok; + /* jshint expr: false */ + done(); + }); + }); + } +}); + diff --git a/test2/app.engines.js b/test2/app.engines.js new file mode 100644 index 0000000..16bd062 --- /dev/null +++ b/test2/app.engines.js @@ -0,0 +1,160 @@ +'use strict'; + +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('engine support', function() { + beforeEach(function() { + app = new App(); + }); + + it('should throw an error when engine name is invalid:', function() { + (function() { + app.engine(null, {}); + }).should.throw('expected engine ext to be a string or array.'); + }); + + it('should register an engine to the given extension', function() { + app.engine('hbs', function() {}); + assert(typeof app.engines['.hbs'] === 'object'); + }); + + it('should set an engine with the given extension', function() { + var hbs = function() {}; + hbs.render = function() {}; + hbs.renderFile = function() {}; + app.engine('hbs', hbs); + assert(app.engines['.hbs']); + assert(app.engines['.hbs'].renderFile); + assert(app.engines['.hbs'].render); + }); + + it('should get an engine:', function() { + app.engine('hbs', function() {}); + var hbs = app.engine('hbs'); + assert(typeof hbs === 'object'); + assert(hbs.hasOwnProperty('render')); + assert(hbs.hasOwnProperty('compile')); + }); + + it('should return undefined if no engine is found:', function() { + var hbs = app.getEngine(); + assert.equal(typeof hbs, 'undefined'); + }); + + it('should register multiple engines to the given extension', function() { + app.engine(['hbs', 'md'], function() {}); + assert(typeof app.engines['.hbs'] === 'object'); + assert(typeof app.engines['.md'] === 'object'); + }); +}); + +describe('engines', function() { + beforeEach(function() { + app = new App(); + app.create('pages'); + app.pages('foo.tmpl', {content: 'A <%= letter %> {{= letter }} C'}); + app.pages('bar.tmpl', {content: 'A <%= letter %> {{ letter }} C'}); + }); + + it('should register an engine:', function() { + app.engine('a', {render: function() {}}); + app.engines.should.have.property('.a'); + }); + + it('should use custom delimiters:', function(cb) { + app.engine('tmpl', require('engine-base'), { + delims: ['{{', '}}'] + }); + app.render('foo.tmpl', {letter: 'B'}, function(err, res) { + if (err) return cb(err); + res.contents.toString().should.equal('A <%= letter %> B C'); + cb(); + }); + }); + + it('should override individual delims values:', function(cb) { + app.engine('tmpl', require('engine-base'), { + interpolate: /\{{([^}]+)}}/g, + evaluate: /\{{([^}]+)}}/g, + escape: /\{{-([^}]+)}}/g + }); + app.render('bar.tmpl', {letter: 'B'}, function(err, res) { + if (err) return cb(err); + res.contents.toString().should.equal('A <%= letter %> B C'); + cb(); + }); + }); + + it('should get an engine:', function() { + app.engine('a', { + render: function() {} + }); + var a = app.engine('a'); + a.should.have.property('render'); + }); +}); + +describe('engine selection:', function() { + beforeEach(function(cb) { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.engine('hbs', require('engine-handlebars')); + app.create('pages'); + cb(); + }); + + it('should get the engine from file extension:', function(cb) { + app.page('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}) + .render(function(err, view) { + if (err) return cb(err); + assert(view.content === 'b'); + cb(); + }); + }); + + it('should use the engine defined on the collection:', function(cb) { + app.create('posts', {engine: 'hbs'}); + + app.post('a', {content: '{{a}}', locals: {a: 'b'}}) + .render(function(err, view) { + if (err) return cb(err); + assert(view.content === 'b'); + cb(); + }); + }); + + it('should use the engine defined on the view:', function(cb) { + app.create('posts'); + app.post('a', {content: '{{a}}', engine: 'hbs', locals: {a: 'b'}}) + .render(function(err, view) { + if (err) return cb(err); + assert(view.content === 'b'); + cb(); + }); + }); + + it('should use the engine defined on `view.data`:', function(cb) { + app.create('posts'); + app.post('a', {content: '{{a}}', locals: {a: 'b'}, data: {engine: 'hbs'}}) + .render(function(err, view) { + if (err) return cb(err); + assert(view.content === 'b'); + cb(); + }); + }); + + it('should use the engine defined on render locals:', function(cb) { + app.create('posts'); + app.post('a', {content: '{{a}}', locals: {a: 'b'}}) + .render({engine: 'hbs'}, function(err, view) { + if (err) return cb(err); + assert(view.content === 'b'); + cb(); + }); + }); +}); diff --git a/test2/app.events.js b/test2/app.events.js new file mode 100644 index 0000000..bae16fe --- /dev/null +++ b/test2/app.events.js @@ -0,0 +1,113 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('events', function() { + beforeEach(function() { + app = new App(); + }); + + it('should listen for an event:', function() { + var app = new App(); + app.on('foo', function() {}); + assert(Array.isArray(app._callbacks['$foo'])); + }); + + it('should emit an event:', function(done) { + var app = new App(); + app.on('foo', function(val) { + assert(val === 'bar'); + done(); + }); + assert(Array.isArray(app._callbacks['$foo'])); + app.emit('foo', 'bar'); + }); + + it('should listen for `view` events:', function() { + app = new App(); + + app.on('view', function(view) { + view.foo = 'bar'; + }); + + var view = app.view({path: 'a', content: 'b'}); + assert(view.foo === 'bar'); + }); +}); + +describe('onLoad', function() { + beforeEach(function() { + app = new App(); + }); + + describe('app.collection', function() { + it('should emit a `view` event when view is created', function(done) { + var collection = app.collection(); + + app.on('view', function(view) { + assert(view.path === 'blog/foo.js'); + done(); + }); + + app.onLoad('blog/:title', function(view, next) { + assert(view.path === 'blog/foo.js'); + next(); + }); + + collection.addView('whatever', {path: 'blog/foo.js', content: 'bar baz'}); + }); + + it('should emit an onLoad event when view is created', function(done) { + var collection = app.collection(); + + app.on('onLoad', function(view) { + assert(view.path === 'blog/foo.js'); + done(); + }); + + app.onLoad('blog/:title', function(view, next) { + assert(view.path === 'blog/foo.js'); + next(); + }); + + collection.addView('whatever', {path: 'blog/foo.js', content: 'bar baz'}); + }); + }); + + describe('view collections', function() { + it('should emit a view event when view is created', function(done) { + app.create('posts'); + + app.on('view', function(view) { + assert(view.path === 'blog/foo.js'); + done(); + }); + + app.onLoad('blog/:title', function(view, next) { + assert(view.path === 'blog/foo.js'); + next(); + }); + + app.post('whatever', {path: 'blog/foo.js', content: 'bar baz'}); + }); + + it('should emit an onLoad event when view is created', function(done) { + app.create('posts'); + + app.on('onLoad', function(view) { + assert(view.path === 'blog/foo.js'); + done(); + }); + + app.onLoad('blog/:title', function(view, next) { + assert(view.path === 'blog/foo.js'); + next(); + }); + + app.post('whatever', {path: 'blog/foo.js', content: 'bar baz'}); + }); + }); +}); diff --git a/test2/app.get-set.js b/test2/app.get-set.js new file mode 100644 index 0000000..8e8a597 --- /dev/null +++ b/test2/app.get-set.js @@ -0,0 +1,72 @@ +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('app.set()', function() { + beforeEach(function() { + app = new App(); + }); + + it('should set a value', function() { + app.set('a', 'b'); + app.get('a').should.equal('b'); + }); + + it('should set properties on the instance.', function() { + app.set('a', 'b'); + app.a.should.equal('b'); + }); + + it('should allow an object to be set directly.', function() { + app.set({x: 'y'}); + app.x.should.equal('y'); + app.get('x').should.equal('y'); + }); + + it('should set nested properties on the instance.', function() { + app.set('c', {d: 'e'}); + app.get('c').d.should.equal('e'); + }); + + it('should use dot notation to `set` values.', function() { + app.set('h.i', 'j'); + app.get('h').should.eql({i: 'j'}); + }); + + it('should use dot notation to `get` values.', function() { + app.set('h', {i: 'j'}); + app.get('h.i').should.equal('j'); + }); + + it('should return `this` for chaining', function() { + app.set('a', 'b').should.equal(app); + app + .set('aa', 'bb') + .set('bb', 'cc') + .set('cc', 'dd'); + app.get('aa').should.equal('bb'); + app.get('bb').should.equal('cc'); + app.get('cc').should.equal('dd'); + }); + + it('should return undefined when not set', function() { + app.set('a', undefined).should.equal(app); + }); +}); + +describe('app.get()', function() { + beforeEach(function() { + app = new App(); + }); + + it('should return undefined when no set', function() { + assert(app.get('a') === undefined); + }); + + it('should otherwise return the value', function() { + app.set('a', 'b'); + app.get('a').should.equal('b'); + }); +}); diff --git a/test2/app.handle.js b/test2/app.handle.js new file mode 100644 index 0000000..d27d82d --- /dev/null +++ b/test2/app.handle.js @@ -0,0 +1,37 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('handler', function() { + beforeEach(function() { + app = new App(); + app.create('pages'); + app.handlers(['foo']); + }); + + it('should support custom handle methods:', function(done) { + var page = app.page('foo', {contents: null}); + + app.handle('foo', page, function(err, view) { + if (err) return done(err); + + assert(typeof view.path === 'string'); + done(); + }); + }); + + it('should not blow up if `options.handled` does not exist:', function(done) { + var page = app.page('foo', {contents: null}); + delete page.options.handled; + + app.handle('foo', page, function(err, view) { + if (err) return done(err); + + assert(typeof view.path === 'string'); + done(); + }); + }); +}); diff --git a/test2/app.handlers.js b/test2/app.handlers.js new file mode 100644 index 0000000..f2b94d2 --- /dev/null +++ b/test2/app.handlers.js @@ -0,0 +1,72 @@ +require('should'); +var fs = require('fs'); +var path = require('path'); +var assert = require('assert'); +var resolve = require('resolve-glob'); +var support = require('./support'); +var App = support.resolve(); +var app; + +function decorateViews(views) { + var fn = views.decorateView; + views.decorateView = function() { + var view = fn.apply(fn, arguments); + view.read = function() { + if (!this.contents) { + this.contents = fs.readFileSync(this.path); + } + }; + return view; + }; + views.loader = function(pattern) { + var files = resolve.sync(pattern); + return files.reduce(function(acc, fp) { + acc[fp] = {path: fp}; + return acc; + }, {}); + }; + return views; +} + +describe('handlers', function() { + describe('custom handlers', function() { + beforeEach(function() { + app = new App(); + app.create('pages') + .use(decorateViews) + .option('renameKey', function(key) { + return path.basename(key); + }); + }); + + it('should add custom middleware handlers:', function() { + app.handler('foo'); + app.router.should.have.property('foo'); + assert.equal(typeof app.router.foo, 'function'); + }); + + it('should add custom middleware handlers:', function() { + app.handler('foo'); + app.handler('bar'); + + app.foo(/./, function(view, next) { + view.one = 'aaa'; + next(); + }); + + app.bar(/./, function(view, next) { + view.two = 'zzz'; + next(); + }); + + app.page('abc', {content: '...'}) + .use(function(view) { + app.handleView('foo', view); + app.handleView('bar', view); + }); + + app.views.pages.abc.should.have.property('one', 'aaa'); + app.views.pages.abc.should.have.property('two', 'zzz'); + }); + }); +}); diff --git a/test2/app.js b/test2/app.js new file mode 100644 index 0000000..5ff835b --- /dev/null +++ b/test2/app.js @@ -0,0 +1,130 @@ +/* deps: coveralls istanbul */ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var Base = App.Base; +var app; + +describe('app', function() { + describe('constructor', function() { + it('should create an instance of App:', function() { + app = new App(); + assert(app instanceof App); + }); + + it('should new up without new:', function() { + app = App(); + assert(app instanceof App); + }); + }); + + describe('static methods', function() { + it('should expose `extend`:', function() { + assert(typeof App.extend === 'function'); + }); + }); + + describe('prototype methods', function() { + beforeEach(function() { + app = new App(); + }); + + it('should expose `set`', function() { + assert(typeof app.set === 'function'); + }); + it('should expose `get`', function() { + assert(typeof app.get === 'function'); + }); + it('should expose `visit`', function() { + assert(typeof app.visit === 'function'); + }); + it('should expose `define`', function() { + assert(typeof app.define === 'function'); + }); + it('should expose `views`', function() { + assert(typeof app.views === 'object'); + }); + }); + + describe('instance', function() { + beforeEach(function() { + app = new App(); + }); + + it('should set a value on the instance:', function() { + app.set('a', 'b'); + assert(app.a === 'b'); + }); + + it('should get a value from the instance:', function() { + app.set('a', 'b'); + assert(app.get('a') === 'b'); + }); + }); + + describe('initialization', function() { + it('should listen for errors:', function(done) { + app = new App(); + app.on('error', function(err) { + assert(err.message === 'foo'); + done(); + }); + app.emit('error', new Error('foo')); + }); + + it('should mixin methods after init:', function() { + app = new App(); + app.option({ + mixins: { + foo: function() {} + } + }); + assert(typeof app.foo === 'function'); + }); + + it('should expose constructors from `lib`:', function() { + app = new App(); + app.expose('Collection'); + assert(typeof app.Collection === 'function'); + }); + + it('should update constructors after init:', function() { + var Group = App.Group; + function MyGroup() { + Base.call(this); + } + Base.extend(MyGroup); + + app = new App(); + assert.equal(app.Group, Group); + assert.equal(app.get('Group'), Group); + app.option('Group', MyGroup); + assert.equal(app.Group, MyGroup); + assert.equal(app.get('Group'), MyGroup); + }); + + it('should mixin prototype methods defined on options:', function() { + app = new App({ + mixins: { + foo: function() {} + } + }); + assert(typeof app.foo === 'function'); + delete App.prototype.foo; + }); + + it('should expose `_` on app:', function() { + app = new App(); + assert(typeof app._ === 'object'); + }); + + it('should not re-add `_` in init:', function() { + app = new App(); + app._.foo = 'bar'; + app.defaultConfig(); + assert(app._.foo === 'bar'); + }); + }); +}); diff --git a/test2/app.list.compile.js b/test2/app.list.compile.js new file mode 100644 index 0000000..1efa791 --- /dev/null +++ b/test2/app.list.compile.js @@ -0,0 +1,44 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var List = App.List; +var list; + +describe('app.list.compile', function() { + beforeEach(function() { + list = new List(); + list.engine('tmpl', require('engine-base')); + }); + + it('should compile an item:', function() { + var buffer = new Buffer('a b c'); + var item = list.addItem('a.tmpl', {contents: buffer}) + .compile(); + + assert(typeof item.fn === 'function'); + }); + + it('should use the compiled function to render:', function() { + var buffer = new Buffer('a <%= title %> c'); + var item = list.addItem('a.tmpl', {contents: buffer}) + .compile(); + + assert(item.fn({title: 'z'})); + assert(typeof item.fn({title: 'z'}) === 'string'); + assert(item.fn({title: 'z'}) === 'a z c'); + }); + + it('should compile a view by name:', function() { + var buffer = new Buffer('a <%= title %> c'); + list.addItem('a.tmpl', {contents: buffer}); + + var item = list.compile('a.tmpl'); + + assert(item.fn({title: 'z'})); + assert(typeof item.fn({title: 'z'}) === 'string'); + assert(item.fn({title: 'z'}) === 'a z c'); + }); +}); + diff --git a/test2/app.list.js b/test2/app.list.js new file mode 100644 index 0000000..84527ec --- /dev/null +++ b/test2/app.list.js @@ -0,0 +1,111 @@ +'use strict'; + +require('mocha'); +require('should'); +var fs = require('fs'); +var path = require('path'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var List = App.List; +var app; + +describe('list', function() { + describe('method', function() { + beforeEach(function() { + app = new App(); + }); + + it('should expose the list method', function() { + assert(typeof app.list === 'function'); + }); + + it('should return a new list', function() { + var list = app.list(); + assert(typeof list === 'object'); + }); + + it('should have isList property', function() { + var list = app.list(); + assert(list.isList === true); + }); + }); + + describe('adding items', function() { + beforeEach(function() { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.create('pages', { + renameKey: function(fp) { + return path.relative(process.cwd(), fp); + } + }); + }); + + it('should add an item to a list:', function() { + app.pages('test/fixtures/pages/a.hbs'); + var list = app.list(); + list.addItem(app.pages.getView('test/fixtures/pages/a.hbs')); + assert(list.hasItem('test/fixtures/pages/a.hbs')); + }); + + it('should expose the `option` method from a list:', function() { + var list = app.list(); + list.option('a', 'b'); + assert(list.options); + assert(list.options.a === 'b'); + }); + }); + + describe('addItem', function() { + beforeEach(function() { + app = new App(); + }); + + it('should add items to a list', function() { + var pages = app.list({List: List}); + pages.addItem('foo'); + pages.addItem('bar'); + pages.addItem('baz'); + + pages.items.hasOwnProperty('foo'); + pages.items.hasOwnProperty('bar'); + pages.items.hasOwnProperty('baz'); + }); + + it('should create a list from an existing list:', function() { + var pages = app.list({List: List}); + pages.addItem('foo'); + pages.addItem('bar'); + pages.addItem('baz'); + + var posts = app.list(pages); + posts.items.hasOwnProperty('foo'); + posts.items.hasOwnProperty('bar'); + posts.items.hasOwnProperty('baz'); + }); + }); + + describe('rendering items', function() { + beforeEach(function() { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.create('pages'); + }); + + it('should render a item with inherited app.render', function(done) { + app.page('test/fixtures/templates/a.tmpl') + .use(function(item) { + if (!item.content) { + item.contents = fs.readFileSync(item.path); + } + }) + .set('data.name', 'Brian') + .render(function(err, res) { + if (err) return done(err); + assert(res.contents.toString() === 'Brian'); + done(); + }); + }); + }); +}); diff --git a/test2/app.lookups.js b/test2/app.lookups.js new file mode 100644 index 0000000..afab53b --- /dev/null +++ b/test2/app.lookups.js @@ -0,0 +1,112 @@ +require('mocha'); +require('should'); +var fs = require('fs'); +var path = require('path'); +var assert = require('assert'); +var resolve = require('resolve-glob'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('lookups', function() { + beforeEach(function() { + app = new App(); + app.option('renameKey', function(key) { + return path.basename(key); + }); + app.create('pages') + .use(function(pages) { + pages.on('addViews', function(glob) { + var files = resolve.sync(glob); + files.forEach(function(fp) { + pages.addView(fp, {path: fp}); + }); + pages.loaded = true; + }); + return function(view) { + view.read = function() { + this.contents = fs.readFileSync(this.path); + }; + return view; + }; + }); + + app.pages('test/fixtures/templates/*.tmpl'); + }); + + describe('getView', function() { + it('should find a view', function() { + var view = app.getView('pages', 'a.tmpl'); + assert(typeof view.path === 'string'); + }); + + it('should find a view using the renameKey function', function() { + var view = app.getView('pages', 'test/fixtures/templates/a.tmpl'); + assert(typeof view.path === 'string'); + }); + + it('should return null when nothing is found', function() { + var view = app.getView('pages', 'test/fixtures/templates/foo.tmpl'); + assert(view === null); + }); + + it('should find a view using a glob pattern', function() { + var view = app.getView('pages', 'a', function(key) { + return key + '.tmpl'; + }); + assert(typeof view.path === 'string'); + }); + }); + + describe('getViews', function() { + it('should return the collection object if passed:', function() { + var views = app.getViews(app.views.pages); + assert(Object.keys(views).length > 1); + }); + + it('should return the specified collection with the plural name:', function() { + var views = app.getViews('pages'); + assert(Object.keys(views).length > 1); + }); + + it('should return the specified collection with the singular name:', function() { + var views = app.getViews('page'); + assert(Object.keys(views).length > 1); + }); + + it('should return null when the collection is not found:', function() { + (function() { + app.getViews('nada'); + }).should.throw('getViews cannot find collection: nada'); + }); + }); + + describe('find', function() { + it('should return null when a view is not found:', function() { + (function() { + app.find({}); + }).should.throw('expected name to be a string.'); + }); + + it('should find a view by collection name:', function() { + var view = app.find('a.tmpl', 'pages'); + assert(typeof view.path === 'string'); + }); + + it('should find a view by collection name:', function() { + app = new App(); + app.option('renameKey', function(key) { + return path.basename(key); + }); + app.create('pages'); + app.page('a/b/c.md', {content: '...'}); + var view = app.find('a/b/c.md'); + assert(typeof view.path === 'string'); + }); + + it('should find a view without a collection name:', function() { + var view = app.find('a.tmpl'); + assert(typeof view.path === 'string'); + }); + }); +}); diff --git a/test2/app.middleware.js b/test2/app.middleware.js new file mode 100644 index 0000000..e32ca1a --- /dev/null +++ b/test2/app.middleware.js @@ -0,0 +1,60 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('middleware', function() { + beforeEach(function() { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.create('pages'); + }); + + it('should call the all method for every middleware method:', function() { + var i = 0; + app.all(/./, function(view, next) { + assert(typeof view.path === 'string'); + i++; + next(); + }); + + assert(i === 0); + app.page('foo.tmpl', {content: 'foo'}); + assert(i === 1); + }); + + it('should call the onLoad method when a view is loaded:', function() { + var i = 0; + app.onLoad(/./, function(view, next) { + assert(typeof view.path === 'string'); + i++; + next(); + }); + + assert(i === 0); + app.page('foo.tmpl', {content: 'foo'}); + assert(i === 1); + }); + + it('should emit an event when a handler is called:', function(done) { + var i = 0; + app.on('onLoad', function() { + i++; + }); + app.on('preRender', function() { + i++; + }); + app.on('preCompile', function() { + i++; + }); + + app.page('foo.tmpl', {content: 'foo'}) + .render(function(err) { + if (err) return done(err); + assert(i === 3); + done(); + }); + }); +}); diff --git a/test2/app.onLoad.js b/test2/app.onLoad.js new file mode 100644 index 0000000..66bffbe --- /dev/null +++ b/test2/app.onLoad.js @@ -0,0 +1,48 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('onLoad', function() { + beforeEach(function() { + app = new App(); + }); + + describe('app.collection', function() { + it('should emit an onLoad when view is created', function(done) { + var collection = app.collection(); + + app.on('onLoad', function(view) { + assert(view.path === 'blog/foo.js'); + done(); + }); + + app.onLoad('blog/:title', function(view, next) { + assert(view.path === 'blog/foo.js'); + next(); + }); + + collection.addView('whatever', {path: 'blog/foo.js', content: 'bar baz'}); + }); + }); + + describe('view collections', function() { + it('should emit an onLoad when view is created', function(done) { + app.create('posts'); + + app.on('onLoad', function(view) { + assert(view.path === 'blog/foo.js'); + done(); + }); + + app.onLoad('blog/:title', function(view, next) { + assert(view.path === 'blog/foo.js'); + next(); + }); + + app.post('whatever', {path: 'blog/foo.js', content: 'bar baz'}); + }); + }); +}); diff --git a/test2/app.option.js b/test2/app.option.js new file mode 100644 index 0000000..c4043e7 --- /dev/null +++ b/test2/app.option.js @@ -0,0 +1,98 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('app.option', function() { + beforeEach(function() { + app = new App(); + }); + + it('should set a key-value pair on options:', function() { + app.option('a', 'b'); + assert(app.options.a === 'b'); + }); + + it('should set an object on options:', function() { + app.option({c: 'd'}); + assert(app.options.c === 'd'); + }); + + it('should set an option.', function() { + app.option('a', 'b'); + app.options.should.have.property('a'); + }); + + it('should get an option.', function() { + app.option('a', 'b'); + app.option('a').should.equal('b'); + }); + + it('should extend the `options` object.', function() { + app.option({x: 'xxx', y: 'yyy', z: 'zzz'}); + app.option('x').should.equal('xxx'); + app.option('y').should.equal('yyy'); + app.option('z').should.equal('zzz'); + }); + + it('options should be on the `options` object.', function() { + app.option({x: 'xxx', y: 'yyy', z: 'zzz'}); + app.options.x.should.equal('xxx'); + app.options.y.should.equal('yyy'); + app.options.z.should.equal('zzz'); + }); + + it('should be chainable.', function() { + app.option({x: 'xxx', y: 'yyy', z: 'zzz'}); + app.option({a: 'aaa', b: 'bbb', c: 'ccc'}); + + app.option('x').should.equal('xxx'); + app.option('a').should.equal('aaa'); + }); + + it('should extend the `options` object when the first param is a string.', function() { + app.option('foo', {x: 'xxx', y: 'yyy', z: 'zzz'}); + app.option('bar', {a: 'aaa', b: 'bbb', c: 'ccc'}); + + app.option('foo').should.have.property('x'); + app.option('bar').should.have.property('a'); + + app.options.foo.should.have.property('x'); + app.options.bar.should.have.property('a'); + }); + + it('should set an option.', function() { + app.option('a', 'b'); + app.options.should.have.property('a'); + }); + + it('should get an option.', function() { + app.option('a', 'b'); + app.option('a').should.equal('b'); + }); + + it('should extend the `options` object.', function() { + app.option({x: 'xxx', y: 'yyy', z: 'zzz'}); + app.option('x').should.equal('xxx'); + app.option('y').should.equal('yyy'); + app.option('z').should.equal('zzz'); + }); + + it('options should be on the `options` object.', function() { + app.option({x: 'xxx', y: 'yyy', z: 'zzz'}); + app.options.x.should.equal('xxx'); + app.options.y.should.equal('yyy'); + app.options.z.should.equal('zzz'); + }); + + it('should be chainable.', function() { + app + .option({x: 'xxx', y: 'yyy', z: 'zzz'}) + .option({a: 'aaa', b: 'bbb', c: 'ccc'}); + + app.option('x').should.equal('xxx'); + app.option('a').should.equal('aaa'); + }); +}); diff --git a/test2/app.render.js b/test2/app.render.js new file mode 100644 index 0000000..98c194c --- /dev/null +++ b/test2/app.render.js @@ -0,0 +1,88 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('render', function() { + describe('rendering', function() { + beforeEach(function() { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.create('page'); + }); + + it('should throw an error when no callback is given:', function() { + (function() { + app.render({}); + }).should.throw('Templates#render is async and expects a callback function'); + }); + + it('should throw an error when an engine is not defined:', function(done) { + app.page('foo.bar', {content: '<%= name %>'}); + var page = app.pages.getView('foo.bar'); + + app.render(page, function(err) { + assert(err.message === 'Templates#render cannot find an engine for: .bar'); + done(); + }); + }); + + it('should use helpers to render a view:', function(done) { + var locals = {name: 'Halle'}; + + app.helper('upper', function(str) { + return str.toUpperCase(str); + }); + + app.page('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); + var page = app.pages.getView('a.tmpl'); + + app.render(page, function(err, res) { + if (err) return done(err); + + assert(res.contents.toString() === 'a HALLE b'); + done(); + }); + }); + + it('should use helpers when rendering a view:', function(done) { + var locals = {name: 'Halle'}; + app.helper('upper', function(str) { + return str.toUpperCase(str); + }); + + app.page('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); + var page = app.pages.getView('a.tmpl'); + + app.render(page, function(err, res) { + if (err) return done(err); + assert(res.contents.toString() === 'a HALLE b'); + done(); + }); + }); + + it('should render a template when contents is a buffer:', function(done) { + app.pages('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); + var view = app.pages.getView('a.tmpl'); + + app.render(view, function(err, view) { + if (err) return done(err); + assert(view.contents.toString() === 'b'); + done(); + }); + }); + + it('should render a template when content is a string:', function(done) { + app.pages('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); + var view = app.pages.getView('a.tmpl'); + + app.render(view, function(err, view) { + if (err) return done(err); + assert(view.contents.toString() === 'b'); + done(); + }); + }); + }); +}); diff --git a/test2/app.renderFile.js b/test2/app.renderFile.js new file mode 100644 index 0000000..4e3041a --- /dev/null +++ b/test2/app.renderFile.js @@ -0,0 +1,135 @@ +'use strict'; + +var assemble = require('..'); +var assert = require('assert'); +var should = require('should'); +var path = require('path'); +var app; + +describe('app.renderFile()', function() { + beforeEach(function() { + app = assemble(); + app.engine('hbs', require('engine-handlebars')); + app.engine('*', require('engine-base')); + + app.create('files', {engine: '*'}); + app.file('a', {content: 'this is <%= title() %>'}); + app.file('b', {content: 'this is <%= title() %>'}); + app.file('c', {content: 'this is <%= title() %>'}); + + app.option('renameKey', function(key) { + return path.basename(key, path.extname(key)); + }); + + app.helper('title', function() { + var view = this.context.view; + var key = view.key; + var ctx = this.context[key]; + if (ctx && ctx.title) return ctx.title; + return key; + }); + }); + + it('should render views from src', function(done) { + var stream = app.src(path.join(__dirname, 'fixtures/pages/*.hbs')); + var files = []; + + stream.pipe(app.renderFile()) + .on('error', done) + .on('data', function(file) { + files.push(file); + }) + .on('end', function() { + assert.equal(files[0].basename, 'a.hbs'); + assert.equal(files[1].basename, 'b.hbs'); + assert.equal(files[2].basename, 'c.hbs'); + done(); + }); + }); + + it('should render views with the engine that matches the file extension', function(done) { + var stream = app.src(path.join(__dirname, 'fixtures/pages/*.hbs')); + var files = []; + + stream.pipe(app.renderFile()) + .on('error', done) + .on('data', function(file) { + files.push(file); + }) + .on('end', function() { + assert(/

a<\/h1>/.test(files[0].content)); + assert(/

b<\/h1>/.test(files[1].content)); + assert(/

c<\/h1>/.test(files[2].content)); + done(); + }); + }); + + it('should render views from src with the engine passed on the opts', function(done) { + var stream = app.src(path.join(__dirname, 'fixtures/pages/*.hbs')); + var files = []; + + stream.pipe(app.renderFile({engine: '*'})) + .on('error', done) + .on('data', function(file) { + files.push(file); + }) + .on('end', function() { + assert(/

a<\/h2>/.test(files[0].content)); + assert(/

b<\/h2>/.test(files[1].content)); + assert(/

c<\/h2>/.test(files[2].content)); + done(); + }); + }); + + it('should use the context passed on the opts', function(done) { + var stream = app.src(path.join(__dirname, 'fixtures/pages/*.hbs')); + var files = []; + + stream.pipe(app.renderFile({a: {title: 'foo'}})) + .on('error', done) + .on('data', function(file) { + files.push(file); + }) + .on('end', function() { + assert(/

foo<\/h1>/.test(files[0].content)); + assert(/

b<\/h1>/.test(files[1].content)); + assert(/

c<\/h1>/.test(files[2].content)); + done(); + }); + }); + + it('should render the files in a collection', function(cb) { + var files = []; + app.toStream('files') + .pipe(app.renderFile()) + .on('error', cb) + .on('data', function(file) { + should.exist(file); + should.exist(file.path); + should.exist(file.contents); + files.push(file); + }) + .on('end', function() { + assert(/this is a/.test(files[0].content)); + assert(/this is b/.test(files[1].content)); + assert(/this is c/.test(files[2].content)); + assert.equal(files.length, 3); + cb(); + }); + }); + + it('should handle engine errors', function(cb) { + app.create('notdefined', {engine: '*'}); + app.notdefined('foo', {content: '<%= bar %>'}); + app.toStream('notdefined') + .pipe(app.renderFile()) + .on('error', function(err) { + assert.equal(typeof err, 'object'); + assert.equal(err.message, 'bar is not defined'); + cb(); + }) + .on('end', function() { + cb(new Error('expected renderFile to handle the error.')); + }); + }); +}); diff --git a/test2/app.route.js b/test2/app.route.js new file mode 100644 index 0000000..213dd56 --- /dev/null +++ b/test2/app.route.js @@ -0,0 +1,93 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('routes', function() { + beforeEach(function() { + app = new App(); + }); + + describe('routes', function() { + it('should create a route for the given path:', function(done) { + app = new App(); + app.create('posts'); + + app.on('all', function(msg) { + assert(msg === 'done'); + done(); + }); + + app.route('blog/:title') + .all(function(view, next) { + app.emit('all', 'done'); + next(); + }); + + app.post('whatever', {path: 'blog/foo.js', content: 'bar baz'}); + }); + + it('should emit events when a route method is called:', function(done) { + app = new App(); + app.create('posts'); + + app.on('onLoad', function(view) { + assert(view.path === 'blog/foo.js'); + done(); + }); + + app.param('title', function(view, next, title) { + assert(title === 'foo.js'); + next(); + }); + + app.onLoad('blog/:title', function(view, next) { + assert(view.path === 'blog/foo.js'); + next(); + }); + + app.post('whatever', {path: 'blog/foo.js', content: 'bar baz'}); + }); + + it('should emit errors', function(done) { + app = new App(); + app.create('posts'); + + app.on('error', function(err) { + assert(err.message === 'false == true'); + done(); + }); + + // wrong... + app.param('title', function(view, next, title) { + assert(title === 'fo.js'); + next(); + }); + + app.onLoad('/blog/:title', function(view, next) { + assert(view.path === '/blog/foo.js'); + next(); + }); + + app.post('whatever', {path: '/blog/foo.js', content: 'bar baz'}); + }); + + it('should have path property', function() { + var route = new app.Route('/blog/:year/:month/:day/:slug').all([ + function() {} + ]); + route.path.should.equal('/blog/:year/:month/:day/:slug'); + }); + + it('should have stack property', function() { + var route = new app.Route('/blog/:year/:month/:day/:slug').all([ + function() {} + ]); + + route.stack.should.be.instanceof(Array); + route.stack.should.have.length(1); + }); + }); +}); diff --git a/test2/app.src.js b/test2/app.src.js new file mode 100644 index 0000000..034917b --- /dev/null +++ b/test2/app.src.js @@ -0,0 +1,295 @@ +'use strict'; + +var App = require('..'); +var assert = require('assert'); +var should = require('should'); +var join = require('path').join; +var app; + +describe('src()', function() { + beforeEach(function() { + app = new App(); + }); + + it('should return a stream', function(done) { + var stream = app.src(join(__dirname, './fixtures/*.coffee')); + assert(stream); + assert.equal(typeof stream.on, 'function'); + assert.equal(typeof stream.pipe, 'function'); + done(); + }); + + it('should return an input stream from a flat glob', function(done) { + var stream = app.src(join(__dirname, './fixtures/*.coffee')); + stream.on('error', done); + stream.on('data', function(file) { + should.exist(file); + should.exist(file.path); + should.exist(file.contents); + join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); + String(file.contents).should.equal('Hello world!'); + }); + stream.on('end', function() { + done(); + }); + }); + + it('should return an input stream for multiple globs', function(done) { + var globArray = [ + join(__dirname, './fixtures/generic/run.dmc'), + join(__dirname, './fixtures/generic/test.dmc') + ]; + var stream = app.src(globArray); + + var files = []; + stream.on('error', done); + stream.on('data', function(file) { + should.exist(file); + should.exist(file.path); + files.push(file); + }); + stream.on('end', function() { + files.length.should.equal(2); + files[0].path.should.equal(globArray[0]); + files[1].path.should.equal(globArray[1]); + done(); + }); + }); + + it('should return an input stream for multiple globs with negation', function(done) { + var expectedPath = join(__dirname, './fixtures/generic/run.dmc'); + var globArray = [ + join(__dirname, './fixtures/generic/*.dmc'), + '!' + join(__dirname, './fixtures/generic/test.dmc'), + ]; + var stream = app.src(globArray); + + var files = []; + stream.on('error', done); + stream.on('data', function(file) { + should.exist(file); + should.exist(file.path); + files.push(file); + }); + stream.on('end', function() { + files.length.should.equal(1); + files[0].path.should.equal(expectedPath); + done(); + }); + }); + + it('should return an input stream with no contents when read is false', function(done) { + var stream = app.src(join(__dirname, './fixtures/*.coffee'), {read: false}); + stream.on('error', done); + stream.on('data', function(file) { + should.exist(file); + should.exist(file.path); + should.not.exist(file.contents); + join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); + }); + stream.on('end', function() { + done(); + }); + }); + + it('should return an input stream with contents as stream when buffer is false', function(done) { + var stream = app.src(join(__dirname, './fixtures/*.coffee'), {buffer: false}); + stream.on('error', done); + stream.on('data', function(file) { + should.exist(file); + should.exist(file.path); + should.exist(file.contents); + var buf = ''; + file.contents.on('data', function(d) { + buf += d; + }); + file.contents.on('end', function() { + buf.should.equal('Hello world!'); + done(); + }); + join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); + }); + }); + + it('should return an input stream from a deep glob', function(done) { + var stream = app.src(join(__dirname, './fixtures/**/*.jade')); + stream.on('error', done); + stream.on('data', function(file) { + should.exist(file); + should.exist(file.path); + should.exist(file.contents); + join(file.path, '').should.equal(join(__dirname, './fixtures/test/run.jade')); + String(file.contents).should.equal('test template'); + }); + stream.on('end', function() { + done(); + }); + }); + + it('should return an input stream from a deeper glob', function(done) { + var stream = app.src(join(__dirname, './fixtures/**/*.dmc')); + var a = 0; + stream.on('error', done); + stream.on('data', function() { + ++a; + }); + stream.on('end', function() { + a.should.equal(2); + done(); + }); + }); + + it('should return a file stream from a flat path', function(done) { + var a = 0; + var stream = app.src(join(__dirname, './fixtures/test.coffee')); + stream.on('error', done); + stream.on('data', function(file) { + ++a; + should.exist(file); + should.exist(file.path); + should.exist(file.contents); + join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); + String(file.contents).should.equal('Hello world!'); + }); + stream.on('end', function() { + a.should.equal(1); + done(); + }); + }); + + it('should return a stream', function(done) { + var stream = app.src(join(__dirname, './fixtures/*.coffee')); + should.exist(stream); + should.exist(stream.on); + done(); + }); + + it('should return an input stream from a flat glob', function(done) { + var stream = app.src(join(__dirname, './fixtures/*.coffee')); + stream.on('error', done); + stream.on('data', function(file) { + should.exist(file); + should.exist(file.path); + should.exist(file.contents); + join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); + String(file.contents).should.equal('Hello world!'); + }); + stream.on('end', function() { + done(); + }); + }); + + it('should return an input stream for multiple globs', function(done) { + var globArray = [ + join(__dirname, './fixtures/generic/run.dmc'), + join(__dirname, './fixtures/generic/test.dmc') + ]; + var stream = app.src(globArray); + + var files = []; + stream.on('error', done); + stream.on('data', function(file) { + should.exist(file); + should.exist(file.path); + files.push(file); + }); + stream.on('end', function() { + files.length.should.equal(2); + files[0].path.should.equal(globArray[0]); + files[1].path.should.equal(globArray[1]); + done(); + }); + }); + + it('should return an input stream for multiple globs, with negation', function(done) { + var expectedPath = join(__dirname, './fixtures/generic/run.dmc'); + var globArray = [ + join(__dirname, './fixtures/generic/*.dmc'), + '!' + join(__dirname, './fixtures/generic/test.dmc'), + ]; + var stream = app.src(globArray); + + var files = []; + stream.on('error', done); + stream.on('data', function(file) { + should.exist(file); + should.exist(file.path); + files.push(file); + }); + stream.on('end', function() { + files.length.should.equal(1); + files[0].path.should.equal(expectedPath); + done(); + }); + }); + + it('should return an input stream with no contents when read is false', function(done) { + var stream = app.src(join(__dirname, './fixtures/*.coffee'), {read: false}); + stream.on('error', done); + stream.on('data', function(file) { + should.exist(file); + should.exist(file.path); + should.not.exist(file.contents); + join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); + }); + stream.on('end', function() { + done(); + }); + }); + + it.skip('should throw an error when buffer is false', function(done) { + app.src(join(__dirname, './fixtures/*.coffee'), {buffer: false}) + .on('error', function() { + done(); + }) + .on('data', function() { + done(new Error('should have thrown an error')); + }); + }); + + it('should return an input stream from a deep glob', function(done) { + app.src(join(__dirname, './fixtures/**/*.jade')) + .on('error', done) + .on('data', function(file) { + should.exist(file); + should.exist(file.path); + should.exist(file.contents); + join(file.path, '').should.equal(join(__dirname, './fixtures/test/run.jade')); + String(file.contents).should.equal('test template'); + }) + .on('end', function() { + done(); + }); + }); + + it('should return an input stream from a deeper glob', function(done) { + var stream = app.src(join(__dirname, './fixtures/**/*.dmc')); + var a = 0; + stream.on('error', done); + stream.on('data', function() { + ++a; + }); + stream.on('end', function() { + a.should.equal(2); + done(); + }); + }); + + it('should return a file stream from a flat path', function(done) { + var a = 0; + var stream = app.src(join(__dirname, './fixtures/test.coffee')); + stream.on('error', done); + stream.on('data', function(file) { + ++a; + should.exist(file); + should.exist(file.path); + should.exist(file.contents); + join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); + String(file.contents).should.equal('Hello world!'); + }); + stream.on('end', function() { + a.should.equal(1); + done(); + }); + }); +}); diff --git a/test2/app.symlink.js b/test2/app.symlink.js new file mode 100644 index 0000000..69e8d45 --- /dev/null +++ b/test2/app.symlink.js @@ -0,0 +1,396 @@ +require('mocha'); +var should = require('should'); +var fs = require('graceful-fs'); +var path = require('path'); +var rimraf = require('rimraf'); +var bufEqual = require('buffer-equal'); +var through = require('through2'); +var File = require('vinyl'); +var assemble = require('..'); +var spies = require('./support/spy'); +var chmodSpy = spies.chmodSpy; +var statSpy = spies.statSpy; +var app, bufferStream; + +var wipeOut = function(cb) { + rimraf(path.join(__dirname, './actual/'), cb); + spies.setError('false'); + statSpy.reset(); + chmodSpy.reset(); + app = assemble(); +}; + +var dataWrap = function(fn) { + return function(data, enc, cb) { + fn(data); + cb(); + }; +}; + +var realMode = function(n) { + return n & 07777; +}; + +describe('symlink stream', function() { + beforeEach(wipeOut); + afterEach(wipeOut); + + it('should pass through writes with cwd', function(done) { + var inputPath = path.join(__dirname, './fixtures/test.coffee'); + + var expectedFile = new File({ + base: __dirname, + cwd: __dirname, + path: inputPath, + contents: null + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + done(); + }; + + var stream = app.symlink('./actual/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should pass through writes with default cwd', function(done) { + var inputPath = path.join(__dirname, './fixtures/test.coffee'); + + var expectedFile = new File({ + base: __dirname, + cwd: __dirname, + path: inputPath, + contents: null + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + done(); + }; + + var stream = app.symlink(path.join(__dirname, './actual/')); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should make link to the right folder with relative cwd', function(done) { + var inputPath = path.join(__dirname, './fixtures/test.coffee'); + var inputBase = path.join(__dirname, './fixtures/'); + var expectedPath = path.join(__dirname, './actual/test.coffee'); + var expectedBase = path.join(__dirname, './actual'); + var expectedContents = fs.readFileSync(inputPath); + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + buffered[0].cwd.should.equal(__dirname, 'cwd should have changed'); + buffered[0].base.should.equal(expectedBase, 'base should have changed'); + buffered[0].path.should.equal(expectedPath, 'path should have changed'); + fs.existsSync(expectedPath).should.equal(true); + bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); + fs.readlinkSync(expectedPath).should.equal(inputPath); + done(); + }; + + var stream = app.symlink('./actual/', {cwd: path.relative(process.cwd(), __dirname)}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should write buffer files to the right folder with function and relative cwd', function(done) { + var inputPath = path.join(__dirname, './fixtures/test.coffee'); + var inputBase = path.join(__dirname, './fixtures/'); + var expectedPath = path.join(__dirname, './actual/test.coffee'); + var expectedBase = path.join(__dirname, './actual'); + var expectedContents = fs.readFileSync(inputPath); + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + buffered[0].cwd.should.equal(__dirname, 'cwd should have changed'); + buffered[0].base.should.equal(expectedBase, 'base should have changed'); + buffered[0].path.should.equal(expectedPath, 'path should have changed'); + fs.existsSync(expectedPath).should.equal(true); + bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); + fs.readlinkSync(expectedPath).should.equal(inputPath); + done(); + }; + + var stream = app.symlink(function(file){ + should.exist(file); + file.should.equal(expectedFile); + return './actual'; + }, {cwd: path.relative(process.cwd(), __dirname)}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should write buffer files to the right folder', function(done) { + var inputPath = path.join(__dirname, './fixtures/test.coffee'); + var inputBase = path.join(__dirname, './fixtures/'); + var expectedPath = path.join(__dirname, './actual/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedBase = path.join(__dirname, './actual'); + var expectedMode = 0655; + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents, + stat: { + mode: expectedMode + } + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + buffered[0].cwd.should.equal(__dirname, 'cwd should have changed'); + buffered[0].base.should.equal(expectedBase, 'base should have changed'); + buffered[0].path.should.equal(expectedPath, 'path should have changed'); + fs.existsSync(expectedPath).should.equal(true); + bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); + fs.readlinkSync(expectedPath).should.equal(inputPath); + done(); + }; + + var stream = app.symlink('./actual/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should write streaming files to the right folder', function(done) { + var inputPath = path.join(__dirname, './fixtures/test.coffee'); + var inputBase = path.join(__dirname, './fixtures/'); + var expectedPath = path.join(__dirname, './actual/test.coffee'); + var expectedContents = fs.readFileSync(inputPath); + var expectedBase = path.join(__dirname, './actual'); + var expectedMode = 0655; + + var contentStream = through.obj(); + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: contentStream, + stat: { + mode: expectedMode + } + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + buffered[0].cwd.should.equal(__dirname, 'cwd should have changed'); + buffered[0].base.should.equal(expectedBase, 'base should have changed'); + buffered[0].path.should.equal(expectedPath, 'path should have changed'); + fs.existsSync(expectedPath).should.equal(true); + bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); + fs.readlinkSync(expectedPath).should.equal(inputPath); + done(); + }; + + var stream = app.symlink('./actual/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + setTimeout(function(){ + contentStream.write(expectedContents); + contentStream.end(); + }, 100); + stream.end(); + }); + + it('should write directories to the right folder', function(done) { + var inputPath = path.join(__dirname, './fixtures/wow'); + var inputBase = path.join(__dirname, './fixtures/'); + var expectedPath = path.join(__dirname, './actual/wow'); + var expectedBase = path.join(__dirname, './actual'); + var expectedMode = 0655; + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: null, + stat: { + isDirectory: function(){ + return true; + }, + mode: expectedMode + } + }); + + var onEnd = function(){ + buffered.length.should.equal(1); + buffered[0].should.equal(expectedFile); + buffered[0].cwd.should.equal(__dirname, 'cwd should have changed'); + buffered[0].base.should.equal(expectedBase, 'base should have changed'); + buffered[0].path.should.equal(expectedPath, 'path should have changed'); + fs.readlinkSync(expectedPath).should.equal(inputPath); + fs.lstatSync(expectedPath).isDirectory().should.equal(false); + fs.statSync(expectedPath).isDirectory().should.equal(true); + done(); + }; + + var stream = app.symlink('./actual/', {cwd: __dirname}); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + stream.pipe(bufferStream); + stream.write(expectedFile); + stream.end(); + }); + + it('should use different modes for files and directories', function(done) { + var inputBase = path.join(__dirname, './fixtures'); + var inputPath = path.join(__dirname, './fixtures/wow/suchempty'); + var expectedBase = path.join(__dirname, './actual/wow'); + var expectedDirMode = 0755; + var expectedFileMode = 0655; + + var firstFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + stat: fs.statSync(inputPath) + }); + + var onEnd = function(){ + realMode(fs.lstatSync(expectedBase).mode).should.equal(expectedDirMode); + realMode(buffered[0].stat.mode).should.equal(expectedFileMode); + done(); + }; + + var stream = app.symlink('./actual/', { + cwd: __dirname, + mode: expectedFileMode, + dirMode: expectedDirMode + }); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + + stream.pipe(bufferStream); + stream.write(firstFile); + stream.end(); + }); + + it('should change to the specified base', function(done) { + var inputBase = path.join(__dirname, './fixtures'); + var inputPath = path.join(__dirname, './fixtures/wow/suchempty'); + + var firstFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + stat: fs.statSync(inputPath) + }); + + var onEnd = function(){ + buffered[0].base.should.equal(inputBase); + done(); + }; + + var stream = app.symlink('./actual/', { + cwd: __dirname, + base: inputBase + }); + + var buffered = []; + bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); + + stream.pipe(bufferStream); + stream.write(firstFile); + stream.end(); + }); + + it('should report IO errors', function(done) { + var inputPath = path.join(__dirname, './fixtures/test.coffee'); + var inputBase = path.join(__dirname, './fixtures/'); + var expectedContents = fs.readFileSync(inputPath); + var expectedBase = path.join(__dirname, './actual'); + var expectedMode = 0722; + + var expectedFile = new File({ + base: inputBase, + cwd: __dirname, + path: inputPath, + contents: expectedContents, + stat: { + mode: expectedMode + } + }); + + fs.mkdirSync(expectedBase); + fs.chmodSync(expectedBase, 0); + + var stream = app.symlink('./actual/', {cwd: __dirname}); + stream.on('error', function(err) { + err.code.should.equal('EACCES'); + done(); + }); + stream.write(expectedFile); + }); + + ['end', 'finish'].forEach(function(eventName) { + it('should emit ' + eventName + ' event', function(done) { + var srcPath = path.join(__dirname, './fixtures/test.coffee'); + var stream = app.symlink('./actual/', {cwd: __dirname}); + + stream.on(eventName, function() { + done(); + }); + + var file = new File({ + path: srcPath, + cwd: __dirname, + contents: new Buffer("1234567890") + }); + + stream.write(file); + stream.end(); + }); + }); +}); diff --git a/test2/app.task.js b/test2/app.task.js new file mode 100644 index 0000000..333f4fd --- /dev/null +++ b/test2/app.task.js @@ -0,0 +1,159 @@ +'use strict'; + +var assert = require('assert'); +var App = require('..'); +var app; + +describe('task()', function() { + beforeEach(function() { + app = new App(); + app.tasks = {}; + }); + + it('should register a task', function() { + var fn = function(done) { + done(); + }; + app.task('default', fn); + assert.equal(typeof app.tasks.default, 'object'); + assert.equal(app.tasks.default.fn, fn); + }); + + it('should register a task with an array of dependencies', function() { + app.task('default', ['foo', 'bar'], function(done) { + done(); + }); + assert.equal(typeof app.tasks.default, 'object'); + assert.deepEqual(app.tasks.default.deps, ['foo', 'bar']); + }); + + it('should register a task with a list of strings as dependencies', function() { + app.task('default', 'foo', 'bar', function(done) { + done(); + }); + assert.equal(typeof app.tasks.default, 'object'); + assert.deepEqual(app.tasks.default.deps, ['foo', 'bar']); + }); + + it('should run a task', function(done) { + var count = 0; + app.task('default', function(cb) { + count++; + cb(); + }); + + app.build('default', function(err) { + if (err) return done(err); + assert.equal(count, 1); + done(); + }); + }); + + it('should throw an error when a task with unregistered dependencies is run', function(done) { + var count = 0; + app.task('default', ['foo', 'bar'], function(cb) { + count++; + cb(); + }); + + app.build('default', function(err) { + if (!err) return done(new Error('Expected an error to be thrown.')); + assert.equal(count, 0); + done(); + }); + }); + + it('should throw an error when `.build` is called without a callback function.', function() { + try { + app.build('default'); + throw new Error('Expected an error to be thrown.'); + } catch (err) { + } + }); + + it('should emit task events', function(done) { + var events = []; + app.on('task:starting', function(task) { + events.push('starting.' + task.name); + }); + app.on('task:finished', function(task) { + events.push('finished.' + task.name); + }); + app.on('task:error', function(err, task) { + events.push('error.' + task.name); + }); + + app.task('foo', function(cb) { + cb(); + }); + app.task('bar', ['foo'], function(cb) { + cb(); + }); + app.task('default', ['bar']); + app.build('default', function(err) { + if (err) return done(err); + assert.deepEqual(events, [ + 'starting.default', + 'starting.bar', + 'starting.foo', + 'finished.foo', + 'finished.bar', + 'finished.default' + ]); + done(); + }); + }); + + it('should emit an error event when an error is passed back in a task', function(done) { + app.on('error', function(err) { + assert(err); + assert.equal(err.message, 'This is an error'); + }); + app.task('default', function(cb) { + return cb(new Error('This is an error')); + }); + app.build('default', function(err) { + if (err) return done(); + done(new Error('Expected an error')); + }); + }); + + it('should emit an error event when an error is thrown in a task', function(done) { + var errors = 0; + app.on('error', function(err) { + errors++; + assert(err); + assert.equal(err.message, 'This is an error'); + }); + app.task('default', function(cb) { + cb(new Error('This is an error')); + }); + app.build('default', function(err) { + assert.equal(errors, 1); + if (err) return done(); + done(new Error('Expected an error')); + }); + }); + + it('should run dependencies before running the dependent task.', function(done) { + var seq = []; + app.task('foo', function(cb) { + seq.push('foo'); + cb(); + }); + app.task('bar', function(cb) { + seq.push('bar'); + cb(); + }); + app.task('default', ['foo', 'bar'], function(cb) { + seq.push('default'); + cb(); + }); + + app.build('default', function(err) { + if (err) return done(err); + assert.deepEqual(seq, ['foo', 'bar', 'default']); + done(); + }); + }); +}); diff --git a/test2/app.toStream.js b/test2/app.toStream.js new file mode 100644 index 0000000..fddfb93 --- /dev/null +++ b/test2/app.toStream.js @@ -0,0 +1,64 @@ +'use strict'; + +var assemble = require('..'); +var assert = require('assert'); +var should = require('should'); +var app; + +describe('toStream()', function() { + beforeEach(function() { + app = assemble(); + app.create('pages'); + app.page('a', {content: 'this is A'}); + app.page('b', {content: 'this is B'}); + app.page('c', {content: 'this is C'}); + + app.create('posts'); + app.post('x', {content: 'this is X'}); + app.post('y', {content: 'this is Y'}); + app.post('z', {content: 'this is Z'}); + }); + + it('should return a stream', function(cb) { + var stream = app.toStream(); + should.exist(stream); + should.exist(stream.on); + cb(); + }); + + it('should return a stream for a collection', function(cb) { + var stream = app.toStream('pages'); + should.exist(stream); + should.exist(stream.on); + cb(); + }); + + it('should stack handle multiple collections', function(cb) { + var files = []; + app.toStream('pages') + .pipe(app.toStream('posts')) + .on('data', function(file) { + files.push(file); + }) + .on('end', function() { + assert.equal(files.length, 6); + cb(); + }); + }); + + it('should push each item in the collection into the stream', function(cb) { + var files = []; + app.toStream('pages') + .on('error', cb) + .on('data', function(file) { + should.exist(file); + should.exist(file.path); + should.exist(file.contents); + files.push(file.path); + }) + .on('end', function() { + assert.equal(files.length, 3); + cb(); + }); + }); +}); \ No newline at end of file diff --git a/test2/app.use.js b/test2/app.use.js new file mode 100644 index 0000000..6ca99ac --- /dev/null +++ b/test2/app.use.js @@ -0,0 +1,281 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var Views = App.Views; +var View = App.View; +var app; + +describe('app.use', function() { + beforeEach(function() { + app = new App(); + }); + + it('should expose the instance to `use`:', function(done) { + app.use(function(inst) { + assert(inst instanceof App); + done(); + }); + }); + + it('should be chainable:', function(done) { + app.use(function(inst) { + assert(inst instanceof App); + }) + .use(function(inst) { + assert(inst instanceof App); + }) + .use(function(inst) { + assert(inst instanceof App); + done(); + }); + }); + + it('should pass to collection `use` if a function is returned:', function() { + app.use(function(inst) { + assert(inst instanceof App); + return function(collection) { + collection.foo = collection.addView; + assert(collection instanceof Views); + return collection; + }; + }); + + app.create('pages') + .foo({path: 'a.md', content: '...'}) + .addView({path: 'b.md', content: '...'}) + .addView({path: 'c.md', content: '...'}); + + assert(app.views.pages.hasOwnProperty('a.md')); + assert(app.views.pages.hasOwnProperty('b.md')); + assert(app.views.pages.hasOwnProperty('c.md')); + }); + + it('should be chainable when a collection function is returned:', function() { + app + .use(function(inst) { + assert(inst instanceof App); + return function(collection) { + collection.foo = collection.addView; + assert(collection instanceof Views); + return collection; + }; + }) + .use(function(inst) { + assert(inst instanceof App); + return function(collection) { + collection.bar = collection.addView; + assert(collection instanceof Views); + return collection; + }; + }) + .use(function(inst) { + assert(inst instanceof App); + return function(collection) { + collection.baz = collection.addView; + assert(collection instanceof Views); + return collection; + }; + }); + + var pages = app.create('pages'); + + pages.foo({path: 'a.md', content: '...'}); + pages.bar({path: 'b.md', content: '...'}); + pages.baz({path: 'c.md', content: '...'}); + + assert(app.views.pages.hasOwnProperty('a.md')); + assert(app.views.pages.hasOwnProperty('b.md')); + assert(app.views.pages.hasOwnProperty('c.md')); + }); + + it('should pass to view `use` if collection.use returns a function:', function() { + app.use(function(inst) { + assert(inst instanceof App); + + return function(collection) { + assert(collection instanceof Views); + collection.foo = collection.addView; + + return function(view) { + assert(view instanceof View); + view.foo = collection.addView.bind(collection); + return view; + }; + }; + }); + + app.create('pages') + .foo({path: 'a.md', content: '...'}) + .foo({path: 'b.md', content: '...'}) + .foo({path: 'c.md', content: '...'}); + + assert(app.views.pages.hasOwnProperty('a.md')); + assert(app.views.pages.hasOwnProperty('b.md')); + assert(app.views.pages.hasOwnProperty('c.md')); + }); + + it('should be chainable when a view function is returned:', function() { + app + .use(function(inst) { + assert(inst instanceof App); + + return function(collection) { + assert(collection instanceof Views); + collection.foo = collection.addView; + + return function(view) { + assert(view instanceof View); + view.a = collection.addView.bind(collection); + return view; + }; + }; + }) + .use(function(inst) { + assert(inst instanceof App); + + return function(collection) { + assert(collection instanceof Views); + collection.bar = collection.addView; + + return function(view) { + assert(view instanceof View); + view.b = collection.addView.bind(collection); + return view; + }; + }; + }) + .use(function(inst) { + assert(inst instanceof App); + + return function(collection) { + assert(collection instanceof Views); + collection.baz = collection.addView; + + return function(view) { + assert(view instanceof View); + view.c = collection.addView.bind(collection); + return view; + }; + }; + }); + + var pages = app.create('pages'); + + pages.foo({path: 'a.md', content: '...'}); + pages.bar({path: 'b.md', content: '...'}); + pages.baz({path: 'c.md', content: '...'}) + .a({path: 'x.md', content: '...'}) + .b({path: 'y.md', content: '...'}) + .c({path: 'z.md', content: '...'}); + + assert(app.views.pages.hasOwnProperty('a.md')); + assert(app.views.pages.hasOwnProperty('b.md')); + assert(app.views.pages.hasOwnProperty('c.md')); + + assert(app.views.pages.hasOwnProperty('x.md')); + assert(app.views.pages.hasOwnProperty('y.md')); + assert(app.views.pages.hasOwnProperty('z.md')); + }); + + it('should work with multiple collections:', function() { + app + .use(function(inst) { + assert(inst instanceof App); + + return function(collection) { + assert(collection instanceof Views); + collection.foo = collection.addView; + + return function(view) { + assert(view instanceof View); + view.a = collection.addView.bind(collection); + return view; + }; + }; + }) + .use(function(inst) { + assert(inst instanceof App); + + return function(collection) { + assert(collection instanceof Views); + collection.bar = collection.addView; + + return function(view) { + assert(view instanceof View); + view.b = collection.addView.bind(collection); + return view; + }; + }; + }) + .use(function(inst) { + assert(inst instanceof App); + assert(this instanceof App); + + return function(collection) { + collection.baz = collection.addView; + assert(collection instanceof Views); + assert(this instanceof Views); + + return function(view) { + assert(this instanceof View); + assert(view instanceof View); + view.c = collection.addView.bind(collection); + return view; + }; + }; + }); + + var pages = app.create('pages'); + + pages.foo({path: 'a.md', content: '...'}); + pages.bar({path: 'b.md', content: '...'}); + pages.baz({path: 'c.md', content: '...'}) + .a({path: 'x.md', content: '...'}) + .b({path: 'y.md', content: '...'}) + .c({path: 'z.md', content: '...'}); + + assert(app.views.pages.hasOwnProperty('a.md')); + assert(app.views.pages.hasOwnProperty('b.md')); + assert(app.views.pages.hasOwnProperty('c.md')); + + assert(app.views.pages.hasOwnProperty('x.md')); + assert(app.views.pages.hasOwnProperty('y.md')); + assert(app.views.pages.hasOwnProperty('z.md')); + + var posts = app.create('posts'); + + posts.foo({path: 'a.md', content: '...'}); + posts.bar({path: 'b.md', content: '...'}); + posts.baz({path: 'c.md', content: '...'}) + .a({path: 'x.md', content: '...'}) + .b({path: 'y.md', content: '...'}) + .c({path: 'z.md', content: '...'}); + + assert(app.views.posts.hasOwnProperty('a.md')); + assert(app.views.posts.hasOwnProperty('b.md')); + assert(app.views.posts.hasOwnProperty('c.md')); + + assert(app.views.posts.hasOwnProperty('x.md')); + assert(app.views.posts.hasOwnProperty('y.md')); + assert(app.views.posts.hasOwnProperty('z.md')); + + var docs = app.create('docs'); + + docs.foo({path: 'a.md', content: '...'}); + docs.bar({path: 'b.md', content: '...'}); + docs.baz({path: 'c.md', content: '...'}) + .a({path: 'x.md', content: '...'}) + .b({path: 'y.md', content: '...'}) + .c({path: 'z.md', content: '...'}); + + assert(app.views.docs.hasOwnProperty('a.md')); + assert(app.views.docs.hasOwnProperty('b.md')); + assert(app.views.docs.hasOwnProperty('c.md')); + + assert(app.views.docs.hasOwnProperty('x.md')); + assert(app.views.docs.hasOwnProperty('y.md')); + assert(app.views.docs.hasOwnProperty('z.md')); + }); +}); diff --git a/test2/app.view.compile.js b/test2/app.view.compile.js new file mode 100644 index 0000000..4dca5cd --- /dev/null +++ b/test2/app.view.compile.js @@ -0,0 +1,38 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('app.view.compile', function() { + describe('compile method', function() { + beforeEach(function() { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.create('page'); + }); + + it('should compile a view:', function() { + var buffer = new Buffer('a b c'); + var view = app.page('a.tmpl', {contents: buffer}) + .compile(); + assert(typeof view.fn === 'function'); + }); + + it('should compile a view with settings:', function() { + var buffer = new Buffer('a b c'); + var view = app.page('a.tmpl', {contents: buffer}) + .compile({foo: 'bar'}); + assert(typeof view.fn === 'function'); + }); + + it('should compile a view with isAsync flag:', function() { + var buffer = new Buffer('a b c'); + var view = app.page('a.tmpl', {contents: buffer}) + .compile(true); + assert(typeof view.fn === 'function'); + }); + }); +}); + diff --git a/test2/app.view.render.js b/test2/app.view.render.js new file mode 100644 index 0000000..1cfd929 --- /dev/null +++ b/test2/app.view.render.js @@ -0,0 +1,92 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('helpers', function() { + describe('rendering', function() { + beforeEach(function() { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.create('page'); + }); + + it('should use helpers to render a view:', function(done) { + var locals = {name: 'Halle'}; + + app.helper('upper', function(str) { + return str.toUpperCase(str); + }); + + var buffer = new Buffer('a <%= upper(name) %> b'); + app.page('a.tmpl', {contents: buffer, locals: locals}) + .render(function(err, res) { + if (err) return done(err); + + assert(res.contents.toString() === 'a HALLE b'); + done(); + }); + }); + + it('should support helpers as an array:', function(done) { + var locals = {name: 'Halle'}; + + app.helpers([ + { + lower: function(str) { + return str.toLowerCase(str); + } + } + ]); + + var buffer = new Buffer('a <%= lower(name) %> b'); + app.page('a.tmpl', {contents: buffer, locals: locals}) + .render(function(err, res) { + if (err) return done(err); + + assert(res.contents.toString() === 'a halle b'); + done(); + }); + }); + + it('should support helpers as an object:', function(done) { + var locals = {name: 'Halle'}; + + app.helpers({ + prepend: function(prefix, str) { + return prefix + str; + } + }); + + var buffer = new Buffer('a <%= prepend("foo ", name) %> b'); + app.page('a.tmpl', {contents: buffer, locals: locals}) + .render(function(err, res) { + if (err) return done(err); + assert(res.contents.toString() === 'a foo Halle b'); + done(); + }); + }); + + it('should use the engine defined on view options:', function(done) { + app.engine('hbs', require('engine-handlebars')); + var locals = {name: 'Halle'}; + + app.helpers({ + prepend: function(prefix, str) { + return prefix + str; + } + }); + + var buffer = new Buffer('a {{prepend "foo " name}} b'); + app.page('a.tmpl', {contents: buffer, locals: locals, options: {engine: 'hbs'}}) + .render(function(err, res) { + if (err) return done(err); + assert(res.contents.toString() === 'a foo Halle b'); + done(); + }); + }); + }); +}); + diff --git a/test2/app.watch.js b/test2/app.watch.js new file mode 100644 index 0000000..0990e70 --- /dev/null +++ b/test2/app.watch.js @@ -0,0 +1,42 @@ +'use strict'; + +var assert = require('assert'); +var fs = require('fs'); +var App = require('..'); +var app; + +describe.skip('watch()', function() { + beforeEach(function() { + app = new App({runtimes: false}); + }); + + it('should watch files and run a task when files change', function(done) { + this.timeout(750); + + var count = 0, watch; + app.task('default', function(cb) { + count++; + cb(); + }); + + app.task('close', function(cb) { + watch.close(); + app.emit('close'); + cb(); + }); + + app.task('watch', function(cb) { + watch = app.watch('test/fixtures/watch/*.txt', ['default', 'close']); + fs.writeFile('test/fixtures/watch/test.txt', 'test', function(err) { + if (err) return cb(err); + app.on('close', cb); + }); + }); + + app.build(['watch'], function(err) { + if (err) return done(err); + assert.equal(count, 1); + done(); + }); + }); +}); diff --git a/test2/collection.engines.js b/test2/collection.engines.js new file mode 100644 index 0000000..3465c9e --- /dev/null +++ b/test2/collection.engines.js @@ -0,0 +1,176 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var Views = App.Views; +var collection, pages; + +describe('collection engines', function() { + beforeEach(function() { + pages = new Views(); + }); + + it('should throw an error when engine name is invalid:', function() { + (function() { + pages.engine(null, {}); + }).should.throw('expected engine ext to be a string or array.'); + }); + + it('should register an engine to the given extension', function() { + pages.engine('hbs', function() {}); + assert(typeof pages.engines['.hbs'] === 'object'); + }); + + it('should set an engine with the given extension', function() { + var hbs = function() {}; + hbs.render = function() {}; + hbs.renderFile = function() {}; + pages.engine('hbs', hbs); + assert(pages.engines['.hbs']); + assert(pages.engines['.hbs'].renderFile); + assert(pages.engines['.hbs'].render); + }); + + it('should get an engine:', function() { + pages.engine('hbs', function() {}); + var hbs = pages.engine('hbs'); + assert(typeof hbs === 'object'); + assert(hbs.hasOwnProperty('render')); + assert(hbs.hasOwnProperty('compile')); + }); + + it('should register multiple engines to the given extension', function() { + pages.engine(['hbs', 'md'], function() {}); + assert(typeof pages.engines['.hbs'] === 'object'); + assert(typeof pages.engines['.md'] === 'object'); + }); +}); + +describe('engines', function() { + beforeEach(function() { + pages = new Views(); + pages.addView('foo.tmpl', {content: 'A <%= letter %> {{= letter }} C'}); + pages.addView('bar.tmpl', {content: 'A <%= letter %> {{ letter }} C'}); + }); + + it('should register an engine:', function() { + pages.engine('a', {render: function() {}}); + pages.engines.should.have.property('.a'); + }); + + it('should use custom delimiters:', function(done) { + pages.engine('tmpl', require('engine-base'), { + delims: ['{{', '}}'] + }); + + pages.render('foo.tmpl', {letter: 'B'}, function(err, res) { + if (err) return done(err); + res.content.should.equal('A <%= letter %> B C'); + done(); + }); + }); + + it('should override individual delims values:', function(done) { + pages.engine('tmpl', require('engine-base'), { + interpolate: /\{{([^}]+)}}/g, + evaluate: /\{{([^}]+)}}/g, + escape: /\{{-([^}]+)}}/g + }); + pages.render('bar.tmpl', {letter: 'B'}, function(err, res) { + if (err) return done(err); + res.content.should.equal('A <%= letter %> B C'); + done(); + }); + }); + + it('should get an engine:', function() { + pages.engine('a', { + render: function() {} + }); + var a = pages.engine('a'); + a.should.have.property('render'); + }); +}); + +describe('engine selection:', function() { + beforeEach(function(done) { + collection = new Views(); + collection.engine('tmpl', require('engine-base')); + collection.engine('hbs', require('engine-handlebars')); + done(); + }); + + it('should get the engine from file extension:', function(done) { + var pages = new Views(); + pages.engine('tmpl', require('engine-base')); + pages.engine('hbs', require('engine-handlebars')); + pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}) + .render(function(err, view) { + if (err) return done(err); + assert(view.content === 'b'); + done(); + }); + }); + + it('should use the engine defined on the collection:', function(done) { + var posts = new Views({engine: 'hbs'}); + posts.engine('tmpl', require('engine-base')); + posts.engine('hbs', require('engine-handlebars')); + + posts.addView('a', {content: '{{a}}', locals: {a: 'b'}}) + .render(function(err, view) { + if (err) return done(err); + assert(view.content === 'b'); + done(); + }); + }); + + it('should use the engine defined on the view:', function(done) { + var posts = new Views(); + posts.engine('tmpl', require('engine-base')); + posts.engine('hbs', require('engine-handlebars')); + posts.addView('a', {content: '{{a}}', engine: 'hbs', locals: {a: 'b'}}) + .render(function(err, view) { + if (err) return done(err); + assert(view.content === 'b'); + done(); + }); + }); + + it('should use the engine defined on view.options:', function(done) { + var posts = new Views(); + posts.engine('tmpl', require('engine-base')); + posts.engine('hbs', require('engine-handlebars')); + posts.addView('a', {content: '{{a}}', data: {a: 'b'}, options: {engine: 'hbs'}}) + .render(function(err, view) { + if (err) return done(err); + assert(view.content === 'b'); + done(); + }); + }); + + it('should use the engine defined on view.data:', function(done) { + var posts = new Views(); + posts.engine('tmpl', require('engine-base')); + posts.engine('hbs', require('engine-handlebars')); + posts.addView('a', {content: '{{a}}', locals: {a: 'b'}, data: {engine: 'hbs'}}) + .render(function(err, view) { + if (err) return done(err); + assert(view.content === 'b'); + done(); + }); + }); + + it('should use the engine defined on render locals:', function(done) { + var posts = new Views(); + posts.engine('tmpl', require('engine-base')); + posts.engine('hbs', require('engine-handlebars')); + posts.addView('a', {content: '{{a}}', locals: {a: 'b'}}) + .render({engine: 'hbs'}, function(err, view) { + if (err) return done(err); + assert(view.content === 'b'); + done(); + }); + }); +}); diff --git a/test2/collection.events.js b/test2/collection.events.js new file mode 100644 index 0000000..bdf9eaf --- /dev/null +++ b/test2/collection.events.js @@ -0,0 +1,27 @@ +require('should'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('collection events', function() { + beforeEach(function() { + app = new App(); + app.create('page'); + }); + + it('should emit events:', function() { + app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}); + var events = []; + + app.pages.on('option', function(key) { + events.push(key); + }); + + app.pages.option('a', 'b'); + app.pages.option('c', 'd'); + app.pages.option('e', 'f'); + app.pages.option({g: 'h'}); + + events.should.eql(['a', 'c', 'e', 'g']); + }); +}); diff --git a/test2/collection.getView.js b/test2/collection.getView.js new file mode 100644 index 0000000..fb58723 --- /dev/null +++ b/test2/collection.getView.js @@ -0,0 +1,34 @@ +'use strict'; + +var path = require('path'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('collection.getView', function() { + beforeEach(function() { + app = new App(); + app.create('page'); + + app.page('foo', {content: 'this is foo'}); + app.page('bar.md', {content: 'this is bar'}); + app.page('a/b/c/baz.md', {content: 'this is baz'}); + app.page('test/fixtures/templates/a.tmpl'); + }); + + it('should get a view by name', function() { + assert(app.pages.getView('foo')); + }); + + it('should get a view with the key modified by the given function', function() { + var view = app.pages.getView('foo.md', function(key) { + return path.basename(key, path.extname(key)); + }); + assert(view); + }); + + it('should get a view by full path', function() { + assert(app.pages.getView('a/b/c/baz.md')); + }); +}); diff --git a/test2/collection.js b/test2/collection.js new file mode 100644 index 0000000..7c0982c --- /dev/null +++ b/test2/collection.js @@ -0,0 +1,540 @@ +require('mocha'); +require('should'); +var path = require('path'); +var assert = require('assert'); +var typeOf = require('kind-of'); +var isBuffer = require('is-buffer'); +var support = require('./support'); +var App = support.resolve(); +var List = App.List; +var Item = App.Item; +var Collection = App.Collection; +var collection; + +describe('collection', function() { + describe('constructor', function() { + it('should create an instance of Collection', function() { + var collection = new Collection(); + assert(collection instanceof Collection); + assert(typeof collection === 'object'); + }); + + it('should instantiate without new', function() { + var collection = Collection(); + assert(collection instanceof Collection); + assert(typeof collection === 'object'); + }); + }); + + describe('static methods', function() { + it('should expose `extend`', function() { + assert(typeof Collection.extend === 'function'); + }); + }); + + describe('prototype methods', function() { + beforeEach(function() { + collection = new Collection(); + }); + + var methods = [ + 'use', + 'setItem', + 'addItem', + 'addItems', + 'addList', + 'getItem', + 'constructor', + 'set', + 'get', + 'del', + 'define', + 'visit', + 'on', + 'once', + 'off', + 'emit', + 'listeners', + 'hasListeners' + ]; + + methods.forEach(function(method) { + it('should expose ' + method + ' method', function() { + assert(typeof collection[method] === 'function'); + }); + }); + + it('should expose isCollection property', function() { + assert(typeof collection.isCollection === 'boolean'); + }); + + it('should expose queue property', function() { + assert(Array.isArray(collection.queue)); + }); + + it('should expose items property', function() { + assert(typeOf(collection.items) === 'object'); + }); + + it('should expose options property', function() { + assert(typeOf(collection.options) === 'object'); + }); + }); +}); + +describe('methods', function() { + beforeEach(function() { + collection = new Collection(); + }); + + describe('chaining', function() { + it('should allow collection methods to be chained', function() { + collection + .addItems({'a.hbs': {path: 'a.hbs'}}) + .addItems({'b.hbs': {path: 'b.hbs'}}) + .addItems({'c.hbs': {path: 'c.hbs'}}); + + collection.items.should.have.properties([ + 'a.hbs', + 'b.hbs', + 'c.hbs' + ]); + }); + }); + + describe('use', function() { + it('should expose the instance to plugins', function() { + collection + .use(function(inst) { + inst.foo = 'bar'; + }); + + assert(collection.foo === 'bar'); + }); + + it('should expose `item` when the plugin returns a function', function() { + collection + .use(function() { + return function(item) { + item.foo = 'bar'; + }; + }); + + collection.addItem('aaa'); + collection.addItem('bbb'); + collection.addItem('ccc'); + + assert(collection.items.aaa.foo === 'bar'); + assert(collection.items.bbb.foo === 'bar'); + assert(collection.items.ccc.foo === 'bar'); + }); + }); + + describe('get / set', function() { + it('should set a value on the instance', function() { + collection.set('a', 'b'); + assert(collection.a === 'b'); + }); + + it('should get a value from the instance', function() { + collection.set('a', 'b'); + assert(collection.get('a') === 'b'); + }); + }); + + describe('adding items', function() { + beforeEach(function() { + collection = new Collection(); + }); + + it('should load a item onto the respective collection', function() { + collection.addItem('a.hbs'); + collection.items.should.have.property('a.hbs'); + }); + }); + + describe('item', function() { + beforeEach(function() { + collection = new Collection(); + }); + + it('should return a single collection item from a key-value pair', function() { + var one = collection.item('one', {content: 'foo'}); + var two = collection.item('two', {content: 'bar'}); + + assert(one instanceof Item); + assert(one instanceof collection.Item); + assert(one.path === 'one'); + assert(two instanceof Item); + assert(two instanceof collection.Item); + assert(two.path === 'two'); + }); + + it('should return a single collection item from an object', function() { + var one = collection.item({path: 'one', content: 'foo'}); + var two = collection.item({path: 'two', content: 'bar'}); + + assert(one instanceof Item); + assert(one.path === 'one'); + assert(two instanceof Item); + assert(two.path === 'two'); + }); + }); + + describe('addItem', function() { + beforeEach(function() { + collection = new Collection(); + }); + + it('should throw an error when args are invalid', function() { + (function() { + collection.addItem(function() {}); + }).should.throw('expected value to be an object.'); + }); + + it('should add a item to `items`', function() { + collection.addItem('foo'); + collection.items.should.have.property('foo'); + + collection.addItem('one', {content: '...'}); + assert(typeof collection.items.one === 'object'); + assert(isBuffer(collection.items.one.contents)); + }); + + it('should create an instance of `Item`', function() { + collection.addItem('one', {content: '...'}); + assert(collection.items.one instanceof collection.Item); + }); + + it('should allow an `Item` constructor to be passed', function() { + Item.prototype.foo = function(key, value) { + this[key] = value; + }; + collection = new Collection({Item: Item}); + collection.addItem('one', {content: '...'}); + collection.items.one.foo('bar', 'baz'); + assert(collection.items.one.bar === 'baz'); + }); + + it('should allow an instance of `Item` to be passed', function() { + var collection = new Collection({Item: Item}); + var item = new Item({content: '...'}); + collection.addItem('one', item); + item.set('abc', 'xyz'); + assert(collection.items.one instanceof collection.Item); + assert(isBuffer(collection.items.one.contents)); + assert(collection.items.one.abc === 'xyz'); + }); + }); + + describe('addItems', function() { + beforeEach(function() { + collection = new Collection(); + }); + + it('should add multiple items', function() { + collection.addItems({ + one: {content: 'foo'}, + two: {content: 'bar'} + }); + assert(isBuffer(collection.items.one.contents)); + assert(isBuffer(collection.items.two.contents)); + }); + + it('should create items from an instance of Collection', function() { + collection.addItems({ + one: {content: 'foo'}, + two: {content: 'bar'} + }); + var pages = new Collection(collection); + assert(isBuffer(pages.items.one.contents)); + assert(isBuffer(pages.items.two.contents)); + }); + + it('should add an array of plain-objects', function() { + collection.addItems([ + {path: 'one', content: 'foo'}, + {path: 'two', content: 'bar'} + ]); + assert(isBuffer(collection.items.one.contents)); + assert(isBuffer(collection.items.two.contents)); + }); + + it('should add an array of items', function() { + var list = new List([ + {path: 'one', content: 'foo'}, + {path: 'two', content: 'bar'} + ]); + + collection.addItems(list.items); + assert(isBuffer(collection.items.one.contents)); + assert(isBuffer(collection.items.two.contents)); + }); + }); + + describe('addList', function() { + beforeEach(function() { + collection = new Collection(); + }); + + it('should add a list of items', function() { + collection.addList([ + {path: 'one', content: 'foo'}, + {path: 'two', content: 'bar'} + ]); + assert(isBuffer(collection.items.one.contents)); + assert(isBuffer(collection.items.two.contents)); + }); + + it('should add a list of items from the constructor', function() { + var list = new List([ + {path: 'one', content: 'foo'}, + {path: 'two', content: 'bar'} + ]); + + collection = new Collection(list); + assert(isBuffer(collection.items.one.contents)); + assert(isBuffer(collection.items.two.contents)); + }); + + it('should throw an error when list is not an array', function() { + var items = new Collection(); + (function() { + items.addList(); + }).should.throw('expected list to be an array.'); + + (function() { + items.addList({}); + }).should.throw('expected list to be an array.'); + + (function() { + items.addList('foo'); + }).should.throw('expected list to be an array.'); + }); + + it('should load an array of items from an event', function() { + var collection = new Collection(); + + collection.on('addList', function(list) { + while (list.length) { + collection.addItem({path: list.pop()}); + } + }); + + collection.addList(['a.txt', 'b.txt', 'c.txt']); + assert(collection.items.hasOwnProperty('a.txt')); + assert(collection.items['a.txt'].path === 'a.txt'); + }); + + it('should load an array of items from the addList callback:', function() { + var collection = new Collection(); + + collection.addList(['a.txt', 'b.txt', 'c.txt'], function(fp) { + return {path: fp}; + }); + assert(collection.items.hasOwnProperty('a.txt')); + assert(collection.items['a.txt'].path === 'a.txt'); + }); + + it('should load an object of items from an event', function() { + var collection = new Collection(); + + collection.on('addItems', function(items) { + for (var key in items) { + collection.addItem('foo/' + key, items[key]); + delete items[key]; + } + }); + + collection.addItems({ + a: {path: 'a.txt'}, + b: {path: 'b.txt'}, + c: {path: 'c.txt'} + }); + + assert(collection.items.hasOwnProperty('foo/a')); + assert(collection.items['foo/a'].path === 'a.txt'); + }); + + it('should signal `loaded` when finished (addItems)', function() { + var collection = new Collection(); + + collection.on('addItems', function(items) { + for (var key in items) { + if (key === 'c') { + collection.loaded = true; + break; + } + collection.addItem('foo/' + key, items[key]); + } + }); + + collection.addItems({ + a: {path: 'a.txt'}, + b: {path: 'b.txt'}, + c: {path: 'c.txt'} + }); + + assert(collection.items.hasOwnProperty('foo/a')); + assert(!collection.items.hasOwnProperty('foo/c')); + assert(collection.items['foo/a'].path === 'a.txt'); + }); + + it('should signal `loaded` when finished (addList)', function() { + var collection = new Collection(); + + collection.on('addList', function(items) { + for (var i = 0; i < items.length; i++) { + var item = items[i]; + if (item.key === 'c') { + collection.loaded = true; + break; + } + item.key = 'foo/' + item.key; + collection.addItem(item.key, item); + } + }); + + collection.addList([ + {key: 'a', path: 'a.txt'}, + {key: 'b', path: 'b.txt'}, + {key: 'c', path: 'c.txt'} + ]); + + assert(collection.items.hasOwnProperty('foo/a')); + assert(collection.items['foo/a'].path === 'a.txt'); + assert(!collection.items.hasOwnProperty('foo/c')); + }); + }); + + describe('getItem', function() { + beforeEach(function() { + collection = new Collection(); + }); + it('should get a item from `items`', function() { + collection.addItem('one', {content: 'aaa'}); + collection.addItem('two', {content: 'zzz'}); + assert(isBuffer(collection.items.one.contents)); + assert(isBuffer(collection.getItem('one').contents)); + assert(collection.getItem('one').contents.toString() === 'aaa'); + assert(collection.getItem('two').contents.toString() === 'zzz'); + }); + }); +}); + +describe('queue', function() { + beforeEach(function() { + collection = new Collection(); + }); + + it('should emit arguments on addItem', function(done) { + collection.on('addItem', function(args) { + assert(args[0] === 'a'); + assert(args[1] === 'b'); + assert(args[2] === 'c'); + assert(args[3] === 'd'); + assert(args[4] === 'e'); + done(); + }); + + collection.addItem('a', 'b', 'c', 'd', 'e'); + }); + + it('should expose the `queue` property for loading items', function() { + collection.queue.push(collection.item('b', {path: 'b'})); + + collection.addItem('a', {path: 'a'}); + assert(collection.items.hasOwnProperty('a')); + assert(collection.items.hasOwnProperty('b')); + }); + + it('should load all items on the queue when addItem is called', function() { + collection.on('addItem', function(args) { + var len = args.length; + var last = args[len - 1]; + if (typeof last === 'string') { + args[len - 1] = { content: last }; + } + }); + + collection.addItem('a.html', 'aaa'); + collection.addItem('b.html', 'bbb'); + collection.addItem('c.html', 'ccc'); + + assert(collection.items.hasOwnProperty('a.html')); + assert(collection.getItem('a.html').content === 'aaa'); + assert(collection.items.hasOwnProperty('b.html')); + assert(collection.getItem('b.html').content === 'bbb'); + assert(collection.items.hasOwnProperty('c.html')); + assert(collection.getItem('c.html').content === 'ccc'); + }); +}); + +describe('options', function() { + describe('option', function() { + beforeEach(function() { + collection = new Collection(); + }); + + it('should expose the `option` method', function() { + collection.option('foo', 'bar'); + collection.options.should.have.property('foo', 'bar'); + }); + + it('should be chainable', function() { + collection.option('foo', 'bar') + .addItems('a.hbs') + .addItems('b.hbs') + .addItems('c.hbs'); + + collection.options.should.have.property('foo', 'bar'); + collection.items.should.have.properties([ + 'a.hbs', + 'b.hbs', + 'c.hbs' + ]); + }); + + it('should set a key/value pair on options', function() { + collection.option('a', 'b'); + assert(collection.options.a === 'b'); + }); + + it('should set an object on options', function() { + collection.option({c: 'd'}); + assert(collection.options.c === 'd'); + }); + + it('should get an option', function() { + collection.option({c: 'd'}); + var c = collection.option('c'); + assert(c === 'd'); + }); + }); + + describe('options.renameKey', function() { + beforeEach(function() { + collection = new Collection({ + renameKey: function(key) { + return path.basename(key); + } + }); + }); + + it('should use a custom rename key function on item keys', function() { + collection.addItem('a/b/c/d.hbs', {content: 'foo bar baz'}); + assert(collection.items['d.hbs'].contents.toString() === 'foo bar baz'); + }); + + it('should get a item with the renamed key', function() { + collection.addItem('a/b/c/d.hbs', {content: 'foo bar baz'}); + assert(collection.getItem('d.hbs').contents.toString() === 'foo bar baz'); + }); + + it('should get a item with the original key', function() { + collection.addItem('a/b/c/d.hbs', {content: 'foo bar baz'}); + assert(collection.getItem('a/b/c/d.hbs').contents.toString() === 'foo bar baz'); + }); + }); +}); + diff --git a/test2/collection.options.js b/test2/collection.options.js new file mode 100644 index 0000000..d1af0af --- /dev/null +++ b/test2/collection.options.js @@ -0,0 +1,25 @@ +require('should'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('collection.option()', function() { + beforeEach(function() { + app = new App(); + app.create('page'); + }); + + it('should set an option:', function() { + app.pages.options.should.not.have.property('foo'); + app.pages.option('foo', 'bar'); + app.pages.options.should.have.property('foo'); + }); + + it('should extend options:', function() { + app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}); + app.pages.option('a', 'b'); + app.pages.option('c', 'd'); + app.pages.option('e', 'f'); + app.pages.options.should.have.properties(['a', 'c', 'e']); + }); +}); diff --git a/test2/collection.render.js b/test2/collection.render.js new file mode 100644 index 0000000..896c805 --- /dev/null +++ b/test2/collection.render.js @@ -0,0 +1,138 @@ +require('mocha'); +require('should'); +var async = require('async'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var List = App.List; +var Views = App.Views; +var pages; + +describe('render', function() { + describe('rendering', function() { + beforeEach(function() { + pages = new Views(); + pages.engine('tmpl', require('engine-base')); + }); + + it('should throw an error when no callback is given:', function() { + (function() { + pages.render({}); + }).should.throw('Views#render is async and expects a callback function'); + }); + + it('should throw an error when an engine is not defined:', function(done) { + pages.addView('foo.bar', {content: '<%= name %>'}); + var page = pages.getView('foo.bar'); + + pages.render(page, function(err) { + assert(err.message === 'Views#render cannot find an engine for: .bar'); + done(); + }); + }); + + it('should use helpers to render a view:', function(done) { + var locals = {name: 'Halle'}; + + pages.helper('upper', function(str) { + return str.toUpperCase(str); + }); + + pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); + var page = pages.getView('a.tmpl'); + + pages.render(page, function(err, res) { + if (err) return done(err); + + assert(res.content === 'a HALLE b'); + done(); + }); + }); + + it('should use helpers when rendering a view:', function(done) { + var locals = {name: 'Halle'}; + pages.helper('upper', function(str) { + return str.toUpperCase(str); + }); + + pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); + var page = pages.getView('a.tmpl'); + + pages.render(page, function(err, res) { + if (err) return done(err); + assert(res.content === 'a HALLE b'); + done(); + }); + }); + + it('should render a template when contents is a buffer:', function(done) { + pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); + var view = pages.getView('a.tmpl'); + + pages.render(view, function(err, view) { + if (err) return done(err); + assert(view.contents.toString() === 'b'); + done(); + }); + }); + + it('should render a template when content is a string:', function(done) { + pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); + var view = pages.getView('a.tmpl'); + + pages.render(view, function(err, view) { + if (err) return done(err); + assert(view.contents.toString() === 'b'); + done(); + }); + }); + + it('should render a view from its path:', function(done) { + pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); + + pages.render('a.tmpl', function(err, view) { + if (err) return done(err); + assert(view.content === 'b'); + done(); + }); + }); + + it('should use a plugin for rendering:', function(done) { + pages.engine('tmpl', require('engine-base')); + pages.option('engine', 'tmpl'); + + pages.addViews({ + 'a': {content: '<%= title %>', locals: {title: 'aaa'}}, + 'b': {content: '<%= title %>', locals: {title: 'bbb'}}, + 'c': {content: '<%= title %>', locals: {title: 'ccc'}}, + 'd': {content: '<%= title %>', locals: {title: 'ddd'}}, + 'e': {content: '<%= title %>', locals: {title: 'eee'}}, + 'f': {content: '<%= title %>', locals: {title: 'fff'}}, + 'g': {content: '<%= title %>', locals: {title: 'ggg'}}, + 'h': {content: '<%= title %>', locals: {title: 'hhh'}}, + 'i': {content: '<%= title %>', locals: {title: 'iii'}}, + 'j': {content: '<%= title %>', locals: {title: 'jjj'}} + }); + + pages.use(function(collection) { + collection.option('pager', false); + + collection.renderEach = function(cb) { + var list = new List(collection); + + async.map(list.items, function(item, next) { + collection.render(item, next); + }, cb); + }; + }); + + pages.renderEach(function(err, items) { + if (err) return done(err); + assert(items[0].content === 'aaa'); + assert(items[9].content === 'jjj'); + assert(items.length === 10); + done(); + }); + }); + }); +}); diff --git a/test2/collection.use.js b/test2/collection.use.js new file mode 100644 index 0000000..f09bd86 --- /dev/null +++ b/test2/collection.use.js @@ -0,0 +1,156 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var Collection = App.Collection; +var Item = App.Item; +var collection; + +describe('collection.use', function() { + beforeEach(function() { + collection = new Collection(); + }); + + it('should expose the instance to `use`:', function(done) { + collection.use(function(inst) { + assert(inst instanceof Collection); + done(); + }); + }); + + it('should be chainable:', function(done) { + collection.use(function(inst) { + assert(inst instanceof Collection); + }) + .use(function(inst) { + assert(inst instanceof Collection); + }) + .use(function(inst) { + assert(inst instanceof Collection); + done(); + }); + }); + + it('should expose the collection to a plugin:', function() { + collection.use(function(items) { + assert(items instanceof Collection); + items.foo = items.addItem.bind(items); + }); + + collection.foo('a', {content: '...'}); + assert(collection.items.hasOwnProperty('a')); + }); + + it('should expose collection when chained:', function() { + collection + .use(function(items) { + assert(items instanceof Collection); + items.foo = items.addItem.bind(items); + }) + .use(function(items) { + assert(items instanceof Collection); + items.bar = items.addItem.bind(items); + }) + .use(function(items) { + assert(items instanceof Collection); + items.baz = items.addItem.bind(items); + }); + + var pages = collection; + + pages.foo({path: 'a', content: '...'}); + pages.bar({path: 'b', content: '...'}); + pages.baz({path: 'c', content: '...'}); + + assert(collection.items.hasOwnProperty('a')); + assert(collection.items.hasOwnProperty('b')); + assert(collection.items.hasOwnProperty('c')); + }); + + it('should work when a custom `Item` constructor is passed:', function() { + collection = new Collection({Item: require('vinyl')}); + collection + .use(function(items) { + assert(items instanceof Collection); + items.foo = items.addItem.bind(items); + }) + .use(function(items) { + assert(items instanceof Collection); + items.bar = items.addItem.bind(items); + }) + .use(function(items) { + assert(items instanceof Collection); + items.baz = items.addItem.bind(items); + }); + + var pages = collection; + + pages.foo({path: 'a', content: '...'}); + pages.bar({path: 'b', content: '...'}); + pages.baz({path: 'c', content: '...'}); + + assert(collection.items.hasOwnProperty('a')); + assert(collection.items.hasOwnProperty('b')); + assert(collection.items.hasOwnProperty('c')); + }); + + it('should pass to item `use` if a function is returned:', function() { + collection.use(function(items) { + assert(items instanceof Collection); + + return function(item) { + item.foo = items.addItem.bind(items); + assert(item instanceof Item); + }; + }); + + collection.addItem('a', {content: '...'}) + .foo({path: 'b', content: '...'}) + .foo({path: 'c', content: '...'}) + .foo({path: 'd', content: '...'}); + + assert(collection.items.hasOwnProperty('a')); + assert(collection.items.hasOwnProperty('b')); + assert(collection.items.hasOwnProperty('c')); + assert(collection.items.hasOwnProperty('d')); + }); + + it('should be chainable when a item function is returned:', function() { + collection + .use(function(items) { + assert(items instanceof Collection); + + return function(item) { + item.foo = items.addItem.bind(items); + assert(item instanceof Item); + }; + }) + .use(function(items) { + assert(items instanceof Collection); + + return function(item) { + item.bar = items.addItem.bind(items); + assert(item instanceof Item); + }; + }) + .use(function(items) { + assert(items instanceof Collection); + + return function(item) { + item.baz = items.addItem.bind(items); + assert(item instanceof Item); + }; + }); + + collection.addItem('a', {content: '...'}) + .foo({path: 'b', content: '...'}) + .bar({path: 'c', content: '...'}) + .baz({path: 'd', content: '...'}); + + assert(collection.items.hasOwnProperty('a')); + assert(collection.items.hasOwnProperty('b')); + assert(collection.items.hasOwnProperty('c')); + assert(collection.items.hasOwnProperty('d')); + }); +}); diff --git a/test2/fixtures/bom-utf16be.txt b/test2/fixtures/bom-utf16be.txt new file mode 100644 index 0000000000000000000000000000000000000000..b9dce78a5d31af4803acd1a0f0dfc14f064a5de1 GIT binary patch literal 244 zcmX|+I}XAy5Jacu26Qf=0Eq@sM*@i=qJaY#5&}waq&RRn3Xa4Tc-{cbe#Sc=zn?E@ zuZymVayru+l}y7P<@I1MK)hWXxZY@{g_hJzYt4Dvs;8dRDlmE2!LB37&GahJPDg5G zyEjIUb8?Hu>i*d9+Q4pAn^J>j{bf4+Qmn{O;+32Wrj#?&PC0#o+uan?UxFJmPf0ua E03tFf0ssI2 literal 0 HcmV?d00001 diff --git a/test2/fixtures/bom-utf16le.txt b/test2/fixtures/bom-utf16le.txt new file mode 100644 index 0000000000000000000000000000000000000000..07cc600c98675d221bb56d10af38e650538734c9 GIT binary patch literal 244 zcmX|+%?`mp6otRFH?W%}3lbZ#mXJt@4G%E1O4?47PI);CkK`4cxy9!GoVn*`-p>~Y zuH1+?F6tGzrhboj9@;Y@-Y$;1UNd3FTy@Kesopkps%IL4CNFld>nNl)y+UZqNwu)u z8>5qRa*M`l|5*Q8iQQ0|QYFpu%XIuwER-RaS8~oYrJPIl?9@kcyPIPAOJL|a#!5Tj E15l{{title}}

+

<%= title() %>

\ No newline at end of file diff --git a/test2/fixtures/pages/b.hbs b/test2/fixtures/pages/b.hbs new file mode 100644 index 0000000..51320bd --- /dev/null +++ b/test2/fixtures/pages/b.hbs @@ -0,0 +1,2 @@ +

{{title}}

+

<%= title() %>

\ No newline at end of file diff --git a/test2/fixtures/pages/c.hbs b/test2/fixtures/pages/c.hbs new file mode 100644 index 0000000..51320bd --- /dev/null +++ b/test2/fixtures/pages/c.hbs @@ -0,0 +1,2 @@ +

{{title}}

+

<%= title() %>

\ No newline at end of file diff --git a/test/fixtures/pipeline/a.js b/test2/fixtures/pipeline/a.js similarity index 100% rename from test/fixtures/pipeline/a.js rename to test2/fixtures/pipeline/a.js diff --git a/test/fixtures/pipeline/b.js b/test2/fixtures/pipeline/b.js similarity index 100% rename from test/fixtures/pipeline/b.js rename to test2/fixtures/pipeline/b.js diff --git a/test/fixtures/pipeline/c.js b/test2/fixtures/pipeline/c.js similarity index 100% rename from test/fixtures/pipeline/c.js rename to test2/fixtures/pipeline/c.js diff --git a/test/fixtures/pipeline/d.js b/test2/fixtures/pipeline/d.js similarity index 100% rename from test/fixtures/pipeline/d.js rename to test2/fixtures/pipeline/d.js diff --git a/test2/fixtures/posts/a.txt b/test2/fixtures/posts/a.txt new file mode 100644 index 0000000..bca29ee --- /dev/null +++ b/test2/fixtures/posts/a.txt @@ -0,0 +1,4 @@ +--- +title: AAA +--- +This is <%= title %> \ No newline at end of file diff --git a/test2/fixtures/posts/b.txt b/test2/fixtures/posts/b.txt new file mode 100644 index 0000000..1e128c7 --- /dev/null +++ b/test2/fixtures/posts/b.txt @@ -0,0 +1,4 @@ +--- +title: BBB +--- +This is <%= title %> \ No newline at end of file diff --git a/test2/fixtures/posts/c.txt b/test2/fixtures/posts/c.txt new file mode 100644 index 0000000..32f9187 --- /dev/null +++ b/test2/fixtures/posts/c.txt @@ -0,0 +1,4 @@ +--- +title: CCC +--- +This is <%= title %> \ No newline at end of file diff --git a/test2/fixtures/templates/a.tmpl b/test2/fixtures/templates/a.tmpl new file mode 100644 index 0000000..36f1f1b --- /dev/null +++ b/test2/fixtures/templates/a.tmpl @@ -0,0 +1 @@ +<%= name %> \ No newline at end of file diff --git a/test2/fixtures/templates/b.tmpl b/test2/fixtures/templates/b.tmpl new file mode 100644 index 0000000..36f1f1b --- /dev/null +++ b/test2/fixtures/templates/b.tmpl @@ -0,0 +1 @@ +<%= name %> \ No newline at end of file diff --git a/test2/fixtures/templates/c.tmpl b/test2/fixtures/templates/c.tmpl new file mode 100644 index 0000000..36f1f1b --- /dev/null +++ b/test2/fixtures/templates/c.tmpl @@ -0,0 +1 @@ +<%= name %> \ No newline at end of file diff --git a/test2/fixtures/test-symlink b/test2/fixtures/test-symlink new file mode 120000 index 0000000..3fcfe6c --- /dev/null +++ b/test2/fixtures/test-symlink @@ -0,0 +1 @@ +test.coffee \ No newline at end of file diff --git a/test2/fixtures/test-symlink-dir/suchempty b/test2/fixtures/test-symlink-dir/suchempty new file mode 100644 index 0000000..65bbcaa --- /dev/null +++ b/test2/fixtures/test-symlink-dir/suchempty @@ -0,0 +1 @@ +suchempty \ No newline at end of file diff --git a/test2/fixtures/test.coffee b/test2/fixtures/test.coffee new file mode 100644 index 0000000..6769dd6 --- /dev/null +++ b/test2/fixtures/test.coffee @@ -0,0 +1 @@ +Hello world! \ No newline at end of file diff --git a/test2/fixtures/updaters/a.txt b/test2/fixtures/updaters/a.txt new file mode 100644 index 0000000..7c4a013 --- /dev/null +++ b/test2/fixtures/updaters/a.txt @@ -0,0 +1 @@ +aaa \ No newline at end of file diff --git a/test2/fixtures/updaters/b.txt b/test2/fixtures/updaters/b.txt new file mode 100644 index 0000000..01f02e3 --- /dev/null +++ b/test2/fixtures/updaters/b.txt @@ -0,0 +1 @@ +bbb \ No newline at end of file diff --git a/test2/fixtures/updaters/c.txt b/test2/fixtures/updaters/c.txt new file mode 100644 index 0000000..2383bd5 --- /dev/null +++ b/test2/fixtures/updaters/c.txt @@ -0,0 +1 @@ +ccc \ No newline at end of file diff --git a/test2/fixtures/vinyl/bom-utf16be.txt b/test2/fixtures/vinyl/bom-utf16be.txt new file mode 100644 index 0000000000000000000000000000000000000000..b9dce78a5d31af4803acd1a0f0dfc14f064a5de1 GIT binary patch literal 244 zcmX|+I}XAy5Jacu26Qf=0Eq@sM*@i=qJaY#5&}waq&RRn3Xa4Tc-{cbe#Sc=zn?E@ zuZymVayru+l}y7P<@I1MK)hWXxZY@{g_hJzYt4Dvs;8dRDlmE2!LB37&GahJPDg5G zyEjIUb8?Hu>i*d9+Q4pAn^J>j{bf4+Qmn{O;+32Wrj#?&PC0#o+uan?UxFJmPf0ua E03tFf0ssI2 literal 0 HcmV?d00001 diff --git a/test2/fixtures/vinyl/bom-utf16le.txt b/test2/fixtures/vinyl/bom-utf16le.txt new file mode 100644 index 0000000000000000000000000000000000000000..07cc600c98675d221bb56d10af38e650538734c9 GIT binary patch literal 244 zcmX|+%?`mp6otRFH?W%}3lbZ#mXJt@4G%E1O4?47PI);CkK`4cxy9!GoVn*`-p>~Y zuH1+?F6tGzrhboj9@;Y@-Y$;1UNd3FTy@Kesopkps%IL4CNFld>nNl)y+UZqNwu)u z8>5qRa*M`l|5*Q8iQQ0|QYFpu%XIuwER-RaS8~oYrJPIl?9@kcyPIPAOJL|a#!5Tj E15l', locals: {a: 'bbb'}}); + app.helper('upper', function(str) { + return str.toUpperCase(); + }); + + var page = app.pages.getView('a.tmpl'); + + app.render(page, function(err, view) { + if (err) return cb(err); + + assert.equal(typeof view.contents.toString(), 'string'); + assert.equal(view.contents.toString(), 'BBB'); + cb(); + }); + }); + + it('should use a namespaced helper:', function(cb) { + app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= foo.upper(a) %>', locals: {a: 'bbb'}}); + + app.helperGroup('foo', { + upper: function(str) { + return str.toUpperCase(); + } + }); + + // console.log(app._.helpers) + + var page = app.pages.getView('a.tmpl'); + app.render(page, function(err, view) { + if (err) return cb(err); + + assert.equal(typeof view.contents.toString(), 'string'); + assert.equal(view.contents.toString(), 'BBB'); + cb(); + }); + }); +}); + +describe('async helpers', function() { + beforeEach(function() { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.create('page'); + }); + + it('should register an async helper:', function() { + app.asyncHelper('a', function() {}); + app.asyncHelper('b', function() {}); + app._.helpers.async.should.have.property('a'); + app._.helpers.async.should.have.property('b'); + }); + + it('should use an async helper:', function(cb) { + app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= lower(a) %>', locals: {a: 'BBB'}}); + app.asyncHelper('lower', function(str, next) { + if (typeof next !== 'function') return str; + next(null, str.toLowerCase()); + }); + + var page = app.pages.getView('a.tmpl'); + app.render(page, function(err, view) { + if (err) return cb(err); + assert.equal(typeof view.content, 'string'); + assert.equal(view.content, 'bbb'); + cb(); + }); + }); +}); + +describe('built-in helpers:', function() { + describe('automatically generated helpers for default view types:', function() { + beforeEach(function() { + app = new App({rethrow: false}); + app.engine('md', require('engine-base')); + app.engine('tmpl', require('engine-base')); + app.create('partials', { viewType: 'partial' }); + app.create('pages'); + + // parse front matter + app.onLoad(/./, function(view, next) { + matter.parse(view, next); + }); + }); + + it('should expose front matter to the `partial` helper.', function(cb) { + app.partial('a.md', {content: '---\nname: "AAA"\n---\n<%= name %>', locals: {name: 'BBB'}}); + app.page('b.md', {path: 'b.md', content: 'foo <%= partial("a.md") %> bar'}); + + app.render('b.md', function(err, res) { + if (err) return cb(err); + res.content.should.equal('foo AAA bar'); + cb(); + }); + }); + + it('should use helper locals.', function(cb) { + app.partial('abc.md', {content: '<%= name %>', locals: {name: 'BBB'}}); + app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md", { name: "CCC" }) %> bar'}); + + app.render('xyz.md', {name: 'DDD'}, function(err, res) { + if (err) return cb(err); + res.content.should.equal('foo CCC bar'); + cb(); + }); + }); + + it('should use front matter data.', function(cb) { + app.partial('abc.md', {content: '---\nname: "AAA"\n---\n<%= name %>'}); + app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}); + + app.render('xyz.md', {name: 'DDD'}, function(err, res) { + if (err) return cb(err); + res.content.should.equal('foo AAA bar'); + cb(); + }); + }); + + it('should prefer helper locals over front-matter', function(cb) { + app.partial('abc.md', {content: '---\nname: "AAA"\n---\n<%= name %>', locals: {name: 'BBB'}}); + app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md", { name: "CCC" }) %> bar'}); + + app.render('xyz.md', {name: 'DDD'}, function(err, res) { + if (err) return cb(err); + res.content.should.equal('foo CCC bar'); + cb(); + }); + }); + + it('should use partial locals:', function(cb) { + app.partial('abc.md', {content: '<%= name %>', locals: {name: 'EEE'}}); + + app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}) + .render({name: 'DDD'}, function(err, res) { + if (err) return cb(err); + res.content.should.equal('foo EEE bar'); + cb(); + }); + }); + + it('should use locals from the `view.render` method:', function(cb) { + app.partial('abc.md', {content: '<%= name %>', locals: {name: 'EEE'}}); + + app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}) + .render({name: 'DDD'}, function(err, res) { + if (err) return cb(err); + + res.content.should.equal('foo EEE bar'); + cb(); + }); + }); + + it('should use locals from the `app.render` method:', function(cb) { + app.partial('abc.md', {content: '<%= name %>'}); + app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}); + + app.render('xyz.md', {name: 'DDD'}, function(err, res) { + if (err) return cb(err); + res.content.should.equal('foo DDD bar'); + cb(); + }); + }); + + it('should use a `helperContext` function from app.options', function(cb) { + app.option('helperContext', function(view, locals) { + return { name: 'blah' }; + }); + + app.partial('abc.md', {content: '<%= name %>'}); + app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}); + + app.render('xyz.md', {name: 'DDD'}, function(err, res) { + if (err) return cb(err); + res.content.should.equal('foo blah bar'); + cb(); + }); + }); + + it('should return an empty string when the partial is missing.', function(cb) { + app.partial('abc.md', {content: '---\nname: "AAA"\n---\n<%= name %>', locals: {name: 'BBB'}}); + app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("def.md", { name: "CCC" }) %> bar'}); + app.render('xyz.md', {name: 'DDD'}, function(err, res) { + if (err) return cb(err); + res.content.should.equal('foo bar'); + cb(); + }); + }); + }); + + describe('helper context:', function() { + beforeEach(function() { + app = new App({rethrow: false}); + app.engine(['tmpl', 'md'], require('engine-base')); + app.create('partial', { viewType: 'partial' }); + app.create('page'); + + // parse front matter + app.onLoad(/./, function(view, next) { + matter.parse(view, next); + }); + }); + + it('should prefer helper locals over view locals.', function(cb) { + app.partial('abc.md', {content: '<%= name %>', name: 'BBB'}); + app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md", { name: "CCC" }) %> bar'}); + + app.render('xyz.md', {name: 'DDD'}, function(err, res) { + if (err) return cb(err); + res.content.should.equal('foo CCC bar'); + cb(); + }); + }); + + it('should give preference to view locals over render locals.', function(cb) { + app.partial('abc.md', {content: '<%= name %>', locals: {name: 'BBB'}}); + app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}); + + var page = app.pages.getView('xyz.md'); + + app.render(page, {name: 'DDD'}, function(err, res) { + if (err) return cb(err); + res.content.should.equal('foo BBB bar'); + cb(); + }); + }); + + it('should use render locals when other locals are not defined.', function(cb) { + app.partial('abc.md', {content: '<%= name %>'}); + app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}); + + app.render('xyz.md', {name: 'DDD'}, function(err, res) { + if (err) return cb(err); + res.content.should.equal('foo DDD bar'); + cb(); + }); + }); + }); + + describe('user-defined engines:', function() { + beforeEach(function() { + app = new App({rethrow: false}); + app.create('partial', { viewType: 'partial' }); + app.create('page'); + + // parse front matter + app.onLoad(/./, function(view, next) { + matter.parse(view, next); + }); + }); + + it('should use the `partial` helper with handlebars.', function(cb) { + app.engine(['tmpl', 'md'], require('engine-base')); + app.engine('hbs', handlebars); + + app.partial('title.hbs', {content: '{{name}}', locals: {name: 'BBB'}}); + app.page('a.hbs', {path: 'a.hbs', content: 'foo {{{partial "title.hbs" this}}} bar'}); + + app.render('a.hbs', {name: 'Halle Nicole'}, function(err, res) { + if (err) return cb(err); + res.content.should.equal('foo Halle Nicole bar'); + cb(); + }); + }); + + it('should use the `partial` helper with any engine.', function(cb) { + app.engine('hbs', handlebars); + app.engine('md', handlebars); + app.engine('swig', swig); + app.engine('tmpl', require('engine-base')); + + /** + * Partial + */ + + app.partial('a.hbs', { + content: '---\nname: "AAA"\n---\n{{name}}', + locals: { + name: 'BBB' + } + }); + + /** + * Pages + */ + + app.page('a.hbs', { + path: 'a.hbs', + content: '{{author}}', + locals: { + author: 'Halle Nicole' + } + }); + app.page('b.tmpl', { + path: 'b.tmpl', + content: '<%= author %>', + locals: { + author: 'Halle Nicole' + } + }); + app.page('d.swig', { + path: 'd.swig', + content: '{{author}}', + locals: { + author: 'Halle Nicole' + } + }); + app.page('e.swig', { + path: 'e.swig', + content: '{{author}}', + locals: { + author: 'Halle Nicole' + } + }); + app.page('f.hbs', { + content: '{{author}}', + locals: { + author: 'Halle Nicole' + } + }); + app.page('g.md', { + content: '---\nauthor: Brian Woodward\n---\n{{author}}', + locals: { + author: 'Halle Nicole' + } + }); + app.page('with-partial.hbs', { + path: 'with-partial.hbs', + content: '{{{partial "a.hbs" custom.locals}}}' + }); + + app.on('error', function(err) { + cb(err); + }); + + var locals = {custom: {locals: {name: 'Halle Nicole' }}}; + app.render('a.hbs', locals, function(err, res) { + if (err) { + app.emit('error', err); + return; + } + res.content.should.equal('Halle Nicole'); + }); + + app.render('with-partial.hbs', locals, function(err, res) { + if (err) { + app.emit('error', err); + return; + } + res.content.should.equal('Halle Nicole'); + }); + + var page = app.pages.getView('g.md'); + locals.author = page.data.author || locals.author; + page.render(locals, function(err, res) { + if (err) { + app.emit('error', err); + return; + } + res.content.should.equal('Brian Woodward'); + cb(null, res.content); + }); + }); + }); +}); + +describe('helpers integration', function() { + beforeEach(function() { + app = new App(); + app.create('pages'); + app.engine('md', require('engine-base')); + }); + + describe('.helpers()', function() { + it('should add helpers and use them in templates.', function(cb) { + app.helpers({ + upper: function(str) { + return str.toUpperCase(); + } + }); + + app.page('doc.md', {content: 'a <%= upper(name) %> b'}) + .render({name: 'Halle'}, function(err, res) { + if (err) return cb(err); + assert(res.content === 'a HALLE b'); + cb(); + }); + }); + }); + + describe('helper options:', function() { + it('should expose `this.options` to helpers:', function(cb) { + app.helper('cwd', function(fp) { + return path.join(this.options.cwd, fp); + }); + + app.option('one', 'two'); + app.option('cwd', 'foo/bar'); + app.page('doc.md', {content: 'a <%= cwd("baz") %> b'}) + .render(function(err, res) { + if (err) return cb(err); + assert(res.content === 'a foo/bar/baz b'); + cb(); + }); + }); + + it('should pass helper options to helpers:', function(cb) { + app.helper('cwd', function(fp) { + return path.join(this.options.cwd, fp); + }); + + app.option('helper.cwd', 'foo/bar'); + app.option('helper.whatever', '...'); + + app.page('doc.md', {content: 'a <%= cwd("baz") %> b'}) + .render(function(err, res) { + if (err) return cb(err); + assert(res.content === 'a foo/bar/baz b'); + cb(); + }); + }); + }); + + describe('options.helpers', function() { + it('should register helpers passed on the options:', function(cb) { + app.option({ + helpers: { + upper: function(str) { + return str.toUpperCase(); + }, + foo: function(str) { + return 'foo' + str; + } + } + }); + + app.page('doc.md', {content: 'a <%= upper(name) %> <%= foo("bar") %> b'}) + .render({name: 'Halle'}, function(err, res) { + if (err) return cb(err); + assert(res.content === 'a HALLE foobar b'); + cb(); + }); + }); + }); + + describe('options.helpers', function() { + it('should add helpers and use them in templates.', function(cb) { + app.options.helpers = { + upper: function(str) { + return str.toUpperCase(); + }, + foo: function(str) { + return 'foo' + str; + } + }; + + app.page('doc.md', {content: 'a <%= upper(name) %> b'}) + .render({name: 'Halle'}, function(err, res) { + if (err) return cb(err); + assert(res.content === 'a HALLE b'); + cb(); + }); + }); + }); +}); + +describe('collection helpers', function() { + beforeEach(function() { + app = new App(); + app.create('posts'); + app.create('pages', {engine: 'hbs'}); + app.create('partials', {viewType: 'partial', engine: 'hbs'}); + app.create('snippet', {viewType: 'partial'}); + app.engine('hbs', require('engine-handlebars')); + app.helper('log', function(ctx) { + console.log(ctx); + }); + }); + + describe('plural', function() { + it('should get the given collection', function(cb) { + app.post('a.hbs', {content: 'foo'}); + app.post('b.hbs', {content: 'bar'}); + app.post('c.hbs', {content: 'baz'}); + + app.partial('list.hbs', { + content: '{{#posts}}{{#each items}}{{content}}{{/each}}{{/posts}}' + }); + + app.page('index.hbs', { + content: '{{> list.hbs }}' + }) + .render(function(err, res) { + if (err) return cb(err); + assert(res.content === 'foobarbaz'); + cb(); + }); + }); + }); + + describe('single', function() { + it('should get a view from an unspecified collection', function(cb) { + app.post('a.hbs', {content: 'post-a'}); + app.post('b.hbs', {content: 'post-b'}); + + var one = app.page('one', {content: '{{view "a.hbs"}}'}) + .compile() + .fn(); + + var two = app.page('two', {content: '{{view "b.hbs"}}'}) + .compile() + .fn(); + + assert(one === 'post-a'); + assert(two === 'post-b'); + cb(); + }); + + it('should return an empty string if not found', function(cb) { + var one = app.page('one', {content: '{{view "foo.hbs"}}'}) + .compile() + .fn(); + assert(one === ''); + cb(); + }); + + it('should handle engine errors', function(cb) { + app.post('foo.hbs', {content: '{{one "two"}}'}); + app.page('one', {content: '{{posts "foo.hbs"}}'}) + .render(function(err) { + assert(err); + assert(typeof err === 'object'); + assert(typeof err.message === 'string'); + assert(/Missing helper: "one"/.test(err.message)); + cb(); + }); + }); + + it('should handle engine errors2', function(cb) { + app.engine('tmpl', require('engine-base')); + app.create('foo', {engine: 'tmpl'}); + app.create('bar', {engine: 'tmpl'}); + + app.create('foo', {viewType: 'partial'}); + app.foo('foo.tmpl', {path: 'foo.tmpl', content: '<%= blah.bar %>'}); + app.bar('one.tmpl', {content: '<%= foo("foo.tmpl") %>'}) + .render(function(err) { + assert(err); + assert(typeof err === 'object'); + assert(/blah is not defined/.test(err.message)); + cb(); + }); + }); + + it('should work with non-handlebars engine', function(cb) { + app.engine('tmpl', require('engine-base')); + app.create('foo', {engine: 'tmpl'}); + app.create('bar', {engine: 'tmpl'}); + + app.foo('a.tmpl', {content: 'foo-a'}); + app.foo('b.tmpl', {content: 'foo-b'}); + + var one = app.bar('one', {content: '<%= view("a.tmpl") %>'}) + .compile() + .fn(); + + var two = app.bar('two', {content: '<%= view("b.tmpl") %>'}) + .compile() + .fn(); + + assert(one === 'foo-a'); + assert(two === 'foo-b'); + cb(); + }); + + it('should get a specific view from the given collection', function(cb) { + app.post('a.hbs', {content: 'post-a'}); + app.post('b.hbs', {content: 'post-b'}); + app.post('c.hbs', {content: 'post-c'}); + app.page('a.hbs', {content: 'page-a'}); + app.page('b.hbs', {content: 'page-b'}); + app.page('c.hbs', {content: 'page-c'}); + + var one = app.page('one', {content: '{{view "a.hbs" "posts"}}'}) + .compile() + .fn(); + + var two = app.page('two', {content: '{{view "b.hbs" "pages"}}'}) + .compile() + .fn(); + + assert(one === 'post-a'); + assert(two === 'page-b'); + cb(); + }); + }); +}); diff --git a/test2/item.js b/test2/item.js new file mode 100644 index 0000000..ca0f435 --- /dev/null +++ b/test2/item.js @@ -0,0 +1,1060 @@ +require('mocha'); +var should = require('should'); +var fs = require('fs'); +var path = require('path'); +var assert = require('assert'); +var es = require('event-stream'); +var Stream = require('stream'); +var support = require('./support'); +var App = support.resolve(); +var Item = App.Item; +var item; + +describe('Item', function() { + describe('instance', function() { + it('should create an instance of Item:', function() { + item = new Item(); + assert(item instanceof Item); + }); + + it('should instantiate without new:', function() { + item = Item(); + assert(item instanceof Item); + }); + + it('inspect should not double name `Stream` when ctor is `Stream`', function(done) { + var val = new Stream(); + item = new Item({contents: val}); + done(); + }); + }); + + describe('static methods', function() { + it('should expose `extend`:', function() { + assert(typeof Item.extend === 'function'); + }); + }); + + describe('prototype methods', function() { + beforeEach(function() { + item = new Item(); + }); + + it('should expose `set`:', function() { + assert(typeof item.set === 'function'); + }); + it('should expose `get`:', function() { + assert(typeof item.get === 'function'); + }); + it('should expose `del`:', function() { + assert(typeof item.del === 'function'); + }); + it('should expose `define`:', function() { + assert(typeof item.define === 'function'); + }); + it('should expose `visit`:', function() { + assert(typeof item.visit === 'function'); + }); + }); + + describe('properties', function() { + it('should expose an `options` property', function() { + item = new Item({}); + assert.deepEqual(item.options, {}); + assert(item.hasOwnProperty('options')); + }); + + it('should add `options` when passed on the constructor', function() { + item = new Item({options: {foo: 'bar'}}); + assert(item.options.foo === 'bar'); + }); + + it('should expose a `data` property', function() { + item = new Item({app: {}}); + assert.deepEqual(item.data, {}); + assert(item.hasOwnProperty('data')); + }); + + it('should add `data` when passed on the constructor', function() { + item = new Item({data: {foo: 'bar'}}); + assert(item.data.foo === 'bar'); + }); + + it('should add `locals` when passed on the constructor', function() { + item = new Item({locals: {foo: 'bar'}}); + assert(item.locals.foo === 'bar'); + }); + }); + + describe('set', function() { + it('should set properties on the object', function() { + item = new Item(); + item.set('foo', 'bar'); + assert.equal(item.foo, 'bar'); + }); + }); + + describe('get', function() { + it('should get properties from the object', function() { + item = new Item(); + item.set('foo', 'bar'); + assert.equal(item.get('foo'), 'bar'); + }); + }); + + describe('cwd', function() { + it('should get properties from the object', function() { + item = new Item({cwd: 'test/fixtures'}); + assert(item.cwd === 'test/fixtures'); + }); + }); + + describe('clone', function() { + it('should clone the item:', function() { + item = new Item({content: 'foo'}); + item.set({path: 'foo/bar'}); + item.set('options.one', 'two'); + var clone = item.clone(); + assert(clone.contents); + clone.set('baz', 'quux'); + clone.set('options.three', 'four'); + assert.equal(clone.get('foo'), item.get('foo')); + assert(clone.get('baz') === 'quux'); + assert(!item.get('baz')); + // not deep cloned + assert(clone.get('options.three') === 'four'); + assert(item.get('options.three') === 'four'); + }); + + it('should deep clone the entire object', function() { + item = new Item({content: 'foo'}); + item.set({path: 'foo/bar'}); + item.set('options.one', 'two'); + var clone = item.clone({deep: true}); + clone.set('options.three', 'four'); + assert(item.get('options.one') === 'two'); + assert(clone.get('options.one') === 'two'); + assert(clone.get('options.three') === 'four'); + assert(!item.get('options.three')); + }); + }); + + describe('visit', function() { + it('should visit all properties on an object and call the specified method', function() { + item = new Item(); + var obj = { + foo: 'bar', + bar: 'baz', + baz: 'bang' + }; + item.visit('set', obj); + assert.equal(item.get('foo'), 'bar'); + assert.equal(item.get('bar'), 'baz'); + assert.equal(item.get('baz'), 'bang'); + }); + + it('should visit all properties on all objects in an array and call the specified method', function() { + item = new Item(); + var arr = [{foo: 'bar', bar: 'baz', baz: 'bang'}]; + item.visit('set', arr); + assert.equal(item.get('foo'), 'bar'); + assert.equal(item.get('bar'), 'baz'); + assert.equal(item.get('baz'), 'bang'); + }); + }); +}); + +/** + * The following unit tests are from Vinyl + * Since we inherit vinyl in Item, we need + * to ensure that these still pass. + */ + +describe('Item', function() { + describe('isVinyl()', function() { + it('should return true on a vinyl object', function(done) { + item = new Item(); + assert(Item.isVinyl(item) === true); + done(); + }); + it('should return false on a normal object', function(done) { + assert(Item.isVinyl({}) === false); + done(); + }); + it('should return false on a null object', function(done) { + assert(Item.isVinyl({}) === false); + done(); + }); + }); + + describe('constructor()', function() { + it('should default cwd to process.cwd', function(done) { + item = new Item(); + item.cwd.should.equal(process.cwd()); + done(); + }); + + it('should default base to cwd', function(done) { + var cwd = '/'; + item = new Item({cwd: cwd}); + item.base.should.equal(cwd); + done(); + }); + + it('should default base to cwd even when none is given', function(done) { + item = new Item(); + item.base.should.equal(process.cwd()); + done(); + }); + + it('should default path to null', function(done) { + item = new Item(); + should.not.exist(item.path); + done(); + }); + + it('should default history to []', function(done) { + item = new Item(); + item.history.should.eql([]); + done(); + }); + + it('should default stat to null', function(done) { + item = new Item(); + should.not.exist(item.stat); + done(); + }); + + it('should default contents to null', function(done) { + item = new Item(); + should.not.exist(item.contents); + done(); + }); + + it('should set base to given value', function(done) { + var val = '/'; + item = new Item({base: val}); + item.base.should.equal(val); + done(); + }); + + it('should set cwd to given value', function(done) { + var val = '/'; + item = new Item({cwd: val}); + item.cwd.should.equal(val); + done(); + }); + + it('should set path to given value', function(done) { + var val = '/test.coffee'; + item = new Item({path: val}); + item.path.should.equal(val); + item.history.should.eql([val]); + done(); + }); + + it('should set history to given value', function(done) { + var val = '/test.coffee'; + item = new Item({history: [val]}); + item.path.should.equal(val); + item.history.should.eql([val]); + done(); + }); + + it('should set stat to given value', function(done) { + var val = {}; + item = new Item({stat: val}); + item.stat.should.equal(val); + done(); + }); + + it('should set contents to given value', function(done) { + var val = new Buffer('test'); + item = new Item({contents: val}); + item.contents.should.equal(val); + done(); + }); + }); + + describe('isBuffer()', function() { + it('should return true when the contents are a Buffer', function(done) { + var val = new Buffer('test'); + item = new Item({contents: val}); + item.isBuffer().should.equal(true); + done(); + }); + + it('should return false when the contents are a Stream', function(done) { + var val = new Stream(); + var item = new Item({contents: val}); + item.isBuffer().should.equal(false); + done(); + }); + + it('should return false when the contents are a null', function(done) { + var item = new Item({contents: null}); + item.isBuffer().should.equal(false); + done(); + }); + }); + + describe('isStream()', function() { + it('should return false when the contents are a Buffer', function(done) { + var val = new Buffer('test'); + var item = new Item({contents: val}); + item.isStream().should.equal(false); + done(); + }); + + it('should return true when the contents are a Stream', function(done) { + var val = new Stream(); + var item = new Item({contents: val}); + item.isStream().should.equal(true); + done(); + }); + + it('should return false when the contents are a null', function(done) { + var item = new Item({contents: null}); + item.isStream().should.equal(false); + done(); + }); + }); + + describe('isNull()', function() { + it('should return false when the contents are a Buffer', function(done) { + var val = new Buffer('test'); + var item = new Item({contents: val}); + item.isNull().should.equal(false); + done(); + }); + + it('should return false when the contents are a Stream', function(done) { + var val = new Stream(); + var item = new Item({contents: val}); + item.isNull().should.equal(false); + done(); + }); + + it('should return true when the contents are a null', function(done) { + var item = new Item({contents: null}); + item.isNull().should.equal(true); + done(); + }); + }); + + describe('isDirectory()', function() { + var fakeStat = { + isDirectory: function() { + return true; + } + }; + + it('should return false when the contents are a Buffer', function(done) { + var val = new Buffer('test'); + var item = new Item({contents: val, stat: fakeStat}); + item.isDirectory().should.equal(false); + done(); + }); + + it('should return false when the contents are a Stream', function(done) { + var val = new Stream(); + var item = new Item({contents: val, stat: fakeStat}); + item.isDirectory().should.equal(false); + done(); + }); + + it('should return true when the contents are a null', function(done) { + var item = new Item({contents: null, stat: fakeStat}); + item.isDirectory().should.equal(true); + done(); + }); + }); + + describe('clone()', function() { + it('should copy all attributes over with Buffer', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: new Buffer('test') + }; + var item = new Item(options); + var item2 = item.clone(); + + item2.should.not.equal(item, 'refs should be different'); + item2.cwd.should.equal(item.cwd); + item2.base.should.equal(item.base); + item2.path.should.equal(item.path); + item2.contents.should.not.equal(item.contents, 'buffer ref should be different'); + item2.contents.toString('utf8').should.equal(item.contents.toString('utf8')); + done(); + }); + + it('should copy buffer\'s reference with option contents: false', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.js', + contents: new Buffer('test') + }; + + var item = new Item(options); + + var copy1 = item.clone({ contents: false }); + copy1.contents.should.equal(item.contents); + + var copy2 = item.clone({}); + copy2.contents.should.not.equal(item.contents); + + var copy3 = item.clone({ contents: 'any string' }); + copy3.contents.should.not.equal(item.contents); + + done(); + }); + + it('should copy all attributes over with Stream', function(done) { + var contents = new Stream.PassThrough(); + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: contents + }; + var item = new Item(options); + var item2 = item.clone(); + + contents.write(new Buffer('wa')); + + process.nextTick(function() { + contents.write(new Buffer('dup')); + contents.end(); + }); + + item2.should.not.equal(item, 'refs should be different'); + item2.cwd.should.equal(item.cwd); + item2.base.should.equal(item.base); + item2.path.should.equal(item.path); + item2.contents.should.not.equal(item.contents, 'stream ref should not be the same'); + item.contents.pipe(es.wait(function(err, data) { + if (err) return done(err); + item2.contents.pipe(es.wait(function(err, data2) { + if (err) return done(err); + data2.should.not.equal(data, 'stream contents ref should not be the same'); + data2.should.eql(data, 'stream contents should be the same'); + })); + })); + done(); + }); + + it('should copy all attributes over with null', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: null + }; + var item = new Item(options); + var item2 = item.clone(); + + item2.should.not.equal(item, 'refs should be different'); + item2.cwd.should.equal(item.cwd); + item2.base.should.equal(item.base); + item2.path.should.equal(item.path); + should.not.exist(item2.contents); + done(); + }); + + it('should properly clone the `stat` property', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.js', + contents: new Buffer('test'), + stat: fs.statSync(__filename) + }; + + var item = new Item(options); + var copy = item.clone(); + + assert(copy.stat.isFile()); + assert(!copy.stat.isDirectory()); + done(); + }); + + it('should properly clone the `history` property', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.js', + contents: new Buffer('test'), + stat: fs.statSync(__filename) + }; + + var item = new Item(options); + var copy = item.clone(); + + copy.history[0].should.equal(options.path); + copy.path = 'lol'; + item.path.should.not.equal(copy.path); + done(); + }); + + it('should copy custom properties', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: null + }; + + var item = new Item(options); + item.custom = { a: 'custom property' }; + var item2 = item.clone(); + + item2.should.not.equal(item, 'refs should be different'); + item2.cwd.should.equal(item.cwd); + item2.base.should.equal(item.base); + item2.path.should.equal(item.path); + item2.custom.should.equal(item.custom); + item2.custom.a.should.equal(item.custom.a); + + done(); + }); + + it('should copy history', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: null + }; + + var item = new Item(options); + item.path = '/test/test.js'; + item.path = '/test/test-938di2s.js'; + var item2 = item.clone(); + + item2.history.should.eql([ + '/test/test.coffee', + '/test/test.js', + '/test/test-938di2s.js' + ]); + item2.history.should.not.equal([ + '/test/test.coffee', + '/test/test.js', + '/test/test-938di2s.js' + ]); + item2.path.should.eql('/test/test-938di2s.js'); + + done(); + }); + + it('should copy all attributes deeply', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: null + }; + + var item = new Item(options); + item.custom = { a: 'custom property' }; + + var item2 = item.clone(true); + item2.custom.should.eql(item.custom); + item2.custom.should.not.equal(item.custom); + item2.custom.a.should.equal(item.custom.a); + + var item3 = item.clone({ deep: true }); + item3.custom.should.eql(item.custom); + item3.custom.should.not.equal(item.custom); + item3.custom.a.should.equal(item.custom.a); + + var item4 = item.clone(false); + item4.custom.should.eql(item.custom); + item4.custom.should.equal(item.custom); + item4.custom.a.should.equal(item.custom.a); + + var item5 = item.clone({ deep: false }); + item5.custom.should.eql(item.custom); + item5.custom.should.equal(item.custom); + item5.custom.a.should.equal(item.custom.a); + + done(); + }); + }); + + describe('pipe()', function() { + it('should write to stream with Buffer', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: new Buffer('test') + }; + var item = new Item(options); + var stream = new Stream.PassThrough(); + stream.on('data', function(chunk) { + should.exist(chunk); + (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); + chunk.toString('utf8').should.equal(options.contents.toString('utf8')); + }); + stream.on('end', function() { + done(); + }); + var ret = item.pipe(stream); + ret.should.equal(stream, 'should return the stream'); + }); + + it('should pipe to stream with Stream', function(done) { + var testChunk = new Buffer('test'); + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: new Stream.PassThrough() + }; + var item = new Item(options); + var stream = new Stream.PassThrough(); + stream.on('data', function(chunk) { + should.exist(chunk); + (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); + chunk.toString('utf8').should.equal(testChunk.toString('utf8')); + done(); + }); + var ret = item.pipe(stream); + ret.should.equal(stream, 'should return the stream'); + + item.contents.write(testChunk); + }); + + it('should do nothing with null', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: null + }; + var item = new Item(options); + var stream = new Stream.PassThrough(); + stream.on('data', function() { + throw new Error('should not write'); + }); + stream.on('end', function() { + done(); + }); + var ret = item.pipe(stream); + ret.should.equal(stream, 'should return the stream'); + }); + + it('should write to stream with Buffer', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: new Buffer('test') + }; + var item = new Item(options); + var stream = new Stream.PassThrough(); + stream.on('data', function(chunk) { + should.exist(chunk); + (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); + chunk.toString('utf8').should.equal(options.contents.toString('utf8')); + done(); + }); + stream.on('end', function() { + throw new Error('should not end'); + }); + var ret = item.pipe(stream, {end: false}); + ret.should.equal(stream, 'should return the stream'); + }); + + it('should pipe to stream with Stream', function(done) { + var testChunk = new Buffer('test'); + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: new Stream.PassThrough() + }; + var item = new Item(options); + var stream = new Stream.PassThrough(); + stream.on('data', function(chunk) { + should.exist(chunk); + (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); + chunk.toString('utf8').should.equal(testChunk.toString('utf8')); + done(); + }); + stream.on('end', function() { + throw new Error('should not end'); + }); + var ret = item.pipe(stream, {end: false}); + ret.should.equal(stream, 'should return the stream'); + + item.contents.write(testChunk); + }); + + it('should do nothing with null', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: null + }; + var item = new Item(options); + var stream = new Stream.PassThrough(); + stream.on('data', function() { + throw new Error('should not write'); + }); + stream.on('end', function() { + throw new Error('should not end'); + }); + var ret = item.pipe(stream, {end: false}); + ret.should.equal(stream, 'should return the stream'); + process.nextTick(done); + }); + }); + + describe('inspect()', function() { + it('should return correct format when no contents and no path', function(done) { + var item = new Item(); + item.inspect().should.equal(''); + done(); + }); + + it('should return correct format when Buffer and no path', function(done) { + var val = new Buffer('test'); + var item = new Item({ + contents: val + }); + item.inspect().should.equal('>'); + done(); + }); + + it('should return correct format when Buffer and relative path', function(done) { + var val = new Buffer('test'); + var item = new Item({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: val + }); + item.inspect().should.equal('>'); + done(); + }); + + it('should return correct format when Buffer and only path and no base', function(done) { + var val = new Buffer('test'); + var item = new Item({ + cwd: '/', + path: '/test/test.coffee', + contents: val + }); + delete item.base; + item.inspect().should.equal('>'); + done(); + }); + + it('should return correct format when Stream and relative path', function(done) { + var item = new Item({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: new Stream.PassThrough() + }); + item.inspect().should.equal('>'); + done(); + }); + + it('should return correct format when null and relative path', function(done) { + var item = new Item({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: null + }); + item.inspect().should.equal(''); + done(); + }); + }); + + describe('contents get/set', function() { + it('should work with Buffer', function(done) { + var val = new Buffer('test'); + var item = new Item(); + item.contents = val; + item.contents.should.equal(val); + done(); + }); + + it('should work with Stream', function(done) { + var val = new Stream.PassThrough(); + var item = new Item(); + item.contents = val; + item.contents.should.equal(val); + done(); + }); + + it('should work with null', function(done) { + var val = null; + var item = new Item(); + item.contents = val; + (item.contents === null).should.equal(true); + done(); + }); + + it('should work with string', function(done) { + var val = 'test'; + var item = new Item(); + item.contents = val; + item.contents.should.deepEqual(new Buffer(val)); + done(); + }); + }); + + describe('relative get/set', function() { + it('should error on set', function(done) { + var item = new Item(); + try { + item.relative = 'test'; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should error on get when no base', function(done) { + var item = new Item(); + delete item.base; + try { + item.relative; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should error on get when no path', function(done) { + var item = new Item(); + try { + item.relative; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should return a relative path from base', function(done) { + var item = new Item({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee' + }); + item.relative.should.equal('test.coffee'); + done(); + }); + + it('should return a relative path from cwd', function(done) { + var item = new Item({ + cwd: '/', + path: '/test/test.coffee' + }); + item.relative.should.equal(path.join('test', 'test.coffee')); + done(); + }); + }); + + describe('dirname get/set', function() { + it('should error on get when no path', function(done) { + var item = new Item(); + try { + item.dirname; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should return the dirname of the path', function(done) { + var item = new Item({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee' + }); + item.dirname.should.equal('/test'); + done(); + }); + + it('should error on set when no path', function(done) { + var item = new Item(); + try { + item.dirname = '/test'; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should set the dirname of the path', function(done) { + var item = new Item({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee' + }); + item.dirname = '/test/foo'; + item.path.should.equal('/test/foo/test.coffee'); + done(); + }); + }); + + describe('basename get/set', function() { + it('should error on get when no path', function(done) { + item = new Item(); + try { + item.basename; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should return the basename of the path', function(done) { + item = new Item({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee' + }); + item.basename.should.equal('test.coffee'); + done(); + }); + + it('should error on set when no path', function(done) { + item = new Item(); + try { + item.basename = 'test.coffee'; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should set the basename of the path', function(done) { + item = new Item({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee' + }); + item.basename = 'foo.png'; + item.path.should.equal('/test/foo.png'); + done(); + }); + }); + + describe('extname get/set', function() { + it('should error on get when no path', function(done) { + item = new Item(); + try { + item.extname; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should return the extname of the path', function(done) { + item = new Item({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee' + }); + item.extname.should.equal('.coffee'); + done(); + }); + + it('should error on set when no path', function(done) { + item = new Item(); + try { + item.extname = '.coffee'; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should set the extname of the path', function(done) { + item = new Item({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee' + }); + item.extname = '.png'; + item.path.should.equal('/test/test.png'); + done(); + }); + }); + + describe('path get/set', function() { + + it('should record history when instantiation', function() { + var item = new Item({ + cwd: '/', + path: '/test/test.coffee' + }); + + item.path.should.eql('/test/test.coffee'); + item.history.should.eql(['/test/test.coffee']); + }); + + it('should record history when path change', function() { + var item = new Item({ + cwd: '/', + path: '/test/test.coffee' + }); + + item.path = '/test/test.js'; + item.path.should.eql('/test/test.js'); + item.history.should.eql(['/test/test.coffee', '/test/test.js']); + + item.path = '/test/test.coffee'; + item.path.should.eql('/test/test.coffee'); + item.history.should.eql(['/test/test.coffee', '/test/test.js', '/test/test.coffee']); + }); + + it('should not record history when set the same path', function() { + var item = new Item({ + cwd: '/', + path: '/test/test.coffee' + }); + + item.path = '/test/test.coffee'; + item.path = '/test/test.coffee'; + item.path.should.eql('/test/test.coffee'); + item.history.should.eql(['/test/test.coffee']); + + // ignore when set empty string + item.path = ''; + item.path.should.eql('/test/test.coffee'); + item.history.should.eql(['/test/test.coffee']); + }); + + it('should throw when set path null in constructor', function() { + (function() { + Item({ + cwd: '/', + path: null + }); + }).should.throw('path should be string'); + }); + + it('should throw when set path null', function() { + item = new Item({ + cwd: '/', + path: 'foo' + }); + + (function() { + item.path = null; + }).should.throw('path should be string'); + }); + }); +}); diff --git a/test2/layouts.js b/test2/layouts.js new file mode 100644 index 0000000..76f65bc --- /dev/null +++ b/test2/layouts.js @@ -0,0 +1,127 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('layouts', function() { + beforeEach(function() { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.create('layout', { viewType: 'layout' }); + app.create('page'); + }); + + it('should apply a layout to a view:', function(done) { + app.layout('base', {path: 'base.tmpl', content: 'a {% body %} c'}); + app.pages('a.tmpl', {path: 'a.tmpl', content: 'b', layout: 'base'}); + var page = app.pages.getView('a.tmpl'); + + app.render(page, function(err, view) { + if (err) return done(err); + assert.equal(typeof view.content, 'string'); + assert.equal(view.content, 'a b c'); + done(); + }); + }); + + it('should not apply a layout when `layoutApplied` is set:', function(done) { + app.layout('base', {path: 'base.tmpl', content: 'a {% body %} c'}); + app.pages('a.tmpl', {path: 'a.tmpl', content: 'b', layout: 'base'}); + var page = app.pages.getView('a.tmpl'); + page.option('layoutApplied', true); + + app.render(page, function(err, view) { + if (err) return done(err); + assert.equal(typeof view.content, 'string'); + assert.equal(view.content, 'b'); + done(); + }); + }); + + it('should not apply a layout to itself:', function(done) { + app.layout('base', {path: 'base.tmpl', content: 'a {% body %} c', layout: 'base'}); + app.pages('a.tmpl', {path: 'a.tmpl', content: 'b', layout: 'base'}); + var page = app.pages.getView('a.tmpl'); + + app.render(page, function(err, view) { + if (err) return done(err); + assert.equal(typeof view.content, 'string'); + assert.equal(view.content, 'a b c'); + done(); + }); + }); + + it('should apply nested layouts to a view:', function(done) { + app.layout('a', {path: 'a.tmpl', content: 'a {% body %} a', layout: 'b'}); + app.layout('b', {path: 'b.tmpl', content: 'b {% body %} b', layout: 'c'}); + app.layout('c', {path: 'c.tmpl', content: 'c {% body %} c', layout: 'base'}); + app.layout('base', {path: 'base.tmpl', content: 'outter {% body %} outter'}); + + app.pages('z.tmpl', {path: 'a.tmpl', content: 'inner', layout: 'a'}); + var page = app.pages.getView('z.tmpl'); + + app.render(page, function(err, view) { + if (err) return done(err); + assert.equal(typeof view.content, 'string'); + assert.equal(view.content, 'outter c b a inner a b c outter'); + done(); + }); + }); + + it('should track layout stack history on `layoutStack`:', function(done) { + app.layout('a', {path: 'a.tmpl', content: 'a {% body %} a', layout: 'b'}); + app.layout('b', {path: 'b.tmpl', content: 'b {% body %} b', layout: 'c'}); + app.layout('c', {path: 'c.tmpl', content: 'c {% body %} c', layout: 'base'}); + app.layout('base', {path: 'base.tmpl', content: 'outter {% body %} outter'}); + + app.pages('z.tmpl', {path: 'a.tmpl', content: 'inner', layout: 'a'}); + var page = app.pages.getView('z.tmpl'); + + app.render(page, function(err, view) { + if (err) return done(err); + assert(view.layoutStack.length === 4); + assert(typeof view.layoutStack[0] === 'object'); + assert(typeof view.layoutStack[0].depth === 'number'); + done(); + }); + }); + + it('should track layout stack history on `layoutStack`:', function(done) { + app.layout('a', {path: 'a.tmpl', content: 'a {% body %} a', layout: 'b'}); + app.layout('b', {path: 'b.tmpl', content: 'b {% body %} b', layout: 'c'}); + app.layout('c', {path: 'c.tmpl', content: 'c {% body %} c', layout: 'base'}); + app.layout('base', {path: 'base.tmpl', content: 'outter {% body %} outter'}); + + app.pages('z.tmpl', {path: 'a.tmpl', content: 'inner', layout: 'a'}); + var page = app.pages.getView('z.tmpl'); + + app.render(page, function(err, view) { + if (err) return done(err); + assert.equal(typeof view.content, 'string'); + assert.equal(view.content, 'outter c b a inner a b c outter'); + done(); + }); + }); + + it('should get layouts from `layout` viewTypes:', function(done) { + app.create('section', { viewType: 'layout' }); + app.create('block', { viewType: 'layout' }); + + app.section('a', {path: 'a.tmpl', content: 'a {% body %} a', layout: 'b'}); + app.block('b', {path: 'b.tmpl', content: 'b {% body %} b', layout: 'c'}); + app.section('c', {path: 'c.tmpl', content: 'c {% body %} c', layout: 'base'}); + app.block('base', {path: 'base.tmpl', content: 'outter {% body %} outter'}); + + app.pages('z.tmpl', {path: 'a.tmpl', content: 'inner', layout: 'a'}); + var page = app.pages.getView('z.tmpl'); + + app.render(page, function(err, view) { + if (err) return done(err); + assert.equal(typeof view.content, 'string'); + assert.equal(view.content, 'outter c b a inner a b c outter'); + done(); + }); + }); +}); diff --git a/test2/list.js b/test2/list.js new file mode 100644 index 0000000..8f9cf5d --- /dev/null +++ b/test2/list.js @@ -0,0 +1,689 @@ +require('mocha'); +require('should'); +var path = require('path'); +var get = require('get-value'); +var assert = require('assert'); +var typeOf = require('kind-of'); +var support = require('./support/'); +var isBuffer = require('is-buffer'); +assert.containEql = support.containEql; +var App = support.resolve(); +var List = App.List; +var Views = App.Views; +var list, views; + +describe('list', function() { + describe('constructor', function() { + it('should create an instance of List', function() { + var list = new List(); + assert(list instanceof List); + }); + + it('should instaniate without `new`', function() { + var list = List(); + assert(list instanceof List); + }); + }); + + describe('static methods', function() { + it('should expose `extend`', function() { + assert(typeof List.extend === 'function'); + }); + }); + + describe('prototype methods', function() { + beforeEach(function() { + list = new List(); + }); + + var methods = [ + 'use', + 'setItem', + 'addItem', + 'addItems', + 'addList', + 'getItem', + 'constructor', + 'set', + 'get', + 'del', + 'define', + 'visit', + 'on', + 'once', + 'off', + 'emit', + 'listeners', + 'hasListeners' + ]; + + methods.forEach(function(method) { + it('should expose the ' + method + ' method', function() { + assert(typeof list[method] === 'function'); + }); + }); + + it('should expose the isList property', function() { + assert(typeof list.isList === 'boolean'); + }); + + it('should expose the keys property', function() { + assert(Array.isArray(list.keys)); + }); + + it('should expose the queue property', function() { + assert(Array.isArray(list.queue)); + }); + + it('should expose the items property', function() { + assert(Array.isArray(list.items)); + }); + + it('should expose the options property', function() { + assert(typeOf(list.options) === 'object'); + }); + }); + + describe('instance', function() { + beforeEach(function() { + list = new List(); + }); + + it('should set a value on the instance', function() { + list.set('a', 'b'); + assert(list.a === 'b'); + }); + + it('should get a value from the instance', function() { + list.set('a', 'b'); + assert(list.get('a') === 'b'); + }); + }); + + describe('use', function() { + beforeEach(function() { + list = new List(); + }); + + it('should expose the instance to plugins', function() { + list + .use(function(inst) { + inst.foo = 'bar'; + }); + + assert(list.foo === 'bar'); + }); + + it('should expose `item` when the plugin returns a function', function() { + list + .use(function() { + return function(item) { + item.foo = 'bar'; + }; + }); + + list.addItem('aaa'); + list.addItem('bbb'); + list.addItem('ccc'); + + assert(list.items[0].foo === 'bar'); + assert(list.items[1].foo === 'bar'); + assert(list.items[2].foo === 'bar'); + }); + }); + + describe('addItem', function() { + beforeEach(function() { + list = new List(); + }); + + it('should add items to a list', function() { + list.addItem('a', {content: '...'}); + list.addItem('b', {content: '...'}); + list.addItem('c', {content: '...'}); + assert(list.items.length === 3); + }); + }); + + describe('removeItem', function() { + beforeEach(function() { + list = new List(); + }); + + it('should remove an item from `items`', function() { + list.addItem('a', {content: '...'}); + list.addItem('b', {content: '...'}); + list.addItem('c', {content: '...'}); + assert(list.items.length === 3); + var a = list.getItem('a'); + list.removeItem(a); + assert(list.items.length === 2); + var c = list.getItem(c); + list.removeItem(c); + assert(list.items[0].key === 'b'); + }); + + it('should remove an item from `items` by key', function() { + list.addItem('a', {content: '...'}); + list.addItem('b', {content: '...'}); + list.addItem('c', {content: '...'}); + assert(list.items.length === 3); + list.removeItem('c'); + assert(list.items.length === 2); + list.removeItem('b'); + assert(list.items[0].key === 'a'); + }); + }); + + describe('addItems', function() { + beforeEach(function() { + list = new List(); + }); + + it('should add an object with multiple items', function() { + list.addItems({ + one: {content: 'foo'}, + two: {content: 'bar'} + }); + assert(isBuffer(list.items[0].contents)); + assert(isBuffer(list.items[1].contents)); + }); + + it('should signal `loaded` when finished (addItems)', function() { + list.on('addItems', function(items) { + for (var key in items) { + if (key === 'c') { + list.loaded = true; + break; + } + list.addItem('foo/' + key, items[key]); + } + }); + + list.addItems({ + a: {path: 'a.txt'}, + b: {path: 'b.txt'}, + c: {path: 'c.txt'} + }); + + assert.equal(list.items.length, 2); + assert.equal(list.items[0].key, 'foo/a'); + assert.equal(list.items[0].path, 'a.txt'); + }); + }); + + describe('addList', function() { + beforeEach(function() { + list = new List(); + }); + + it('should add an array with multiple items', function() { + list.addList([ + {path: 'one', content: 'foo'}, + {path: 'two', content: 'bar'} + ]); + assert(isBuffer(list.items[0].contents)); + assert(isBuffer(list.items[1].contents)); + }); + + it('should take a callback on `addList`', function() { + function addContents(item) { + item.contents = new Buffer(item.path.charAt(0)); + } + + list.addList([ + { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, + { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, + { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } } + ], addContents); + + assert(isBuffer(list.items[0].contents)); + assert(isBuffer(list.items[1].contents)); + assert(isBuffer(list.items[2].contents)); + }); + + it('should throw an error when the list is not an array', function() { + function addContents(item) { + item.contents = new Buffer(item.path.charAt(0)); + } + + (function() { + list.addList({ + 'a.md': {locals: { date: '2014-01-01', foo: 'zzz', bar: 1 }}, + 'f.md': {locals: { date: '2014-01-01', foo: 'mmm', bar: 2 }}, + 'd.md': {locals: { date: '2014-01-01', foo: 'xxx', bar: 3 }} + }, addContents); + + assert(isBuffer(list.items[0].contents)); + assert(isBuffer(list.items[1].contents)); + assert(isBuffer(list.items[2].contents)); + }).should.throw('expected list to be an array.'); + }); + + it('should signal `loaded` when finished (addList)', function() { + list.on('addList', function(items) { + var len = items.length; + var i = -1; + + while (++i < len) { + if (items[i].path === 'd.md') { + list.loaded = true; + break; + } + list.addItem('foo/' + items[i].path, items[i]); + } + }); + + list.addList([ + { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, + { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, + { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } } + ]); + + assert.equal(list.items.length, 2); + assert.equal(list.keys.indexOf('d.md'), -1); + }); + }); + + describe('queue', function() { + beforeEach(function() { + list = new List(); + }); + + it('should emit arguments on addItem', function(done) { + list.on('addItem', function(args) { + assert(args[0] === 'a'); + assert(args[1] === 'b'); + assert(args[2] === 'c'); + assert(args[3] === 'd'); + assert(args[4] === 'e'); + done(); + }); + + list.addItem('a', 'b', 'c', 'd', 'e'); + }); + + it('should expose the `queue` property for loading items', function() { + list.queue.push(list.item('b', {path: 'b'})); + + list.addItem('a', {path: 'a'}); + assert(list.items[0].key === 'a'); + assert(list.items[1].key === 'b'); + }); + + it('should load all items on the queue when addItem is called', function() { + list.on('addItem', function(args) { + var len = args.length; + var last = args[len - 1]; + if (typeof last === 'string') { + args[len - 1] = { content: last }; + } + }); + + list.addItem('a.html', 'aaa'); + list.addItem('b.html', 'bbb'); + list.addItem('c.html', 'ccc'); + + assert(list.items[0].path === 'a.html'); + assert(list.getItem('a.html').content === 'aaa'); + assert(list.items[1].path === 'b.html'); + assert(list.getItem('b.html').content === 'bbb'); + assert(list.items[2].path === 'c.html'); + assert(list.getItem('c.html').content === 'ccc'); + }); + }); + + describe('sortBy', function() { + var items = [ + { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, + { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, + { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, + { path: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, + { path: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, + { path: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, + { path: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, + { path: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, + { path: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, + { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, + { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, + { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, + { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } } + ]; + + it('should sort a list', function() { + list = new List(); + list.addList(items); + + var compare = function(prop) { + return function(a, b, fn) { + var valA = get(a, prop); + var valB = get(b, prop); + return fn(valA, valB); + }; + }; + + var res = list.sortBy('locals.date', 'doesnt.exist', [ + compare('locals.foo'), + compare('locals.bar') + ]); + + assert.containEql(res.items, [ + { key: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, + { key: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, + { key: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, + { key: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, + { key: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, + { key: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, + { key: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, + { key: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, + { key: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, + { key: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, + { key: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, + { key: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, + { key: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } } + ]); + }); + + it('should not sort the (original) instance list `items`', function() { + list = new List(); + list.addList(items); + + var compare = function(prop) { + return function(a, b, fn) { + var valA = get(a, prop); + var valB = get(b, prop); + return fn(valA, valB); + }; + }; + + var res = list.sortBy('locals.date', 'doesnt.exist', [ + compare('locals.foo'), + compare('locals.bar') + ]); + + // should not be sorted + assert.containEql(list.items, items); + + // should be sorted + assert.containEql(res.items, [ + { key: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, + { key: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, + { key: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, + { key: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, + { key: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, + { key: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, + { key: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, + { key: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, + { key: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, + { key: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, + { key: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, + { key: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, + { key: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } } + ]); + }); + + it('should pass options to array-sort from the constructor', function() { + list = new List({sort: {reverse: true}}); + list.addList(items); + + var compare = function(prop) { + return function(a, b, fn) { + var valA = get(a, prop); + var valB = get(b, prop); + return fn(valA, valB); + }; + }; + + var res = list.sortBy('locals.date', 'doesnt.exist', [ + compare('locals.foo'), + compare('locals.bar') + ]); + + assert.containEql(res.items, [ + { key: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, + { key: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, + { key: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, + { key: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, + { key: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, + { key: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, + { key: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, + { key: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, + { key: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, + { key: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, + { key: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, + { key: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, + { key: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } } + ]); + }); + + it('should pass options to array-sort from the sortBy method', function() { + list = new List(); + list.addList(items); + + var compare = function(prop) { + return function(a, b, fn) { + var valA = get(a, prop); + var valB = get(b, prop); + return fn(valA, valB); + }; + }; + + var res = list.sortBy('locals.date', 'doesnt.exist', [ + compare('locals.foo'), + compare('locals.bar') + ], {reverse: true}); + + assert.containEql(res.items, [ + { key: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, + { key: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, + { key: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, + { key: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, + { key: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, + { key: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, + { key: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, + { key: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, + { key: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, + { key: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, + { key: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, + { key: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, + { key: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } } + ]); + }); + }); + + describe('groupBy', function() { + var items = [ + { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, + { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, + { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, + { path: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, + { path: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, + { path: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, + { path: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, + { path: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, + { path: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, + { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, + { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, + { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, + { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } } + ]; + + it('should group a list by a property', function() { + list = new List(); + list.addList(items); + + var res = list.groupBy('locals.foo'); + var keys = ['zzz', 'mmm', 'xxx', 'aaa', 'ccc', 'rrr', 'ttt', 'yyy']; + assert.deepEqual(Object.keys(res), keys); + }); + }); + + describe('sort and group', function() { + var items = [ + { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, + { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, + { path: 'd.md', locals: { date: '2013-01-01', foo: 'xxx', bar: 3 } }, + { path: 'i.md', locals: { date: '2013-02-01', foo: 'xxx', bar: 5 } }, + { path: 'i.md', locals: { date: '2013-02-01', foo: 'lll', bar: 5 } }, + { path: 'k.md', locals: { date: '2013-03-01', foo: 'xxx', bar: 1 } }, + { path: 'j.md', locals: { date: '2013-02-01', foo: 'xxx', bar: 4 } }, + { path: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, + { path: 'm.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, + { path: 'n.md', locals: { date: '2013-01-01', foo: 'xxx', bar: 7 } }, + { path: 'o.md', locals: { date: '2013-01-01', foo: 'xxx', bar: 7 } }, + { path: 'p.md', locals: { date: '2013-01-01', foo: 'xxx', bar: 7 } }, + { path: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, + { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, + { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, + { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, + { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } } + ]; + + it('should group a list by a property', function() { + list = new List(items); + + var context = list + .sortBy('locals.date') + .groupBy(function(view) { + var date = view.locals.date; + view.locals.year = date.slice(0, 4); + view.locals.month = date.slice(5, 7); + view.locals.day = date.slice(8, 10); + return view.locals.year; + }, 'locals.month'); + + var keys = Object.keys(context); + assert(keys[0] === '2012'); + assert(keys[1] === '2013'); + assert(keys[2] === '2014'); + assert(keys[3] === '2015'); + }); + }); + + describe('paginate', function() { + var items = [ + { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, + { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, + { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, + { path: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, + { path: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, + { path: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, + { path: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, + { path: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, + { path: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, + { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, + { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, + { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, + { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } } + ]; + + it('should paginate a list', function() { + list = new List(items); + + var res = list.paginate(); + assert.equal(res.length, 2); + assert.containEql(res[0].items, items.slice(0, 10)); + assert.containEql(res[1].items, items.slice(10)); + }); + + it('should add pager properties', function() { + list = new List({pager: true}); + list.addList(items); + list.items.forEach(function(item, i) { + assert.equal(item.data.pager.index, i); + }); + }); + + it('should paginate a list with given options', function() { + list = new List(items); + var res = list.paginate({limit: 5}); + + assert.equal(res.length, 3); + assert.containEql(res[0].items, items.slice(0, 5)); + assert.containEql(res[1].items, items.slice(5, 10)); + assert.containEql(res[2].items, items.slice(10)); + }); + }); + + describe('Views instance', function() { + beforeEach(function() { + views = new Views(); + }); + + it('should add views from an instance of Views', function() { + views.addViews({ + one: {content: 'foo'}, + two: {content: 'bar'} + }); + + list = new List(views); + assert(isBuffer(list.items[0].contents)); + assert(isBuffer(list.items[1].contents)); + }); + }); + + describe('getIndex', function() { + beforeEach(function() { + list = new List(); + }); + it('should get the index of a key when key is not renamed', function() { + list.addItem('a/b/c/ddd.hbs', {content: 'ddd'}); + list.addItem('a/b/c/eee.hbs', {content: 'eee'}); + assert(list.getIndex('a/b/c/ddd.hbs') === 0); + assert(list.getIndex('a/b/c/eee.hbs') === 1); + }); + + it('should get the index of a key when key is renamed', function() { + list = new List({ + renameKey: function(key) { + return path.basename(key); + } + }); + list.addItem('a/b/c/ddd.hbs', {content: 'ddd'}); + list.addItem('a/b/c/eee.hbs', {content: 'eee'}); + assert(list.getIndex('a/b/c/ddd.hbs') === 0); + assert(list.getIndex('ddd.hbs') === 0); + assert(list.getIndex('a/b/c/eee.hbs') === 1); + assert(list.getIndex('eee.hbs') === 1); + }); + }); + + describe('getItem', function() { + beforeEach(function() { + list = new List(); + }); + + it('should get an view from `views`', function() { + list.addItem('one', {content: 'aaa'}); + list.addItem('two', {content: 'zzz'}); + assert(list.items.length === 2); + assert(isBuffer(list.items[0].contents)); + assert(isBuffer(list.getItem('one').contents)); + assert(list.getItem('one').contents.toString() === 'aaa'); + assert(list.getItem('two').contents.toString() === 'zzz'); + }); + }); + + describe('use', function() { + beforeEach(function() { + list = new List(); + }); + + it('should use middleware on a list', function() { + list.addItem('one', {content: 'aaa'}); + list.addItem('two', {content: 'zzz'}); + + list + .use(function() { + this.set('foo', 'bar'); + }) + .use(function() { + this.set('one', 'two'); + }); + + assert(list.one === 'two'); + assert(list.foo === 'bar'); + }); + }); +}); + diff --git a/test2/list.render.js b/test2/list.render.js new file mode 100644 index 0000000..7b226a8 --- /dev/null +++ b/test2/list.render.js @@ -0,0 +1,137 @@ +require('mocha'); +require('should'); +var async = require('async'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var List = App.List; +var pages; + +describe('render', function() { + describe('rendering', function() { + beforeEach(function() { + pages = new List(); + pages.engine('tmpl', require('engine-base')); + }); + + it('should throw an error when no callback is given:', function() { + (function() { + pages.render({}); + }).should.throw('List#render is async and expects a callback function'); + }); + + it('should throw an error when an engine is not defined:', function(done) { + pages.addItem('foo.bar', {content: '<%= name %>'}); + var page = pages.getItem('foo.bar'); + + pages.render(page, function(err) { + assert(err.message === 'List#render cannot find an engine for: .bar'); + done(); + }); + }); + + it('should use helpers to render a item:', function(done) { + var locals = {name: 'Halle'}; + + pages.helper('upper', function(str) { + return str.toUpperCase(str); + }); + + pages.addItem('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); + var page = pages.getItem('a.tmpl'); + + pages.render(page, function(err, res) { + if (err) return done(err); + + assert(res.content === 'a HALLE b'); + done(); + }); + }); + + it('should use helpers when rendering a item:', function(done) { + var locals = {name: 'Halle'}; + pages.helper('upper', function(str) { + return str.toUpperCase(str); + }); + + pages.addItem('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); + var page = pages.getItem('a.tmpl'); + + pages.render(page, function(err, res) { + if (err) return done(err); + assert(res.content === 'a HALLE b'); + done(); + }); + }); + + it('should render a template when contents is a buffer:', function(done) { + pages.addItem('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); + var item = pages.getItem('a.tmpl'); + + pages.render(item, function(err, item) { + if (err) return done(err); + assert(item.contents.toString() === 'b'); + done(); + }); + }); + + it('should render a template when content is a string:', function(done) { + pages.addItem('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); + var item = pages.getItem('a.tmpl'); + + pages.render(item, function(err, item) { + if (err) return done(err); + assert(item.contents.toString() === 'b'); + done(); + }); + }); + + it('should render a item from its path:', function(done) { + pages.addItem('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); + + pages.render('a.tmpl', function(err, item) { + if (err) return done(err); + assert(item.content === 'b'); + done(); + }); + }); + + it('should use a plugin for rendering:', function(done) { + pages.engine('tmpl', require('engine-base')); + pages.option('engine', 'tmpl'); + + pages.addItems({ + 'a': {content: '<%= title %>', locals: {title: 'aaa'}}, + 'b': {content: '<%= title %>', locals: {title: 'bbb'}}, + 'c': {content: '<%= title %>', locals: {title: 'ccc'}}, + 'd': {content: '<%= title %>', locals: {title: 'ddd'}}, + 'e': {content: '<%= title %>', locals: {title: 'eee'}}, + 'f': {content: '<%= title %>', locals: {title: 'fff'}}, + 'g': {content: '<%= title %>', locals: {title: 'ggg'}}, + 'h': {content: '<%= title %>', locals: {title: 'hhh'}}, + 'i': {content: '<%= title %>', locals: {title: 'iii'}}, + 'j': {content: '<%= title %>', locals: {title: 'jjj'}} + }); + + pages.use(function(collection) { + collection.option('pager', false); + + collection.renderEach = function(cb) { + var list = new List(collection); + + async.map(list.items, function(item, next) { + collection.render(item, next); + }, cb); + }; + }); + + pages.renderEach(function(err, items) { + if (err) return done(err); + assert(items[0].content === 'aaa'); + assert(items[9].content === 'jjj'); + assert(items.length === 10); + done(); + }); + }); + }); +}); diff --git a/test2/list.use.js b/test2/list.use.js new file mode 100644 index 0000000..5ac2c1b --- /dev/null +++ b/test2/list.use.js @@ -0,0 +1,156 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var List = App.List; +var Item = App.Item; +var list; + +describe('list.use', function() { + beforeEach(function() { + list = new List(); + }); + + it('should expose the instance to `use`:', function(done) { + list.use(function(inst) { + assert(inst instanceof List); + done(); + }); + }); + + it('should be chainable:', function(done) { + list.use(function(inst) { + assert(inst instanceof List); + }) + .use(function(inst) { + assert(inst instanceof List); + }) + .use(function(inst) { + assert(inst instanceof List); + done(); + }); + }); + + it('should expose the list to a plugin:', function() { + list.use(function(items) { + assert(items instanceof List); + items.foo = items.addItem.bind(items); + }); + + list.foo('a', {content: '...'}); + assert(list.hasItem('a')); + }); + + it('should expose list when chained:', function() { + list + .use(function(items) { + assert(items instanceof List); + items.foo = items.addItem.bind(items); + }) + .use(function(items) { + assert(items instanceof List); + items.bar = items.addItem.bind(items); + }) + .use(function(items) { + assert(items instanceof List); + items.baz = items.addItem.bind(items); + }); + + var pages = list; + + pages.foo({path: 'a', content: '...'}); + pages.bar({path: 'b', content: '...'}); + pages.baz({path: 'c', content: '...'}); + + assert(list.hasItem('a')); + assert(list.hasItem('b')); + assert(list.hasItem('c')); + }); + + it('should work when a custom `Item` constructor is passed:', function() { + list = new List({Item: require('vinyl')}); + list + .use(function(items) { + assert(items instanceof List); + items.foo = items.addItem.bind(items); + }) + .use(function(items) { + assert(items instanceof List); + items.bar = items.addItem.bind(items); + }) + .use(function(items) { + assert(items instanceof List); + items.baz = items.addItem.bind(items); + }); + + var pages = list; + + pages.foo({path: 'a', content: '...'}); + pages.bar({path: 'b', content: '...'}); + pages.baz({path: 'c', content: '...'}); + + assert(list.hasItem('a')); + assert(list.hasItem('b')); + assert(list.hasItem('c')); + }); + + it('should pass to item `use` if a function is returned:', function() { + list.use(function(items) { + assert(items instanceof List); + + return function(item) { + item.foo = items.addItem.bind(items); + assert(item.isItem || item.isView); + }; + }); + + list.addItem('a', {content: '...'}) + .foo({path: 'b', content: '...'}) + .foo({path: 'c', content: '...'}) + .foo({path: 'd', content: '...'}); + + assert(list.hasItem('a')); + assert(list.hasItem('b')); + assert(list.hasItem('c')); + assert(list.hasItem('d')); + }); + + it('should be chainable when a item function is returned:', function() { + list + .use(function(items) { + assert(items instanceof List); + + return function(item) { + item.foo = items.addItem.bind(items); + assert(item instanceof Item); + }; + }) + .use(function(items) { + assert(items instanceof List); + + return function(item) { + item.bar = items.addItem.bind(items); + assert(item instanceof Item); + }; + }) + .use(function(items) { + assert(items instanceof List); + + return function(item) { + item.baz = items.addItem.bind(items); + assert(item instanceof Item); + }; + }); + + list.addItem('a', {content: '...'}) + .foo({path: 'b', content: '...'}) + .bar({path: 'c', content: '...'}) + .baz({path: 'd', content: '...'}); + + assert(list.hasItem('a')); + assert(list.hasItem('b')); + assert(list.hasItem('c')); + assert(list.hasItem('d')); + }); +}); diff --git a/test2/mergePartials.js b/test2/mergePartials.js new file mode 100644 index 0000000..38f23be --- /dev/null +++ b/test2/mergePartials.js @@ -0,0 +1,104 @@ +require('should'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('mergePartials', function() { + beforeEach(function() { + app = new App(); + }); + + it('should merge multiple partials collections onto one collection:', function() { + var opts = { viewType: 'partial' }; + app.create('foo', opts); + app.create('bar', opts); + app.create('baz', opts); + + app.foo('a', {path: 'a', content: 'aaa'}); + app.bar('b', {path: 'b', content: 'bbb'}); + app.baz('c', {path: 'c', content: 'ccc'}); + + var actual = app.mergePartials(); + actual.should.have.property('partials'); + actual.partials.should.have.properties(['a', 'b', 'c']); + }); + + it('should keep partials collections on separaet collections:', function() { + var opts = { viewType: 'partial' }; + app.create('foo', opts); + app.create('bar', opts); + app.create('baz', opts); + + app.foo('a', {path: 'a', content: 'aaa'}); + app.bar('b', {path: 'b', content: 'bbb'}); + app.baz('c', {path: 'c', content: 'ccc'}); + + var actual = app.mergePartials({mergePartials: false}); + actual.should.not.have.property('partials'); + actual.should.eql({ foos: { a: 'aaa' }, bars: { b: 'bbb' }, bazs: { c: 'ccc' } }); + }); + + it('should emit `mergePartials`:', function() { + var opts = { viewType: 'partial' }; + app.create('foo', opts); + app.create('bar', opts); + app.create('baz', opts); + var arr = []; + + app.on('onMerge', function(view) { + arr.push(view.content); + }); + + app.foo('a', {path: 'a', content: 'aaa'}); + app.bar('b', {path: 'b', content: 'bbb'}); + app.baz('c', {path: 'c', content: 'ccc'}); + + var actual = app.mergePartials({mergePartials: false}); + actual.should.not.have.property('partials'); + actual.should.eql({ foos: { a: 'aaa' }, bars: { b: 'bbb' }, bazs: { c: 'ccc' } }); + arr.should.eql(['aaa', 'bbb', 'ccc']); + }); + + it('should handle `onMerge` middleware:', function() { + var opts = { viewType: 'partial' }; + app.create('foo', opts); + app.create('bar', opts); + app.create('baz', opts); + + app.onMerge(/./, function(view, next) { + view.content += ' onMerge'; + next(); + }); + + app.foo('a', {path: 'a', content: 'aaa'}); + app.bar('b', {path: 'b', content: 'bbb'}); + app.baz('c', {path: 'c', content: 'ccc'}); + + var actual = app.mergePartials({mergePartials: false}); + actual.should.eql({ + foos: {a: 'aaa onMerge'}, + bars: {b: 'bbb onMerge'}, + bazs: {c: 'ccc onMerge'} + }); + }); + + it('should skip views with `nomerge=true`:', function() { + var opts = { viewType: 'partial' }; + + app.create('foo', opts); + app.create('bar', opts); + app.create('baz', opts); + + app.onMerge(/[ab]/, function(view, next) { + view.options.nomerge = true; + next(); + }); + + app.foo('a', {path: 'a', content: 'aaa'}); + app.bar('b', {path: 'b', content: 'bbb'}); + app.baz('c', {path: 'c', content: 'ccc'}); + + var actual = app.mergePartials({mergePartials: false}); + actual.should.eql({ bazs: { c: 'ccc' } }); + }); +}); diff --git a/test2/partials.js b/test2/partials.js new file mode 100644 index 0000000..b5fbb94 --- /dev/null +++ b/test2/partials.js @@ -0,0 +1,202 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app, pages; + +describe('partials', function() { + beforeEach(function() { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.engine('hbs', require('engine-handlebars')); + + app.create('partials', { viewType: 'partial' }); + app.create('include', { viewType: 'partial' }); + app.create('layouts', { viewType: 'layout' }); + pages = app.create('page'); + }); + + it('should inject a partial with a helper:', function(done) { + app.include('base', {path: 'base.tmpl', content: 'xyz'}); + app.pages('a.tmpl', {path: 'a.tmpl', content: 'a <%= include("base") %> c'}); + var page = app.pages.getView('a.tmpl'); + + app.render(page, function(err, view) { + if (err) return done(err); + assert.equal(typeof view.content, 'string'); + assert.equal(view.content, 'a xyz c'); + done(); + }); + }); + + it('should inject a partial with a helper on a collection:', function(done) { + app.include('base', {path: 'base.tmpl', content: 'xyz'}); + pages.engine('.tmpl', require('engine-handlebars')); + pages.helpers(app._.helpers.sync); + pages.asyncHelpers(app._.helpers.async); + pages.addView('a.tmpl', {path: 'a.tmpl', content: 'a {{include "base" }} c'}); + var page = pages.getView('a.tmpl'); + + pages.render(page, function(err, view) { + if (err) return done(err); + assert.equal(typeof view.content, 'string'); + assert.equal(view.content, 'a xyz c'); + done(); + }); + }); + + it('should use handlebars partial with a helper on a collection:', function(done) { + app.include('base', {path: 'base.tmpl', content: 'xyz'}); + pages.engine('.tmpl', require('engine-handlebars')); + pages.helpers(app._.helpers.sync); + pages.asyncHelpers(app._.helpers.async); + pages.addView('a.tmpl', {path: 'a.tmpl', content: 'a {{> base }} c'}); + + var page = pages.getView('a.tmpl'); + var locals = app.mergePartials(this.options); + + pages.render(page, locals, function(err, view) { + if (err) return done(err); + assert.equal(typeof view.content, 'string'); + assert.equal(view.content, 'a xyz c'); + done(); + }); + }); + + it('should use layouts with partials:', function(done) { + app.layout('default', {path: 'a.tmpl', content: 'a {% body %} c'}); + app.include('b', {path: 'b.tmpl', content: 'b', layout: 'default'}); + app.pages('a.tmpl', {path: 'c.tmpl', content: '<%= include("b") %>'}); + var page = app.pages.getView('a.tmpl'); + + app.render(page, function(err, view) { + if (err) return done(err); + assert.equal(typeof view.content, 'string'); + assert.equal(view.content, 'a b c'); + done(); + }); + }); + + it('should add `layoutApplied` after layout is applied:', function(done) { + app.layout('default', {path: 'a.tmpl', content: 'a {% body %} c'}); + app.include('b', {path: 'b.tmpl', content: 'b', layout: 'default'}); + app.pages('a.tmpl', {path: 'c.tmpl', content: '<%= include("b") %>'}); + var page = app.pages.getView('a.tmpl'); + + app.render(page, function(err, view) { + if (err) return done(err); + assert.equal(typeof view.content, 'string'); + assert.equal(app.layouts.getView('default').options.layoutApplied); + assert.equal(view.content, 'a b c'); + done(); + }); + }); + + it('should pass partials to handlebars:', function(done) { + app.onMerge(/\.hbs$/, function(view, next) { + app.applyLayout(view); + next(); + }); + + app.layout('default', {path: 'a.hbs', content: 'a {% body %} c'}); + app.include('foo', {path: 'foo.hbs', content: 'foo', layout: 'default'}); + app.pages('a.hbs', {path: 'c.hbs', content: '{{> foo }}'}); + var page = app.pages.getView('a.hbs'); + + app.render(page, function(err, view) { + if (err) return done(err); + assert.equal(typeof view.content, 'string'); + assert.equal(view.content, 'a foo c'); + done(); + }); + }); + + it('should only merge in the specified viewTypes:', function(done) { + app.onMerge(/\.hbs$/, function(view, next) { + app.applyLayout(view); + next(); + }); + + app.layout('default', {path: 'a.hbs', content: 'a {% body %} c'}); + app.option('mergeTypes', ['includes']); + + app.partial('foo', {path: 'bar.hbs', content: 'bar', layout: 'default'}); + app.include('foo', {path: 'foo.hbs', content: 'foo', layout: 'default'}); + + app.pages('a.hbs', {path: 'c.hbs', content: '{{> foo }}'}); + app.pages.getView('a.hbs') + .render(function(err, view) { + if (err) return done(err); + assert.equal(typeof view.content, 'string'); + assert.equal(view.content, 'a foo c'); + done(); + }); + + }); + + it('should merge the specified viewTypes in the order defined:', function(done) { + app.onMerge(/\.hbs$/, function(view, next) { + app.applyLayout(view); + next(); + }); + + app.layout('default', {path: 'a.hbs', content: 'a {% body %} c'}); + app.option('mergeTypes', ['includes', 'partials']); + + app.partial('foo', {path: 'bar.hbs', content: 'bar', layout: 'default'}); + app.include('foo', {path: 'foo.hbs', content: 'foo', layout: 'default'}); + + app.pages('a.hbs', {path: 'c.hbs', content: '{{> foo }}'}); + app.pages.getView('a.hbs') + .render(function(err, view) { + if (err) return done(err); + assert.equal(typeof view.content, 'string'); + assert.equal(view.content, 'a bar c'); + done(); + }); + }); + + it('should not merge in partials with `options.nomerge` defined:', function(done) { + app.onMerge(/\.hbs$/, function(view, next) { + app.applyLayout(view); + next(); + }); + + app.layout('default', {path: 'a.hbs', content: 'a {% body %} c'}); + app.option('mergeTypes', ['includes', 'partials']); + + app.partial('foo', {path: 'bar.hbs', content: 'bar', layout: 'default', options: {nomerge: true}}); + app.include('foo', {path: 'foo.hbs', content: 'foo', layout: 'default'}); + + app.pages('a.hbs', {path: 'c.hbs', content: '{{> foo }}'}); + app.pages.getView('a.hbs') + .render(function(err, view) { + if (err) return done(err); + assert.equal(typeof view.content, 'string'); + assert.equal(view.content, 'a foo c'); + done(); + }); + }); + + it('should emit an `onMerge` event:', function(done) { + app.on('onMerge', function(view) { + app.applyLayout(view); + }); + + app.layout('default', {path: 'a.hbs', content: 'a {% body %} c'}); + app.option('mergeTypes', ['includes', 'partials']); + + app.partial('foo', {path: 'bar.hbs', content: 'bar', layout: 'default'}); + app.include('foo', {path: 'foo.hbs', content: 'foo', layout: 'default'}); + + app.pages('a.hbs', {path: 'c.hbs', content: '{{> foo }}'}); + app.pages.getView('a.hbs') + .render(function(err, view) { + if (err) return done(err); + assert.equal(typeof view.content, 'string'); + assert.equal(view.content, 'a bar c'); + done(); + }); + }); +}); diff --git a/test2/questions.js b/test2/questions.js new file mode 100644 index 0000000..4318e64 --- /dev/null +++ b/test2/questions.js @@ -0,0 +1,58 @@ +require('mocha'); +require('should'); +var fs = require('fs'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe.skip('content', function() { + beforeEach(function() { + app = new App(); + }); + + it('should store a question:', function() { + app.question('a', 'b'); + assert(app.questions); + assert(app.questions.cache); + assert(app.questions.cache.a); + assert(app.questions.cache.a.name === 'a'); + assert(app.questions.cache.a.message === 'b'); + }); + + it('should ask a question and use data value to answer:', function(done) { + app.question('a', 'b'); + app.data('a', 'b'); + + app.ask('a', function(err, answer) { + assert(!err); + assert(answer); + assert(answer === 'b'); + done(); + }) + }); + + it('should ask a question and use store value to answer:', function(done) { + app.question('a', 'b'); + app.store.set('a', 'c'); + + app.ask('a', function(err, answer) { + assert(!err); + assert(answer); + assert(answer === 'c'); + done(); + }) + }); + + it('should ask a question and use config value to answer:', function(done) { + app.question('a', 'b'); + app.store.set('a', 'c'); + + app.ask('a', function(err, answer) { + assert(!err); + assert(answer); + assert(answer === 'c'); + done(); + }) + }); +}); diff --git a/test2/renameKey.js b/test2/renameKey.js new file mode 100644 index 0000000..94b3ab2 --- /dev/null +++ b/test2/renameKey.js @@ -0,0 +1,350 @@ +var path = require('path'); +var support = require('./support'); +var App = support.resolve(); +var app; + +function renameKey(key) { + return path.basename(key, path.extname(key)); +} + +describe('renameKey', function() { + beforeEach(function() { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.create('pages'); + app.create('posts'); + }); + + describe('global options:', function() { + it('should use `renameKey` function defined on global opts:', function() { + app.option('renameKey', renameKey); + + app.posts('a/b/c/a.txt', {content: '...'}); + app.posts('a/b/c/b.txt', {content: '...'}); + app.posts('a/b/c/c.txt', {content: '...'}); + app.post('a/b/c/d.txt', {content: '...'}); + app.post('a/b/c/e.txt', {content: '...'}); + + app.views.posts.should.have.property('a'); + app.views.posts.should.have.property('b'); + app.views.posts.should.have.property('c'); + app.views.posts.should.have.property('d'); + app.views.posts.should.have.property('e'); + }); + + it('should not have conflicts when view name is the collection name:', function() { + app.option('renameKey', renameKey); + + app.post('a/b/c/post.txt', {content: 'this is contents'}); + app.page('a/b/c/page.txt', {content: 'this is contents'}); + + app.views.posts.should.have.property('post'); + app.views.pages.should.have.property('page'); + }); + }); + + describe('create method:', function() { + it('should use `renameKey` option chained from the `create` method:', function() { + app.create('post') + .option('renameKey', function(key) { + return 'posts/' + path.basename(key); + }); + + app.posts('a/b/c/a.txt', {content: '...'}); + app.posts('a/b/c/b.txt', {content: '...'}); + app.posts('a/b/c/c.txt', {content: '...'}); + app.post('a/b/c/d.txt', {content: '...'}); + app.post('a/b/c/e.txt', {content: '...'}); + + app.views.posts.should.have.property('posts/a.txt'); + app.views.posts.should.have.property('posts/b.txt'); + app.views.posts.should.have.property('posts/c.txt'); + app.views.posts.should.have.property('posts/d.txt'); + app.views.posts.should.have.property('posts/e.txt'); + }); + }); + + describe('create method:', function() { + it('should use `renameKey` defined on the `create` method:', function() { + app.create('post', { + renameKey: function(key) { + return 'posts/' + path.basename(key); + } + }); + + app.posts('a/b/c/a.txt', {content: '...'}); + app.posts('a/b/c/b.txt', {content: '...'}); + app.posts('a/b/c/c.txt', {content: '...'}); + app.post('a/b/c/d.txt', {content: '...'}); + app.post('a/b/c/e.txt', {content: '...'}); + + app.views.posts.should.have.property('posts/a.txt'); + app.views.posts.should.have.property('posts/b.txt'); + app.views.posts.should.have.property('posts/c.txt'); + app.views.posts.should.have.property('posts/d.txt'); + app.views.posts.should.have.property('posts/e.txt'); + }); + }); + + describe('collections:', function() { + describe('setting:', function() { + it('should get a view with the `renameKey` defined on app.options:', function() { + app.option('renameKey', function(key) { + return 'foo/' + path.basename(key); + }); + + app.posts('a/b/c/a.txt', {content: '...'}); + app.posts('a/b/c/b.txt', {content: '...'}); + app.post('a/b/c/c.txt', {content: '...'}); + + app.views.posts.should.have.property('foo/a.txt'); + app.views.posts.should.have.property('foo/b.txt'); + app.views.posts.should.have.property('foo/c.txt'); + }); + + it('should use `renameKey` defined on collection.options:', function() { + app.pages.option('renameKey', function(key) { + return 'page/' + path.basename(key); + }); + + app.posts.option('renameKey', function(key) { + return 'post/' + path.basename(key); + }); + + app.pages('a/b/c/a.txt', {content: '...'}); + app.pages('a/b/c/b.txt', {content: '...'}); + app.pages('a/b/c/c.txt', {content: '...'}); + app.page('a/b/c/d.txt', {content: '...'}); + app.page('a/b/c/e.txt', {content: '...'}); + + app.posts('a/b/c/a.txt', {content: '...'}); + app.posts('a/b/c/b.txt', {content: '...'}); + app.posts('a/b/c/c.txt', {content: '...'}); + app.post('a/b/c/d.txt', {content: '...'}); + app.post('a/b/c/e.txt', {content: '...'}); + + app.views.pages.should.have.property('page/a.txt'); + app.views.pages.should.have.property('page/b.txt'); + app.views.pages.should.have.property('page/c.txt'); + app.views.pages.should.have.property('page/d.txt'); + app.views.pages.should.have.property('page/e.txt'); + + app.views.posts.should.have.property('post/a.txt'); + app.views.posts.should.have.property('post/b.txt'); + app.views.posts.should.have.property('post/c.txt'); + app.views.posts.should.have.property('post/d.txt'); + app.views.posts.should.have.property('post/e.txt'); + }); + + it('should use the `collection.renameKey()` method:', function() { + app.pages.renameKey(function(key) { + return 'baz/' + path.basename(key); + }); + + app.pages('a/b/c/a.txt', {content: '...'}); + app.pages('a/b/c/b.txt', {content: '...'}); + app.pages('a/b/c/c.txt', {content: '...'}); + app.page('a/b/c/d.txt', {content: '...'}); + app.page('a/b/c/e.txt', {content: '...'}); + + app.views.pages.should.have.property('baz/a.txt'); + app.views.pages.should.have.property('baz/b.txt'); + app.views.pages.should.have.property('baz/c.txt'); + app.views.pages.should.have.property('baz/d.txt'); + app.views.pages.should.have.property('baz/e.txt'); + }); + + it('should use the `app.renameKey()` method:', function() { + app.renameKey(function(key) { + return 'app/' + path.basename(key); + }); + + app.pages('a/b/c/a.txt', {content: '...'}); + app.pages('a/b/c/b.txt', {content: '...'}); + app.pages('a/b/c/c.txt', {content: '...'}); + app.page('a/b/c/d.txt', {content: '...'}); + app.page('a/b/c/e.txt', {content: '...'}); + + app.views.pages.should.have.property('app/a.txt'); + app.views.pages.should.have.property('app/b.txt'); + app.views.pages.should.have.property('app/c.txt'); + app.views.pages.should.have.property('app/d.txt'); + app.views.pages.should.have.property('app/e.txt'); + }); + + it('should prefer collection method over app.options:', function() { + // this works when you switch the order around... + app.pages.renameKey(function pagesRenameKey(key) { + return 'aaa/' + path.basename(key); + }); + app.option('renameKey', function optsRenameKey(key) { + return 'foo/' + path.basename(key); + }); + + app.pages('a/b/c/a.txt', {content: '...'}); + app.pages('a/b/c/b.txt', {content: '...'}); + app.pages('a/b/c/c.txt', {content: '...'}); + app.page('a/b/c/d.txt', {content: '...'}); + app.page('a/b/c/e.txt', {content: '...'}); + + app.views.pages.should.have.property('aaa/a.txt'); + app.views.pages.should.have.property('aaa/b.txt'); + app.views.pages.should.have.property('aaa/c.txt'); + app.views.pages.should.have.property('aaa/d.txt'); + app.views.pages.should.have.property('aaa/e.txt'); + }); + + it('should prefer collection method over app method:', function() { + app.pages.renameKey(function(key) { + return 'aaa/' + path.basename(key); + }); + app.renameKey(function(key) { + return 'zzz/' + path.basename(key); + }); + + app.pages('a/b/c/a.txt', {content: '...'}); + app.pages('a/b/c/b.txt', {content: '...'}); + app.pages('a/b/c/c.txt', {content: '...'}); + app.page('a/b/c/d.txt', {content: '...'}); + app.page('a/b/c/e.txt', {content: '...'}); + + app.views.pages.should.have.property('aaa/a.txt'); + app.views.pages.should.have.property('aaa/b.txt'); + app.views.pages.should.have.property('aaa/c.txt'); + app.views.pages.should.have.property('aaa/d.txt'); + app.views.pages.should.have.property('aaa/e.txt'); + }); + + it('should prefer collection options over app.options:', function() { + app.pages.option('renameKey', function(key) { + return 'collection/' + path.basename(key); + }); + app.option('renameKey', function(key) { + return 'app/' + path.basename(key); + }); + + app.pages('a/b/c/a.txt', {content: '...'}); + app.pages('a/b/c/b.txt', {content: '...'}); + app.pages('a/b/c/c.txt', {content: '...'}); + app.page('a/b/c/d.txt', {content: '...'}); + app.page('a/b/c/e.txt', {content: '...'}); + + app.views.pages.should.have.property('collection/a.txt'); + app.views.pages.should.have.property('collection/b.txt'); + app.views.pages.should.have.property('collection/c.txt'); + app.views.pages.should.have.property('collection/d.txt'); + app.views.pages.should.have.property('collection/e.txt'); + }); + + it('should prefer collection options over app method:', function() { + app.pages.option('renameKey', function(key) { + return 'collection/' + path.basename(key); + }); + app.renameKey(function(key) { + return 'app/' + path.basename(key); + }); + + app.pages('a/b/c/a.txt', {content: '...'}); + app.pages('a/b/c/b.txt', {content: '...'}); + app.pages('a/b/c/c.txt', {content: '...'}); + app.page('a/b/c/d.txt', {content: '...'}); + app.page('a/b/c/e.txt', {content: '...'}); + + app.views.pages.should.have.property('collection/a.txt'); + app.views.pages.should.have.property('collection/b.txt'); + app.views.pages.should.have.property('collection/c.txt'); + app.views.pages.should.have.property('collection/d.txt'); + app.views.pages.should.have.property('collection/e.txt'); + }); + + it('should use renameKey on chained methods:', function() { + app.page('test/fixtures/pages/a.txt', { + options: { + renameKey: function foo(key) { + return 'foo/' + path.basename(key); + } + } + }); + + app.page('test/fixtures/pages/a.hbs', { + options: { + renameKey: function bar(key) { + return 'bar/' + path.basename(key); + } + } + }); + + app.views.pages.should.have.properties([ + 'foo/a.txt', + 'bar/a.hbs' + ]); + }); + }); + + describe('getting', function() { + beforeEach(function() { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.create('post'); + app.create('page'); + }); + + it('should get a view with the `renameKey` defined on the `create` method:', function() { + app.create('post', { + renameKey: function createRenameKey(key) { + return 'posts/' + path.basename(key); + } + }); + + app.posts('a/b/c/a.txt', {content: '...'}); + app.posts('a/b/c/b.txt', {content: '...'}); + app.post('a/b/c/c.txt', {content: '...'}); + + app.posts.getView('a.txt').should.have.property('path', 'a/b/c/a.txt'); + app.posts.getView('posts/a.txt').should.have.property('path', 'a/b/c/a.txt'); + }); + + it('should get a view with `renameKey` on collection.options:', function() { + app.pages.option('renameKey', function(key) { + return 'bar/' + path.basename(key); + }); + + app.pages('a/b/c/a.txt', {content: '...'}); + app.pages('a/b/c/b.txt', {content: '...'}); + app.page('a/b/c/c.txt', {content: '...'}); + + app.views.pages.should.have.property('bar/a.txt'); + app.views.pages.should.have.property('bar/b.txt'); + app.views.pages.should.have.property('bar/c.txt'); + }); + + it('should get a view with the the `app.renameKey()` method:', function() { + app.renameKey(function(key) { + return 'baz/' + path.basename(key); + }); + + app.pages('a/b/c/a.txt', {content: '...'}); + app.pages('a/b/c/b.txt', {content: '...'}); + app.page('a/b/c/c.txt', {content: '...'}); + + app.views.pages.should.have.property('baz/a.txt'); + app.views.pages.should.have.property('baz/b.txt'); + app.views.pages.should.have.property('baz/c.txt'); + }); + + it('should get a view with the the `collection.renameKey()` method:', function() { + app.pages.renameKey(function(key) { + return 'baz/' + path.basename(key); + }); + + app.pages('a/b/c/a.txt', {content: '...'}); + app.pages('a/b/c/b.txt', {content: '...'}); + app.page('a/b/c/c.txt', {content: '...'}); + + app.views.pages.should.have.property('baz/a.txt'); + app.views.pages.should.have.property('baz/b.txt'); + app.views.pages.should.have.property('baz/c.txt'); + }); + }); + }); +}); diff --git a/test2/render.js b/test2/render.js new file mode 100644 index 0000000..04540b6 --- /dev/null +++ b/test2/render.js @@ -0,0 +1,72 @@ +'use strict'; + +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe.skip('render', function() { + describe('engine', function() { + var view; + + beforeEach(function() { + app = new App({silent: true}); + app.engine('tmpl', require('engine-base')); + app.create('page'); + view = {contents: new Buffer('a <%= name %> b'), locals: {name: 'Halle'}}; + }); + + it('should render a view from an object:', function(done) { + app.page('a.tmpl', view) + .render(function(err, res) { + if (err) return done(err); + assert(res.contents.toString() === 'a Halle b'); + done(); + }); + }); + + it('should throw an error when a variable is undefined:', function(done) { + delete view.locals.name; + + app.page('a.tmpl', view) + .render(function(err) { + assert(err.message === 'name is not defined'); + done(); + }); + }); + + it('should re-throw an error when rethrow is true:', function(done) { + delete view.locals.name; + + app = new App({rethrow: true, silent: true}); + app.engine('tmpl', require('engine-base')); + app.create('page'); + + app.page('a.tmpl', view) + .render(function(err) { + assert(err.message === 'name is not defined'); + done(); + }); + }); + + it('should emit a re-thrown error when rethrow is true:', function(done) { + delete view.locals.name; + + app = new App({rethrow: true, silent: false}); + app.engine('tmpl', require('engine-base')); + app.create('page'); + + app.on('error', function(err) { + assert(err.message === 'name is not defined'); + done(); + }); + + app.page('a.tmpl', view) + .render(function(err) { + assert(err.message === 'name is not defined'); + }); + }); + }); +}); diff --git a/test2/routes.js b/test2/routes.js new file mode 100644 index 0000000..af66b93 --- /dev/null +++ b/test2/routes.js @@ -0,0 +1,98 @@ +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +function append(str) { + return function(view, next) { + var content = view.contents.toString(); + view.contents = new Buffer(content + ' ' + str); + next(); + }; +} +function prepend(str) { + return function(view, next) { + var content = view.contents.toString(); + view.contents = new Buffer(str + ' ' + content); + next(); + }; +} + +describe('routes', function() { + beforeEach(function() { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.create('page'); + }); + + describe('params', function() { + it('should call param function when routing', function(done) { + app.param('id', function(view, next, id) { + assert.equal(id, '123'); + next(); + }); + + app.all('/foo/:id/bar', function(view, next) { + assert.equal(view.options.params.id, '123'); + next(); + }); + + app.router.handle({ path: '/foo/123/bar' }, done); + }); + }); + + describe('onLoad middleware', function() { + it('should run when templates are loaded:', function() { + app.onLoad(/\.tmpl/, prepend('onLoad')); + app.pages('a.tmpl', { path: 'a.tmpl', content: '<%= name %>'}); + + var page = app.pages.getView('a.tmpl'); + page.contents.toString().should.equal('onLoad <%= name %>'); + }); + }); + + describe('preCompile middleware', function() { + it('should run before templates are compiled:', function() { + + }); + }); + + describe('postCompile middleware', function() { + it('should run after templates are compiled:', function() { + + }); + }); + + describe('preRender middleware', function() { + it('should run before templates are rendered:', function(done) { + app.preRender(/\.tmpl/, prepend('preRender')); + app.pages('a.tmpl', { path: 'a.tmpl', content: '<%= name %>', locals: {name: 'aaa'} }); + + var page = app.pages.getView('a.tmpl'); + page.contents.toString().should.equal('<%= name %>'); + + page.render({}, function(err, res) { + if (err) return done(err); + res.contents.toString().should.equal('preRender aaa'); + done(); + }); + }); + }); + + describe('postRender middleware', function() { + it('should run after templates are rendered:', function(done) { + app.postRender(/\.tmpl/, append('postRender')); + app.pages('a.tmpl', { path: 'a.tmpl', content: '<%= name %>', locals: {name: 'aaa' }}); + + var page = app.pages.getView('a.tmpl'); + page.contents.toString().should.equal('<%= name %>'); + + page.render({}, function(err, res) { + if (err) return done(err); + res.contents.toString().should.equal('aaa postRender'); + done(); + }); + }); + }); +}); diff --git a/test2/store.js b/test2/store.js new file mode 100644 index 0000000..e75ec6e --- /dev/null +++ b/test2/store.js @@ -0,0 +1,243 @@ +'use strict'; + +require('mocha'); +require('should'); +var fs = require('fs'); +var path = require('path'); +var Store = require('data-store'); +var assert = require('assert'); +var App = require('../'); +var app; + +describe('store', function() { + beforeEach(function() { + app = new App(); + }); + + afterEach(function(cb) { + app.store.del({force: true}); + app.store.data = {}; + cb(); + }); + + it('should create a store at the given `cwd`', function() { + app = new App({store: {cwd: __dirname + '/actual'}}); + app.store.set('foo', 'bar'); + assert(path.basename(app.store.path) === 'update.json'); + assert(app.store.data.hasOwnProperty('foo')); + assert(app.store.data.foo === 'bar'); + assert(fs.existsSync(path.join(__dirname, 'actual', 'update.json'))); + }); + + it('should create a store using the given `indent` value', function() { + app = new App({store: {cwd: __dirname + '/actual', indent: 0}}); + app.store.set('foo', 'bar'); + var contents = fs.readFileSync(path.join(__dirname, 'actual', 'update.json'), 'utf8'); + assert(contents === '{"foo":"bar"}'); + }); + + it('should set a value on the store', function() { + app.store.set('one', 'two'); + app.store.data.one.should.equal('two'); + }); + + it('should set an object', function() { + app.store.set({four: 'five', six: 'seven'}); + app.store.data.four.should.equal('five'); + app.store.data.six.should.equal('seven'); + }); + + it('should set a nested value', function() { + app.store.set('a.b.c.d', {e: 'f'}); + app.store.data.a.b.c.d.e.should.equal('f'); + }); + + it('should union a value onto an array on the store', function() { + app.store.union('one', 'two'); + app.store.data.one.should.eql(['two']); + }); + + it('should not union duplicate values', function() { + app.store.union('one', 'two'); + app.store.data.one.should.eql(['two']); + + app.store.union('one', ['two']); + app.store.data.one.should.eql(['two']); + }); + + it('should concat an existing array:', function() { + app.store.union('one', 'a'); + app.store.data.one.should.eql(['a']); + + app.store.union('one', ['b']); + app.store.data.one.should.eql(['a', 'b']); + + app.store.union('one', ['c', 'd']); + app.store.data.one.should.eql(['a', 'b', 'c', 'd']); + }); + + it('should return true if a key exists on the store', function() { + app.store.set('foo', 'bar'); + assert(app.store.has('foo')); + }); + + it('should return true when the value is null', function() { + app.store.set('baz', null); + assert(app.store.has('baz')); + }); + + it('should return false when the value is undefined', function() { + app.store.set('qux', undefined); + assert(!app.store.has('qux')); + }); + + it('should return true if a nested key exists on the store', function() { + app.store.set('a.b.c.d', {x: 'zzz'}); + app.store.set('a.b.c.e', {f: null}); + app.store.set('a.b.g.j', {k: undefined}); + + assert(!app.store.has('a.b.bar')); + assert(app.store.has('a.b.c.d')); + assert(app.store.has('a.b.c.d.x')); + assert(!app.store.has('a.b.c.d.z')); + assert(app.store.has('a.b.c.e')); + assert(app.store.has('a.b.c.e.f')); + assert(!app.store.has('a.b.c.e.z')); + assert(app.store.has('a.b.g.j')); + assert(!app.store.has('a.b.g.j.k')); + assert(!app.store.has('a.b.g.j.z')); + }); + + it('should return true if a key exists `.hasOwn()` on the store', function() { + app.store.set('foo', 'bar'); + app.store.set('baz', null); + app.store.set('qux', undefined); + + assert(app.store.hasOwn('foo')); + assert(!app.store.hasOwn('bar')); + assert(app.store.hasOwn('baz')); + assert(app.store.hasOwn('qux')); + }); + + it('should return true if a nested key exists `.hasOwn()` on the store', function() { + app.store.set('a.b.c.d', {x: 'zzz'}); + app.store.set('a.b.c.e', {f: null}); + app.store.set('a.b.g.j', {k: undefined}); + + assert(app.store.hasOwn('a.b.c.d')); + assert(app.store.hasOwn('a.b.c.d.x')); + assert(app.store.has('a.b.c.e.f')); + assert(app.store.hasOwn('a.b.c.e.f')); + assert(app.store.hasOwn('a.b.g.j.k')); + + assert(!app.store.hasOwn('a.b.bar')); + assert(!app.store.hasOwn('a.b.c.d.z')); + assert(!app.store.hasOwn('a.b.c.e.bar')); + assert(!app.store.has('a.b.g.j.k')); + assert(!app.store.hasOwn('a.b.g.j.foo')); + }); + + it('should `.get()` a stored value', function() { + app.store.set('three', 'four'); + app.store.get('three').should.equal('four'); + }); + + it('should `.get()` a nested value', function() { + app.store.set({a: {b: {c: 'd'}}}); + app.store.get('a.b.c').should.equal('d'); + }); + + it('should `.del()` a stored value', function() { + app.store.set('a', 'b'); + app.store.set('c', 'd'); + app.store.del('a'); + assert(!app.store.hasOwnProperty('a')); + }); + + it('should `.del()` multiple stored values', function() { + app.store.set('a', 'b'); + app.store.set('c', 'd'); + app.store.set('e', 'f'); + app.store.del(['a', 'c', 'e']); + app.store.data.should.eql({}); + }); +}); + +describe('events', function() { + beforeEach(function() { + app = new App(); + app.store = new Store('update-tests'); + }); + + afterEach(function(cb) { + app.store.del({force: true}); + cb(); + }); + + it('should emit `set` when an object is set:', function() { + var keys = []; + app.store.on('set', function(key) { + keys.push(key); + }); + + app.store.set({a: {b: {c: 'd'}}}); + assert(keys[0] === 'a'); + }); + + it('should emit `set` when a key/value pair is set:', function() { + var keys = []; + app.store.on('set', function(key) { + keys.push(key); + }); + + app.store.set('a', 'b'); + assert(keys[0] === 'a'); + }); + + it('should emit `set` when an object value is set:', function() { + var keys = []; + app.store.on('set', function(key) { + keys.push(key); + }); + + app.store.set('a', {b: 'c'}); + assert(keys[0] === 'a'); + }); + + it('should emit `set` when an array of objects is passed:', function() { + var keys = []; + app.store.on('set', function(key) { + keys.push(key); + }); + + app.store.set([{a: 'b'}, {c: 'd'}]); + assert(keys[0] === 'a'); + assert(keys[1] === 'c'); + }); + + it('should emit `del` when a value is deleted:', function(cb) { + app.store.on('del', function(key) { + assert(key === 'a'); + cb(); + }); + + app.store.set('a', {b: 'c'}); + app.store.get('a').should.eql({b: 'c'}); + app.store.del('a'); + }); + + it('should emit deleted keys on `del`:', function(cb) { + app.store.once('del', function(key) { + console.log(key) + assert(key === 'a'); + cb(); + }); + + app.store.set('a', 'b'); + app.store.set('c', 'd'); + app.store.set('e', 'f'); + assert.deepEqual(Object.keys(app.store.data), ['a', 'c', 'e']); + app.store.del({force: true}); + assert.deepEqual(Object.keys(app.store.data), []); + }); +}); diff --git a/test2/support/ignore.js b/test2/support/ignore.js new file mode 100644 index 0000000..7dcbb75 --- /dev/null +++ b/test2/support/ignore.js @@ -0,0 +1,6 @@ +module.exports = [ + 'addEventListener', + 'removeEventListener', + 'removeAllListeners', + 'removeListener' +]; diff --git a/test2/support/index.js b/test2/support/index.js new file mode 100644 index 0000000..e2194a5 --- /dev/null +++ b/test2/support/index.js @@ -0,0 +1,64 @@ +'use strict'; + +var path = require('path'); +var loadpkg = require('load-pkg'); +var assert = require('assert'); +var ignore = require('./ignore'); +var cache = {}; + +exports.containEql = function containEql(actual, expected) { + if (Array.isArray(expected)) { + var len = expected.length; + while (len--) { + exports.containEql(actual[len], expected[len]); + } + } else { + for (var key in expected) { + assert.deepEqual(actual[key], expected[key]); + } + } +}; + +exports.keys = function keys(obj) { + var arr = []; + for (var key in obj) { + if (ignore.indexOf(key) === -1) { + arr.push(key); + } + } + return arr; +}; + +exports.resolve = function(filepath) { + filepath = filepath || ''; + var key = 'app:' + filepath; + if (cache.hasOwnProperty(key)) { + return cache[key]; + } + + var pkg = loadpkg.sync(process.cwd()); + var prefix = pkg.name !== 'templates' + ? 'templates' + : ''; + + var base = filepath + ? path.join(prefix, filepath) + : process.cwd(); + + var fp = tryResolve(base); + + if (typeof fp === 'undefined') { + throw new Error('cannot resolve: ' + fp); + } + return (cache[key] = require(fp)); +}; + +function tryResolve(name) { + try { + return require.resolve(name); + } catch (err) {} + + try { + return require.resolve(path.resolve(name)); + } catch (err) {} +} diff --git a/test2/support/spy.js b/test2/support/spy.js new file mode 100644 index 0000000..e14512b --- /dev/null +++ b/test2/support/spy.js @@ -0,0 +1,27 @@ +var fs = require('fs'); +var sinon = require('sinon'); + +var errorfn = false; + +function maybeCallAsync(module, func) { + var original = module[func]; + return sinon.stub(module, func, function() { + var args = Array.prototype.slice.call(arguments); + args.unshift(module, func); + var err = typeof errorfn === 'function' && + errorfn.apply(this, args); + if (!err) { + original.apply(this, arguments); + } else { + arguments[arguments.length - 1](err); + } + }); +} + +module.exports = { + setError: function(fn) { + errorfn = fn; + }, + chmodSpy: maybeCallAsync(fs, 'chmod'), + statSpy: maybeCallAsync(fs, 'stat') +}; diff --git a/test2/view.content.js b/test2/view.content.js new file mode 100644 index 0000000..80ea113 --- /dev/null +++ b/test2/view.content.js @@ -0,0 +1,29 @@ +require('mocha'); +require('should'); +var fs = require('fs'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('content', function() { + beforeEach(function() { + app = new App(); + app.create('page'); + app.engine('tmpl', require('engine-base')); + }); + + it('should normalize the `content` property on a view to a string:', function(done) { + app.page('abc', {path: 'test/fixtures/templates/a.tmpl'}) + .set('read', function() { + this.contents = fs.readFileSync(this.path); + return this; + }); + + app.views.pages.abc.read(); + + assert('content' in app.views.pages.abc); + assert(typeof app.views.pages.abc.content === 'string'); + done(); + }); +}); diff --git a/test2/view.events.js b/test2/view.events.js new file mode 100644 index 0000000..3956379 --- /dev/null +++ b/test2/view.events.js @@ -0,0 +1,28 @@ +require('should'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('view.option()', function() { + beforeEach(function() { + app = new App(); + app.create('page'); + }); + + it('should emit events:', function() { + app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}); + var page = app.pages.getView('a.tmpl'); + var events = []; + + page.on('option', function(key) { + events.push(key); + }); + + page.option('a', 'b'); + page.option('c', 'd'); + page.option('e', 'f'); + page.option({g: 'h'}); + + events.should.eql(['a', 'c', 'e', 'g']); + }); +}); diff --git a/test2/view.js b/test2/view.js new file mode 100644 index 0000000..92ddf3c --- /dev/null +++ b/test2/view.js @@ -0,0 +1,1148 @@ +require('mocha'); +var should = require('should'); +var fs = require('fs'); +var path = require('path'); +var assert = require('assert'); +var Stream = require('stream'); +var es = require('event-stream'); +var support = require('./support'); +var App = support.resolve(); +var View = App.View; +var view; + +describe('View', function() { + describe('instance', function() { + it('should create an instance of View:', function() { + view = new View(); + assert(view instanceof View); + }); + }); + + describe('static methods', function() { + it('should expose `extend`:', function() { + assert(typeof View.extend === 'function'); + }); + }); + + describe('prototype methods', function() { + beforeEach(function() { + view = new View(); + }); + + it('should expose `set`:', function() { + assert(typeof view.set === 'function'); + }); + it('should expose `get`:', function() { + assert(typeof view.get === 'function'); + }); + it('should expose `del`:', function() { + assert(typeof view.del === 'function'); + }); + it('should expose `define`:', function() { + assert(typeof view.define === 'function'); + }); + it('should expose `visit`:', function() { + assert(typeof view.visit === 'function'); + }); + it('should expose `compile`:', function() { + assert(typeof view.compile === 'function'); + }); + it('should expose `render`:', function() { + assert(typeof view.render === 'function'); + }); + it('should expose `isType`:', function() { + assert(typeof view.isType === 'function'); + }); + }); + + describe('properties', function() { + it('should expose an `options` property', function() { + view = new View({}); + assert.deepEqual(view.options, {}); + assert(view.hasOwnProperty('options')); + }); + + it('should add `options` when passed on the constructor', function() { + view = new View({options: {foo: 'bar'}}); + assert(view.options.foo === 'bar'); + }); + + it('should expose a `data` property', function() { + view = new View({app: {}}); + assert.deepEqual(view.data, {}); + assert(view.hasOwnProperty('data')); + }); + + it('should add `data` when passed on the constructor', function() { + view = new View({data: {foo: 'bar'}}); + assert(view.data.foo === 'bar'); + }); + + it('should add `locals` when passed on the constructor', function() { + view = new View({locals: {foo: 'bar'}}); + assert(view.locals.foo === 'bar'); + }); + }); + + describe('set', function() { + it('should set properties on the object', function() { + view = new View(); + view.set('foo', 'bar'); + assert.equal(view.foo, 'bar'); + }); + }); + + describe('get', function() { + it('should get properties from the object', function() { + view = new View(); + view.set('foo', 'bar'); + assert.equal(view.get('foo'), 'bar'); + }); + }); + + describe('cwd', function() { + it('should get properties from the object', function() { + view = new View({cwd: 'test/fixtures'}); + assert(view.cwd === 'test/fixtures'); + }); + }); + + describe('clone', function() { + it('should clone the view:', function() { + view = new View({content: 'foo'}); + view.set({path: 'foo/bar'}); + view.set('options.one', 'two'); + var clone = view.clone(); + assert(clone.contents); + clone.set('baz', 'quux'); + clone.set('options.three', 'four'); + assert.equal(clone.get('foo'), view.get('foo')); + assert(clone.get('baz') === 'quux'); + assert(!view.get('baz')); + // not deep cloned + assert(clone.get('options.three') === 'four'); + assert(view.get('options.three') === 'four'); + }); + + it('should deep clone the entire object', function() { + view = new View({content: 'foo'}); + view.set({path: 'foo/bar'}); + view.set('options.one', 'two'); + var clone = view.clone({deep: true}); + clone.set('options.three', 'four'); + assert(view.get('options.one') === 'two'); + assert(clone.get('options.one') === 'two'); + assert(clone.get('options.three') === 'four'); + assert(!view.get('options.three')); + }); + }); + + describe('visit', function() { + it('should visit all properties on an object and call the specified method', function() { + view = new View(); + var obj = { + foo: 'bar', + bar: 'baz', + baz: 'bang' + }; + view.visit('set', obj); + assert.equal(view.get('foo'), 'bar'); + assert.equal(view.get('bar'), 'baz'); + assert.equal(view.get('baz'), 'bang'); + }); + + it('should visit all properties on all objects in an array and call the specified method', function() { + view = new View(); + var arr = [{foo: 'bar', bar: 'baz', baz: 'bang'}]; + view.visit('set', arr); + assert.equal(view.get('foo'), 'bar'); + assert.equal(view.get('bar'), 'baz'); + assert.equal(view.get('baz'), 'bang'); + }); + }); + + describe('compile', function() { + it('should get view.layout from view.data.layout', function() { + view = new View({path: 'foo', contents: 'a b c', data: {layout: 'default'}}); + assert(view.layout === 'default'); + }); + it('should get view.layout from view.options.layout', function() { + view = new View({path: 'foo', contents: 'a b c', options: {layout: 'default'}}); + assert(view.layout === 'default'); + }); + it('should get view.layout from view.locals.layout', function() { + view = new View({path: 'foo', contents: 'a b c', locals: {layout: 'default'}}); + assert(view.layout === 'default'); + }); + it('should get view.layout from the view', function() { + view = new View({path: 'foo', contents: 'a b c', layout: 'default'}); + assert(view.layout === 'default'); + }); + + it('should add a compiled function to `view.fn`', function() { + view = new View({path: 'foo', contents: 'a <%= name %> z'}); + view.compile(); + assert(typeof view.fn === 'function'); + }); + + it('should render a compiled template', function(done) { + view = new View({path: 'foo', contents: 'a <%= name %> z'}); + view.compile(); + view.render({name: 'Halle'}, function(err, res) { + if (err) return done(err); + assert(res.contents.toString() === 'a Halle z'); + done(); + }); + }); + + it('should render `fn` using data passed on the constructor', function(done) { + view = new View({ + path: 'foo', + contents: 'a <%= name %> z', + data: { + name: 'Brooke' + } + }); + + view.compile(); + view.render(function(err, res) { + if (err) return done(err); + assert(res.contents.toString() === 'a Brooke z'); + done(); + }); + }); + }); + + describe('render', function() { + it('should render a template', function(done) { + view = new View({path: 'foo', contents: 'a <%= name %> z'}); + view.render({name: 'Halle'}, function(err, res) { + if (err) return done(err); + assert(res.contents.toString() === 'a Halle z'); + done(); + }); + }); + + it('should render fn using data passed on the constructor', function(done) { + view = new View({ + path: 'foo', + contents: 'a <%= name %> z', + data: { + name: 'Brooke' + } + }); + + view.render(function(err, res) { + if (err) return done(err); + assert(res.contents.toString() === 'a Brooke z'); + done(); + }); + }); + + it('should pass errors in the callback.', function(done) { + view = new View({ + path: 'foo', + contents: 'a <%= name %> z' + }); + + view.render(function(err) { + assert(err.message === 'name is not defined'); + done(); + }); + }); + }); +}); + +/** + * The following unit tests are from Vinyl + * Since we inherit vinyl in View, we need + * to ensure that these still pass. + */ + +describe('View', function() { + describe('isVinyl()', function() { + it('should return true on a vinyl object', function(done) { + var view = new View(); + assert(View.isVinyl(view) === true); + done(); + }); + it('should return false on a normal object', function(done) { + assert(View.isVinyl({}) === false); + done(); + }); + it('should return false on a null object', function(done) { + assert(View.isVinyl({}) === false); + done(); + }); + }); + + describe('constructor()', function() { + it('should default cwd to process.cwd', function(done) { + var view = new View(); + view.cwd.should.equal(process.cwd()); + done(); + }); + + it('should default base to cwd', function(done) { + var cwd = '/'; + var view = new View({cwd: cwd}); + view.base.should.equal(cwd); + done(); + }); + + it('should default base to cwd even when none is given', function(done) { + var view = new View(); + view.base.should.equal(process.cwd()); + done(); + }); + + it('should default path to null', function(done) { + var view = new View(); + should.not.exist(view.path); + done(); + }); + + it('should default history to []', function(done) { + var view = new View(); + view.history.should.eql([]); + done(); + }); + + it('should default stat to null', function(done) { + var view = new View(); + should.not.exist(view.stat); + done(); + }); + + it('should default contents to null', function(done) { + var view = new View(); + should.not.exist(view.contents); + done(); + }); + + it('should set base to given value', function(done) { + var val = '/'; + var view = new View({base: val}); + view.base.should.equal(val); + done(); + }); + + it('should set cwd to given value', function(done) { + var val = '/'; + var view = new View({cwd: val}); + view.cwd.should.equal(val); + done(); + }); + + it('should set path to given value', function(done) { + var val = '/test.coffee'; + var view = new View({path: val}); + view.path.should.equal(val); + view.history.should.eql([val]); + done(); + }); + + it('should set history to given value', function(done) { + var val = '/test.coffee'; + var view = new View({history: [val]}); + view.path.should.equal(val); + view.history.should.eql([val]); + done(); + }); + + it('should set stat to given value', function(done) { + var val = {}; + var view = new View({stat: val}); + view.stat.should.equal(val); + done(); + }); + + it('should set contents to given value', function(done) { + var val = new Buffer('test'); + var view = new View({contents: val}); + view.contents.should.equal(val); + done(); + }); + }); + + describe('isBuffer()', function() { + it('should return true when the contents are a Buffer', function(done) { + var val = new Buffer('test'); + var view = new View({contents: val}); + view.isBuffer().should.equal(true); + done(); + }); + + it('should return false when the contents are a Stream', function(done) { + var val = new Stream(); + var view = new View({contents: val}); + assert(!view.isBuffer()); + done(); + }); + + it('should return false when the contents are a null', function(done) { + var view = new View({contents: null}); + assert(!view.isBuffer()); + done(); + }); + }); + + describe('isStream()', function() { + it('should return false when the contents are a Buffer', function(done) { + var val = new Buffer('test'); + var view = new View({contents: val}); + assert(!view.isStream()); + done(); + }); + + it('should return true when the contents are a Stream', function(done) { + var val = new Stream(); + var view = new View({contents: val}); + view.isStream().should.equal(true); + done(); + }); + + it('should return false when the contents are a null', function(done) { + var view = new View({contents: null}); + assert(!view.isStream()); + done(); + }); + }); + + describe('isNull()', function() { + it('should return false when the contents are a Buffer', function(done) { + var val = new Buffer('test'); + var view = new View({contents: val}); + assert(!view.isNull()); + done(); + }); + + it('should return false when the contents are a Stream', function(done) { + var val = new Stream(); + var view = new View({contents: val}); + assert(!view.isNull()); + done(); + }); + + it('should return true when the contents are a null', function(done) { + var view = new View({contents: null}); + view.isNull().should.equal(true); + done(); + }); + }); + + describe('isDirectory()', function() { + var fakeStat = { + isDirectory: function() { + return true; + } + }; + + it('should return false when the contents are a Buffer', function(done) { + var val = new Buffer('test'); + var view = new View({contents: val, stat: fakeStat}); + assert(!view.isDirectory()); + done(); + }); + + it('should return false when the contents are a Stream', function(done) { + var val = new Stream(); + var view = new View({contents: val, stat: fakeStat}); + assert(!view.isDirectory()); + done(); + }); + + it('should return true when the contents are a null', function(done) { + var view = new View({contents: null, stat: fakeStat}); + view.isDirectory().should.equal(true); + done(); + }); + }); + + describe('clone()', function() { + it('should copy all attributes over with Buffer', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: new Buffer('test') + }; + var view = new View(options); + var view2 = view.clone(); + + view2.should.not.equal(view, 'refs should be different'); + view2.cwd.should.equal(view.cwd); + view2.base.should.equal(view.base); + view2.path.should.equal(view.path); + view2.contents.should.not.equal(view.contents, 'buffer ref should be different'); + view2.contents.toString('utf8').should.equal(view.contents.toString('utf8')); + done(); + }); + + it('should copy buffer\'s reference with option contents: false', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.js', + contents: new Buffer('test') + }; + + var view = new View(options); + + var copy1 = view.clone({ contents: false }); + copy1.contents.should.equal(view.contents); + + var copy2 = view.clone({}); + copy2.contents.should.not.equal(view.contents); + + var copy3 = view.clone({ contents: 'any string' }); + copy3.contents.should.not.equal(view.contents); + + done(); + }); + + it('should copy all attributes over with Stream', function(done) { + var contents = new Stream.PassThrough(); + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: contents + }; + var view = new View(options); + var view2 = view.clone(); + + contents.write(new Buffer('wa')); + + process.nextTick(function() { + contents.write(new Buffer('dup')); + contents.end(); + }); + + view2.should.not.equal(view, 'refs should be different'); + view2.cwd.should.equal(view.cwd); + view2.base.should.equal(view.base); + view2.path.should.equal(view.path); + view2.contents.should.not.equal(view.contents, 'stream ref should not be the same'); + view.contents.pipe(es.wait(function(err, data) { + if (err) return done(err); + view2.contents.pipe(es.wait(function(err, data2) { + if (err) return done(err); + data2.should.not.equal(data, 'stream contents ref should not be the same'); + data2.should.eql(data, 'stream contents should be the same'); + })); + })); + done(); + }); + + it('should copy all attributes over with null', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: null + }; + var view = new View(options); + var view2 = view.clone(); + + view2.should.not.equal(view, 'refs should be different'); + view2.cwd.should.equal(view.cwd); + view2.base.should.equal(view.base); + view2.path.should.equal(view.path); + should.not.exist(view2.contents); + done(); + }); + + it('should properly clone the `stat` property', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.js', + contents: new Buffer('test'), + stat: fs.statSync(__filename) + }; + + var view = new View(options); + var copy = view.clone(); + + assert(copy.stat.isFile()); + assert(!copy.stat.isDirectory()); + done(); + }); + + it('should properly clone the `history` property', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.js', + contents: new Buffer('test'), + stat: fs.statSync(__filename) + }; + + var view = new View(options); + var copy = view.clone(); + + copy.history[0].should.equal(options.path); + copy.path = 'lol'; + view.path.should.not.equal(copy.path); + done(); + }); + + it('should copy custom properties', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: null + }; + + var view = new View(options); + view.custom = { a: 'custom property' }; + var view2 = view.clone(); + + view2.should.not.equal(view, 'refs should be different'); + view2.cwd.should.equal(view.cwd); + view2.base.should.equal(view.base); + view2.path.should.equal(view.path); + view2.custom.should.equal(view.custom); + view2.custom.a.should.equal(view.custom.a); + + done(); + }); + + it('should copy history', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: null + }; + + var view = new View(options); + view.path = '/test/test.js'; + view.path = '/test/test-938di2s.js'; + var view2 = view.clone(); + + view2.history.should.eql([ + '/test/test.coffee', + '/test/test.js', + '/test/test-938di2s.js' + ]); + view2.history.should.not.equal([ + '/test/test.coffee', + '/test/test.js', + '/test/test-938di2s.js' + ]); + view2.path.should.eql('/test/test-938di2s.js'); + + done(); + }); + + it('should copy all attributes deeply', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: null + }; + + var view = new View(options); + view.custom = { a: 'custom property' }; + + var view2 = view.clone(true); + view2.custom.should.eql(view.custom); + view2.custom.should.not.equal(view.custom); + view2.custom.a.should.equal(view.custom.a); + + var view3 = view.clone({ deep: true }); + view3.custom.should.eql(view.custom); + view3.custom.should.not.equal(view.custom); + view3.custom.a.should.equal(view.custom.a); + + var view4 = view.clone(false); + view4.custom.should.eql(view.custom); + view4.custom.should.equal(view.custom); + view4.custom.a.should.equal(view.custom.a); + + var view5 = view.clone({ deep: false }); + view5.custom.should.eql(view.custom); + view5.custom.should.equal(view.custom); + view5.custom.a.should.equal(view.custom.a); + + done(); + }); + }); + + describe('pipe()', function() { + it('should write to stream with Buffer', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: new Buffer('test') + }; + var view = new View(options); + var stream = new Stream.PassThrough(); + stream.on('data', function(chunk) { + should.exist(chunk); + (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); + chunk.toString('utf8').should.equal(options.contents.toString('utf8')); + }); + stream.on('end', function() { + done(); + }); + var ret = view.pipe(stream); + ret.should.equal(stream, 'should return the stream'); + }); + + it('should pipe to stream with Stream', function(done) { + var testChunk = new Buffer('test'); + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: new Stream.PassThrough() + }; + var view = new View(options); + var stream = new Stream.PassThrough(); + stream.on('data', function(chunk) { + should.exist(chunk); + (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); + chunk.toString('utf8').should.equal(testChunk.toString('utf8')); + done(); + }); + var ret = view.pipe(stream); + ret.should.equal(stream, 'should return the stream'); + + view.contents.write(testChunk); + }); + + it('should do nothing with null', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: null + }; + var view = new View(options); + var stream = new Stream.PassThrough(); + stream.on('data', function() { + throw new Error('should not write'); + }); + stream.on('end', function() { + done(); + }); + var ret = view.pipe(stream); + ret.should.equal(stream, 'should return the stream'); + }); + + it('should write to stream with Buffer', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: new Buffer('test') + }; + var view = new View(options); + var stream = new Stream.PassThrough(); + stream.on('data', function(chunk) { + should.exist(chunk); + (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); + chunk.toString('utf8').should.equal(options.contents.toString('utf8')); + done(); + }); + stream.on('end', function() { + throw new Error('should not end'); + }); + var ret = view.pipe(stream, {end: false}); + ret.should.equal(stream, 'should return the stream'); + }); + + it('should pipe to stream with Stream', function(done) { + var testChunk = new Buffer('test'); + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: new Stream.PassThrough() + }; + var view = new View(options); + var stream = new Stream.PassThrough(); + stream.on('data', function(chunk) { + should.exist(chunk); + (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); + chunk.toString('utf8').should.equal(testChunk.toString('utf8')); + done(); + }); + stream.on('end', function() { + throw new Error('should not end'); + }); + var ret = view.pipe(stream, {end: false}); + ret.should.equal(stream, 'should return the stream'); + + view.contents.write(testChunk); + }); + + it('should do nothing with null', function(done) { + var options = { + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: null + }; + var view = new View(options); + var stream = new Stream.PassThrough(); + stream.on('data', function() { + throw new Error('should not write'); + }); + stream.on('end', function() { + throw new Error('should not end'); + }); + var ret = view.pipe(stream, {end: false}); + ret.should.equal(stream, 'should return the stream'); + process.nextTick(done); + }); + }); + + describe('inspect()', function() { + it('should return correct format when no contents and no path', function(done) { + var view = new View(); + view.inspect().should.equal(''); + done(); + }); + + it('should return correct format when Buffer and no path', function(done) { + var val = new Buffer('test'); + var view = new View({ + contents: val + }); + view.inspect().should.equal('>'); + done(); + }); + + it('should return correct format when Buffer and relative path', function(done) { + var val = new Buffer('test'); + var view = new View({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: val + }); + view.inspect().should.equal('>'); + done(); + }); + + it('should return correct format when Buffer and only path and no base', function(done) { + var val = new Buffer('test'); + var view = new View({ + cwd: '/', + path: '/test/test.coffee', + contents: val + }); + delete view.base; + view.inspect().should.equal('>'); + done(); + }); + + it('should return correct format when Stream and relative path', function(done) { + var view = new View({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: new Stream.PassThrough() + }); + view.inspect().should.equal('>'); + done(); + }); + + it('should return correct format when null and relative path', function(done) { + var view = new View({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee', + contents: null + }); + view.inspect().should.equal(''); + done(); + }); + }); + + describe('contents get/set', function() { + it('should work with Buffer', function(done) { + var val = new Buffer('test'); + var view = new View(); + view.contents = val; + view.contents.should.equal(val); + done(); + }); + + it('should work with Stream', function(done) { + var val = new Stream.PassThrough(); + var view = new View(); + view.contents = val; + view.contents.should.equal(val); + done(); + }); + + it('should work with null', function(done) { + var val = null; + var view = new View(); + view.contents = val; + (view.contents === null).should.equal(true); + done(); + }); + + it('should work with string', function(done) { + var val = 'test'; + var view = new View(); + view.contents = val; + view.contents.should.deepEqual(new Buffer(val)); + done(); + }); + }); + + describe('relative get/set', function() { + it('should error on set', function(done) { + var view = new View(); + try { + view.relative = 'test'; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should error on get when no base', function(done) { + var view = new View(); + delete view.base; + try { + view.relative; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should error on get when no path', function(done) { + var view = new View(); + try { + view.relative; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should return a relative path from base', function(done) { + var view = new View({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee' + }); + view.relative.should.equal('test.coffee'); + done(); + }); + + it('should return a relative path from cwd', function(done) { + var view = new View({ + cwd: '/', + path: '/test/test.coffee' + }); + view.relative.should.equal(path.join('test', 'test.coffee')); + done(); + }); + }); + + describe('dirname get/set', function() { + it('should error on get when no path', function(done) { + var view = new View(); + try { + view.dirname; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should return the dirname of the path', function(done) { + var view = new View({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee' + }); + view.dirname.should.equal('/test'); + done(); + }); + + it('should error on set when no path', function(done) { + var view = new View(); + try { + view.dirname = '/test'; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should set the dirname of the path', function(done) { + var view = new View({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee' + }); + view.dirname = '/test/foo'; + view.path.should.equal('/test/foo/test.coffee'); + done(); + }); + }); + + describe('basename get/set', function() { + it('should error on get when no path', function(done) { + var view = new View(); + try { + view.basename; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should return the basename of the path', function(done) { + var view = new View({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee' + }); + view.basename.should.equal('test.coffee'); + done(); + }); + + it('should error on set when no path', function(done) { + var view = new View(); + try { + view.basename = 'test.coffee'; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should set the basename of the path', function(done) { + var view = new View({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee' + }); + view.basename = 'foo.png'; + view.path.should.equal('/test/foo.png'); + done(); + }); + }); + + describe('extname get/set', function() { + it('should error on get when no path', function(done) { + var view = new View(); + try { + view.extname; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should return the extname of the path', function(done) { + var view = new View({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee' + }); + view.extname.should.equal('.coffee'); + done(); + }); + + it('should error on set when no path', function(done) { + var view = new View(); + try { + view.extname = '.coffee'; + } catch (err) { + should.exist(err); + done(); + } + }); + + it('should set the extname of the path', function(done) { + var view = new View({ + cwd: '/', + base: '/test/', + path: '/test/test.coffee' + }); + view.extname = '.png'; + view.path.should.equal('/test/test.png'); + done(); + }); + }); + + describe('path get/set', function() { + it('should record history when instantiation', function() { + var view = new View({ + cwd: '/', + path: '/test/test.coffee' + }); + + view.path.should.eql('/test/test.coffee'); + view.history.should.eql(['/test/test.coffee']); + }); + + it('should record history when path change', function() { + var view = new View({ + cwd: '/', + path: '/test/test.coffee' + }); + + view.path = '/test/test.js'; + view.path.should.eql('/test/test.js'); + view.history.should.eql(['/test/test.coffee', '/test/test.js']); + + view.path = '/test/test.coffee'; + view.path.should.eql('/test/test.coffee'); + view.history.should.eql(['/test/test.coffee', '/test/test.js', '/test/test.coffee']); + }); + + it('should not record history when set the same path', function() { + var view = new View({ + cwd: '/', + path: '/test/test.coffee' + }); + + view.path = '/test/test.coffee'; + view.path = '/test/test.coffee'; + view.path.should.eql('/test/test.coffee'); + view.history.should.eql(['/test/test.coffee']); + + // ignore when set empty string + view.path = ''; + view.path.should.eql('/test/test.coffee'); + view.history.should.eql(['/test/test.coffee']); + }); + + it('should throw when set path null in constructor', function() { + (function() { + View({ + cwd: '/', + path: null + }); + }).should.throw('path should be string'); + }); + + it('should throw when set path null', function() { + var view = new View({ + cwd: '/', + path: 'foo' + }); + + (function() { + view.path = null; + }).should.throw('path should be string'); + }); + }); +}); diff --git a/test2/view.methods.js b/test2/view.methods.js new file mode 100644 index 0000000..08f064e --- /dev/null +++ b/test2/view.methods.js @@ -0,0 +1,39 @@ +require('should'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('view.option()', function() { + beforeEach(function() { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.create('page'); + }); + + describe('.use', function() { + it('should expose `.use` for running plugins on a view:', function() { + app.page('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}) + .use(function() { + this.options.foo = 'bar'; + }) + .use(function() { + this.options.bar = 'baz'; + }); + + var page = app.pages.getView('a.tmpl'); + page.options.should.have.property('foo'); + page.options.should.have.property('bar'); + }); + }); + + describe('.render:', function() { + it('should expose `.render` for rendering a view:', function(done) { + app.page('a.tmpl', {path: 'a.tmpl', content: '<%= a %>', locals: {a: 'bbb'}}) + .render({}, function(err, res) { + if (err) return done(err); + res.contents.toString().should.equal('bbb'); + done(); + }); + }); + }); +}); diff --git a/test2/view.option.js b/test2/view.option.js new file mode 100644 index 0000000..087399c --- /dev/null +++ b/test2/view.option.js @@ -0,0 +1,29 @@ +require('should'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('view.option()', function() { + beforeEach(function() { + app = new App(); + app.create('page'); + }); + + it('should set an option:', function() { + app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}); + var page = app.pages.getView('a.tmpl'); + + page.options.should.not.have.property('foo'); + page.option('foo', 'bar'); + page.options.should.have.property('foo'); + }); + + it('should extend options:', function() { + app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}); + var page = app.pages.getView('a.tmpl'); + page.option('a', 'b'); + page.option('c', 'd'); + page.option('e', 'f'); + page.options.should.have.properties(['a', 'c', 'e']); + }); +}); diff --git a/test2/view.render.js b/test2/view.render.js new file mode 100644 index 0000000..a7d6d71 --- /dev/null +++ b/test2/view.render.js @@ -0,0 +1,52 @@ +require('mocha'); +require('should'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('helpers', function() { + describe('rendering', function() { + beforeEach(function() { + app = new App(); + app.engine('tmpl', require('engine-base')); + app.create('layouts', {viewType: 'layout'}); + app.create('pages'); + }); + + it('should expose `.render` for rendering a view:', function(done) { + app.page('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}) + .render({a: 'bbb'}, function(err, res) { + if (err) return done(err); + res.content.should.equal('bbb'); + done(); + }); + }); + + it('should render a view with a layout', function(done) { + app.layout('default.tmpl', {content: 'a {% body %} b'}); + app.page('a.tmpl', {content: '<%= title %>', layout: 'default.tmpl'}) + .render({title: 'zzz'}, function(err, res) { + if (err) return done(err); + res.content.should.equal('a zzz b'); + done(); + }); + }); + + it('should render a view with a layout', function(done) { + app.layout('foo.tmpl', {content: 'a {% body %} a'}); + app.layout('bar.tmpl', {content: 'b {% body %} b'}); + app.pages('a.tmpl', {content: '<%= title %>'}); + + app.pages.getView('a.tmpl') + .option('resolveLayout', function() { + return 'bar.tmpl'; + }) + .render({title: 'zzz'}, function(err, res) { + if (err) return done(err); + res.content.should.equal('b zzz b'); + done(); + }); + }); + }); +}); + diff --git a/test2/view.set.js b/test2/view.set.js new file mode 100644 index 0000000..018df6e --- /dev/null +++ b/test2/view.set.js @@ -0,0 +1,34 @@ +require('mocha'); +require('should'); +var fs = require('fs'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('set', function() { + beforeEach(function() { + app = new App(); + app.create('page'); + app.engine('tmpl', require('engine-base')); + }); + + it('should set a property on a view:', function(done) { + app.page('abc', {path: 'test/fixtures/templates/a.tmpl'}) + .set('read', function() { + this.contents = fs.readFileSync(this.path); + return this; + }); + + assert('read' in app.views.pages.abc); + app.views.pages.abc + .read() + .set('data.name', 'Brooke') + .render(function(err, res) { + if (err) return done(err); + + assert(res.content === 'Brooke'); + done(); + }); + }); +}); diff --git a/test2/view.use.js b/test2/view.use.js new file mode 100644 index 0000000..2883424 --- /dev/null +++ b/test2/view.use.js @@ -0,0 +1,60 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var View = App.View; +var view; + +describe('view.use', function() { + beforeEach(function() { + view = new View(); + }); + + it('should expose the instance to `use`:', function(done) { + view.use(function(inst) { + assert(inst instanceof View); + done(); + }); + }); + + it('should be chainable:', function(done) { + view.use(function(inst) { + assert(inst instanceof View); + }) + .use(function(inst) { + assert(inst instanceof View); + }) + .use(function(inst) { + assert(inst instanceof View); + done(); + }); + }); + + it('should expose the view to a plugin:', function() { + view.use(function(view) { + assert(view instanceof View); + view.foo = function(str) { + return str + ' ' + 'bar'; + }; + }); + assert(view.foo('foo') === 'foo bar'); + }); + + it('should be chainable:', function() { + view + .use(function(view) { + view.a = 'aaa'; + }) + .use(function(view) { + view.b = 'bbb'; + }) + .use(function(view) { + view.c = 'ccc'; + }); + + assert(view.a === 'aaa'); + assert(view.b === 'bbb'); + assert(view.c === 'ccc'); + }); +}); diff --git a/test2/viewTypes.js b/test2/viewTypes.js new file mode 100644 index 0000000..b96115e --- /dev/null +++ b/test2/viewTypes.js @@ -0,0 +1,52 @@ +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var app; + +describe('viewType', function() { + describe('view types', function() { + beforeEach(function() { + app = new App(); + app.engine('tmpl', require('engine-base')); + }); + + it('should add collection (plural) to the `viewTypes` object', function() { + app.viewTypes = []; // reset + app.create('foo', {viewType: 'layout'}); + app.create('bar', {viewType: 'layout'}); + assert.deepEqual(app.viewTypes.layout, [ 'foos', 'bars' ]); + + app.create('baz', {viewType: 'renderable'}); + assert.deepEqual(app.viewTypes.renderable, [ 'bazs' ]); + }); + + it('should add collection to the given viewType', function() { + app.create('layout', {viewType: 'layout'}); + assert(app.layouts.options.viewType[0] === 'layout'); + }); + + it('should return true if a collection has the given viewType', function() { + app.create('layout', {viewType: 'layout'}); + assert(app.layouts.isType('layout')); + assert(!app.layouts.isType('partial')); + }); + + it('should add types to the collection', function() { + app.create('layout', {viewType: 'layout'}); + app.layouts.viewType('partial'); + assert(app.layouts.options.viewType[0] === 'layout'); + assert(app.layouts.options.viewType[1] === 'partial'); + }); + + it('should add a collection to multiple viewTypes', function() { + app.create('foo', {viewType: ['layout', 'renderable']}); + assert.deepEqual(app.foos.options.viewType, ['layout', 'renderable']); + }); + + it('should expose viewType on `view`', function() { + app.create('foo', {viewType: ['layout', 'renderable']}); + var a = app.foo('a', {content: ''}); + assert.deepEqual(app.foos.options.viewType, a.options.viewType); + }); + }); +}); diff --git a/test2/views.js b/test2/views.js new file mode 100644 index 0000000..f3f9c21 --- /dev/null +++ b/test2/views.js @@ -0,0 +1,525 @@ +require('mocha'); +require('should'); +var path = require('path'); +var assert = require('assert'); +var typeOf = require('kind-of'); +var support = require('./support'); +var isBuffer = require('is-buffer'); +var App = support.resolve(); +var List = App.List; +var View = App.View; +var Views = App.Views; +var collection; + +describe('views', function() { + describe('constructor', function() { + it('should create an instance of Views:', function() { + var collection = new Views(); + assert(collection instanceof Views); + }); + + it('should instantiate without `new`:', function() { + var collection = Views(); + assert(collection instanceof Views); + }); + }); + + describe('static methods', function() { + it('should expose `extend`:', function() { + assert(typeof Views.extend === 'function'); + }); + }); + + describe('prototype methods', function() { + beforeEach(function() { + collection = new Views(); + }); + + var methods = [ + 'use', + 'setView', + 'addView', + 'addViews', + 'addList', + 'getView', + 'constructor', + 'set', + 'get', + 'del', + 'define', + 'visit', + 'on', + 'once', + 'off', + 'emit', + 'listeners', + 'hasListeners' + ]; + + methods.forEach(function(method) { + it('should expose ' + method + ' method', function() { + assert(typeof collection[method] === 'function'); + }); + }); + + it('should expose isCollection property', function() { + assert(typeof collection.isCollection === 'boolean'); + }); + + it('should expose queue property', function() { + assert(Array.isArray(collection.queue)); + }); + + it('should expose views property', function() { + assert(typeOf(collection.views) === 'object'); + }); + + it('should expose options property', function() { + assert(typeOf(collection.options) === 'object'); + }); + }); + + describe('instance', function() { + beforeEach(function() { + collection = new Views(); + }); + + it('should set a value on the instance:', function() { + collection.set('a', 'b'); + assert(collection.a === 'b'); + }); + + it('should get a value from the instance:', function() { + collection.set('a', 'b'); + assert(collection.get('a') === 'b'); + }); + }); + + describe('option', function() { + beforeEach(function() { + collection = new Views(); + }); + + it('should set a key/value pair on options:', function() { + collection.option('a', 'b'); + assert(collection.options.a === 'b'); + }); + + it('should set an object on options:', function() { + collection.option({c: 'd'}); + assert(collection.options.c === 'd'); + }); + + it('should get an option:', function() { + collection.option({c: 'd'}); + var c = collection.option('c'); + assert(c === 'd'); + }); + }); + + describe('addView', function() { + beforeEach(function() { + collection = new Views(); + }); + + it('should throw an error when args are invalid:', function() { + (function() { + collection.addView(function() {}); + }).should.throw('expected value to be an object.'); + }); + + it('should add a view to `views`:', function() { + collection.addView('foo'); + collection.views.should.have.property('foo'); + + collection.addView('one', {content: '...'}); + assert(typeof collection.views.one === 'object'); + assert(isBuffer(collection.views.one.contents)); + }); + + it('should create an instance of `View`:', function() { + collection.addView('one', {content: '...'}); + assert(collection.views.one instanceof collection.View); + }); + + it('should allow an `View` constructor to be passed:', function() { + View.prototype.foo = function(key, value) { + this[key] = value; + }; + collection = new Views({View: View}); + collection.addView('one', {content: '...'}); + collection.views.one.foo('bar', 'baz'); + assert(collection.views.one.bar === 'baz'); + }); + + it('should allow an instance of `View` to be passed:', function() { + var collection = new Views({View: View}); + var view = new View({content: '...'}); + collection.addView('one', view); + view.set('abc', 'xyz'); + assert(collection.views.one instanceof collection.View); + assert(isBuffer(collection.views.one.contents)); + assert(collection.views.one.abc === 'xyz'); + }); + + it('should expose the `isType` method on items', function() { + var collection = new Views({View: View}); + var view = new View({content: '...'}); + collection.setView('one', view); + + var one = collection.getView('one'); + assert(one.isType('renderable')); + }); + + it('should set viewTypes on a collection', function() { + var collection = new Views({View: View}); + collection.viewType(['partial']); + + var view = new View({content: '...'}); + collection.setView('one', view); + + var one = collection.getView('one'); + assert(!one.isType('renderable')); + assert(one.isType('partial')); + }); + }); + + describe('addViews', function() { + beforeEach(function() { + collection = new Views(); + }); + + it('should emit an error if a string glob pattern is passed', function(done) { + try { + collection.addViews('*.js'); + done(new Error('expected an error')); + } catch (err) { + assert(err); + assert(err.message); + assert(/glob/.test(err.message)); + done(); + } + }); + + it('should emit an error if an array glob pattern is passed', function(done) { + try { + collection.addViews(['*.js']); + done(new Error('expected an error')); + } catch (err) { + assert(err); + assert(err.message); + assert(/glob/.test(err.message)); + done(); + } + }); + + it('should add multiple views:', function() { + collection.addViews({ + one: {content: 'foo'}, + two: {content: 'bar'} + }); + assert(isBuffer(collection.views.one.contents)); + assert(isBuffer(collection.views.two.contents)); + }); + + it('should return the collection instance for chaining:', function() { + var views = collection.addViews({ + one: {content: 'foo'}, + two: {content: 'bar'} + }); + + var view = views.getView('one'); + assert(view); + assert(view.content); + assert(view.content === 'foo'); + }); + + it('should create views from an instance of Views', function() { + collection.addViews({ + one: {content: 'foo'}, + two: {content: 'bar'} + }); + var pages = new Views(collection); + assert(isBuffer(pages.views.one.contents)); + assert(isBuffer(pages.views.two.contents)); + }); + + it('should add an array of views:', function() { + collection.addViews([ + {path: 'one', content: 'foo'}, + {path: 'two', content: 'bar'} + ]); + assert(isBuffer(collection.views.one.contents)); + assert(isBuffer(collection.views.two.contents)); + }); + }); + + describe('view', function() { + beforeEach(function() { + collection = new Views(); + }); + + it('should return a single collection view from a key-value pair', function() { + var one = collection.view('one', {content: 'foo'}); + var two = collection.view('two', {content: 'bar'}); + + assert(one.isView); + assert(one.path === 'one'); + assert(two.isView); + assert(two.path === 'two'); + }); + + it('should return a single collection view from an object', function() { + var one = collection.view({path: 'one', content: 'foo'}); + var two = collection.view({path: 'two', content: 'bar'}); + + assert(one.isView); + assert(one.path === 'one'); + assert(two.isView); + assert(two.path === 'two'); + }); + }); + + describe('addList', function() { + beforeEach(function() { + collection = new Views(); + }); + + it('should emit an error if a string glob pattern is passed', function(done) { + try { + collection.addList('*.js'); + done(new Error('expected an error')); + } catch (err) { + assert(err); + assert(err.message); + assert(/glob/.test(err.message)); + done(); + } + }); + + it('should emit an error if an array glob pattern is passed', function(done) { + try { + collection.addList(['*.js']); + done(new Error('expected an error')); + } catch (err) { + assert(err); + assert(err.message); + assert(/glob/.test(err.message)); + done(); + } + }); + + it('should add a list of views:', function() { + collection.addList([ + {path: 'one', content: 'foo'}, + {path: 'two', content: 'bar'} + ]); + assert(isBuffer(collection.views.one.contents)); + assert(isBuffer(collection.views.two.contents)); + }); + + it('should add a list from the constructor:', function() { + var list = new List([ + {path: 'one', content: 'foo'}, + {path: 'two', content: 'bar'} + ]); + + collection = new Views(list); + assert(isBuffer(collection.views.one.contents)); + assert(isBuffer(collection.views.two.contents)); + }); + + it('should add list items from the constructor:', function() { + var list = new List([ + {path: 'one', content: 'foo'}, + {path: 'two', content: 'bar'} + ]); + + collection = new Views(list.items); + assert(isBuffer(collection.views.one.contents)); + assert(isBuffer(collection.views.two.contents)); + }); + + it('should throw an error when list is not an array:', function() { + var views = new Views(); + (function() { + views.addList(); + }).should.throw('expected list to be an array.'); + + (function() { + views.addList({}); + }).should.throw('expected list to be an array.'); + + (function() { + views.addList('foo'); + }).should.throw('expected list to be an array.'); + }); + + it('should load an array of items from an event:', function() { + var pages = new Views(); + + pages.on('addList', function(list) { + while (list.length) { + pages.addView({path: list.pop()}); + } + this.loaded = true; + }); + + pages.addList(['a.txt', 'b.txt', 'c.txt']); + assert(pages.views.hasOwnProperty('a.txt')); + assert(pages.views['a.txt'].path === 'a.txt'); + }); + + it('should load an array of items from the addList callback:', function() { + var collection = new Views(); + + collection.addList(['a.txt', 'b.txt', 'c.txt'], function(fp) { + return {path: fp}; + }); + assert(collection.views.hasOwnProperty('a.txt')); + assert(collection.views['a.txt'].path === 'a.txt'); + }); + + it('should load an object of views from an event:', function() { + var collection = new Views(); + + collection.on('addViews', function(views) { + for (var key in views) { + collection.addView('foo/' + key, views[key]); + delete views[key]; + } + }); + + collection.addViews({ + a: {path: 'a.txt'}, + b: {path: 'b.txt'}, + c: {path: 'c.txt'} + }); + + assert(collection.views.hasOwnProperty('foo/a')); + assert(collection.views['foo/a'].path === 'a.txt'); + }); + + it('should signal `loaded` when finished:', function() { + var collection = new Views(); + + collection.on('addViews', function(views) { + for (var key in views) { + if (key === 'c') break; + collection.addView('foo/' + key, views[key]); + } + }); + + collection.addViews({ + a: {path: 'a.txt'}, + b: {path: 'b.txt'}, + c: {path: 'c.txt'} + }); + + assert(collection.views.hasOwnProperty('foo/a')); + assert(!collection.views.hasOwnProperty('foo/c')); + assert(collection.views['foo/a'].path === 'a.txt'); + }); + }); + + describe('getView', function() { + beforeEach(function() { + collection = new Views(); + }); + it('should get a view from `views`:', function() { + collection.addView('one', {content: 'aaa'}); + collection.addView('two', {content: 'zzz'}); + assert(isBuffer(collection.views.one.contents)); + assert(isBuffer(collection.getView('one').contents)); + assert(collection.getView('one').contents.toString() === 'aaa'); + assert(collection.getView('two').contents.toString() === 'zzz'); + }); + }); + + describe('count', function() { + beforeEach(function() { + collection = new Views(); + }); + + it('should get the number of views:', function() { + collection.addView('one', {content: 'aaa'}); + collection.addView('two', {content: 'zzz'}); + assert(Object.keys(collection.views).length === 2); + }); + }); +}); + +describe('options', function() { + describe('options.renameKey', function() { + beforeEach(function() { + collection = new Views({ + renameKey: function(key) { + return path.basename(key); + } + }); + }); + + it('should use a custom rename key function on view keys', function() { + collection.addView('a/b/c/d.hbs', {content: 'foo bar baz'}); + assert(collection.views['d.hbs'].contents.toString() === 'foo bar baz'); + }); + + it('should get a view with the renamed key:', function() { + collection.addView('a/b/c/d.hbs', {content: 'foo bar baz'}); + assert(collection.getView('d.hbs').contents.toString() === 'foo bar baz'); + }); + + it('should get a view with the original key:', function() { + collection.addView('a/b/c/d.hbs', {content: 'foo bar baz'}); + assert(collection.getView('a/b/c/d.hbs').contents.toString() === 'foo bar baz'); + }); + }); +}); + +describe('queue', function() { + beforeEach(function() { + collection = new Views(); + }); + + it('should emit arguments on addView', function(done) { + collection.on('addView', function(args) { + assert(args[0] === 'a'); + assert(args[1] === 'b'); + assert(args[2] === 'c'); + assert(args[3] === 'd'); + assert(args[4] === 'e'); + done(); + }); + + collection.addView('a', 'b', 'c', 'd', 'e'); + }); + + it('should expose the `queue` property for loading views', function() { + collection.queue.push(collection.view('b', {path: 'b'})); + + collection.addView('a', {path: 'a'}); + assert(collection.views.hasOwnProperty('a')); + assert(collection.views.hasOwnProperty('b')); + }); + + it('should load all views on the queue when addView is called', function() { + collection.on('addView', function(args) { + var len = args.length; + var last = args[len - 1]; + if (typeof last === 'string') { + args[len - 1] = { content: last }; + } + }); + + collection.addView('a.html', 'aaa'); + collection.addView('b.html', 'bbb'); + collection.addView('c.html', 'ccc'); + + assert(collection.views.hasOwnProperty('a.html')); + assert(collection.getView('a.html').content === 'aaa'); + assert(collection.views.hasOwnProperty('b.html')); + assert(collection.getView('b.html').content === 'bbb'); + assert(collection.views.hasOwnProperty('c.html')); + assert(collection.getView('c.html').content === 'ccc'); + }); +}); diff --git a/test2/views.use.js b/test2/views.use.js new file mode 100644 index 0000000..4329b31 --- /dev/null +++ b/test2/views.use.js @@ -0,0 +1,156 @@ +require('mocha'); +require('should'); +var assert = require('assert'); +var support = require('./support'); +var App = support.resolve(); +var Views = App.Views; +var View = App.View; +var collection; + +describe('views.use', function() { + beforeEach(function() { + collection = new Views(); + }); + + it('should expose the instance to `use`:', function(done) { + collection.use(function(inst) { + assert(inst instanceof Views); + done(); + }); + }); + + it('should be chainable:', function(done) { + collection.use(function(inst) { + assert(inst instanceof Views); + }) + .use(function(inst) { + assert(inst instanceof Views); + }) + .use(function(inst) { + assert(inst instanceof Views); + done(); + }); + }); + + it('should expose the collection to a plugin:', function() { + collection.use(function(views) { + assert(views instanceof Views); + views.foo = views.addView.bind(views); + }); + + collection.foo('a', {content: '...'}); + assert(collection.views.hasOwnProperty('a')); + }); + + it('should expose collection when chained:', function() { + collection + .use(function(views) { + assert(views instanceof Views); + views.foo = views.addView.bind(views); + }) + .use(function(views) { + assert(views instanceof Views); + views.bar = views.addView.bind(views); + }) + .use(function(views) { + assert(views instanceof Views); + views.baz = views.addView.bind(views); + }); + + var pages = collection; + + pages.foo({path: 'a', content: '...'}); + pages.bar({path: 'b', content: '...'}); + pages.baz({path: 'c', content: '...'}); + + assert(collection.views.hasOwnProperty('a')); + assert(collection.views.hasOwnProperty('b')); + assert(collection.views.hasOwnProperty('c')); + }); + + it('should work when a custom `View` constructor is passed:', function() { + collection = new Views({View: require('vinyl')}); + collection + .use(function(views) { + assert(views instanceof Views); + views.foo = views.addView.bind(views); + }) + .use(function(views) { + assert(views instanceof Views); + views.bar = views.addView.bind(views); + }) + .use(function(views) { + assert(views instanceof Views); + views.baz = views.addView.bind(views); + }); + + var pages = collection; + + pages.foo({path: 'a', content: '...'}); + pages.bar({path: 'b', content: '...'}); + pages.baz({path: 'c', content: '...'}); + + assert(collection.views.hasOwnProperty('a')); + assert(collection.views.hasOwnProperty('b')); + assert(collection.views.hasOwnProperty('c')); + }); + + it('should pass to view `use` if a function is returned:', function() { + collection.use(function(views) { + assert(views instanceof Views); + + return function(view) { + view.foo = views.addView.bind(views); + assert(view instanceof View); + }; + }); + + collection.addView('a', {content: '...'}) + .foo({path: 'b', content: '...'}) + .foo({path: 'c', content: '...'}) + .foo({path: 'd', content: '...'}); + + assert(collection.views.hasOwnProperty('a')); + assert(collection.views.hasOwnProperty('b')); + assert(collection.views.hasOwnProperty('c')); + assert(collection.views.hasOwnProperty('d')); + }); + + it('should be chainable when a view function is returned:', function() { + collection + .use(function(views) { + assert(views instanceof Views); + + return function(view) { + view.foo = views.addView.bind(views); + assert(view instanceof View); + }; + }) + .use(function(views) { + assert(views instanceof Views); + + return function(view) { + view.bar = views.addView.bind(views); + assert(view instanceof View); + }; + }) + .use(function(views) { + assert(views instanceof Views); + + return function(view) { + view.baz = views.addView.bind(views); + assert(view instanceof View); + }; + }); + + collection.addView('a', {content: '...'}) + .foo({path: 'b', content: '...'}) + .bar({path: 'c', content: '...'}) + .baz({path: 'd', content: '...'}); + + assert(collection.views.hasOwnProperty('a')); + assert(collection.views.hasOwnProperty('b')); + assert(collection.views.hasOwnProperty('c')); + assert(collection.views.hasOwnProperty('d')); + }); +}); diff --git a/updatefile.js b/updatefile.js index 2126403..134ef44 100644 --- a/updatefile.js +++ b/updatefile.js @@ -1,7 +1,138 @@ 'use strict'; +var fs = require('fs'); +var path = require('path'); +var utils = require('./lib/utils'); + /** - * temporary noop + * This is the defalt updater, it can also be used + * to extend other updaters. */ -module.exports = function() {}; +module.exports = function(update, base, env) { + var async = utils.async; + var glob = utils.glob; + + /** + * TODO: User help and defaults + */ + + update.register('defaults', function(app) { + app.task('init', function(cb) { + app.questions.set('init', 'Want to generate a new project?'); + app.ask('init', { force: true }, function(err, answers) { + if (err) return cb(err); + + if (answers.init === 'y') { + var generate = require('generate')(); + generate.fn(update, update, update.env); + update.build(['prompt', 'files', 'write'], cb); + } else { + cb(); + } + }); + }); + + app.task('help', function(cb) { + console.log('Would you like to choose a updater to run?'); + console.log('(implement me!)') + cb(); + }); + + app.task('error', function(cb) { + console.log('update > error (implement me!)'); + cb(); + }); + }); + + /** + * Data store tasks + */ + + update.register('store', function(app) { + app.task('del', function(cb) { + update.store.del({ force: true }); + console.log('deleted data store'); + cb(); + }); + }); + + /** + * Default configuration settings + */ + + update.task('defaultConfig', function(cb) { + update.engine(['md', 'text'], require('engine-base')); + update.data({year: new Date().getFullYear()}); + update.create('files', { + renameKey: function(key) { + return path.basename(key, path.extname(key)); + } + }); + + cb(); + }); + + /** + * User prompts + */ + + update.task('prompt', function(cb) { + var pkg = env.config.pkg; + + if (!pkg || env.user.isEmpty || env.argv.raw.init) { + forceQuestions(update); + } + + update.questions.setData(pkg || {}); + update.ask({ save: false }, function(err, answers) { + if (err) return cb(err); + if (!pkg) answers = {}; + + answers.name = answers.name || utils.project(); + answers.varname = utils.namify(answers.name); + update.set('answers', answers); + cb(); + }); + }); + + /** + * Load files to be rendered + */ + + update.task('files', ['defaultConfig'], function(cb) { + var opts = { cwd: env.user.cwd, dot: true, ignore: ['**/node_modules/**'] }; + var patterns = ['**/*', 'lib/*.js', 'bin/*.js', 'test/*.js']; + update.files(patterns, opts); + cb(); + }); + + /** + * Write files to disk + */ + + // update.task('write', function() { + // var data = update.get('answers'); + // return update.toStream('files') + // // .pipe(update.renderFile('text', data)) + // .pipe(update.dest(rename(dest))); + // }); + + /** + * Generate a new project + */ + + update.task('updaters', ['files']); + + /** + * Default task to be run + */ + + update.task('default', function(cb) { + update.build('files', cb); + }); +}; + +function forceQuestions(update) { + update.questions.options.forceAll = true; +} From b9b718a292288fbfb00d557b7e6621479f982d21 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sun, 10 Jan 2016 12:05:29 -0500 Subject: [PATCH 161/274] lint tests --- test/fixtures/two/updatefile.js | 6 +++--- test/update.compose.js | 8 ++++---- test/update.extendGenerator.js | 8 ++++---- test/update.generator.js | 8 ++++---- test/update.getGenerator.js | 4 ++-- test/update.js | 24 ++++++++++++------------ test/update.process.js | 6 +++--- test/update.register.js | 12 ++++++------ test/update.registerPath.js | 8 ++++---- 9 files changed, 42 insertions(+), 42 deletions(-) diff --git a/test/fixtures/two/updatefile.js b/test/fixtures/two/updatefile.js index df28790..ad4cb16 100644 --- a/test/fixtures/two/updatefile.js +++ b/test/fixtures/two/updatefile.js @@ -1,7 +1,7 @@ 'use strict'; -var Generate = require('../../..'); -var update = new Generate(); +var Update = require('../../..'); +var update = new Update(); update.task('default', function() {}); update.task('a', function() {}); @@ -15,7 +15,7 @@ update.register('foo', function(app) { }); /** - * Expose this instance of `Generate` + * Expose this instance of `Update` */ module.exports = update; diff --git a/test/update.compose.js b/test/update.compose.js index 4e09adc..2e14954 100644 --- a/test/update.compose.js +++ b/test/update.compose.js @@ -3,17 +3,17 @@ require('mocha'); require('should'); var assert = require('assert'); var support = require('./support'); -var Generate = support.resolve(); -var Base = Generate.Base; +var Update = support.resolve(); +var Base = Update.Base; var update; describe('update.compose', function() { beforeEach(function() { - update = new Generate(); + update = new Update(); }); it('should throw an error when trying to compose an instance', function(cb) { - var foo = new Generate({name: 'foo'}); + var foo = new Update({name: 'foo'}); try { update.compose(foo); cb(new Error('Expected an error.')); diff --git a/test/update.extendGenerator.js b/test/update.extendGenerator.js index d55e4c6..f418bd5 100644 --- a/test/update.extendGenerator.js +++ b/test/update.extendGenerator.js @@ -3,17 +3,17 @@ require('mocha'); require('should'); var assert = require('assert'); var support = require('./support'); -var Generate = support.resolve(); -var Base = Generate.Base; +var Update = support.resolve(); +var Base = Update.Base; var update; describe('update.extendGenerator', function() { beforeEach(function() { - update = new Generate(); + update = new Update(); }); it('should throw an error when trying to extend an instance', function(done) { - var foo = new Generate({name: 'foo'}); + var foo = new Update({name: 'foo'}); try { update.extendGenerator(foo); done(new Error('Expected an error.')); diff --git a/test/update.generator.js b/test/update.generator.js index 6cf9c48..ba213ca 100644 --- a/test/update.generator.js +++ b/test/update.generator.js @@ -3,15 +3,15 @@ require('mocha'); require('should'); var assert = require('assert'); var support = require('./support'); -var Generate = support.resolve(); -var Base = Generate.Base; +var Update = support.resolve(); +var Base = Update.Base; var update; var one; var two; describe('update.generator', function() { before(function() { - update = new Generate(); + update = new Update(); }); it('should register a generator function from a file path', function() { @@ -21,7 +21,7 @@ describe('update.generator', function() { update.generators.one.should.deepEqual(one); }); - it('should register a Generate instance from a file path', function() { + it('should register a Update instance from a file path', function() { two = update.generator('two', './test/fixtures/two/updatefile.js'); update.generators.should.have.property('two'); assert(typeof update.generators.two === 'object'); diff --git a/test/update.getGenerator.js b/test/update.getGenerator.js index f711007..1f868b7 100644 --- a/test/update.getGenerator.js +++ b/test/update.getGenerator.js @@ -1,10 +1,10 @@ var assert = require('assert'); -var Generate = require('..'); +var Update = require('..'); var update; describe('.getGenerator', function() { beforeEach(function() { - update = new Generate(); + update = new Update(); }); it('should get a generator from the base instance', function() { diff --git a/test/update.js b/test/update.js index 27d461d..51b7630 100644 --- a/test/update.js +++ b/test/update.js @@ -5,25 +5,25 @@ require('mocha'); require('should'); var assert = require('assert'); var support = require('./support'); -var Generate = support.resolve(); +var Update = support.resolve(); var update; describe('update', function() { describe('constructor', function() { - it('should create an instance of Generate:', function() { - update = new Generate(); - assert(update instanceof Generate); + it('should create an instance of Update:', function() { + update = new Update(); + assert(update instanceof Update); }); it('should new up without new:', function() { - update = Generate(); - assert(update instanceof Generate); + update = Update(); + assert(update instanceof Update); }); }); describe('prototype methods', function() { beforeEach(function() { - update = new Generate(); + update = new Update(); }); it('should expose `addLeaf`', function() { @@ -73,7 +73,7 @@ describe('update', function() { describe('prototype properties', function() { beforeEach(function() { - update = new Generate(); + update = new Update(); }); it('should expose `name`', function() { @@ -87,7 +87,7 @@ describe('update', function() { describe('instance', function() { beforeEach(function() { - update = new Generate(); + update = new Update(); }); it('should set `name` to `update` when `_name` is defined', function() { @@ -111,7 +111,7 @@ describe('update', function() { }); it('should use `options.name` for `name`', function() { - update = new Generate({name: 'update'}); + update = new Update({name: 'update'}); delete update._name; assert.equal(update.name, 'update'); }); @@ -121,13 +121,13 @@ describe('update', function() { }); it('should return generator "base" as `base`', function() { - var base = new Generate(); + var base = new Update(); update.register('base', base); update.base.should.deepEqual(base); }); it('should return update as `base`', function() { - var child = new Generate(); + var child = new Update(); update.register('child', child); child.base.should.deepEqual(update); }); diff --git a/test/update.process.js b/test/update.process.js index cf9a4bd..6064edd 100644 --- a/test/update.process.js +++ b/test/update.process.js @@ -7,7 +7,7 @@ var assert = require('assert'); var rimraf = require('rimraf'); var through = require('through2'); var files = require('expand-files'); -var Generate = require('..'); +var Update = require('..'); var app, files, config; var fixtures = path.join(__dirname, 'fixtures'); @@ -44,7 +44,7 @@ function base(cb) { describe('process plugins', function() { beforeEach(function(cb) { - app = new Generate(); + app = new Update(); rimraf(actual, cb); }); @@ -150,7 +150,7 @@ describe('process plugins', function() { describe('process()', function() { beforeEach(function(cb) { - app = new Generate(); + app = new Update(); rimraf(actual, cb); }); diff --git a/test/update.register.js b/test/update.register.js index 30b0cf0..0386fa4 100644 --- a/test/update.register.js +++ b/test/update.register.js @@ -3,17 +3,17 @@ require('mocha'); require('should'); var assert = require('assert'); var support = require('./support'); -var Generate = support.resolve(); -var Base = Generate.Base; +var Update = support.resolve(); +var Base = Update.Base; var update; describe('update.register', function() { beforeEach(function() { - update = new Generate(); + update = new Update(); }); - it('should register a Generate instance', function() { - var child = new Generate(); + it('should register a Update instance', function() { + var child = new Update(); update.register('child', child); update.generators.should.have.property('child'); assert(typeof update.generators.child === 'object'); @@ -25,7 +25,7 @@ describe('update.register', function() { var child = update.register('child', function(app, base, env) { registered = true; assert(typeof app === 'object'); - assert(app.isGenerate === true); + assert(app.isUpdate === true); }); assert(registered); update.generators.should.have.property('child'); diff --git a/test/update.registerPath.js b/test/update.registerPath.js index 49ac3b8..bbbe211 100644 --- a/test/update.registerPath.js +++ b/test/update.registerPath.js @@ -3,13 +3,13 @@ require('mocha'); require('should'); var assert = require('assert'); var support = require('./support'); -var Generate = support.resolve(); -var Base = Generate.Base; +var Update = support.resolve(); +var Base = Update.Base; var update; describe('update.registerPath', function() { beforeEach(function() { - update = new Generate(); + update = new Update(); }); it('should register a generator function from a filepath', function() { @@ -19,7 +19,7 @@ describe('update.registerPath', function() { update.generators.one.should.deepEqual(one); }); - it('should register a Generate instance from a filepath', function() { + it('should register a Update instance from a filepath', function() { var two = update.registerPath('two', './test/fixtures/two/updatefile.js'); update.generators.should.have.property('two'); assert(typeof update.generators.two === 'object'); From 9ec6de74cb515bd2a9a2a1ce867fbcfd7bff2ad4 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sun, 10 Jan 2016 12:06:10 -0500 Subject: [PATCH 162/274] adds `cwd` getter/setter, `pkg` method and `ignores` method --- docs/cli.md | 10 +++++++ index.js | 42 ++++++++++++++++++++++++++ lib/generator.js | 5 +++- lib/ignore.js | 75 ++++++++++++++++++++++++++++++++++++++++++++++ lib/utils.js | 18 +++++++++-- package.json | 13 ++++++-- test/update.pkg.js | 42 ++++++++++++++++++++++++++ updatefile.js | 44 ++++++++++++--------------- 8 files changed, 218 insertions(+), 31 deletions(-) create mode 100644 docs/cli.md create mode 100644 lib/ignore.js create mode 100644 test/update.pkg.js diff --git a/docs/cli.md b/docs/cli.md new file mode 100644 index 0000000..4fdfa72 --- /dev/null +++ b/docs/cli.md @@ -0,0 +1,10 @@ +# CLI + +## Resolving updaters + +1. Resolve the CWD (Current Working Directory) +2. Create a `base` instance +3. Try to resolve a "configfile" + - if configfile is a function + - if configfile is an instance + - if configfile does not exist \ No newline at end of file diff --git a/index.js b/index.js index 526ac57..7b00eff 100644 --- a/index.js +++ b/index.js @@ -6,6 +6,7 @@ var path = require('path'); var Generate = require('generate'); +var ignore = require('./lib/ignore'); var utils = require('./lib/utils'); var cli = require('./lib/cli'); @@ -53,9 +54,11 @@ Generate.extend(Update); */ Update.prototype.initPlugins = function(app) { + enable('ignore', ignore); enable('middleware', utils.middleware); enable('loader', utils.loader); enable('config', utils.config); + enable('pkg', utils.pkg); enable('cli', cli); function enable(name, fn) { @@ -66,6 +69,30 @@ Update.prototype.initPlugins = function(app) { } }; +/** + * Set `prop` with the given `value`, but only if `prop` is + * not already defined. + * + * ```js + * app.set('cwd', 'foo'); + * app.fillin('cwd', process.cwd()); + * console.log(app.get('cwd')); + * //=> 'foo' + * ``` + * @param {String} prop + * @param {any} val + * @return {Object} Returns the instance for chaining + * @api public + */ + +Update.prototype.fillin = function(prop, val) { + var current = this.get(prop); + if (typeof current === 'undefined') { + this.set(prop, val); + } + return this; +}; + /** * Ensure `name` is set on the instance for lookups. */ @@ -80,6 +107,21 @@ Object.defineProperty(Update.prototype, 'name', { } }); +/** + * Ensure `name` is set on the instance for lookups. + */ + +Object.defineProperty(Update.prototype, 'cwd', { + configurable: true, + set: function(cwd) { + this.options.cwd = path.resolve(cwd); + }, + get: function() { + var cwd = this.get('env.user.cwd') || this.options.cwd || process.cwd(); + return (this.options.cwd = path.resolve(cwd)); + } +}); + /** * Expose `Update` */ diff --git a/lib/generator.js b/lib/generator.js index 5b57802..75a3d71 100644 --- a/lib/generator.js +++ b/lib/generator.js @@ -1,5 +1,8 @@ 'use strict'; module.exports = function(app, base, env) { - + app.task('default', function(cb) { + console.log('lib/updater > default'); + cb(); + }); }; diff --git a/lib/ignore.js b/lib/ignore.js new file mode 100644 index 0000000..8110e46 --- /dev/null +++ b/lib/ignore.js @@ -0,0 +1,75 @@ +'use strict'; + +var fs = require('fs'); +var path = require('path'); +var utils = require('./utils'); + +/** + * Common directories to ignore + */ + +var exclusions = [ + '.git', + 'actual', + 'coverage', + 'expected', + 'fixtures', + 'node_modules', + 'temp', + 'templates', + 'test/actual', + 'test/fixtures', + 'tmp', + 'vendor', + 'wip' +]; + +/** + * Directories to exclude in the search + */ + +module.exports = function(options) { + var opts = utils.merge({}, options); + + return function(app) { + + this.define('ignores', function(patterns) { + patterns = utils.arrayify(patterns); + + var pkgIgnores = this.pkg.get('update.ignore') || []; + var arr = ignores(patterns, this.cwd); + arr = arr.concat(pkgIgnores); + + var res = arr.map(function(pattern) { + pattern = pattern.replace(/^[*]{2}|[*]{2}$/, ''); + pattern = pattern.replace(/^\/|\/$/, ''); + return '**/' + pattern + '/**'; + }); + return utils.unique(res); + }); + }; +}; + +/** + * Directories to exclude in the search + */ + +function ignores(customPatterns, cwd) { + return gitignore('.gitignore', cwd) + .concat(customPatterns || []) + .concat(exclusions) + .sort(); +} + +/** + * Parse the local `.gitignore` file and add + * the resulting ignore patterns. + */ + +function gitignore(fp, cwd) { + fp = path.resolve(cwd, fp); + if (!utils.exists(fp)) { + return []; + } + return utils.parseGitignore(fp); +} diff --git a/lib/utils.js b/lib/utils.js index c4eaa14..c0bff39 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -12,20 +12,22 @@ require = utils; // plugins, middleware or helpers require('assemble-loader', 'loader'); -require('base-argv', 'argv'); -require('base-cli', 'cli'); require('base-config', 'config'); require('common-middleware', 'middleware'); // other +require('array-unique', 'unique'); require('async'); require('find-pkg'); +require('get-value', 'get'); require('global-modules', 'gm'); require('isobject', 'isObject'); require('matched', 'glob'); require('mixin-deep', 'merge'); require('namify'); require('opn'); +require('parse-gitignore'); +require('pkg-store'); require('project-name', 'project'); require('resolve-dir'); require('try-open'); @@ -38,6 +40,16 @@ require('success-symbol'); require('time-stamp', 'stamp'); require = fn; +/** + * Add `pkg-store` methods to the instance + */ + +utils.pkg = function() { + return function(app) { + app.define('pkg', utils.pkgStore(app.cwd)); + }; +}; + /** * Green checkmark * @@ -130,7 +142,7 @@ utils.exists = function exists(fp) { }; utils.arrayify = function arrayify(val) { - if (!val) return val; + if (!val) return []; return Array.isArray(val) ? val : [val]; }; diff --git a/package.json b/package.json index 8aaeaf1..0fa600a 100644 --- a/package.json +++ b/package.json @@ -35,10 +35,9 @@ }, "dependencies": { "ansi-colors": "^0.1.0", + "array-unique": "^0.2.1", "assemble-loader": "^0.2.6", "async": "^1.5.2", - "base-argv": "^0.3.0", - "base-cli": "^0.4.0", "base-config": "^0.3.3", "common-middleware": "^0.2.2", "define-property": "^0.2.5", @@ -58,7 +57,9 @@ "mixin-deep": "^1.1.3", "namify": "^0.1.3", "opn": "^3.0.3", + "parse-gitignore": "^0.2.0", "parser-front-matter": "^1.3.0", + "pkg-store": "^0.1.0", "project-name": "^0.2.3", "resolve-dir": "^0.1.0", "rimraf": "^2.5.0", @@ -99,6 +100,10 @@ "update" ], "lint-deps": { + "devDpendencies": [ + "coveralls", + "istanbul" + ], "ignore": [ "bin2", "lib2", @@ -141,6 +146,10 @@ "helpers": { "related": "@/helper-related" }, + "ignore": [ + "lib2", + "test2" + ], "plugins": { "./lib/pipeline/a": {}, "./lib/pipeline/b": {}, diff --git a/test/update.pkg.js b/test/update.pkg.js new file mode 100644 index 0000000..637cba6 --- /dev/null +++ b/test/update.pkg.js @@ -0,0 +1,42 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var support = require('./support'); +var Update = support.resolve(); +var update; + +describe('.pkg', function() { + beforeEach(function() { + update = new Update(); + }); + + describe('methods', function() { + it('should expose a pkg object', function() { + assert(update.pkg); + assert.equal(typeof update.pkg, 'object'); + }); + + it('should expose a pkg.set method', function() { + assert.equal(typeof update.pkg.set, 'function'); + }); + + it('should expose a pkg.get method', function() { + assert.equal(typeof update.pkg.get, 'function'); + }); + + it('should expose a pkg.del method', function() { + assert.equal(typeof update.pkg.del, 'function'); + }); + + it('should expose a pkg.union method', function() { + assert.equal(typeof update.pkg.union, 'function'); + }); + }); + + describe('cwd', function() { + it('should get the package.json from the working directory', function() { + assert.equal(update.pkg.get('name'), 'update'); + }); + }); +}); diff --git a/updatefile.js b/updatefile.js index 134ef44..f2b2272 100644 --- a/updatefile.js +++ b/updatefile.js @@ -57,22 +57,6 @@ module.exports = function(update, base, env) { }); }); - /** - * Default configuration settings - */ - - update.task('defaultConfig', function(cb) { - update.engine(['md', 'text'], require('engine-base')); - update.data({year: new Date().getFullYear()}); - update.create('files', { - renameKey: function(key) { - return path.basename(key, path.extname(key)); - } - }); - - cb(); - }); - /** * User prompts */ @@ -96,14 +80,24 @@ module.exports = function(update, base, env) { }); }); + /** + * Default configuration settings + */ + + update.task('defaultConfig', function(cb) { + update.engine(['md', 'text'], require('engine-base')); + update.data({year: new Date().getFullYear()}); + update.create('files'); + cb(); + }); + /** * Load files to be rendered */ update.task('files', ['defaultConfig'], function(cb) { - var opts = { cwd: env.user.cwd, dot: true, ignore: ['**/node_modules/**'] }; - var patterns = ['**/*', 'lib/*.js', 'bin/*.js', 'test/*.js']; - update.files(patterns, opts); + var opts = { cwd: update.cwd, dot: true, ignore: update.ignores()}; + update.files(['**/*'], opts); cb(); }); @@ -111,12 +105,12 @@ module.exports = function(update, base, env) { * Write files to disk */ - // update.task('write', function() { - // var data = update.get('answers'); - // return update.toStream('files') - // // .pipe(update.renderFile('text', data)) - // .pipe(update.dest(rename(dest))); - // }); + update.task('write', function() { + var data = update.get('answers'); + return update.toStream('files') + // .pipe(update.renderFile('text', data)) + // .pipe(update.dest(rename(dest))); + }); /** * Generate a new project From 62694890da0f7f84d4dc8738a37f2cb5fa256873 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sun, 10 Jan 2016 19:32:52 -0500 Subject: [PATCH 163/274] adds/organizes cli commands --- lib/cli.js | 25 +++++++------------------ lib/commands/cwd.js | 14 ++++++++++++++ lib/commands/{ask.js => init.js} | 0 lib/commands/save.js | 11 +++++++++++ lib/settings/index.js | 1 + 5 files changed, 33 insertions(+), 18 deletions(-) create mode 100644 lib/commands/cwd.js rename lib/commands/{ask.js => init.js} (100%) create mode 100644 lib/commands/save.js create mode 100644 lib/settings/index.js diff --git a/lib/cli.js b/lib/cli.js index 94ee1f2..70bd2a0 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -4,7 +4,7 @@ var commands = require('./commands'); var utils = require('./utils'); /** - * Assemble CLI + * Update CLI * * Custom extensions to the built-in mappings * provided by the `base-cli` plugin. @@ -21,31 +21,24 @@ module.exports = function(options) { */ app.cli - .map('init', function(fp) { - console.log('cli > init (implement me!)'); - app.set('questions.options.forceAll', true); - }) + .map('init', commands.init(app)) .map('help', commands.help(app)) .map('show', commands.show(app)) .map('open', commands.open(app)) .map('diff', function(val) { app.option('diff', val); - }) + }); /** * Options, settings and context related */ app.cli - .map('ask', commands.ask(app)) + .map('ask', commands.init(app)) .map('cwd', function(val) { app.option('cwd', val); }) - .map('save', function(val) { - app.store.config.set(val); - val = utils.tableize(val); - console.log('saved > "%j" %s', val, 'in global config store.'); - }) + .map('save', commands.save(app)) .map('data', function(val) { app.data(val); }) @@ -64,14 +57,10 @@ module.exports = function(options) { app.cli .map('choose', function(key) { - if (key === true) { - app.enable('tasks.choose'); - } + app.enable('tasks.choose'); }) .map('tasks', function(key) { - if (key === true) { - app.enable('tasks.display'); - } + app.enable('tasks.display'); }); }; diff --git a/lib/commands/cwd.js b/lib/commands/cwd.js new file mode 100644 index 0000000..915c496 --- /dev/null +++ b/lib/commands/cwd.js @@ -0,0 +1,14 @@ +'use strict'; + +var path = require('path'); + +module.exports = function(app) { + return function(cwd) { + var cwd = path.resolve(cwd); + if (cwd !== process.cwd()) { + process.chdir(cwd); + cwd = process.cwd(); + } + app.option('cwd', cwd); + } +}; diff --git a/lib/commands/ask.js b/lib/commands/init.js similarity index 100% rename from lib/commands/ask.js rename to lib/commands/init.js diff --git a/lib/commands/save.js b/lib/commands/save.js new file mode 100644 index 0000000..0a59249 --- /dev/null +++ b/lib/commands/save.js @@ -0,0 +1,11 @@ +'use strict'; + +var utils = require('../utils'); + +module.exports = function(app) { + return function(val) { + app.store.config.set(val); + val = utils.tableize(val); + console.log('saved > "%j" %s', val, 'in global config store.'); + }; +}; diff --git a/lib/settings/index.js b/lib/settings/index.js new file mode 100644 index 0000000..23b2930 --- /dev/null +++ b/lib/settings/index.js @@ -0,0 +1 @@ +module.exports = require('export-files')(__dirname); From 50120d7342c45d7adda516fa550025f790a46c7e Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sun, 10 Jan 2016 19:33:33 -0500 Subject: [PATCH 164/274] break ignore logic into more re-usable methods --- lib/ignore.js | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/lib/ignore.js b/lib/ignore.js index 8110e46..5430de2 100644 --- a/lib/ignore.js +++ b/lib/ignore.js @@ -25,38 +25,42 @@ var exclusions = [ ]; /** - * Directories to exclude in the search + * Convert gitignore to an array of glob patterns */ -module.exports = function(options) { - var opts = utils.merge({}, options); +exports.gitignore = function(cwd) { + var arr = ignores(cwd).map(function(pattern) { + return exports.toGlob(pattern); + }); + return utils.unique(arr); +}; - return function(app) { +/** + * Convert an array of `gitignore` patterns (git uses wildmatch) to + * close-enough approximations of glob-style patterns. + */ - this.define('ignores', function(patterns) { - patterns = utils.arrayify(patterns); +exports.toGlobs = function(patterns) { + return utils.arrayify(patterns).map(exports.toGlob); +}; - var pkgIgnores = this.pkg.get('update.ignore') || []; - var arr = ignores(patterns, this.cwd); - arr = arr.concat(pkgIgnores); +/** + * Convert a `gitignore` pattern (git uses wildmatch) to + * a close-enough approximation of a glob-style pattern. + */ - var res = arr.map(function(pattern) { - pattern = pattern.replace(/^[*]{2}|[*]{2}$/, ''); - pattern = pattern.replace(/^\/|\/$/, ''); - return '**/' + pattern + '/**'; - }); - return utils.unique(res); - }); - }; +exports.toGlob = function(pattern) { + pattern = pattern.replace(/^[*]{2}|[*]{2}$/, ''); + pattern = pattern.replace(/^\/|\/$/, ''); + return '**/' + pattern + '/**'; }; /** * Directories to exclude in the search */ -function ignores(customPatterns, cwd) { +function ignores(cwd) { return gitignore('.gitignore', cwd) - .concat(customPatterns || []) .concat(exclusions) .sort(); } From 23a7fc4bfe8974da50bbeff406fcfb5f58becd02 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sun, 10 Jan 2016 19:36:28 -0500 Subject: [PATCH 165/274] adds several new methods: - `getFile` for getting/matching files on the `files` collection - `union`, for adding and appending arrays on `update.cache` - `ignore`, for easily creating ignore patterns that will work with glob (from gitignore etc) also reworks plugin init logic, adds code comments --- index.js | 109 +++++++++++++++++++++++++++++++++++++++++++-------- lib/utils.js | 2 + package.json | 1 + 3 files changed, 96 insertions(+), 16 deletions(-) diff --git a/index.js b/index.js index 7b00eff..c335ab2 100644 --- a/index.js +++ b/index.js @@ -6,6 +6,7 @@ var path = require('path'); var Generate = require('generate'); +var config = require('./lib/config'); var ignore = require('./lib/ignore'); var utils = require('./lib/utils'); var cli = require('./lib/cli'); @@ -53,22 +54,44 @@ Generate.extend(Update); * ``` */ -Update.prototype.initPlugins = function(app) { - enable('ignore', ignore); - enable('middleware', utils.middleware); - enable('loader', utils.loader); - enable('config', utils.config); - enable('pkg', utils.pkg); - enable('cli', cli); - - function enable(name, fn) { - if (app.option('plugins') === false) return; - if (app.option('plugins.' + name) !== false) { - app.use(fn(app.options)); - } +Update.prototype.initPlugins = function() { + this.use(utils.middleware()) + .use(utils.loader()) + .use(utils.pkg()) + .use(config()) + .use(cli()); +}; + +/** + * Lazily add gitignore patterns to `update.cache.ignores` + * + * @return {[type]} + */ + +Update.prototype.lazyIgnores = function() { + if (!this.cache.ignores) { + this.set('cache.ignores', ignore.gitignore(this.cwd)); } }; +/** + * Add ignore patterns to the `update.cache.ignores` array. This + * array is initially populated with patterns from `gitignore` + * + * ```js + * update.ignore(['foo', 'bar']); + * ``` + * @param {String|Array} `patterns` + * @return {Object} returns the instance for chaining + * @api public + */ + +Update.prototype.ignore = function(patterns) { + this.lazyIgnores(); + this.union('ignores', ignore.toGlobs(patterns)); + return this; +}; + /** * Set `prop` with the given `value`, but only if `prop` is * not already defined. @@ -79,8 +102,8 @@ Update.prototype.initPlugins = function(app) { * console.log(app.get('cwd')); * //=> 'foo' * ``` - * @param {String} prop - * @param {any} val + * @param {String} `prop` The name of the property to define + * @param {any} `val` The value to use if a value is _not already defined_ * @return {Object} Returns the instance for chaining * @api public */ @@ -93,6 +116,60 @@ Update.prototype.fillin = function(prop, val) { return this; }; +/** + * Get a file from the `update.files` collection. + * + * ```js + * update.getFile('LICENSE'); + * ``` + * @param {String} `pattern` Pattern to use for matching. Checks against + * @return {Object} If successful, a `file` object is returned, otherwise `null` + * @api public + */ + +Update.prototype.getFile = function(pattern) { + // "views" are "template objects", but we're + // exposing them as `files` + var file = this.files.getView(pattern); + if (file) return file; + for (var key in this.views.files) { + var file = this.views.files[key]; + if (file.basename === pattern) return file; + if (file.filename === pattern) return file; + if (file.path === pattern) return file; + if (file.key === pattern) return file; + if (utils.mm.isMatch(key, pattern)) { + return file; + } + } + return null; +}; + +/** + * Create or append array `name` on `update.cache` with the + * given (uniqueified) `items`. Supports setting deeply nested + * properties using using object-paths/dot notation. + * + * ```js + * update.union('foo', 'bar'); + * update.union('foo', 'baz'); + * update.union('foo', 'qux'); + * update.union('foo', 'qux'); + * update.union('foo', 'qux'); + * console.log(update.cache.foo); + * //=> ['bar', 'baz', 'qux']; + * ``` + * @param {String} `name` + * @param {any} `items` + * @return {Object} returns the instance for chaining + * @api public + */ + +Update.prototype.union = function(name, items) { + utils.union(this.cache, name, utils.arrayify(items)); + return this; +}; + /** * Ensure `name` is set on the instance for lookups. */ @@ -118,7 +195,7 @@ Object.defineProperty(Update.prototype, 'cwd', { }, get: function() { var cwd = this.get('env.user.cwd') || this.options.cwd || process.cwd(); - return (this.options.cwd = path.resolve(cwd)); + return path.resolve(cwd); } }); diff --git a/lib/utils.js b/lib/utils.js index c0bff39..fde6509 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -23,6 +23,7 @@ require('get-value', 'get'); require('global-modules', 'gm'); require('isobject', 'isObject'); require('matched', 'glob'); +require('micromatch', 'mm'); require('mixin-deep', 'merge'); require('namify'); require('opn'); @@ -32,6 +33,7 @@ require('project-name', 'project'); require('resolve-dir'); require('try-open'); require('word-wrap', 'wrap'); +require('union-value', 'union'); // CLI require('ansi-colors', 'colors'); diff --git a/package.json b/package.json index 0fa600a..9c35151 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "lazy-cache": "^1.0.3", "load-pkg": "^3.0.1", "matched": "^0.4.1", + "micromatch": "^2.3.7", "minimist": "^1.2.0", "mixin-deep": "^1.1.3", "namify": "^0.1.3", From 386c895bb51102f4faf02688000ae81877bfe1f6 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sun, 10 Jan 2016 21:37:30 -0500 Subject: [PATCH 166/274] lint --- lib/commands/cwd.js | 6 +++--- lib/commands/help.js | 4 +--- lib/commands/init.js | 7 ++++--- lib/commands/show.js | 2 +- lib/ignore.js | 1 - 5 files changed, 9 insertions(+), 11 deletions(-) diff --git a/lib/commands/cwd.js b/lib/commands/cwd.js index 915c496..1ac6a7f 100644 --- a/lib/commands/cwd.js +++ b/lib/commands/cwd.js @@ -3,12 +3,12 @@ var path = require('path'); module.exports = function(app) { - return function(cwd) { - var cwd = path.resolve(cwd); + return function(dir) { + var cwd = path.resolve(dir); if (cwd !== process.cwd()) { process.chdir(cwd); cwd = process.cwd(); } app.option('cwd', cwd); - } + }; }; diff --git a/lib/commands/help.js b/lib/commands/help.js index de64933..1aa501a 100644 --- a/lib/commands/help.js +++ b/lib/commands/help.js @@ -4,9 +4,7 @@ var utils = require('../utils'); module.exports = function(app) { return function(key) { - // var commands = help(); - // console.log(commands.options[key]); - // process.exit(0); + // do help stuff... }; }; diff --git a/lib/commands/init.js b/lib/commands/init.js index 6e72d32..1dc7933 100644 --- a/lib/commands/init.js +++ b/lib/commands/init.js @@ -9,15 +9,16 @@ module.exports = function(app) { return; } + var keys; if (utils.isObject(val)) { - var keys = Object.keys(utils.tableize(val)); + keys = Object.keys(utils.tableize(val)); app.questions.enqueue(keys); app.option('questions.init', keys); return; } - var keys = utils.arrayify(val); + keys = utils.arrayify(val); app.questions.enqueue(keys); app.option('questions.init', keys); - } + }; }; diff --git a/lib/commands/show.js b/lib/commands/show.js index b9ccb5c..7e01c94 100644 --- a/lib/commands/show.js +++ b/lib/commands/show.js @@ -8,7 +8,7 @@ module.exports = function(app) { var val = this.get(key) || this.get(['cache', key]) || this.get(['cache.data', key]) - || this.get(['options', key]) + || this.get(['options', key]); if (val) { console.log('showing >', key, val); diff --git a/lib/ignore.js b/lib/ignore.js index 5430de2..789bd9d 100644 --- a/lib/ignore.js +++ b/lib/ignore.js @@ -1,6 +1,5 @@ 'use strict'; -var fs = require('fs'); var path = require('path'); var utils = require('./utils'); From f119713a0d14884019f1bea4ff1cec8d55acfa35 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sun, 10 Jan 2016 22:21:54 -0500 Subject: [PATCH 167/274] move `getFile` into utils, add `templates` --- index.js | 71 ++++++++++++++++++++++++++++++++++++++-------------- lib/utils.js | 38 ++++++++++++++++++++++------ 2 files changed, 83 insertions(+), 26 deletions(-) diff --git a/index.js b/index.js index c335ab2..ca86350 100644 --- a/index.js +++ b/index.js @@ -31,7 +31,10 @@ function Update(options) { Generate.apply(this, arguments); this.updaters = this.generators; this.isUpdate = true; - this.initPlugins(this); + + this.initPlugins(); + this.lazyCollections(); + this.initUpdate(this); } /** @@ -40,6 +43,17 @@ function Update(options) { Generate.extend(Update); +/** + * Lazily initialize default collections + */ + +Update.prototype.lazyCollections = function() { + if (!this.templates) { + this.create('files'); + this.create('templates'); + } +}; + /** * Load default plugins. Built-in plugins can be disabled * on the `update` options. @@ -64,16 +78,34 @@ Update.prototype.initPlugins = function() { /** * Lazily add gitignore patterns to `update.cache.ignores` - * - * @return {[type]} */ Update.prototype.lazyIgnores = function() { if (!this.cache.ignores) { - this.set('cache.ignores', ignore.gitignore(this.cwd)); + this.union('ignores', ignore.gitignore(this.cwd)); } }; +/** + * Initialize `update` defaults + */ + +Update.prototype.initUpdate = function(app) { + this.on('build', function(app, env) { + var ignores = app.get('cache.ignores'); + var cwd = env.config.cwd; + + app.templates('templates/*', { + ignore: ignores, + cwd: cwd, + renameKey: function(key, view) { + var cwd = path.resolve(env.config.cwd); + return path.relative(cwd, path.resolve(cwd, key)); + } + }); + }); +}; + /** * Add ignore patterns to the `update.cache.ignores` array. This * array is initially populated with patterns from `gitignore` @@ -128,21 +160,22 @@ Update.prototype.fillin = function(prop, val) { */ Update.prototype.getFile = function(pattern) { - // "views" are "template objects", but we're - // exposing them as `files` - var file = this.files.getView(pattern); - if (file) return file; - for (var key in this.views.files) { - var file = this.views.files[key]; - if (file.basename === pattern) return file; - if (file.filename === pattern) return file; - if (file.path === pattern) return file; - if (file.key === pattern) return file; - if (utils.mm.isMatch(key, pattern)) { - return file; - } - } - return null; + return utils.getFile(this, 'files', pattern); +}; + +/** + * Get a template from the `update.templates` collection. + * + * ```js + * update.getTemplate('foo.tmpl'); + * ``` + * @param {String} `pattern` Pattern to use for matching. Checks against + * @return {Object} If successful, a `file` object is returned, otherwise `null` + * @api public + */ + +Update.prototype.getTemplate = function(pattern) { + return utils.getFile(this, 'templates', pattern); }; /** diff --git a/lib/utils.js b/lib/utils.js index fde6509..ba4b324 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -117,6 +117,30 @@ utils.logTask = function(appname, taskname) { utils.timestamp('starting ' + utils.colors.cyan(appname + taskname)); }; +/** + * Create a view lookup method + */ + +utils.getFile = function(app, collectionName, pattern) { + // "views" are "template objects", but we're + // exposing them as `files` + var file = app[collectionName].getView(pattern); + if (file) return file; + + var collection = app.getViews(collectionName); + for (var key in collection) { + var file = collection[key]; + if (file.basename === pattern) return file; + if (file.filename === pattern) return file; + if (file.path === pattern) return file; + if (file.key === pattern) return file; + if (utils.mm.isMatch(key, pattern)) { + return file; + } + } + return null; +}; + /** * Return true if a directory exists and is empty. * @@ -124,7 +148,7 @@ utils.logTask = function(appname, taskname) { * @return {Array} */ -utils.isEmpty = function isEmpty(dir) { +utils.isEmpty = function(dir) { var files; try { if (!utils.exists(dir)) { @@ -139,16 +163,16 @@ utils.isEmpty = function isEmpty(dir) { return true; }; -utils.exists = function exists(fp) { +utils.exists = function(fp) { return !!utils.tryOpen(fp, 'r'); }; -utils.arrayify = function arrayify(val) { +utils.arrayify = function(val) { if (!val) return []; return Array.isArray(val) ? val : [val]; }; -utils.extRegex = function extRegex(exts) { +utils.extRegex = function(exts) { return new RegExp('\\.(' + utils.arrayify(exts).join('|') + ')$'); }; @@ -156,7 +180,7 @@ utils.extRegex = function extRegex(exts) { * Try to require a file */ -utils.tryRequire = function tryRequire(name) { +utils.tryRequire = function(name) { try { return require(name); } catch (err) {} @@ -167,7 +191,7 @@ utils.tryRequire = function tryRequire(name) { return {}; }; -utils.tryResolve = function tryResolve(name) { +utils.tryResolve = function(name) { try { return require.resolve(name); } catch (err) {} @@ -195,7 +219,7 @@ utils.tryResolve = function tryResolve(name) { * @api public */ -utils.tableize = function tableize(obj, opts) { +utils.tableize = function(obj, opts) { var ret = {}; opts = opts || {}; type(ret, obj, '', opts); From e542b99ef01dac30df6a44728883a8b3ddb976bd Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sun, 10 Jan 2016 22:22:19 -0500 Subject: [PATCH 168/274] setup `config` --- bin/cli.js | 8 +++++++- lib/config.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 lib/config.js diff --git a/bin/cli.js b/bin/cli.js index fbb0ad7..c997d81 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -91,6 +91,13 @@ function run(cb) { var args = utils.processArgv(app, argv); app.set('argv', args); + /** + * Process configuration settings defined on the + * `update` property in package.json + */ + + app.config.process(app.get('env.user.pkg.update')); + /** * Show path to updatefile */ @@ -114,7 +121,6 @@ function run(cb) { */ app.env.on('config', function(name, env) { - console.log(name) app.register(name, env.config.fn, env); }); diff --git a/lib/config.js b/lib/config.js new file mode 100644 index 0000000..afb3831 --- /dev/null +++ b/lib/config.js @@ -0,0 +1,28 @@ +'use strict'; + +var settings = require('./settings'); +var utils = require('./utils'); + +/** + * Custom extensions to the built-in mappings + * provided by the `base-config` plugin. + */ + +module.exports = function(options) { + return function(app) { + if (!app.config) { + app.use(utils.config()); + } + + app.config + .map('data', function(val) { + app.data(val); + }) + .map('options', function(val) { + app.option(val); + }) + .map('ignore', function(val) { + app.ignore(val); + }) + }; +}; From b8c58f57f754f563a9e7c96c18f4b056c58b028e Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sun, 10 Jan 2016 22:22:27 -0500 Subject: [PATCH 169/274] start `env` docs --- docs/env.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 docs/env.md diff --git a/docs/env.md b/docs/env.md new file mode 100644 index 0000000..871422b --- /dev/null +++ b/docs/env.md @@ -0,0 +1,10 @@ +# Environment + +At a high level, the `env` object provides easy-access to information about certain "environment" details, like the user's working directory, filepaths to generators or updatefiles, and so on. + +There are three main properties on the `env` object, each is an object with additional properties: + +- [env.user](#env-user) +- [env.config](#env-config) +- [env.module](#env-module) + From 244d0aa66212c4ceb5682d22b578513e6d068236 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sun, 10 Jan 2016 22:24:21 -0500 Subject: [PATCH 170/274] fix ignore patterns in main task --- updatefile.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/updatefile.js b/updatefile.js index f2b2272..93f1917 100644 --- a/updatefile.js +++ b/updatefile.js @@ -85,9 +85,9 @@ module.exports = function(update, base, env) { */ update.task('defaultConfig', function(cb) { + update.lazyIgnores(); update.engine(['md', 'text'], require('engine-base')); update.data({year: new Date().getFullYear()}); - update.create('files'); cb(); }); @@ -96,8 +96,8 @@ module.exports = function(update, base, env) { */ update.task('files', ['defaultConfig'], function(cb) { - var opts = { cwd: update.cwd, dot: true, ignore: update.ignores()}; - update.files(['**/*'], opts); + var ignore = update.get('cache.ignores'); + update.files(['*'], {cwd: update.cwd, dot: true, ignore: ignore}); cb(); }); From 866aa16b999d2fee0c421aeea111224fe75a7afe Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sun, 10 Jan 2016 22:24:29 -0500 Subject: [PATCH 171/274] update docs --- .verb.md | 122 +++++++++++++++++++++++++++++++++++++++++++++++++++--- README.md | 97 ++++++++++++++++++++++++++++++++++++++++--- index.js | 8 ++-- 3 files changed, 213 insertions(+), 14 deletions(-) diff --git a/.verb.md b/.verb.md index 1ffc957..fad0eb2 100644 --- a/.verb.md +++ b/.verb.md @@ -1,11 +1,85 @@ -# {%= name %} {%= badge("fury") %} +# {%= name %} {%= badge("fury") %} {%= badge("travis") %} > {%= description %} -## Install -{%= include("install-npm", {save: true}) %} +## CLI + +### Install + +{%= include("install-global") %} + + +### Commands + +```sh +$ update [options] +``` + +**List updaters** + +Choose from a list of updaters and tasks to run: + +```sh +$ update list +``` + +**Run a specific updater** + +The following would run updater `foo`: + +```sh +$ update foo -## Usage +# run updater "foo" with options +$ update foo --bar=baz +``` + + +### tasks + +_(TODO)_ + +### plugins + +_(TODO)_ + +#### pipeline plugins + +_(TODO)_ + +#### instance plugins + +_(TODO)_ + +### middleware + +A middleware is a function that exposes the following parameters: + +- `file`: **{Object}** [vinyl][] file object +- `next`: **{Function}** must be called to continue on to the next file. + +```js +function rename(file, next) { + file.path = 'foo/' + file.path; + next(); +} + +// example usage: prefix all `.js` file paths with `foo/` +app.onLoad(/\.js/, rename); +``` + +The `onStream` method is a custom [middleware](docs/middleware.md) handler that the `update` + +```js +app.onStream(/lib\//, rename); +``` + + +## API + +### Install + +{%= include("install-npm", {save: true}) %} ```js var update = require('{%= name %}'); @@ -17,6 +91,44 @@ var update = require('{%= name %}'); ## Related projects {%= related(verb.related.list) %} +## Authoring + +### Updaters + +_(TODO)_ + +#### Tasks + +_(TODO)_ + +#### Middleware + +_(TODO)_ + +#### Plugins + +> Updater plugins follow the same signature as gulp plugins + +**Example** + +```js +function myPlugin(options) { + return through.obj(function(file, enc, next) { + var str = file.contents.toString(); + // do stuff to `file` + file.contents = new Buffer(file.contents); + next(null, file); + }); +} +``` + +### Publish + +1. Name your project following the convention: `updater-*` +2. Don't use dots in the name (e.g `.js`) +3. Make sure you add `updater` to the keywords in `package.json` +4. Tweet about your updater! + ## Running tests {%= include("tests") %} @@ -28,7 +140,7 @@ var update = require('{%= name %}'); ## License {%= copyright() %} -{%= license() %} +{%= license %} *** diff --git a/README.md b/README.md index 9718a4c..566deef 100644 --- a/README.md +++ b/README.md @@ -92,13 +92,13 @@ var update = require('update'); ## API -### [Update](index.js#L30) +### [Update](index.js#L26) -Create an `update` application. This is the main function exported by the update module. +Create an instance of `Update`. This is the main function exported by the update module, used for creating `updaters`. **Params** -* `options` **{Object}** +* `options` **{Object}**: Optionally pass default options to use. **Example** @@ -107,6 +107,93 @@ var Update = require('update'); var update = new Update(); ``` +### [.ignore](index.js#L121) + +Add ignore patterns to the `update.cache.ignores` array. This array is initially populated with patterns from `gitignore` + +**Params** + +* `patterns` **{String|Array}** +* `returns` **{Object}**: returns the instance for chaining + +**Example** + +```js +update.ignore(['foo', 'bar']); +``` + +### [.fillin](index.js#L143) + +Set `prop` with the given `value`, but only if `prop` is not already defined. + +**Params** + +* `prop` **{String}**: The name of the property to define +* `val` **{any}**: The value to use if a value is _not already defined_ + +* `returns` **{Object}**: Returns the instance for chaining + +**Example** + +```js +app.set('cwd', 'foo'); +app.fillin('cwd', process.cwd()); +console.log(app.get('cwd')); +//=> 'foo' +``` + +### [.getFile](index.js#L162) + +Get a file from the `update.files` collection. + +**Params** + +* `pattern` **{String}**: Pattern to use for matching. Checks against +* `returns` **{Object}**: If successful, a `file` object is returned, otherwise `null` + +**Example** + +```js +update.getFile('LICENSE'); +``` + +### [.getTemplate](index.js#L177) + +Get a template from the `update.templates` collection. + +**Params** + +* `pattern` **{String}**: Pattern to use for matching. Checks against +* `returns` **{Object}**: If successful, a `file` object is returned, otherwise `null` + +**Example** + +```js +update.getTemplate('foo.tmpl'); +``` + +### [.union](index.js#L201) + +Create or append array `name` on `update.cache` with the given (uniqueified) `items`. Supports setting deeply nested properties using using object-paths/dot notation. + +**Params** + +* `name` **{String}** +* `items` **{any}** +* `returns` **{Object}**: returns the instance for chaining + +**Example** + +```js +update.union('foo', 'bar'); +update.union('foo', 'baz'); +update.union('foo', 'qux'); +update.union('foo', 'qux'); +update.union('foo', 'qux'); +console.log(update.cache.foo); +//=> ['bar', 'baz', 'qux']; +``` + ## Related projects * [assemble](https://www.npmjs.com/package/assemble): Assemble is a powerful, extendable and easy to use static site generator for node.js. Used… [more](https://www.npmjs.com/package/assemble) | [homepage](https://github.com/assemble/assemble) @@ -115,7 +202,7 @@ var update = new Update(); * [generate](https://www.npmjs.com/package/generate): Fast, composable, highly extendable project generator with a user-friendly and expressive API. | [homepage](https://github.com/generate/generate) * [scaffold](https://www.npmjs.com/package/scaffold): Conventions and API for creating declarative configuration objects for project scaffolds - similar in format… [more](https://www.npmjs.com/package/scaffold) | [homepage](https://github.com/jonschlinkert/scaffold) * [templates](https://www.npmjs.com/package/templates): System for creating and managing template collections, and rendering templates with any node.js template engine.… [more](https://www.npmjs.com/package/templates) | [homepage](https://github.com/jonschlinkert/templates) -* [update](https://www.npmjs.com/package/update): Update | [homepage](https://github.com/jonschlinkert/update) +* [update](https://www.npmjs.com/package/update): Easily keep anything in your project up-to-date by installing the updaters you want to use… [more](https://www.npmjs.com/package/update) | [homepage](https://github.com/update/update) * [verb](https://www.npmjs.com/package/verb): Documentation generator for GitHub projects. Verb is extremely powerful, easy to use, and is used… [more](https://www.npmjs.com/package/verb) | [homepage](https://github.com/verbose/verb) ## Authoring @@ -182,4 +269,4 @@ Released under the MIT license. *** -_This file was generated by [verb](https://github.com/verbose/verb) on January 09, 2016._ \ No newline at end of file +_This file was generated by [verb](https://github.com/verbose/verb) on January 10, 2016._ \ No newline at end of file diff --git a/index.js b/index.js index ca86350..2123f58 100644 --- a/index.js +++ b/index.js @@ -12,12 +12,12 @@ var utils = require('./lib/utils'); var cli = require('./lib/cli'); /** - * Create an `update` instance. This is the main function exported - * by the update module. + * Create an instance of `Update`. This is the main function exported + * by the update module, used for creating `updaters`. * * ```js - * var update = require('update'); - * var app = update(); + * var Update = require('update'); + * var update = new Update(); * ``` * @param {Object} `options` Optionally pass default options to use. * @api public From 1920b74c3dabd8ae464e40ff994b262b637f284b Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sun, 10 Jan 2016 22:36:01 -0500 Subject: [PATCH 172/274] remove junk --- _index.js | 169 --- bin2/update.js | 38 - test2/app.applyLayout.js | 87 -- test2/app.collection.compile.js | 50 - test2/app.collection.js | 186 --- test2/app.collection.render.js | 157 --- test2/app.compile.js | 52 - test2/app.copy.js | 31 - test2/app.create.js | 244 ---- test2/app.data.js | 94 -- test2/app.dest.js | 1103 ---------------- test2/app.engines.js | 160 --- test2/app.events.js | 113 -- test2/app.get-set.js | 72 -- test2/app.handle.js | 37 - test2/app.handlers.js | 72 -- test2/app.js | 130 -- test2/app.list.compile.js | 44 - test2/app.list.js | 111 -- test2/app.lookups.js | 112 -- test2/app.middleware.js | 60 - test2/app.onLoad.js | 48 - test2/app.option.js | 98 -- test2/app.render.js | 88 -- test2/app.renderFile.js | 135 -- test2/app.route.js | 93 -- test2/app.src.js | 295 ----- test2/app.symlink.js | 396 ------ test2/app.task.js | 159 --- test2/app.toStream.js | 64 - test2/app.use.js | 281 ---- test2/app.view.compile.js | 38 - test2/app.view.render.js | 92 -- test2/app.watch.js | 42 - test2/collection.engines.js | 176 --- test2/collection.events.js | 27 - test2/collection.getView.js | 34 - test2/collection.js | 540 -------- test2/collection.options.js | 25 - test2/collection.render.js | 138 -- test2/collection.use.js | 156 --- test2/fixtures/bom-utf16be.txt | Bin 244 -> 0 bytes test2/fixtures/bom-utf16le.txt | Bin 244 -> 0 bytes test2/fixtures/bom-utf8.txt | 1 - test2/fixtures/copy/a.txt | 1 - test2/fixtures/copy/b.txt | 1 - test2/fixtures/copy/c.txt | 1 - test2/fixtures/copy/example.txt | 1 - test2/fixtures/data/a.json | 3 - test2/fixtures/data/alert.json | 7 - test2/fixtures/data/b.json | 3 - test2/fixtures/data/c.json | 3 - test2/fixtures/data/data.json | 3 - test2/fixtures/data/test.json | 4 - test2/fixtures/example.txt | 1 - .../front-matter/autodetect-no-lang.md | 5 - .../fixtures/front-matter/autodetect-yaml.md | 5 - test2/fixtures/front-matter/lang-yaml.md | 5 - test2/fixtures/generic/run.dmc | 1 - test2/fixtures/generic/test.dmc | 1 - test2/fixtures/helpers/a.js | 3 - test2/fixtures/helpers/b.js | 3 - test2/fixtures/helpers/c.js | 3 - test2/fixtures/helpers/obj.js | 9 - test2/fixtures/noext/license | 21 - test2/fixtures/pages/a.hbs | 2 - test2/fixtures/pages/b.hbs | 2 - test2/fixtures/pages/c.hbs | 2 - test2/fixtures/pipeline/a.js | 19 - test2/fixtures/pipeline/b.js | 14 - test2/fixtures/pipeline/c.js | 14 - test2/fixtures/pipeline/d.js | 14 - test2/fixtures/posts/a.txt | 4 - test2/fixtures/posts/b.txt | 4 - test2/fixtures/posts/c.txt | 4 - test2/fixtures/templates/a.tmpl | 1 - test2/fixtures/templates/b.tmpl | 1 - test2/fixtures/templates/c.tmpl | 1 - test2/fixtures/test-symlink | 1 - test2/fixtures/test-symlink-dir/suchempty | 1 - test2/fixtures/test.coffee | 1 - test2/fixtures/updaters/a.txt | 1 - test2/fixtures/updaters/b.txt | 1 - test2/fixtures/updaters/c.txt | 1 - test2/fixtures/vinyl/bom-utf16be.txt | Bin 244 -> 0 bytes test2/fixtures/vinyl/bom-utf16le.txt | Bin 244 -> 0 bytes test2/fixtures/vinyl/bom-utf8.txt | 1 - test2/fixtures/vinyl/test-symlink | 1 - test2/fixtures/vinyl/test-symlink-dir | 1 - test2/fixtures/vinyl/test.coffee | 1 - test2/fixtures/vinyl/wow/suchempty | 1 - test2/fixtures/watch/test.txt | 1 - test2/fixtures/wow/suchempty | 1 - test2/group.js | 144 --- test2/handlers.js | 46 - test2/helpers.js | 800 ------------ test2/item.js | 1060 --------------- test2/layouts.js | 127 -- test2/list.js | 689 ---------- test2/list.render.js | 137 -- test2/list.use.js | 156 --- test2/mergePartials.js | 104 -- test2/partials.js | 202 --- test2/questions.js | 58 - test2/renameKey.js | 350 ----- test2/render.js | 72 -- test2/routes.js | 98 -- test2/store.js | 243 ---- test2/support/ignore.js | 6 - test2/support/index.js | 64 - test2/support/spy.js | 27 - test2/view.content.js | 29 - test2/view.events.js | 28 - test2/view.js | 1148 ----------------- test2/view.methods.js | 39 - test2/view.option.js | 29 - test2/view.render.js | 52 - test2/view.set.js | 34 - test2/view.use.js | 60 - test2/viewTypes.js | 52 - test2/views.js | 525 -------- test2/views.use.js | 156 --- 122 files changed, 12762 deletions(-) delete mode 100644 _index.js delete mode 100755 bin2/update.js delete mode 100644 test2/app.applyLayout.js delete mode 100644 test2/app.collection.compile.js delete mode 100644 test2/app.collection.js delete mode 100644 test2/app.collection.render.js delete mode 100644 test2/app.compile.js delete mode 100644 test2/app.copy.js delete mode 100644 test2/app.create.js delete mode 100644 test2/app.data.js delete mode 100644 test2/app.dest.js delete mode 100644 test2/app.engines.js delete mode 100644 test2/app.events.js delete mode 100644 test2/app.get-set.js delete mode 100644 test2/app.handle.js delete mode 100644 test2/app.handlers.js delete mode 100644 test2/app.js delete mode 100644 test2/app.list.compile.js delete mode 100644 test2/app.list.js delete mode 100644 test2/app.lookups.js delete mode 100644 test2/app.middleware.js delete mode 100644 test2/app.onLoad.js delete mode 100644 test2/app.option.js delete mode 100644 test2/app.render.js delete mode 100644 test2/app.renderFile.js delete mode 100644 test2/app.route.js delete mode 100644 test2/app.src.js delete mode 100644 test2/app.symlink.js delete mode 100644 test2/app.task.js delete mode 100644 test2/app.toStream.js delete mode 100644 test2/app.use.js delete mode 100644 test2/app.view.compile.js delete mode 100644 test2/app.view.render.js delete mode 100644 test2/app.watch.js delete mode 100644 test2/collection.engines.js delete mode 100644 test2/collection.events.js delete mode 100644 test2/collection.getView.js delete mode 100644 test2/collection.js delete mode 100644 test2/collection.options.js delete mode 100644 test2/collection.render.js delete mode 100644 test2/collection.use.js delete mode 100644 test2/fixtures/bom-utf16be.txt delete mode 100644 test2/fixtures/bom-utf16le.txt delete mode 100644 test2/fixtures/bom-utf8.txt delete mode 100644 test2/fixtures/copy/a.txt delete mode 100644 test2/fixtures/copy/b.txt delete mode 100644 test2/fixtures/copy/c.txt delete mode 100644 test2/fixtures/copy/example.txt delete mode 100644 test2/fixtures/data/a.json delete mode 100644 test2/fixtures/data/alert.json delete mode 100644 test2/fixtures/data/b.json delete mode 100644 test2/fixtures/data/c.json delete mode 100644 test2/fixtures/data/data.json delete mode 100644 test2/fixtures/data/test.json delete mode 100644 test2/fixtures/example.txt delete mode 100644 test2/fixtures/front-matter/autodetect-no-lang.md delete mode 100644 test2/fixtures/front-matter/autodetect-yaml.md delete mode 100644 test2/fixtures/front-matter/lang-yaml.md delete mode 100644 test2/fixtures/generic/run.dmc delete mode 100644 test2/fixtures/generic/test.dmc delete mode 100644 test2/fixtures/helpers/a.js delete mode 100644 test2/fixtures/helpers/b.js delete mode 100644 test2/fixtures/helpers/c.js delete mode 100644 test2/fixtures/helpers/obj.js delete mode 100644 test2/fixtures/noext/license delete mode 100644 test2/fixtures/pages/a.hbs delete mode 100644 test2/fixtures/pages/b.hbs delete mode 100644 test2/fixtures/pages/c.hbs delete mode 100644 test2/fixtures/pipeline/a.js delete mode 100644 test2/fixtures/pipeline/b.js delete mode 100644 test2/fixtures/pipeline/c.js delete mode 100644 test2/fixtures/pipeline/d.js delete mode 100644 test2/fixtures/posts/a.txt delete mode 100644 test2/fixtures/posts/b.txt delete mode 100644 test2/fixtures/posts/c.txt delete mode 100644 test2/fixtures/templates/a.tmpl delete mode 100644 test2/fixtures/templates/b.tmpl delete mode 100644 test2/fixtures/templates/c.tmpl delete mode 120000 test2/fixtures/test-symlink delete mode 100644 test2/fixtures/test-symlink-dir/suchempty delete mode 100644 test2/fixtures/test.coffee delete mode 100644 test2/fixtures/updaters/a.txt delete mode 100644 test2/fixtures/updaters/b.txt delete mode 100644 test2/fixtures/updaters/c.txt delete mode 100644 test2/fixtures/vinyl/bom-utf16be.txt delete mode 100644 test2/fixtures/vinyl/bom-utf16le.txt delete mode 100644 test2/fixtures/vinyl/bom-utf8.txt delete mode 120000 test2/fixtures/vinyl/test-symlink delete mode 120000 test2/fixtures/vinyl/test-symlink-dir delete mode 100644 test2/fixtures/vinyl/test.coffee delete mode 100644 test2/fixtures/vinyl/wow/suchempty delete mode 100644 test2/fixtures/watch/test.txt delete mode 100644 test2/fixtures/wow/suchempty delete mode 100644 test2/group.js delete mode 100644 test2/handlers.js delete mode 100644 test2/helpers.js delete mode 100644 test2/item.js delete mode 100644 test2/layouts.js delete mode 100644 test2/list.js delete mode 100644 test2/list.render.js delete mode 100644 test2/list.use.js delete mode 100644 test2/mergePartials.js delete mode 100644 test2/partials.js delete mode 100644 test2/questions.js delete mode 100644 test2/renameKey.js delete mode 100644 test2/render.js delete mode 100644 test2/routes.js delete mode 100644 test2/store.js delete mode 100644 test2/support/ignore.js delete mode 100644 test2/support/index.js delete mode 100644 test2/support/spy.js delete mode 100644 test2/view.content.js delete mode 100644 test2/view.events.js delete mode 100644 test2/view.js delete mode 100644 test2/view.methods.js delete mode 100644 test2/view.option.js delete mode 100644 test2/view.render.js delete mode 100644 test2/view.set.js delete mode 100644 test2/view.use.js delete mode 100644 test2/viewTypes.js delete mode 100644 test2/views.js delete mode 100644 test2/views.use.js diff --git a/_index.js b/_index.js deleted file mode 100644 index 44ef9fb..0000000 --- a/_index.js +++ /dev/null @@ -1,169 +0,0 @@ -/*! - * update - * - * Copyright (c) 2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - -'use strict'; - -var path = require('path'); -var Generate = require('generate'); -var expand = require('expand-args'); -var minimist = require('minimist'); -var config = require('./lib/config'); -var locals = require('./lib/locals'); -var utils = require('./lib/utils'); - -/** - * Create an `update` application. This is the main function exported - * by the update module. - * - * ```js - * var Update = require('update'); - * var update = new Update(); - * ``` - * @param {Object} `options` - * @api public - */ - -function Update(options) { - if (!(this instanceof Update)) { - return new Update(options); - } - Base.call(this, options); - this.name = this.options.name || 'update'; - this.isUpdate = true; - this.initUpdate(this); -} - -/** - * Inherit assemble-core - */ - -Base.extend(Update); - -/** - * Initialize Updater defaults - */ - -Update.prototype.initUpdate = function(base) { - this.set('updaters', {}); - - // custom middleware handlers - this.handler('onStream'); - this.handler('preWrite'); - this.handler('postWrite'); - - // parse command line arguments - var argv = expand(minimist(process.argv.slice(2)), { - alias: {v: 'verbose'} - }); - - this.option('argv', argv); - - // expose `argv` on the instance - this.mixin('argv', function(prop) { - var args = [].slice.call(arguments); - args.unshift(argv); - return utils.get.apply(null, args); - }); - - // load the package.json for the updater - this.data(utils.pkg.sync(this.options.path)); - config(this); - - this.use(locals('update')) - .use(utils.runtimes({ - displayName: function(key) { - return base.name === key ? key : (base.name + ':' + key); - } - })) - .use(utils.store()) - .use(utils.pipeline()) - .use(utils.loader()) - .use(utils.cli()) - .use(utils.defaults()) - .use(utils.opts()) - - var data = utils.get(this.cache.data, 'update'); - this.config.process(utils.extend({}, data, argv)); - - this.engine(['md', 'tmpl'], require('engine-base')); - this.onLoad(/\.(md|tmpl)$/, function(view, next) { - utils.matter.parse(view, next); - }); -}; - -/** - * Returns a function for resolving filepaths from the given `directory` - * or from the user's current working directory if no directory - * is passed. - * - * ```js - * var cwd = update.cwd('foo'); - * var a = cwd('bar'); - * var b = cwd('baz'); - * ``` - * @param {String} `dir` - * @return {Function} - */ - -Update.prototype.cwd = function(dir) { - var cwd = dir || process.cwd(); - return function() { - var args = [].slice.call(arguments); - args.unshift(cwd); - return path.resolve.apply(null, args); - }; -}; - -/** - * Temporary logger method. - * TODO: add event logger - */ - -Update.prototype.log = function() { - this.emit.bind(this, 'log').apply(this, arguments); - if (this.enabled('verbose')) { - console.log.apply(console, arguments); - } -}; - -/** - * Register updater `name` with the given `update` - * instance. - * - * @param {String} `name` - * @param {Object} `update` Instance of update - * @return {Object} Returns the instance for chaining - */ - -Update.prototype.updater = function(name, app) { - if (arguments.length === 1 && typeof name === 'string') { - return this.updaters[name]; - } - - app.use(utils.runtimes({ - displayName: function(key) { - return app.name === key ? key : (app.name + ':' + key); - } - })); - - this.emit('updater', name, app); - this.updaters[name] = app; - return app; -}; - -/** - * Expose `Update` - */ - -module.exports = Update; - -/** - * Expose `utils` and package.json metadata - */ - -module.exports.utils = utils; -module.exports.pkg = require('./package'); diff --git a/bin2/update.js b/bin2/update.js deleted file mode 100755 index 2160867..0000000 --- a/bin2/update.js +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env node - -var path = require('path'); -var gm = require('global-modules'); -var Runner = require('../lib/runner/runner')(); -var utils = require('../lib/utils'); -var argv = require('minimist')(process.argv.slice(2), { - alias: {verbose: 'v'} -}); - -var cmd = utils.commands(argv); -var runner = new Runner(argv); - -runner.base.option(argv); -runner.option(argv); - -var task = cmd.list ? ['list', 'default'] : ['default']; - -runner.on('*', function(method, key, val) { - console.log(method + ':', key, val); -}); - -if (argv.verbose) { - runner.on('register', function(key) { - utils.ok(utils.gray('registered'), 'updater', utils.cyan(key)); - }); -} - -runner.registerEach('update-*', {cwd: gm}); - -runner.base.task('run', function(cb) { - runner.run(cb); -}); - -runner.base.build(task, function(err) { - if (err) return console.error(err); - utils.timestamp('finished ' + utils.green(utils.successSymbol)); -}); diff --git a/test2/app.applyLayout.js b/test2/app.applyLayout.js deleted file mode 100644 index 7c8d782..0000000 --- a/test2/app.applyLayout.js +++ /dev/null @@ -1,87 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; -var page = { - content: '<%= name %>', - layout: 'default.tmpl', - locals: { - name: 'Halle' - } -}; - -describe('helpers', function() { - describe('rendering', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('layout', { viewType: 'layout' }); - app.create('page'); - }); - - it('should throw an error when a layout cannot be found:', function(cb) { - app.layout('fofof.tmpl', {content: '..'}); - app.page('a.tmpl', page) - .render(function(err) { - assert.equal(err.message, 'Templates#layouts layout "default.tmpl" was defined on view "a.tmpl"\nbut cannot be not found (common causes are incorrect glob patterns,\nrenameKey function modifying the key, and typos in search pattern)'); - cb(); - }); - }); - - it('should emit an error when a layout cannot be found:', function(cb) { - app.layout('fofof.tmpl', {content: '..'}); - app.on('error', function(err) { - assert.equal(err.message, 'Templates#layouts layout "default.tmpl" was defined on view "a.tmpl"\nbut cannot be not found (common causes are incorrect glob patterns,\nrenameKey function modifying the key, and typos in search pattern)'); - cb(); - }); - - app.page('a.tmpl', page) - .render(function() { - }); - }); - - it('should throw an error - layout defined but no layouts registered:', function(cb) { - app.page('a.tmpl', page) - .render(function(err) { - assert.equal(err.message, 'Templates#layouts layout "default.tmpl" was defined on view "a.tmpl"\nbut cannot be not found (common causes are incorrect glob patterns,\nrenameKey function modifying the key, and typos in search pattern)'); - cb(); - }); - }); - - it('should emit an error - layout defined but no layouts registered:', function(cb) { - app.on('error', function(err) { - assert.equal(err.message, 'Templates#layouts layout "default.tmpl" was defined on view "a.tmpl"\nbut cannot be not found (common causes are incorrect glob patterns,\nrenameKey function modifying the key, and typos in search pattern)'); - cb(); - }); - app.page('a.tmpl', page) - .render(function() { - }); - }); - - it('should wrap a view with a layout (view.render):', function(cb) { - app.layout('default.tmpl', {content: 'before {% body %} after'}); - app.page('a.tmpl', page) - .render(function(err) { - if (err) return cb(err); - cb(); - }); - }); - - it('should wrap a view with a layout (app.render):', function(cb) { - app.layout('default.tmpl', {content: 'before {% body %} after'}); - app.page('a.tmpl', page); - - var view = app.pages.getView('a.tmpl'); - app.render(view, function(err, res) { - if (err) return cb(err); - assert(res.contents.toString() === 'before Halle after'); - cb(); - }); - }); - }); -}); - diff --git a/test2/app.collection.compile.js b/test2/app.collection.compile.js deleted file mode 100644 index 1ceed4e..0000000 --- a/test2/app.collection.compile.js +++ /dev/null @@ -1,50 +0,0 @@ -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var Views = App.Views; -var views; - -describe('compile', function() { - beforeEach(function() { - views = new Views(); - }); - - it('should throw an error when an engine cannot be found:', function() { - views.addView('foo.bar', {content: '<%= name %>'}); - var page = views.getView('foo.bar'); - (function() { - views.compile(page); - }).should.throw('Views#compile cannot find an engine for: .bar'); - }); - - it('should compile a template:', function() { - views.engine('tmpl', require('engine-base')); - views.addView('a.tmpl', {path: 'a.tmpl', content: '<%= a %>', a: 'b'}); - - var page = views.getView('a.tmpl'); - var view = views.compile(page); - assert.equal(typeof view.fn, 'function'); - }); - - it('should compile a template by name:', function() { - views.engine('tmpl', require('engine-base')); - views.addView('a.tmpl', {path: 'a.tmpl', content: '<%= a %>', a: 'b'}); - - var view = views.compile('a.tmpl'); - assert.equal(typeof view.fn, 'function'); - }); - - it('should throw an error when a callback is given:', function() { - views.engine('md', require('engine-base')); - views.addView('foo.md', {content: '<%= name %>'}); - var page = views.getView('foo.md'); - (function() { - views.compile(page, function() {}); - }).should.throw('Views#compile is sync and does not take a callback function'); - - (function() { - views.compile(page, {}, function() {}); - }).should.throw('Views#compile is sync and does not take a callback function'); - }); -}); diff --git a/test2/app.collection.js b/test2/app.collection.js deleted file mode 100644 index ef8c548..0000000 --- a/test2/app.collection.js +++ /dev/null @@ -1,186 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var fs = require('fs'); -var path = require('path'); -var assert = require('assert'); -var define = require('define-property'); -var support = require('./support'); -var App = support.resolve(); -var Collection = App.Collection; -var app; - -describe('collection', function() { - describe('method', function() { - beforeEach(function() { - app = new App(); - }); - - it('should expose the collection method', function() { - assert(typeof app.collection === 'function'); - }); - - it('should return a new collection', function() { - var collection = app.collection(); - assert(typeof collection === 'object'); - }); - - it('should have isCollection property', function() { - var collection = app.collection(); - assert(collection.isCollection === true); - }); - }); - - describe('adding views', function() { - beforeEach(function() { - app = new App() - .use(function() { - return function() { - define(this, 'count', { - get: function() { - return Object.keys(this.views).length; - }, - set: function() { - throw new Error('count is a read-only getter and cannot be defined.'); - } - }); - }; - }); - - app.engine('tmpl', require('engine-base')); - app.create('pages', { - renameKey: function(fp) { - return path.relative(process.cwd(), fp); - } - }); - }); - - it('should load a view onto the respective collection:', function() { - app.pages('test/fixtures/pages/a.hbs'); - app.views.pages.should.have.property('test/fixtures/pages/a.hbs'); - }); - - it('should allow collection methods to be chained:', function() { - app - .pages('test/fixtures/pages/a.hbs') - .pages('test/fixtures/pages/b.hbs') - .pages('test/fixtures/pages/c.hbs'); - - app.views.pages.should.have.properties([ - 'test/fixtures/pages/a.hbs', - 'test/fixtures/pages/b.hbs', - 'test/fixtures/pages/c.hbs' - ]); - }); - - it('should expose the `option` method:', function() { - app.pages.option('foo', 'bar') - .pages('test/fixtures/pages/a.hbs') - .pages('test/fixtures/pages/b.hbs') - .pages('test/fixtures/pages/c.hbs'); - - app.pages.options.should.have.property('foo', 'bar'); - app.views.pages.should.have.properties([ - 'test/fixtures/pages/a.hbs', - 'test/fixtures/pages/b.hbs', - 'test/fixtures/pages/c.hbs' - ]); - }); - - it('should expose the `option` method:', function() { - app.pages.option('foo', 'bar') - .pages('test/fixtures/pages/a.hbs') - .pages('test/fixtures/pages/b.hbs') - .pages('test/fixtures/pages/c.hbs'); - - assert(app.pages.count === 3); - }); - }); - - describe('addItem', function() { - beforeEach(function() { - app = new App(); - }); - - it('should add items to a collection', function() { - var pages = app.collection({Collection: Collection}); - pages.addItem('foo'); - pages.addItem('bar'); - pages.addItem('baz'); - - pages.items.hasOwnProperty('foo'); - pages.items.hasOwnProperty('bar'); - pages.items.hasOwnProperty('baz'); - }); - - it('should create a collection from an existing collection:', function() { - var pages = app.collection({Collection: Collection}); - pages.addItem('foo'); - pages.addItem('bar'); - pages.addItem('baz'); - - var posts = app.collection(pages); - posts.items.hasOwnProperty('foo'); - posts.items.hasOwnProperty('bar'); - posts.items.hasOwnProperty('baz'); - }); - }); - - describe('rendering views', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('pages'); - app.cache.data = {}; - }); - - it('should render a view with inherited app.render', function(cb) { - app.page('test/fixtures/templates/a.tmpl') - .use(function(view) { - view.contents = fs.readFileSync(view.path); - }) - .set('data.name', 'Brian') - .render(function(err, res) { - if (err) return cb(err); - assert(res.content === 'Brian'); - cb(); - }); - }); - }); -}); - -describe('collection singular method', function() { - describe('create', function() { - beforeEach(function() { - app = new App(); - }); - - it('should add a pluralized collection from singular name', function() { - app.create('page'); - assert(typeof app.views.pages === 'object'); - }); - }); - - describe('adding views', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('page', { - renameKey: function(fp) { - return path.relative(process.cwd(), fp); - } - }); - }); - - it('should add a view to the created collection:', function() { - app.page('test/fixtures/pages/a.hbs'); - assert(typeof app.views.pages['test/fixtures/pages/a.hbs'] === 'object'); - }); - - it('should expose the `option` method:', function() { - app.pages.option('foo', 'bar'); - app.pages.options.should.have.property('foo', 'bar'); - }); - }); -}); diff --git a/test2/app.collection.render.js b/test2/app.collection.render.js deleted file mode 100644 index 2d23152..0000000 --- a/test2/app.collection.render.js +++ /dev/null @@ -1,157 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var async = require('async'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var List = App.List; -var pages, app; - -describe('render', function() { - describe('rendering', function() { - beforeEach(function() { - app = App(); - pages = app.create('pages'); - app.engine('tmpl', require('engine-base')); - pages.engine('tmpl', require('engine-base')); - }); - - it('should throw an error when no callback is given:', function() { - (function() { - app.pages.render({}); - }).should.throw('Views#render is async and expects a callback function'); - }); - - it('should throw an error when an engine is not defined:', function(done) { - pages.addView('foo.bar', { content: '<%= name %>' }); - var page = pages.getView('foo.bar'); - - app.pages.render(page, function(err) { - assert(err.message === 'Views#render cannot find an engine for: .bar'); - done(); - }); - }); - - it('should use helpers defined on app to render a view:', function(done) { - var locals = {name: 'Halle'}; - app.helper('upper', function(str) { - return str.toUpperCase(str) + 'app'; - }); - - pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); - var page = pages.getView('a.tmpl'); - - app.render(page, function(err, res) { - if (err) return done(err); - - assert(res.content === 'a HALLEapp b'); - done(); - }); - }); - - it('should use helpers defined on app to render a view with collection.render:', function(done) { - var locals = {name: 'Halle'}; - app.helper('upper', function(str) { - return str.toUpperCase(str) + 'app'; - }); - - pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); - pages.helper('upper', app._.helpers.sync.upper); - var page = pages.getView('a.tmpl'); - - pages.render(page, function(err, res) { - if (err) return done(err); - - assert(res.content === 'a HALLEapp b'); - done(); - }); - }); - - it('should use helpers when rendering a view:', function(done) { - var locals = {name: 'Halle'}; - pages.helper('upper', function(str) { - return str.toUpperCase(str); - }); - - pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); - var page = pages.getView('a.tmpl'); - - pages.render(page, function(err, res) { - if (err) return done(err); - assert(res.content === 'a HALLE b'); - done(); - }); - }); - - it('should render a template when contents is a buffer:', function(done) { - pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - var view = pages.getView('a.tmpl'); - - pages.render(view, function(err, view) { - if (err) return done(err); - assert(view.contents.toString() === 'b'); - done(); - }); - }); - - it('should render a template when content is a string:', function(done) { - pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - var view = pages.getView('a.tmpl'); - - pages.render(view, function(err, view) { - if (err) return done(err); - assert(view.contents.toString() === 'b'); - done(); - }); - }); - - it('should render a view from its path:', function(done) { - pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - - pages.render('a.tmpl', function(err, view) { - if (err) return done(err); - assert(view.content === 'b'); - done(); - }); - }); - - it('should use a plugin for rendering:', function(done) { - pages.engine('tmpl', require('engine-base')); - pages.option('engine', 'tmpl'); - - pages.addViews({ - 'a': {content: '<%= title %>', locals: {title: 'aaa'}}, - 'b': {content: '<%= title %>', locals: {title: 'bbb'}}, - 'c': {content: '<%= title %>', locals: {title: 'ccc'}}, - 'd': {content: '<%= title %>', locals: {title: 'ddd'}}, - 'e': {content: '<%= title %>', locals: {title: 'eee'}}, - 'f': {content: '<%= title %>', locals: {title: 'fff'}}, - 'g': {content: '<%= title %>', locals: {title: 'ggg'}}, - 'h': {content: '<%= title %>', locals: {title: 'hhh'}}, - 'i': {content: '<%= title %>', locals: {title: 'iii'}}, - 'j': {content: '<%= title %>', locals: {title: 'jjj'}} - }); - - pages.use(function(collection) { - collection.option('pager', false); - - collection.renderEach = function(cb) { - var list = new List(collection); - async.map(list.items, function(item, next) { - collection.render(item, next); - }, cb); - }; - }); - - pages.renderEach(function(err, items) { - if (err) return done(err); - assert(items[0].content === 'aaa'); - assert(items[9].content === 'jjj'); - assert(items.length === 10); - done(); - }); - }); - }); -}); diff --git a/test2/app.compile.js b/test2/app.compile.js deleted file mode 100644 index 090676e..0000000 --- a/test2/app.compile.js +++ /dev/null @@ -1,52 +0,0 @@ -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('compile', function() { - beforeEach(function() { - app = new App(); - app.create('page'); - }); - - it('should throw an error when an engine cannot be found:', function() { - app.page('foo.bar', {content: '<%= name %>'}); - var page = app.pages.getView('foo.bar'); - (function() { - app.compile(page); - }).should.throw('Templates#compile cannot find an engine for: .bar'); - }); - - it('should compile a template:', function() { - app.engine('tmpl', require('engine-base')); - app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>', a: 'b'}); - - var page = app.pages.getView('a.tmpl'); - var view = app.compile(page); - assert.equal(typeof view.fn, 'function'); - }); - - it('should compile a template by name:', function() { - app.engine('tmpl', require('engine-base')); - app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>', a: 'b'}); - - var view = app.compile('a.tmpl'); - assert.equal(typeof view.fn, 'function'); - }); - - it('should throw an error when a callback is given:', function() { - app.engine('md', require('engine-base')); - app.page('foo.md', {content: '<%= name %>'}); - var page = app.pages.getView('foo.md'); - (function() { - app.compile(page, function() { - }); - }).should.throw('Templates#compile is sync and does not take a callback function'); - - (function() { - app.compile(page, {}, function() { - }); - }).should.throw('Templates#compile is sync and does not take a callback function'); - }); -}); diff --git a/test2/app.copy.js b/test2/app.copy.js deleted file mode 100644 index cf18b8c..0000000 --- a/test2/app.copy.js +++ /dev/null @@ -1,31 +0,0 @@ -require('mocha'); -var path = require('path'); -var assert = require('assert'); -var rimraf = require('rimraf'); -var App = require('..'); -var app; - -var fixtures = path.join(__dirname, 'fixtures/copy/*.txt'); -var actual = path.join(__dirname, 'actual'); - -describe('copy()', function() { - beforeEach(function(done) { - rimraf(actual, done); - app = new App(); - }); - - afterEach(function(done) { - rimraf(actual, done); - }); - - describe('streams', function() { - it('should copy files', function(done) { - app.copy(fixtures, path.join(__dirname, 'actual')) - .on('error', done) - .on('data', function(file) { - assert.equal(typeof file, 'object'); - }) - .on('end', done); - }); - }); -}); diff --git a/test2/app.create.js b/test2/app.create.js deleted file mode 100644 index 5486eb2..0000000 --- a/test2/app.create.js +++ /dev/null @@ -1,244 +0,0 @@ -require('mocha'); -require('should'); -var fs = require('fs'); -var path = require('path'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('create', function() { - describe('inflections', function() { - beforeEach(function() { - app = new App(); - }); - - it('should expose the create method', function() { - assert(typeof app.create === 'function'); - }); - - it('should add a collection to `views`', function() { - app.create('pages'); - assert(typeof app.views.pages === 'object'); - assert(typeof app.pages === 'function'); - }); - - it('should add a pluralized collection to `views`', function() { - app.create('page'); - assert(typeof app.views.pages === 'object'); - assert(typeof app.page === 'function'); - }); - }); - - describe('renderable views', function() { - beforeEach(function() { - app = new App(); - app.create('pages'); - app.create('partials', {viewType: 'partial'}); - app.create('layout', {viewType: 'layout'}); - }); - - it('should add renderable views when no type is defined', function() { - app.pages.addView('foo', {content: 'bar'}); - assert(app.views.pages.hasOwnProperty('foo')); - }); - - it('should add view Ctor names to views', function() { - app.pages.addView('foo', {content: 'bar'}); - assert(app.views.pages.foo._name === 'Page'); - }); - - it('should add partial views when partial type is defined', function() { - app.partials.addView('abc', {content: 'xyz'}); - assert(app.views.partials.hasOwnProperty('abc')); - }); - - it('should add layout views when layout type is defined', function() { - app.layouts.addView('foo', {content: 'bar'}); - assert(app.views.layouts.hasOwnProperty('foo')); - }); - - it('should set viewType on renderable views', function() { - app.pages.addView('foo', {content: 'bar'}); - var view = app.pages.getView('foo'); - assert(view.isType('renderable')); - assert(!view.isType('layout')); - assert(!view.isType('partial')); - }); - - it('should set viewType on partial views', function() { - app.partials.addView('foo', {content: 'bar'}); - var view = app.partials.getView('foo'); - assert(view.isType('partial')); - assert(!view.isType('layout')); - assert(!view.isType('renderable')); - }); - - it('should set viewType on layout views', function() { - app.layouts.addView('foo', {content: 'bar'}); - var view = app.layouts.getView('foo'); - assert(view.isType('layout')); - assert(!view.isType('renderable')); - assert(!view.isType('partial')); - }); - }); - - describe('custom constructors', function() { - beforeEach(function() { - var Vinyl = require('vinyl'); - Vinyl.prototype.custom = function(key) { - this[key] = 'nonsense'; - return this; - }; - app = new App({View: Vinyl}); - app.create('pages'); - }); - - it('should create views from key-value pairs:', function() { - app.page('a.hbs', {path: 'a.hbs', content: 'a'}); - app.page('b.hbs', {path: 'b.hbs', content: 'b'}); - app.page('c.hbs', {path: 'c.hbs', content: 'c'}); - var a = app.pages.getView('a.hbs'); - a.custom('foo'); - a.foo.should.equal('nonsense'); - }); - }); - - describe('custom instances', function() { - it('should create views from custom `View` and `Views` instance/ctor:', function() { - var Vinyl = require('vinyl'); - Vinyl.prototype.read = function(file) { - return fs.readFileSync(file.path); - }; - - var Views = App.Views; - var views = new Views({View: Vinyl}); - - views.addView('a.hbs', {path: 'a.hbs', content: 'a'}); - views.addView('b.hbs', {path: 'b.hbs', content: 'b'}); - views.addView('c.hbs', {path: 'c.hbs', content: 'c'}); - - app = new App(); - app.create('pages', views); - - var a = app.pages.getView('a.hbs'); - assert(a instanceof Vinyl); - assert(Vinyl.isVinyl(a)); - assert(typeof a.read === 'function'); - - views.addView('d.hbs', {path: 'd.hbs', content: 'd'}); - var d = app.pages.getView('d.hbs'); - assert(d instanceof Vinyl); - assert(Vinyl.isVinyl(d)); - }); - }); - - describe('chaining', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('page', { - renameKey: function(fp) { - return path.relative(process.cwd(), fp); - } - }); - }); - - it('should create views from key-value pairs:', function() { - app.page('a.hbs', {content: 'a'}); - app.page('b.hbs', {content: 'b'}); - app.page('c.hbs', {content: 'c'}); - app.views.pages.should.have.properties(['a.hbs', 'b.hbs', 'c.hbs']); - assert(app.views.pages['a.hbs'].contents.toString() === 'a'); - }); - - it('should create views from file paths:', function() { - app.page('test/fixtures/pages/a.hbs'); - app.page('test/fixtures/pages/b.hbs'); - app.page('test/fixtures/pages/c.hbs'); - - app.views.pages.should.have.properties([ - 'test/fixtures/pages/a.hbs', - 'test/fixtures/pages/b.hbs', - 'test/fixtures/pages/c.hbs' - ]); - }); - }); - - describe('instance', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - }); - - it('should return the collection instance', function() { - var collection = app.create('pages'); - assert(collection instanceof App.Views); - - collection.option('renameKey', function(key) { - return path.basename(key); - }); - collection - .use(function(views) { - views.read = function(name) { - var view = this.getView(name); - view.contents = fs.readFileSync(view.path); - }; - }); - - collection.addView('test/fixtures/templates/a.tmpl'); - collection.read('a.tmpl'); - assert(collection.getView('a.tmpl').contents.toString() === '<%= name %>'); - }); - }); - - describe('viewType', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - }); - - it('should add collection to the given viewType', function() { - app.create('layout', {viewType: 'layout'}); - assert(app.layouts.options.viewType[0] === 'layout'); - }); - - it('should add a collection to multiple viewTypes', function() { - app.create('foo', {viewType: ['layout', 'renderable']}); - assert.deepEqual(app.foos.options.viewType, ['layout', 'renderable']); - }); - }); - - describe('events', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - }); - - it('should emit `create` when a collection is created:', function() { - app.on('create', function(collection) { - if (collection.options.plural === 'layouts') { - collection.options.foo = 'bar'; - } - }); - - app.create('layout'); - app.layout('one', {path: 'two', contents: '...'}); - assert(app.layouts.options.foo === 'bar'); - }); - }); - - describe('collection instantiation', function() { - it('should expose collection instance methods that are created after instantiation on the app collection loader', function() { - app.create('pages'); - app.pages.use(function(collection) { - collection.define('foo', function(msg) { - return 'foo ' + msg; - }); - }); - - assert(app.pages.foo); - assert(typeof app.pages.foo === 'function'); - }); - }); -}); diff --git a/test2/app.data.js b/test2/app.data.js deleted file mode 100644 index f3a1992..0000000 --- a/test2/app.data.js +++ /dev/null @@ -1,94 +0,0 @@ -require('mocha'); -require('should'); -var path = require('path'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('app.data', function() { - beforeEach(function() { - app = new App(); - }); - - it('should set a key-value pair on cache.data:', function() { - app.data('a', 'b'); - assert(app.cache.data.a === 'b'); - }); - - it('should set an object on cache.data:', function() { - app.data({c: 'd'}); - assert(app.cache.data.c === 'd'); - }); - - it('should load data from a file onto cache.data:', function() { - app.data('test/fixtures/data/a.json'); - assert(app.cache.data.a.one.a === 'aaa'); - }); - - it('should load a glob of data onto cache.data:', function() { - app.data('test/fixtures/data/*.json'); - assert(app.cache.data.a.one.a === 'aaa'); - assert(app.cache.data.b.two.b === 'bbb'); - assert(app.cache.data.c.three.c === 'ccc'); - }); - - it('should use `namespace` defined on global opts:', function() { - app.option('namespace', function(key) { - return 'prefix_' + path.basename(key, path.extname(key)); - }); - app.data('test/fixtures/data/*.json'); - assert(app.cache.data.prefix_a.one.a === 'aaa'); - assert(app.cache.data.prefix_b.two.b === 'bbb'); - assert(app.cache.data.prefix_c.three.c === 'ccc'); - }); - - it('should use `namespace` defined on data opts:', function() { - app.data('test/fixtures/data/*.json', { - namespace: function(key) { - return 'prefix_' + path.basename(key, path.extname(key)); - } - }); - assert(app.cache.data.prefix_a.one.a === 'aaa'); - assert(app.cache.data.prefix_b.two.b === 'bbb'); - assert(app.cache.data.prefix_c.three.c === 'ccc'); - }); - - it('should use `renameKey` defined on data opts:', function() { - app.data('test/fixtures/data/*.json', { - renameKey: function(key) { - return 'prefix_' + path.basename(key, path.extname(key)); - } - }); - assert(app.cache.data.prefix_a.one.a === 'aaa'); - assert(app.cache.data.prefix_b.two.b === 'bbb'); - assert(app.cache.data.prefix_c.three.c === 'ccc'); - }); - - it('should extend `cache.data`', function() { - app.data({a: 'aaa', b: 'bbb', c: 'ccc'}); - app.data({x: 'xxx', y: 'yyy', z: 'zzz'}); - assert(app.cache.data.a === 'aaa'); - assert(app.cache.data.b === 'bbb'); - assert(app.cache.data.c === 'ccc'); - assert(app.cache.data.x === 'xxx'); - assert(app.cache.data.y === 'yyy'); - assert(app.cache.data.z === 'zzz'); - }); - - it('should extend the `cache.data` object when the first param is a string.', function() { - app.data('foo', {x: 'xxx', y: 'yyy', z: 'zzz'}); - app.data('bar', {a: 'aaa', b: 'bbb', c: 'ccc'}); - assert(app.cache.data.foo.x === 'xxx'); - assert(app.cache.data.bar.a === 'aaa'); - }); - - it('should be chainable.', function() { - app - .data({x: 'xxx', y: 'yyy', z: 'zzz'}) - .data({a: 'aaa', b: 'bbb', c: 'ccc'}); - - assert(app.cache.data.x === 'xxx'); - assert(app.cache.data.a === 'aaa'); - }); -}); diff --git a/test2/app.dest.js b/test2/app.dest.js deleted file mode 100644 index ff285e8..0000000 --- a/test2/app.dest.js +++ /dev/null @@ -1,1103 +0,0 @@ -var spies = require('./support/spy'); -var chmodSpy = spies.chmodSpy; -var statSpy = spies.statSpy; - -require('mocha'); -var should = require('should'); -var assert = require('assert'); -var App = require('..'); -var app; - -var path = require('path'); -var fs = require('graceful-fs'); -var rimraf = require('rimraf'); - -var bufferStream; -var bufEqual = require('buffer-equal'); -var through = require('through2'); -var File = require('vinyl'); - -var actual = path.join(__dirname, 'actual'); - -var wipeOut = function(cb) { - app = new App(); - rimraf(path.join(__dirname, 'actual/'), cb); - spies.setError('false'); - statSpy.reset(); - chmodSpy.reset(); -}; - -var dataWrap = function(fn) { - return function(data, enc, cb) { - fn(data); - cb(); - }; -}; - -var realMode = function(n) { - return n & 07777; -}; - -describe('dest stream', function() { - beforeEach(wipeOut); - afterEach(wipeOut); - - it('should explode on invalid folder (empty)', function(done) { - var stream; - try { - stream = app.dest(); - } catch (err) { - assert(err && typeof err === 'object'); - should.not.exist(stream); - done(); - } - }); - - it('should explode on invalid folder (empty string)', function(done) { - var stream; - try { - stream = app.dest(''); - } catch (err) { - assert(err && typeof err === 'object'); - should.not.exist(stream); - done(); - } - }); - - it('should pass through writes with cwd', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - - var expectedFile = new File({ - base: __dirname, - cwd: __dirname, - path: inputPath, - contents: null - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - done(); - }; - - var stream = app.dest('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should pass through writes with default cwd', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - - var expectedFile = new File({ - base: __dirname, - cwd: __dirname, - path: inputPath, - contents: null - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - done(); - }; - - var stream = app.dest(path.join(__dirname, 'actual/')); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should not write null files', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedCwd = __dirname; - var expectedBase = path.join(__dirname, 'actual'); - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: null - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(false); - done(); - }; - - var stream = app.dest('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should write buffer files to the right folder with relative cwd', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedCwd = __dirname; - var expectedBase = path.join(__dirname, 'actual'); - var expectedContents = fs.readFileSync(inputPath); - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(true); - bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); - done(); - }; - - var stream = app.dest('./actual/', {cwd: path.relative(process.cwd(), __dirname)}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should write buffer files to the right folder with function and relative cwd', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedCwd = __dirname; - var expectedBase = path.join(__dirname, 'actual'); - var expectedContents = fs.readFileSync(inputPath); - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(true); - bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); - done(); - }; - - var stream = app.dest(function(file){ - should.exist(file); - file.should.equal(expectedFile); - return './actual'; - }, {cwd: path.relative(process.cwd(), __dirname)}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should write buffer files to the right folder', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedCwd = __dirname; - var expectedBase = path.join(__dirname, 'actual'); - var expectedMode = 0655; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: expectedMode - } - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(true); - bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); - realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); - done(); - }; - - var stream = app.dest('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should write streaming files to the right folder', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedCwd = __dirname; - var expectedBase = path.join(__dirname, 'actual'); - var expectedMode = 0655; - - var contentStream = through.obj(); - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: contentStream, - stat: { - mode: expectedMode - } - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(true); - bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); - realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); - done(); - }; - - var stream = app.dest('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - setTimeout(function(){ - contentStream.write(expectedContents); - contentStream.end(); - }, 100); - stream.end(); - }); - - it('should write directories to the right folder', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test'); - var expectedCwd = __dirname; - var expectedBase = path.join(__dirname, 'actual'); - var expectedMode = 0655; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: null, - stat: { - isDirectory: function(){ - return true; - }, - mode: expectedMode - } - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(true); - fs.lstatSync(expectedPath).isDirectory().should.equal(true); - realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); - done(); - }; - - var stream = app.dest('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should allow piping multiple dests in streaming mode', function(done) { - var inputPath1 = path.join(__dirname, 'actual/multiple-first'); - var inputPath2 = path.join(__dirname, 'actual/multiple-second'); - var inputBase = path.join(__dirname, 'actual/'); - var srcPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var stream1 = app.dest('./actual/', {cwd: __dirname}); - var stream2 = app.dest('./actual/', {cwd: __dirname}); - var content = fs.readFileSync(srcPath); - var rename = through.obj(function(file, _, next) { - file.path = inputPath2; - this.push(file); - next(); - }); - - stream1.on('data', function(file) { - file.path.should.equal(inputPath1); - }); - - stream1.pipe(rename).pipe(stream2); - stream2.on('data', function(file) { - file.path.should.equal(inputPath2); - }).once('end', function() { - fs.readFileSync(inputPath1, 'utf8').should.equal(content.toString()); - fs.readFileSync(inputPath2, 'utf8').should.equal(content.toString()); - done(); - }); - - var file = new File({ - base: inputBase, - path: inputPath1, - cwd: __dirname, - contents: content - }); - - stream1.write(file); - stream1.end(); - }); - - it('should write new files with the default user mode', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedMode = 0666 & (~process.umask()); - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - fs.existsSync(expectedPath).should.equal(true); - realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); - done(); - }; - - chmodSpy.reset(); - var stream = app.dest('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should write new files with the specified mode', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedMode = 0744; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - fs.existsSync(expectedPath).should.equal(true); - realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); - done(); - }; - - chmodSpy.reset(); - var stream = app.dest('./actual/', {cwd: __dirname, mode:expectedMode}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should update file mode to match the vinyl mode', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, 'actual'); - var startMode = 0655; - var expectedMode = 0722; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: expectedMode - } - }); - - var onEnd = function(){ - assert(chmodSpy.called); - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - fs.existsSync(expectedPath).should.equal(true); - realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); - done(); - }; - - fs.mkdirSync(expectedBase); - fs.closeSync(fs.openSync(expectedPath, 'w')); - fs.chmodSync(expectedPath, startMode); - - chmodSpy.reset(); - var stream = app.dest('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should use different modes for files and directories', function(done) { - var inputBase = path.join(__dirname, 'fixtures/vinyl'); - var inputPath = path.join(__dirname, 'fixtures/vinyl/wow/suchempty'); - var expectedBase = path.join(__dirname, 'actual/wow'); - var expectedDirMode = 0755; - var expectedFileMode = 0655; - - var firstFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - stat: fs.statSync(inputPath) - }); - - var onEnd = function(){ - realMode(fs.lstatSync(expectedBase).mode).should.equal(expectedDirMode); - realMode(buffered[0].stat.mode).should.equal(expectedFileMode); - done(); - }; - - var stream = app.dest('./actual/', { - cwd: __dirname, - mode: expectedFileMode, - dirMode: expectedDirMode - }); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(firstFile); - stream.end(); - }); - - it('should change to the specified base as string', function(done) { - var inputBase = path.join(__dirname, 'fixtures/vinyl'); - var inputPath = path.join(__dirname, 'fixtures/vinyl/wow/suchempty'); - - var firstFile = new File({ - cwd: __dirname, - path: inputPath, - stat: fs.statSync(inputPath) - }); - - var onEnd = function(){ - buffered[0].base.should.equal(inputBase); - done(); - }; - - var stream = app.dest('./actual/', { - cwd: __dirname, - base: inputBase - }); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(firstFile); - stream.end(); - }); - - it('should change to the specified base as function', function(done) { - var inputBase = path.join(__dirname, 'fixtures/vinyl'); - var inputPath = path.join(__dirname, 'fixtures/vinyl/wow/suchempty'); - - var firstFile = new File({ - cwd: __dirname, - path: inputPath, - stat: fs.statSync(inputPath) - }); - - var onEnd = function() { - buffered[0].base.should.equal(inputBase); - done(); - }; - - var stream = app.dest('./actual/', { - cwd: __dirname, - base: function(file){ - should.exist(file); - file.path.should.equal(inputPath); - return inputBase; - } - }); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(firstFile); - stream.end(); - }); - - it('should report IO errors', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, 'actual'); - var expectedMode = 0722; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: expectedMode - } - }); - - fs.mkdirSync(expectedBase); - fs.closeSync(fs.openSync(expectedPath, 'w')); - fs.chmodSync(expectedPath, 0); - - var stream = app.dest('./actual/', {cwd: __dirname}); - stream.on('error', function(err) { - err.code.should.equal('EACCES'); - done(); - }); - stream.write(expectedFile); - }); - - it('should report stat errors', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, 'actual'); - var expectedMode = 0722; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: expectedMode - } - }); - - fs.mkdirSync(expectedBase); - fs.closeSync(fs.openSync(expectedPath, 'w')); - - spies.setError(function(mod, fn) { - if (fn === 'stat' && arguments[2] === expectedPath) { - return new Error('stat error'); - } - }); - - var stream = app.dest('./actual/', {cwd: __dirname}); - stream.on('error', function(err) { - err.message.should.equal('stat error'); - done(); - }); - stream.write(expectedFile); - }); - - it('should report chmod errors', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, 'actual'); - var expectedMode = 0722; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: expectedMode - } - }); - - fs.mkdirSync(expectedBase); - fs.closeSync(fs.openSync(expectedPath, 'w')); - - spies.setError(function(mod, fn) { - if (fn === 'chmod' && arguments[2] === expectedPath) { - return new Error('chmod error'); - } - }); - - var stream = app.dest('./actual/', {cwd: __dirname}); - stream.on('error', function(err) { - err.message.should.equal('chmod error'); - done(); - }); - stream.write(expectedFile); - }); - - it('should not chmod a matching file', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, 'actual'); - var expectedMode = 0722; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: expectedMode - } - }); - - var expectedCount = 0; - spies.setError(function(mod, fn) { - if (fn === 'stat' && arguments[2] === expectedPath) { - expectedCount++; - } - }); - - var onEnd = function(){ - expectedCount.should.equal(1); - assert(!chmodSpy.called); - realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); - done(); - }; - - fs.mkdirSync(expectedBase); - fs.closeSync(fs.openSync(expectedPath, 'w')); - fs.chmodSync(expectedPath, expectedMode); - - statSpy.reset(); - chmodSpy.reset(); - var stream = app.dest('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should see a file with special chmod (setuid/setgid/sticky) as matching', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, 'actual'); - var expectedMode = 03722; - var normalMode = 0722; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: normalMode - } - }); - - var expectedCount = 0; - spies.setError(function(mod, fn) { - if (fn === 'stat' && arguments[2] === expectedPath) { - expectedCount++; - } - }); - - var onEnd = function(){ - expectedCount.should.equal(1); - assert(!chmodSpy.called); - done(); - }; - - fs.mkdirSync(expectedBase); - fs.closeSync(fs.openSync(expectedPath, 'w')); - fs.chmodSync(expectedPath, expectedMode); - - statSpy.reset(); - chmodSpy.reset(); - var stream = app.dest('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should not overwrite files with overwrite option set to false', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var inputContents = fs.readFileSync(inputPath); - - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedBase = path.join(__dirname, 'actual'); - var existingContents = 'Lorem Ipsum'; - - var inputFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: inputContents - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - bufEqual(fs.readFileSync(expectedPath), new Buffer(existingContents)).should.equal(true); - done(); - }; - - // Write expected file which should not be overwritten - fs.mkdirSync(expectedBase); - fs.writeFileSync(expectedPath, existingContents); - - var stream = app.dest('./actual/', {cwd: __dirname, overwrite: false}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(inputFile); - stream.end(); - }); - - it('should overwrite files with overwrite option set to true', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var inputContents = fs.readFileSync(inputPath); - - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedBase = path.join(__dirname, 'actual'); - var existingContents = 'Lorem Ipsum'; - - var inputFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: inputContents - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - bufEqual(fs.readFileSync(expectedPath), new Buffer(inputContents)).should.equal(true); - done(); - }; - - // This should be overwritten - fs.mkdirSync(expectedBase); - fs.writeFileSync(expectedPath, existingContents); - - var stream = app.dest('./actual/', {cwd: __dirname, overwrite: true}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(inputFile); - stream.end(); - }); - - it('should create symlinks when the `symlink` attribute is set on the file', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test-create-dir-symlink'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var inputRelativeSymlinkPath = 'wow'; - - var expectedPath = path.join(__dirname, 'actual/test-create-dir-symlink'); - - var inputFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: null, //'' - }); - - // `src()` adds this side-effect with `keepSymlinks` option set to false - inputFile.symlink = inputRelativeSymlinkPath; - - var onEnd = function(){ - fs.readlink(buffered[0].path, function() { - buffered[0].symlink.should.equal(inputFile.symlink); - buffered[0].path.should.equal(expectedPath); - done(); - }); - }; - - var stream = app.dest('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(inputFile); - stream.end(); - }); - - it('should emit finish event', function(done) { - var srcPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var stream = app.dest('./actual/', {cwd: __dirname}); - - stream.once('finish', function() { - done(); - }); - - var file = new File({ - path: srcPath, - cwd: __dirname, - contents: new Buffer("1234567890") - }); - - stream.write(file); - stream.end(); - }); -}); - -describe('dest', function() { - beforeEach(function(done) { - rimraf(actual, done); - app = new App(); - }); - - afterEach(function(done) { - rimraf(actual, done); - }); - - describe('streams', function() { - it('should return a stream', function(done) { - var stream = app.dest(path.join(__dirname, 'fixtures/')); - should.exist(stream); - should.exist(stream.on); - done(); - }); - - it('should return an output stream that writes files', function(done) { - var instream = app.src(path.join(__dirname, 'fixtures/copy/e*.txt')); - var outstream = app.dest(actual); - instream.pipe(outstream); - - outstream.on('error', done); - outstream.on('data', function(file) { - // data should be re-emitted correctly - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - path.join(file.path, '').should.equal(path.join(actual, 'example.txt')); - String(file.contents).should.equal('Hello world!'); - }); - outstream.on('end', function() { - fs.readFile(path.join(actual, 'example.txt'), function(err, contents) { - should.not.exist(err); - should.exist(contents); - String(contents).should.equal('Hello world!'); - done(); - }); - }); - }); - - it('should return an output stream that does not write non-read files', function(done) { - var instream = app.src(path.join(__dirname, 'fixtures/copy/e*.txt'), {read: false}); - var outstream = app.dest(actual); - instream.pipe(outstream); - - outstream.on('error', done); - outstream.on('data', function(file) { - // data should be re-emitted correctly - should.exist(file); - should.exist(file.path); - should.not.exist(file.contents); - path.join(file.path, '').should.equal(path.join(actual, 'example.txt')); - }); - - outstream.on('end', function() { - fs.readFile(path.join(actual, 'example.txt'), function(err, contents) { - should.exist(err); - should.not.exist(contents); - done(); - }); - }); - }); - - it('should return an output stream that writes streaming files', function(done) { - var instream = app.src(path.join(__dirname, 'fixtures/copy/e*.txt'), {buffer: false}); - var outstream = instream.pipe(app.dest(actual)); - - outstream.on('error', done); - outstream.on('data', function(file) { - // data should be re-emitted correctly - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - path.join(file.path, '').should.equal(path.join(actual, 'example.txt')); - }); - outstream.on('end', function() { - fs.readFile(path.join(actual, 'example.txt'), function(err, contents) { - should.not.exist(err); - should.exist(contents); - String(contents).should.equal('Hello world!'); - done(); - }); - }); - }); - - it('should return an output stream that writes streaming files to new directories', function(done) { - testWriteDir({}, done); - }); - - it('should return an output stream that writes streaming files to new directories (buffer: false)', function(done) { - testWriteDir({buffer: false}, done); - }); - - it('should return an output stream that writes streaming files to new directories (read: false)', function(done) { - testWriteDir({read: false}, done); - }); - - it('should return an output stream that writes streaming files to new directories (read: false, buffer: false)', function(done) { - testWriteDir({buffer: false, read: false}, done); - }); - - }); - - describe('ext', function() { - beforeEach(function() { - app = new App(); - app.set('ext', '.txt'); - }); - - afterEach(function() { - app.set('ext', '.html'); - }); - - it('should return a stream', function(done) { - var stream = app.dest(path.join(__dirname, 'fixtures/')); - should.exist(stream); - should.exist(stream.on); - done(); - }); - - it('should return an output stream that writes files', function(done) { - var instream = app.src(path.join(__dirname, 'fixtures/copy/e*.txt')); - var outstream = app.dest(actual); - instream.pipe(outstream); - - outstream.on('error', done); - outstream.on('data', function(file) { - // data should be re-emitted correctly - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - path.join(file.path, '').should.equal(path.join(actual, 'example.txt')); - String(file.contents).should.equal('Hello world!'); - }); - outstream.on('end', function() { - fs.readFile(path.join(actual, 'example.txt'), function(err, contents) { - should.not.exist(err); - should.exist(contents); - String(contents).should.equal('Hello world!'); - done(); - }); - }); - }); - - it('should return an output stream that does not write non-read files', function(done) { - var instream = app.src(path.join(__dirname, 'fixtures/dest/*.txt'), {read: false}); - var outstream = app.dest(actual); - instream.pipe(outstream); - - outstream.on('error', done); - outstream.on('data', function(file) { - // data should be re-emitted correctly - should.exist(file); - should.exist(file.path); - should.not.exist(file.contents); - path.join(file.path, '').should.equal(path.join(actual, 'example.txt')); - }); - - outstream.on('end', function() { - fs.readFile(path.join(actual, 'example.txt'), function(err, contents) { - should.exist(err); - should.not.exist(contents); - done(); - }); - }); - }); - }); - - function testWriteDir(srcOptions, done) { - var instream = app.src(path.join(__dirname, 'fixtures/generic'), srcOptions); - var outstream = instream.pipe(app.dest(actual)); - - outstream.on('error', done); - outstream.on('data', function(file) { - // data should be re-emitted correctly - should.exist(file); - should.exist(file.path); - path.join(file.path,'').should.equal(path.join(actual, 'generic')); - }); - - outstream.on('end', function() { - fs.exists(path.join(actual, 'generic'), function(exists) { - /* jshint expr: true */ - should(exists).be.ok; - /* jshint expr: false */ - done(); - }); - }); - } -}); - diff --git a/test2/app.engines.js b/test2/app.engines.js deleted file mode 100644 index 16bd062..0000000 --- a/test2/app.engines.js +++ /dev/null @@ -1,160 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('engine support', function() { - beforeEach(function() { - app = new App(); - }); - - it('should throw an error when engine name is invalid:', function() { - (function() { - app.engine(null, {}); - }).should.throw('expected engine ext to be a string or array.'); - }); - - it('should register an engine to the given extension', function() { - app.engine('hbs', function() {}); - assert(typeof app.engines['.hbs'] === 'object'); - }); - - it('should set an engine with the given extension', function() { - var hbs = function() {}; - hbs.render = function() {}; - hbs.renderFile = function() {}; - app.engine('hbs', hbs); - assert(app.engines['.hbs']); - assert(app.engines['.hbs'].renderFile); - assert(app.engines['.hbs'].render); - }); - - it('should get an engine:', function() { - app.engine('hbs', function() {}); - var hbs = app.engine('hbs'); - assert(typeof hbs === 'object'); - assert(hbs.hasOwnProperty('render')); - assert(hbs.hasOwnProperty('compile')); - }); - - it('should return undefined if no engine is found:', function() { - var hbs = app.getEngine(); - assert.equal(typeof hbs, 'undefined'); - }); - - it('should register multiple engines to the given extension', function() { - app.engine(['hbs', 'md'], function() {}); - assert(typeof app.engines['.hbs'] === 'object'); - assert(typeof app.engines['.md'] === 'object'); - }); -}); - -describe('engines', function() { - beforeEach(function() { - app = new App(); - app.create('pages'); - app.pages('foo.tmpl', {content: 'A <%= letter %> {{= letter }} C'}); - app.pages('bar.tmpl', {content: 'A <%= letter %> {{ letter }} C'}); - }); - - it('should register an engine:', function() { - app.engine('a', {render: function() {}}); - app.engines.should.have.property('.a'); - }); - - it('should use custom delimiters:', function(cb) { - app.engine('tmpl', require('engine-base'), { - delims: ['{{', '}}'] - }); - app.render('foo.tmpl', {letter: 'B'}, function(err, res) { - if (err) return cb(err); - res.contents.toString().should.equal('A <%= letter %> B C'); - cb(); - }); - }); - - it('should override individual delims values:', function(cb) { - app.engine('tmpl', require('engine-base'), { - interpolate: /\{{([^}]+)}}/g, - evaluate: /\{{([^}]+)}}/g, - escape: /\{{-([^}]+)}}/g - }); - app.render('bar.tmpl', {letter: 'B'}, function(err, res) { - if (err) return cb(err); - res.contents.toString().should.equal('A <%= letter %> B C'); - cb(); - }); - }); - - it('should get an engine:', function() { - app.engine('a', { - render: function() {} - }); - var a = app.engine('a'); - a.should.have.property('render'); - }); -}); - -describe('engine selection:', function() { - beforeEach(function(cb) { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.engine('hbs', require('engine-handlebars')); - app.create('pages'); - cb(); - }); - - it('should get the engine from file extension:', function(cb) { - app.page('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}) - .render(function(err, view) { - if (err) return cb(err); - assert(view.content === 'b'); - cb(); - }); - }); - - it('should use the engine defined on the collection:', function(cb) { - app.create('posts', {engine: 'hbs'}); - - app.post('a', {content: '{{a}}', locals: {a: 'b'}}) - .render(function(err, view) { - if (err) return cb(err); - assert(view.content === 'b'); - cb(); - }); - }); - - it('should use the engine defined on the view:', function(cb) { - app.create('posts'); - app.post('a', {content: '{{a}}', engine: 'hbs', locals: {a: 'b'}}) - .render(function(err, view) { - if (err) return cb(err); - assert(view.content === 'b'); - cb(); - }); - }); - - it('should use the engine defined on `view.data`:', function(cb) { - app.create('posts'); - app.post('a', {content: '{{a}}', locals: {a: 'b'}, data: {engine: 'hbs'}}) - .render(function(err, view) { - if (err) return cb(err); - assert(view.content === 'b'); - cb(); - }); - }); - - it('should use the engine defined on render locals:', function(cb) { - app.create('posts'); - app.post('a', {content: '{{a}}', locals: {a: 'b'}}) - .render({engine: 'hbs'}, function(err, view) { - if (err) return cb(err); - assert(view.content === 'b'); - cb(); - }); - }); -}); diff --git a/test2/app.events.js b/test2/app.events.js deleted file mode 100644 index bae16fe..0000000 --- a/test2/app.events.js +++ /dev/null @@ -1,113 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('events', function() { - beforeEach(function() { - app = new App(); - }); - - it('should listen for an event:', function() { - var app = new App(); - app.on('foo', function() {}); - assert(Array.isArray(app._callbacks['$foo'])); - }); - - it('should emit an event:', function(done) { - var app = new App(); - app.on('foo', function(val) { - assert(val === 'bar'); - done(); - }); - assert(Array.isArray(app._callbacks['$foo'])); - app.emit('foo', 'bar'); - }); - - it('should listen for `view` events:', function() { - app = new App(); - - app.on('view', function(view) { - view.foo = 'bar'; - }); - - var view = app.view({path: 'a', content: 'b'}); - assert(view.foo === 'bar'); - }); -}); - -describe('onLoad', function() { - beforeEach(function() { - app = new App(); - }); - - describe('app.collection', function() { - it('should emit a `view` event when view is created', function(done) { - var collection = app.collection(); - - app.on('view', function(view) { - assert(view.path === 'blog/foo.js'); - done(); - }); - - app.onLoad('blog/:title', function(view, next) { - assert(view.path === 'blog/foo.js'); - next(); - }); - - collection.addView('whatever', {path: 'blog/foo.js', content: 'bar baz'}); - }); - - it('should emit an onLoad event when view is created', function(done) { - var collection = app.collection(); - - app.on('onLoad', function(view) { - assert(view.path === 'blog/foo.js'); - done(); - }); - - app.onLoad('blog/:title', function(view, next) { - assert(view.path === 'blog/foo.js'); - next(); - }); - - collection.addView('whatever', {path: 'blog/foo.js', content: 'bar baz'}); - }); - }); - - describe('view collections', function() { - it('should emit a view event when view is created', function(done) { - app.create('posts'); - - app.on('view', function(view) { - assert(view.path === 'blog/foo.js'); - done(); - }); - - app.onLoad('blog/:title', function(view, next) { - assert(view.path === 'blog/foo.js'); - next(); - }); - - app.post('whatever', {path: 'blog/foo.js', content: 'bar baz'}); - }); - - it('should emit an onLoad event when view is created', function(done) { - app.create('posts'); - - app.on('onLoad', function(view) { - assert(view.path === 'blog/foo.js'); - done(); - }); - - app.onLoad('blog/:title', function(view, next) { - assert(view.path === 'blog/foo.js'); - next(); - }); - - app.post('whatever', {path: 'blog/foo.js', content: 'bar baz'}); - }); - }); -}); diff --git a/test2/app.get-set.js b/test2/app.get-set.js deleted file mode 100644 index 8e8a597..0000000 --- a/test2/app.get-set.js +++ /dev/null @@ -1,72 +0,0 @@ -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('app.set()', function() { - beforeEach(function() { - app = new App(); - }); - - it('should set a value', function() { - app.set('a', 'b'); - app.get('a').should.equal('b'); - }); - - it('should set properties on the instance.', function() { - app.set('a', 'b'); - app.a.should.equal('b'); - }); - - it('should allow an object to be set directly.', function() { - app.set({x: 'y'}); - app.x.should.equal('y'); - app.get('x').should.equal('y'); - }); - - it('should set nested properties on the instance.', function() { - app.set('c', {d: 'e'}); - app.get('c').d.should.equal('e'); - }); - - it('should use dot notation to `set` values.', function() { - app.set('h.i', 'j'); - app.get('h').should.eql({i: 'j'}); - }); - - it('should use dot notation to `get` values.', function() { - app.set('h', {i: 'j'}); - app.get('h.i').should.equal('j'); - }); - - it('should return `this` for chaining', function() { - app.set('a', 'b').should.equal(app); - app - .set('aa', 'bb') - .set('bb', 'cc') - .set('cc', 'dd'); - app.get('aa').should.equal('bb'); - app.get('bb').should.equal('cc'); - app.get('cc').should.equal('dd'); - }); - - it('should return undefined when not set', function() { - app.set('a', undefined).should.equal(app); - }); -}); - -describe('app.get()', function() { - beforeEach(function() { - app = new App(); - }); - - it('should return undefined when no set', function() { - assert(app.get('a') === undefined); - }); - - it('should otherwise return the value', function() { - app.set('a', 'b'); - app.get('a').should.equal('b'); - }); -}); diff --git a/test2/app.handle.js b/test2/app.handle.js deleted file mode 100644 index d27d82d..0000000 --- a/test2/app.handle.js +++ /dev/null @@ -1,37 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('handler', function() { - beforeEach(function() { - app = new App(); - app.create('pages'); - app.handlers(['foo']); - }); - - it('should support custom handle methods:', function(done) { - var page = app.page('foo', {contents: null}); - - app.handle('foo', page, function(err, view) { - if (err) return done(err); - - assert(typeof view.path === 'string'); - done(); - }); - }); - - it('should not blow up if `options.handled` does not exist:', function(done) { - var page = app.page('foo', {contents: null}); - delete page.options.handled; - - app.handle('foo', page, function(err, view) { - if (err) return done(err); - - assert(typeof view.path === 'string'); - done(); - }); - }); -}); diff --git a/test2/app.handlers.js b/test2/app.handlers.js deleted file mode 100644 index f2b94d2..0000000 --- a/test2/app.handlers.js +++ /dev/null @@ -1,72 +0,0 @@ -require('should'); -var fs = require('fs'); -var path = require('path'); -var assert = require('assert'); -var resolve = require('resolve-glob'); -var support = require('./support'); -var App = support.resolve(); -var app; - -function decorateViews(views) { - var fn = views.decorateView; - views.decorateView = function() { - var view = fn.apply(fn, arguments); - view.read = function() { - if (!this.contents) { - this.contents = fs.readFileSync(this.path); - } - }; - return view; - }; - views.loader = function(pattern) { - var files = resolve.sync(pattern); - return files.reduce(function(acc, fp) { - acc[fp] = {path: fp}; - return acc; - }, {}); - }; - return views; -} - -describe('handlers', function() { - describe('custom handlers', function() { - beforeEach(function() { - app = new App(); - app.create('pages') - .use(decorateViews) - .option('renameKey', function(key) { - return path.basename(key); - }); - }); - - it('should add custom middleware handlers:', function() { - app.handler('foo'); - app.router.should.have.property('foo'); - assert.equal(typeof app.router.foo, 'function'); - }); - - it('should add custom middleware handlers:', function() { - app.handler('foo'); - app.handler('bar'); - - app.foo(/./, function(view, next) { - view.one = 'aaa'; - next(); - }); - - app.bar(/./, function(view, next) { - view.two = 'zzz'; - next(); - }); - - app.page('abc', {content: '...'}) - .use(function(view) { - app.handleView('foo', view); - app.handleView('bar', view); - }); - - app.views.pages.abc.should.have.property('one', 'aaa'); - app.views.pages.abc.should.have.property('two', 'zzz'); - }); - }); -}); diff --git a/test2/app.js b/test2/app.js deleted file mode 100644 index 5ff835b..0000000 --- a/test2/app.js +++ /dev/null @@ -1,130 +0,0 @@ -/* deps: coveralls istanbul */ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var Base = App.Base; -var app; - -describe('app', function() { - describe('constructor', function() { - it('should create an instance of App:', function() { - app = new App(); - assert(app instanceof App); - }); - - it('should new up without new:', function() { - app = App(); - assert(app instanceof App); - }); - }); - - describe('static methods', function() { - it('should expose `extend`:', function() { - assert(typeof App.extend === 'function'); - }); - }); - - describe('prototype methods', function() { - beforeEach(function() { - app = new App(); - }); - - it('should expose `set`', function() { - assert(typeof app.set === 'function'); - }); - it('should expose `get`', function() { - assert(typeof app.get === 'function'); - }); - it('should expose `visit`', function() { - assert(typeof app.visit === 'function'); - }); - it('should expose `define`', function() { - assert(typeof app.define === 'function'); - }); - it('should expose `views`', function() { - assert(typeof app.views === 'object'); - }); - }); - - describe('instance', function() { - beforeEach(function() { - app = new App(); - }); - - it('should set a value on the instance:', function() { - app.set('a', 'b'); - assert(app.a === 'b'); - }); - - it('should get a value from the instance:', function() { - app.set('a', 'b'); - assert(app.get('a') === 'b'); - }); - }); - - describe('initialization', function() { - it('should listen for errors:', function(done) { - app = new App(); - app.on('error', function(err) { - assert(err.message === 'foo'); - done(); - }); - app.emit('error', new Error('foo')); - }); - - it('should mixin methods after init:', function() { - app = new App(); - app.option({ - mixins: { - foo: function() {} - } - }); - assert(typeof app.foo === 'function'); - }); - - it('should expose constructors from `lib`:', function() { - app = new App(); - app.expose('Collection'); - assert(typeof app.Collection === 'function'); - }); - - it('should update constructors after init:', function() { - var Group = App.Group; - function MyGroup() { - Base.call(this); - } - Base.extend(MyGroup); - - app = new App(); - assert.equal(app.Group, Group); - assert.equal(app.get('Group'), Group); - app.option('Group', MyGroup); - assert.equal(app.Group, MyGroup); - assert.equal(app.get('Group'), MyGroup); - }); - - it('should mixin prototype methods defined on options:', function() { - app = new App({ - mixins: { - foo: function() {} - } - }); - assert(typeof app.foo === 'function'); - delete App.prototype.foo; - }); - - it('should expose `_` on app:', function() { - app = new App(); - assert(typeof app._ === 'object'); - }); - - it('should not re-add `_` in init:', function() { - app = new App(); - app._.foo = 'bar'; - app.defaultConfig(); - assert(app._.foo === 'bar'); - }); - }); -}); diff --git a/test2/app.list.compile.js b/test2/app.list.compile.js deleted file mode 100644 index 1efa791..0000000 --- a/test2/app.list.compile.js +++ /dev/null @@ -1,44 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var List = App.List; -var list; - -describe('app.list.compile', function() { - beforeEach(function() { - list = new List(); - list.engine('tmpl', require('engine-base')); - }); - - it('should compile an item:', function() { - var buffer = new Buffer('a b c'); - var item = list.addItem('a.tmpl', {contents: buffer}) - .compile(); - - assert(typeof item.fn === 'function'); - }); - - it('should use the compiled function to render:', function() { - var buffer = new Buffer('a <%= title %> c'); - var item = list.addItem('a.tmpl', {contents: buffer}) - .compile(); - - assert(item.fn({title: 'z'})); - assert(typeof item.fn({title: 'z'}) === 'string'); - assert(item.fn({title: 'z'}) === 'a z c'); - }); - - it('should compile a view by name:', function() { - var buffer = new Buffer('a <%= title %> c'); - list.addItem('a.tmpl', {contents: buffer}); - - var item = list.compile('a.tmpl'); - - assert(item.fn({title: 'z'})); - assert(typeof item.fn({title: 'z'}) === 'string'); - assert(item.fn({title: 'z'}) === 'a z c'); - }); -}); - diff --git a/test2/app.list.js b/test2/app.list.js deleted file mode 100644 index 84527ec..0000000 --- a/test2/app.list.js +++ /dev/null @@ -1,111 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var fs = require('fs'); -var path = require('path'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var List = App.List; -var app; - -describe('list', function() { - describe('method', function() { - beforeEach(function() { - app = new App(); - }); - - it('should expose the list method', function() { - assert(typeof app.list === 'function'); - }); - - it('should return a new list', function() { - var list = app.list(); - assert(typeof list === 'object'); - }); - - it('should have isList property', function() { - var list = app.list(); - assert(list.isList === true); - }); - }); - - describe('adding items', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('pages', { - renameKey: function(fp) { - return path.relative(process.cwd(), fp); - } - }); - }); - - it('should add an item to a list:', function() { - app.pages('test/fixtures/pages/a.hbs'); - var list = app.list(); - list.addItem(app.pages.getView('test/fixtures/pages/a.hbs')); - assert(list.hasItem('test/fixtures/pages/a.hbs')); - }); - - it('should expose the `option` method from a list:', function() { - var list = app.list(); - list.option('a', 'b'); - assert(list.options); - assert(list.options.a === 'b'); - }); - }); - - describe('addItem', function() { - beforeEach(function() { - app = new App(); - }); - - it('should add items to a list', function() { - var pages = app.list({List: List}); - pages.addItem('foo'); - pages.addItem('bar'); - pages.addItem('baz'); - - pages.items.hasOwnProperty('foo'); - pages.items.hasOwnProperty('bar'); - pages.items.hasOwnProperty('baz'); - }); - - it('should create a list from an existing list:', function() { - var pages = app.list({List: List}); - pages.addItem('foo'); - pages.addItem('bar'); - pages.addItem('baz'); - - var posts = app.list(pages); - posts.items.hasOwnProperty('foo'); - posts.items.hasOwnProperty('bar'); - posts.items.hasOwnProperty('baz'); - }); - }); - - describe('rendering items', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('pages'); - }); - - it('should render a item with inherited app.render', function(done) { - app.page('test/fixtures/templates/a.tmpl') - .use(function(item) { - if (!item.content) { - item.contents = fs.readFileSync(item.path); - } - }) - .set('data.name', 'Brian') - .render(function(err, res) { - if (err) return done(err); - assert(res.contents.toString() === 'Brian'); - done(); - }); - }); - }); -}); diff --git a/test2/app.lookups.js b/test2/app.lookups.js deleted file mode 100644 index afab53b..0000000 --- a/test2/app.lookups.js +++ /dev/null @@ -1,112 +0,0 @@ -require('mocha'); -require('should'); -var fs = require('fs'); -var path = require('path'); -var assert = require('assert'); -var resolve = require('resolve-glob'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('lookups', function() { - beforeEach(function() { - app = new App(); - app.option('renameKey', function(key) { - return path.basename(key); - }); - app.create('pages') - .use(function(pages) { - pages.on('addViews', function(glob) { - var files = resolve.sync(glob); - files.forEach(function(fp) { - pages.addView(fp, {path: fp}); - }); - pages.loaded = true; - }); - return function(view) { - view.read = function() { - this.contents = fs.readFileSync(this.path); - }; - return view; - }; - }); - - app.pages('test/fixtures/templates/*.tmpl'); - }); - - describe('getView', function() { - it('should find a view', function() { - var view = app.getView('pages', 'a.tmpl'); - assert(typeof view.path === 'string'); - }); - - it('should find a view using the renameKey function', function() { - var view = app.getView('pages', 'test/fixtures/templates/a.tmpl'); - assert(typeof view.path === 'string'); - }); - - it('should return null when nothing is found', function() { - var view = app.getView('pages', 'test/fixtures/templates/foo.tmpl'); - assert(view === null); - }); - - it('should find a view using a glob pattern', function() { - var view = app.getView('pages', 'a', function(key) { - return key + '.tmpl'; - }); - assert(typeof view.path === 'string'); - }); - }); - - describe('getViews', function() { - it('should return the collection object if passed:', function() { - var views = app.getViews(app.views.pages); - assert(Object.keys(views).length > 1); - }); - - it('should return the specified collection with the plural name:', function() { - var views = app.getViews('pages'); - assert(Object.keys(views).length > 1); - }); - - it('should return the specified collection with the singular name:', function() { - var views = app.getViews('page'); - assert(Object.keys(views).length > 1); - }); - - it('should return null when the collection is not found:', function() { - (function() { - app.getViews('nada'); - }).should.throw('getViews cannot find collection: nada'); - }); - }); - - describe('find', function() { - it('should return null when a view is not found:', function() { - (function() { - app.find({}); - }).should.throw('expected name to be a string.'); - }); - - it('should find a view by collection name:', function() { - var view = app.find('a.tmpl', 'pages'); - assert(typeof view.path === 'string'); - }); - - it('should find a view by collection name:', function() { - app = new App(); - app.option('renameKey', function(key) { - return path.basename(key); - }); - app.create('pages'); - app.page('a/b/c.md', {content: '...'}); - var view = app.find('a/b/c.md'); - assert(typeof view.path === 'string'); - }); - - it('should find a view without a collection name:', function() { - var view = app.find('a.tmpl'); - assert(typeof view.path === 'string'); - }); - }); -}); diff --git a/test2/app.middleware.js b/test2/app.middleware.js deleted file mode 100644 index e32ca1a..0000000 --- a/test2/app.middleware.js +++ /dev/null @@ -1,60 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('middleware', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('pages'); - }); - - it('should call the all method for every middleware method:', function() { - var i = 0; - app.all(/./, function(view, next) { - assert(typeof view.path === 'string'); - i++; - next(); - }); - - assert(i === 0); - app.page('foo.tmpl', {content: 'foo'}); - assert(i === 1); - }); - - it('should call the onLoad method when a view is loaded:', function() { - var i = 0; - app.onLoad(/./, function(view, next) { - assert(typeof view.path === 'string'); - i++; - next(); - }); - - assert(i === 0); - app.page('foo.tmpl', {content: 'foo'}); - assert(i === 1); - }); - - it('should emit an event when a handler is called:', function(done) { - var i = 0; - app.on('onLoad', function() { - i++; - }); - app.on('preRender', function() { - i++; - }); - app.on('preCompile', function() { - i++; - }); - - app.page('foo.tmpl', {content: 'foo'}) - .render(function(err) { - if (err) return done(err); - assert(i === 3); - done(); - }); - }); -}); diff --git a/test2/app.onLoad.js b/test2/app.onLoad.js deleted file mode 100644 index 66bffbe..0000000 --- a/test2/app.onLoad.js +++ /dev/null @@ -1,48 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('onLoad', function() { - beforeEach(function() { - app = new App(); - }); - - describe('app.collection', function() { - it('should emit an onLoad when view is created', function(done) { - var collection = app.collection(); - - app.on('onLoad', function(view) { - assert(view.path === 'blog/foo.js'); - done(); - }); - - app.onLoad('blog/:title', function(view, next) { - assert(view.path === 'blog/foo.js'); - next(); - }); - - collection.addView('whatever', {path: 'blog/foo.js', content: 'bar baz'}); - }); - }); - - describe('view collections', function() { - it('should emit an onLoad when view is created', function(done) { - app.create('posts'); - - app.on('onLoad', function(view) { - assert(view.path === 'blog/foo.js'); - done(); - }); - - app.onLoad('blog/:title', function(view, next) { - assert(view.path === 'blog/foo.js'); - next(); - }); - - app.post('whatever', {path: 'blog/foo.js', content: 'bar baz'}); - }); - }); -}); diff --git a/test2/app.option.js b/test2/app.option.js deleted file mode 100644 index c4043e7..0000000 --- a/test2/app.option.js +++ /dev/null @@ -1,98 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('app.option', function() { - beforeEach(function() { - app = new App(); - }); - - it('should set a key-value pair on options:', function() { - app.option('a', 'b'); - assert(app.options.a === 'b'); - }); - - it('should set an object on options:', function() { - app.option({c: 'd'}); - assert(app.options.c === 'd'); - }); - - it('should set an option.', function() { - app.option('a', 'b'); - app.options.should.have.property('a'); - }); - - it('should get an option.', function() { - app.option('a', 'b'); - app.option('a').should.equal('b'); - }); - - it('should extend the `options` object.', function() { - app.option({x: 'xxx', y: 'yyy', z: 'zzz'}); - app.option('x').should.equal('xxx'); - app.option('y').should.equal('yyy'); - app.option('z').should.equal('zzz'); - }); - - it('options should be on the `options` object.', function() { - app.option({x: 'xxx', y: 'yyy', z: 'zzz'}); - app.options.x.should.equal('xxx'); - app.options.y.should.equal('yyy'); - app.options.z.should.equal('zzz'); - }); - - it('should be chainable.', function() { - app.option({x: 'xxx', y: 'yyy', z: 'zzz'}); - app.option({a: 'aaa', b: 'bbb', c: 'ccc'}); - - app.option('x').should.equal('xxx'); - app.option('a').should.equal('aaa'); - }); - - it('should extend the `options` object when the first param is a string.', function() { - app.option('foo', {x: 'xxx', y: 'yyy', z: 'zzz'}); - app.option('bar', {a: 'aaa', b: 'bbb', c: 'ccc'}); - - app.option('foo').should.have.property('x'); - app.option('bar').should.have.property('a'); - - app.options.foo.should.have.property('x'); - app.options.bar.should.have.property('a'); - }); - - it('should set an option.', function() { - app.option('a', 'b'); - app.options.should.have.property('a'); - }); - - it('should get an option.', function() { - app.option('a', 'b'); - app.option('a').should.equal('b'); - }); - - it('should extend the `options` object.', function() { - app.option({x: 'xxx', y: 'yyy', z: 'zzz'}); - app.option('x').should.equal('xxx'); - app.option('y').should.equal('yyy'); - app.option('z').should.equal('zzz'); - }); - - it('options should be on the `options` object.', function() { - app.option({x: 'xxx', y: 'yyy', z: 'zzz'}); - app.options.x.should.equal('xxx'); - app.options.y.should.equal('yyy'); - app.options.z.should.equal('zzz'); - }); - - it('should be chainable.', function() { - app - .option({x: 'xxx', y: 'yyy', z: 'zzz'}) - .option({a: 'aaa', b: 'bbb', c: 'ccc'}); - - app.option('x').should.equal('xxx'); - app.option('a').should.equal('aaa'); - }); -}); diff --git a/test2/app.render.js b/test2/app.render.js deleted file mode 100644 index 98c194c..0000000 --- a/test2/app.render.js +++ /dev/null @@ -1,88 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('render', function() { - describe('rendering', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('page'); - }); - - it('should throw an error when no callback is given:', function() { - (function() { - app.render({}); - }).should.throw('Templates#render is async and expects a callback function'); - }); - - it('should throw an error when an engine is not defined:', function(done) { - app.page('foo.bar', {content: '<%= name %>'}); - var page = app.pages.getView('foo.bar'); - - app.render(page, function(err) { - assert(err.message === 'Templates#render cannot find an engine for: .bar'); - done(); - }); - }); - - it('should use helpers to render a view:', function(done) { - var locals = {name: 'Halle'}; - - app.helper('upper', function(str) { - return str.toUpperCase(str); - }); - - app.page('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); - var page = app.pages.getView('a.tmpl'); - - app.render(page, function(err, res) { - if (err) return done(err); - - assert(res.contents.toString() === 'a HALLE b'); - done(); - }); - }); - - it('should use helpers when rendering a view:', function(done) { - var locals = {name: 'Halle'}; - app.helper('upper', function(str) { - return str.toUpperCase(str); - }); - - app.page('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); - var page = app.pages.getView('a.tmpl'); - - app.render(page, function(err, res) { - if (err) return done(err); - assert(res.contents.toString() === 'a HALLE b'); - done(); - }); - }); - - it('should render a template when contents is a buffer:', function(done) { - app.pages('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - var view = app.pages.getView('a.tmpl'); - - app.render(view, function(err, view) { - if (err) return done(err); - assert(view.contents.toString() === 'b'); - done(); - }); - }); - - it('should render a template when content is a string:', function(done) { - app.pages('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - var view = app.pages.getView('a.tmpl'); - - app.render(view, function(err, view) { - if (err) return done(err); - assert(view.contents.toString() === 'b'); - done(); - }); - }); - }); -}); diff --git a/test2/app.renderFile.js b/test2/app.renderFile.js deleted file mode 100644 index 4e3041a..0000000 --- a/test2/app.renderFile.js +++ /dev/null @@ -1,135 +0,0 @@ -'use strict'; - -var assemble = require('..'); -var assert = require('assert'); -var should = require('should'); -var path = require('path'); -var app; - -describe('app.renderFile()', function() { - beforeEach(function() { - app = assemble(); - app.engine('hbs', require('engine-handlebars')); - app.engine('*', require('engine-base')); - - app.create('files', {engine: '*'}); - app.file('a', {content: 'this is <%= title() %>'}); - app.file('b', {content: 'this is <%= title() %>'}); - app.file('c', {content: 'this is <%= title() %>'}); - - app.option('renameKey', function(key) { - return path.basename(key, path.extname(key)); - }); - - app.helper('title', function() { - var view = this.context.view; - var key = view.key; - var ctx = this.context[key]; - if (ctx && ctx.title) return ctx.title; - return key; - }); - }); - - it('should render views from src', function(done) { - var stream = app.src(path.join(__dirname, 'fixtures/pages/*.hbs')); - var files = []; - - stream.pipe(app.renderFile()) - .on('error', done) - .on('data', function(file) { - files.push(file); - }) - .on('end', function() { - assert.equal(files[0].basename, 'a.hbs'); - assert.equal(files[1].basename, 'b.hbs'); - assert.equal(files[2].basename, 'c.hbs'); - done(); - }); - }); - - it('should render views with the engine that matches the file extension', function(done) { - var stream = app.src(path.join(__dirname, 'fixtures/pages/*.hbs')); - var files = []; - - stream.pipe(app.renderFile()) - .on('error', done) - .on('data', function(file) { - files.push(file); - }) - .on('end', function() { - assert(/

a<\/h1>/.test(files[0].content)); - assert(/

b<\/h1>/.test(files[1].content)); - assert(/

c<\/h1>/.test(files[2].content)); - done(); - }); - }); - - it('should render views from src with the engine passed on the opts', function(done) { - var stream = app.src(path.join(__dirname, 'fixtures/pages/*.hbs')); - var files = []; - - stream.pipe(app.renderFile({engine: '*'})) - .on('error', done) - .on('data', function(file) { - files.push(file); - }) - .on('end', function() { - assert(/

a<\/h2>/.test(files[0].content)); - assert(/

b<\/h2>/.test(files[1].content)); - assert(/

c<\/h2>/.test(files[2].content)); - done(); - }); - }); - - it('should use the context passed on the opts', function(done) { - var stream = app.src(path.join(__dirname, 'fixtures/pages/*.hbs')); - var files = []; - - stream.pipe(app.renderFile({a: {title: 'foo'}})) - .on('error', done) - .on('data', function(file) { - files.push(file); - }) - .on('end', function() { - assert(/

foo<\/h1>/.test(files[0].content)); - assert(/

b<\/h1>/.test(files[1].content)); - assert(/

c<\/h1>/.test(files[2].content)); - done(); - }); - }); - - it('should render the files in a collection', function(cb) { - var files = []; - app.toStream('files') - .pipe(app.renderFile()) - .on('error', cb) - .on('data', function(file) { - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - files.push(file); - }) - .on('end', function() { - assert(/this is a/.test(files[0].content)); - assert(/this is b/.test(files[1].content)); - assert(/this is c/.test(files[2].content)); - assert.equal(files.length, 3); - cb(); - }); - }); - - it('should handle engine errors', function(cb) { - app.create('notdefined', {engine: '*'}); - app.notdefined('foo', {content: '<%= bar %>'}); - app.toStream('notdefined') - .pipe(app.renderFile()) - .on('error', function(err) { - assert.equal(typeof err, 'object'); - assert.equal(err.message, 'bar is not defined'); - cb(); - }) - .on('end', function() { - cb(new Error('expected renderFile to handle the error.')); - }); - }); -}); diff --git a/test2/app.route.js b/test2/app.route.js deleted file mode 100644 index 213dd56..0000000 --- a/test2/app.route.js +++ /dev/null @@ -1,93 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('routes', function() { - beforeEach(function() { - app = new App(); - }); - - describe('routes', function() { - it('should create a route for the given path:', function(done) { - app = new App(); - app.create('posts'); - - app.on('all', function(msg) { - assert(msg === 'done'); - done(); - }); - - app.route('blog/:title') - .all(function(view, next) { - app.emit('all', 'done'); - next(); - }); - - app.post('whatever', {path: 'blog/foo.js', content: 'bar baz'}); - }); - - it('should emit events when a route method is called:', function(done) { - app = new App(); - app.create('posts'); - - app.on('onLoad', function(view) { - assert(view.path === 'blog/foo.js'); - done(); - }); - - app.param('title', function(view, next, title) { - assert(title === 'foo.js'); - next(); - }); - - app.onLoad('blog/:title', function(view, next) { - assert(view.path === 'blog/foo.js'); - next(); - }); - - app.post('whatever', {path: 'blog/foo.js', content: 'bar baz'}); - }); - - it('should emit errors', function(done) { - app = new App(); - app.create('posts'); - - app.on('error', function(err) { - assert(err.message === 'false == true'); - done(); - }); - - // wrong... - app.param('title', function(view, next, title) { - assert(title === 'fo.js'); - next(); - }); - - app.onLoad('/blog/:title', function(view, next) { - assert(view.path === '/blog/foo.js'); - next(); - }); - - app.post('whatever', {path: '/blog/foo.js', content: 'bar baz'}); - }); - - it('should have path property', function() { - var route = new app.Route('/blog/:year/:month/:day/:slug').all([ - function() {} - ]); - route.path.should.equal('/blog/:year/:month/:day/:slug'); - }); - - it('should have stack property', function() { - var route = new app.Route('/blog/:year/:month/:day/:slug').all([ - function() {} - ]); - - route.stack.should.be.instanceof(Array); - route.stack.should.have.length(1); - }); - }); -}); diff --git a/test2/app.src.js b/test2/app.src.js deleted file mode 100644 index 034917b..0000000 --- a/test2/app.src.js +++ /dev/null @@ -1,295 +0,0 @@ -'use strict'; - -var App = require('..'); -var assert = require('assert'); -var should = require('should'); -var join = require('path').join; -var app; - -describe('src()', function() { - beforeEach(function() { - app = new App(); - }); - - it('should return a stream', function(done) { - var stream = app.src(join(__dirname, './fixtures/*.coffee')); - assert(stream); - assert.equal(typeof stream.on, 'function'); - assert.equal(typeof stream.pipe, 'function'); - done(); - }); - - it('should return an input stream from a flat glob', function(done) { - var stream = app.src(join(__dirname, './fixtures/*.coffee')); - stream.on('error', done); - stream.on('data', function(file) { - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); - String(file.contents).should.equal('Hello world!'); - }); - stream.on('end', function() { - done(); - }); - }); - - it('should return an input stream for multiple globs', function(done) { - var globArray = [ - join(__dirname, './fixtures/generic/run.dmc'), - join(__dirname, './fixtures/generic/test.dmc') - ]; - var stream = app.src(globArray); - - var files = []; - stream.on('error', done); - stream.on('data', function(file) { - should.exist(file); - should.exist(file.path); - files.push(file); - }); - stream.on('end', function() { - files.length.should.equal(2); - files[0].path.should.equal(globArray[0]); - files[1].path.should.equal(globArray[1]); - done(); - }); - }); - - it('should return an input stream for multiple globs with negation', function(done) { - var expectedPath = join(__dirname, './fixtures/generic/run.dmc'); - var globArray = [ - join(__dirname, './fixtures/generic/*.dmc'), - '!' + join(__dirname, './fixtures/generic/test.dmc'), - ]; - var stream = app.src(globArray); - - var files = []; - stream.on('error', done); - stream.on('data', function(file) { - should.exist(file); - should.exist(file.path); - files.push(file); - }); - stream.on('end', function() { - files.length.should.equal(1); - files[0].path.should.equal(expectedPath); - done(); - }); - }); - - it('should return an input stream with no contents when read is false', function(done) { - var stream = app.src(join(__dirname, './fixtures/*.coffee'), {read: false}); - stream.on('error', done); - stream.on('data', function(file) { - should.exist(file); - should.exist(file.path); - should.not.exist(file.contents); - join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); - }); - stream.on('end', function() { - done(); - }); - }); - - it('should return an input stream with contents as stream when buffer is false', function(done) { - var stream = app.src(join(__dirname, './fixtures/*.coffee'), {buffer: false}); - stream.on('error', done); - stream.on('data', function(file) { - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - var buf = ''; - file.contents.on('data', function(d) { - buf += d; - }); - file.contents.on('end', function() { - buf.should.equal('Hello world!'); - done(); - }); - join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); - }); - }); - - it('should return an input stream from a deep glob', function(done) { - var stream = app.src(join(__dirname, './fixtures/**/*.jade')); - stream.on('error', done); - stream.on('data', function(file) { - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - join(file.path, '').should.equal(join(__dirname, './fixtures/test/run.jade')); - String(file.contents).should.equal('test template'); - }); - stream.on('end', function() { - done(); - }); - }); - - it('should return an input stream from a deeper glob', function(done) { - var stream = app.src(join(__dirname, './fixtures/**/*.dmc')); - var a = 0; - stream.on('error', done); - stream.on('data', function() { - ++a; - }); - stream.on('end', function() { - a.should.equal(2); - done(); - }); - }); - - it('should return a file stream from a flat path', function(done) { - var a = 0; - var stream = app.src(join(__dirname, './fixtures/test.coffee')); - stream.on('error', done); - stream.on('data', function(file) { - ++a; - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); - String(file.contents).should.equal('Hello world!'); - }); - stream.on('end', function() { - a.should.equal(1); - done(); - }); - }); - - it('should return a stream', function(done) { - var stream = app.src(join(__dirname, './fixtures/*.coffee')); - should.exist(stream); - should.exist(stream.on); - done(); - }); - - it('should return an input stream from a flat glob', function(done) { - var stream = app.src(join(__dirname, './fixtures/*.coffee')); - stream.on('error', done); - stream.on('data', function(file) { - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); - String(file.contents).should.equal('Hello world!'); - }); - stream.on('end', function() { - done(); - }); - }); - - it('should return an input stream for multiple globs', function(done) { - var globArray = [ - join(__dirname, './fixtures/generic/run.dmc'), - join(__dirname, './fixtures/generic/test.dmc') - ]; - var stream = app.src(globArray); - - var files = []; - stream.on('error', done); - stream.on('data', function(file) { - should.exist(file); - should.exist(file.path); - files.push(file); - }); - stream.on('end', function() { - files.length.should.equal(2); - files[0].path.should.equal(globArray[0]); - files[1].path.should.equal(globArray[1]); - done(); - }); - }); - - it('should return an input stream for multiple globs, with negation', function(done) { - var expectedPath = join(__dirname, './fixtures/generic/run.dmc'); - var globArray = [ - join(__dirname, './fixtures/generic/*.dmc'), - '!' + join(__dirname, './fixtures/generic/test.dmc'), - ]; - var stream = app.src(globArray); - - var files = []; - stream.on('error', done); - stream.on('data', function(file) { - should.exist(file); - should.exist(file.path); - files.push(file); - }); - stream.on('end', function() { - files.length.should.equal(1); - files[0].path.should.equal(expectedPath); - done(); - }); - }); - - it('should return an input stream with no contents when read is false', function(done) { - var stream = app.src(join(__dirname, './fixtures/*.coffee'), {read: false}); - stream.on('error', done); - stream.on('data', function(file) { - should.exist(file); - should.exist(file.path); - should.not.exist(file.contents); - join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); - }); - stream.on('end', function() { - done(); - }); - }); - - it.skip('should throw an error when buffer is false', function(done) { - app.src(join(__dirname, './fixtures/*.coffee'), {buffer: false}) - .on('error', function() { - done(); - }) - .on('data', function() { - done(new Error('should have thrown an error')); - }); - }); - - it('should return an input stream from a deep glob', function(done) { - app.src(join(__dirname, './fixtures/**/*.jade')) - .on('error', done) - .on('data', function(file) { - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - join(file.path, '').should.equal(join(__dirname, './fixtures/test/run.jade')); - String(file.contents).should.equal('test template'); - }) - .on('end', function() { - done(); - }); - }); - - it('should return an input stream from a deeper glob', function(done) { - var stream = app.src(join(__dirname, './fixtures/**/*.dmc')); - var a = 0; - stream.on('error', done); - stream.on('data', function() { - ++a; - }); - stream.on('end', function() { - a.should.equal(2); - done(); - }); - }); - - it('should return a file stream from a flat path', function(done) { - var a = 0; - var stream = app.src(join(__dirname, './fixtures/test.coffee')); - stream.on('error', done); - stream.on('data', function(file) { - ++a; - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); - String(file.contents).should.equal('Hello world!'); - }); - stream.on('end', function() { - a.should.equal(1); - done(); - }); - }); -}); diff --git a/test2/app.symlink.js b/test2/app.symlink.js deleted file mode 100644 index 69e8d45..0000000 --- a/test2/app.symlink.js +++ /dev/null @@ -1,396 +0,0 @@ -require('mocha'); -var should = require('should'); -var fs = require('graceful-fs'); -var path = require('path'); -var rimraf = require('rimraf'); -var bufEqual = require('buffer-equal'); -var through = require('through2'); -var File = require('vinyl'); -var assemble = require('..'); -var spies = require('./support/spy'); -var chmodSpy = spies.chmodSpy; -var statSpy = spies.statSpy; -var app, bufferStream; - -var wipeOut = function(cb) { - rimraf(path.join(__dirname, './actual/'), cb); - spies.setError('false'); - statSpy.reset(); - chmodSpy.reset(); - app = assemble(); -}; - -var dataWrap = function(fn) { - return function(data, enc, cb) { - fn(data); - cb(); - }; -}; - -var realMode = function(n) { - return n & 07777; -}; - -describe('symlink stream', function() { - beforeEach(wipeOut); - afterEach(wipeOut); - - it('should pass through writes with cwd', function(done) { - var inputPath = path.join(__dirname, './fixtures/test.coffee'); - - var expectedFile = new File({ - base: __dirname, - cwd: __dirname, - path: inputPath, - contents: null - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - done(); - }; - - var stream = app.symlink('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should pass through writes with default cwd', function(done) { - var inputPath = path.join(__dirname, './fixtures/test.coffee'); - - var expectedFile = new File({ - base: __dirname, - cwd: __dirname, - path: inputPath, - contents: null - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - done(); - }; - - var stream = app.symlink(path.join(__dirname, './actual/')); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should make link to the right folder with relative cwd', function(done) { - var inputPath = path.join(__dirname, './fixtures/test.coffee'); - var inputBase = path.join(__dirname, './fixtures/'); - var expectedPath = path.join(__dirname, './actual/test.coffee'); - var expectedBase = path.join(__dirname, './actual'); - var expectedContents = fs.readFileSync(inputPath); - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(__dirname, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(true); - bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); - fs.readlinkSync(expectedPath).should.equal(inputPath); - done(); - }; - - var stream = app.symlink('./actual/', {cwd: path.relative(process.cwd(), __dirname)}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should write buffer files to the right folder with function and relative cwd', function(done) { - var inputPath = path.join(__dirname, './fixtures/test.coffee'); - var inputBase = path.join(__dirname, './fixtures/'); - var expectedPath = path.join(__dirname, './actual/test.coffee'); - var expectedBase = path.join(__dirname, './actual'); - var expectedContents = fs.readFileSync(inputPath); - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(__dirname, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(true); - bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); - fs.readlinkSync(expectedPath).should.equal(inputPath); - done(); - }; - - var stream = app.symlink(function(file){ - should.exist(file); - file.should.equal(expectedFile); - return './actual'; - }, {cwd: path.relative(process.cwd(), __dirname)}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should write buffer files to the right folder', function(done) { - var inputPath = path.join(__dirname, './fixtures/test.coffee'); - var inputBase = path.join(__dirname, './fixtures/'); - var expectedPath = path.join(__dirname, './actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, './actual'); - var expectedMode = 0655; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: expectedMode - } - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(__dirname, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(true); - bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); - fs.readlinkSync(expectedPath).should.equal(inputPath); - done(); - }; - - var stream = app.symlink('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should write streaming files to the right folder', function(done) { - var inputPath = path.join(__dirname, './fixtures/test.coffee'); - var inputBase = path.join(__dirname, './fixtures/'); - var expectedPath = path.join(__dirname, './actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, './actual'); - var expectedMode = 0655; - - var contentStream = through.obj(); - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: contentStream, - stat: { - mode: expectedMode - } - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(__dirname, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(true); - bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); - fs.readlinkSync(expectedPath).should.equal(inputPath); - done(); - }; - - var stream = app.symlink('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - setTimeout(function(){ - contentStream.write(expectedContents); - contentStream.end(); - }, 100); - stream.end(); - }); - - it('should write directories to the right folder', function(done) { - var inputPath = path.join(__dirname, './fixtures/wow'); - var inputBase = path.join(__dirname, './fixtures/'); - var expectedPath = path.join(__dirname, './actual/wow'); - var expectedBase = path.join(__dirname, './actual'); - var expectedMode = 0655; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: null, - stat: { - isDirectory: function(){ - return true; - }, - mode: expectedMode - } - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(__dirname, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.readlinkSync(expectedPath).should.equal(inputPath); - fs.lstatSync(expectedPath).isDirectory().should.equal(false); - fs.statSync(expectedPath).isDirectory().should.equal(true); - done(); - }; - - var stream = app.symlink('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should use different modes for files and directories', function(done) { - var inputBase = path.join(__dirname, './fixtures'); - var inputPath = path.join(__dirname, './fixtures/wow/suchempty'); - var expectedBase = path.join(__dirname, './actual/wow'); - var expectedDirMode = 0755; - var expectedFileMode = 0655; - - var firstFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - stat: fs.statSync(inputPath) - }); - - var onEnd = function(){ - realMode(fs.lstatSync(expectedBase).mode).should.equal(expectedDirMode); - realMode(buffered[0].stat.mode).should.equal(expectedFileMode); - done(); - }; - - var stream = app.symlink('./actual/', { - cwd: __dirname, - mode: expectedFileMode, - dirMode: expectedDirMode - }); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(firstFile); - stream.end(); - }); - - it('should change to the specified base', function(done) { - var inputBase = path.join(__dirname, './fixtures'); - var inputPath = path.join(__dirname, './fixtures/wow/suchempty'); - - var firstFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - stat: fs.statSync(inputPath) - }); - - var onEnd = function(){ - buffered[0].base.should.equal(inputBase); - done(); - }; - - var stream = app.symlink('./actual/', { - cwd: __dirname, - base: inputBase - }); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(firstFile); - stream.end(); - }); - - it('should report IO errors', function(done) { - var inputPath = path.join(__dirname, './fixtures/test.coffee'); - var inputBase = path.join(__dirname, './fixtures/'); - var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, './actual'); - var expectedMode = 0722; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: expectedMode - } - }); - - fs.mkdirSync(expectedBase); - fs.chmodSync(expectedBase, 0); - - var stream = app.symlink('./actual/', {cwd: __dirname}); - stream.on('error', function(err) { - err.code.should.equal('EACCES'); - done(); - }); - stream.write(expectedFile); - }); - - ['end', 'finish'].forEach(function(eventName) { - it('should emit ' + eventName + ' event', function(done) { - var srcPath = path.join(__dirname, './fixtures/test.coffee'); - var stream = app.symlink('./actual/', {cwd: __dirname}); - - stream.on(eventName, function() { - done(); - }); - - var file = new File({ - path: srcPath, - cwd: __dirname, - contents: new Buffer("1234567890") - }); - - stream.write(file); - stream.end(); - }); - }); -}); diff --git a/test2/app.task.js b/test2/app.task.js deleted file mode 100644 index 333f4fd..0000000 --- a/test2/app.task.js +++ /dev/null @@ -1,159 +0,0 @@ -'use strict'; - -var assert = require('assert'); -var App = require('..'); -var app; - -describe('task()', function() { - beforeEach(function() { - app = new App(); - app.tasks = {}; - }); - - it('should register a task', function() { - var fn = function(done) { - done(); - }; - app.task('default', fn); - assert.equal(typeof app.tasks.default, 'object'); - assert.equal(app.tasks.default.fn, fn); - }); - - it('should register a task with an array of dependencies', function() { - app.task('default', ['foo', 'bar'], function(done) { - done(); - }); - assert.equal(typeof app.tasks.default, 'object'); - assert.deepEqual(app.tasks.default.deps, ['foo', 'bar']); - }); - - it('should register a task with a list of strings as dependencies', function() { - app.task('default', 'foo', 'bar', function(done) { - done(); - }); - assert.equal(typeof app.tasks.default, 'object'); - assert.deepEqual(app.tasks.default.deps, ['foo', 'bar']); - }); - - it('should run a task', function(done) { - var count = 0; - app.task('default', function(cb) { - count++; - cb(); - }); - - app.build('default', function(err) { - if (err) return done(err); - assert.equal(count, 1); - done(); - }); - }); - - it('should throw an error when a task with unregistered dependencies is run', function(done) { - var count = 0; - app.task('default', ['foo', 'bar'], function(cb) { - count++; - cb(); - }); - - app.build('default', function(err) { - if (!err) return done(new Error('Expected an error to be thrown.')); - assert.equal(count, 0); - done(); - }); - }); - - it('should throw an error when `.build` is called without a callback function.', function() { - try { - app.build('default'); - throw new Error('Expected an error to be thrown.'); - } catch (err) { - } - }); - - it('should emit task events', function(done) { - var events = []; - app.on('task:starting', function(task) { - events.push('starting.' + task.name); - }); - app.on('task:finished', function(task) { - events.push('finished.' + task.name); - }); - app.on('task:error', function(err, task) { - events.push('error.' + task.name); - }); - - app.task('foo', function(cb) { - cb(); - }); - app.task('bar', ['foo'], function(cb) { - cb(); - }); - app.task('default', ['bar']); - app.build('default', function(err) { - if (err) return done(err); - assert.deepEqual(events, [ - 'starting.default', - 'starting.bar', - 'starting.foo', - 'finished.foo', - 'finished.bar', - 'finished.default' - ]); - done(); - }); - }); - - it('should emit an error event when an error is passed back in a task', function(done) { - app.on('error', function(err) { - assert(err); - assert.equal(err.message, 'This is an error'); - }); - app.task('default', function(cb) { - return cb(new Error('This is an error')); - }); - app.build('default', function(err) { - if (err) return done(); - done(new Error('Expected an error')); - }); - }); - - it('should emit an error event when an error is thrown in a task', function(done) { - var errors = 0; - app.on('error', function(err) { - errors++; - assert(err); - assert.equal(err.message, 'This is an error'); - }); - app.task('default', function(cb) { - cb(new Error('This is an error')); - }); - app.build('default', function(err) { - assert.equal(errors, 1); - if (err) return done(); - done(new Error('Expected an error')); - }); - }); - - it('should run dependencies before running the dependent task.', function(done) { - var seq = []; - app.task('foo', function(cb) { - seq.push('foo'); - cb(); - }); - app.task('bar', function(cb) { - seq.push('bar'); - cb(); - }); - app.task('default', ['foo', 'bar'], function(cb) { - seq.push('default'); - cb(); - }); - - app.build('default', function(err) { - if (err) return done(err); - assert.deepEqual(seq, ['foo', 'bar', 'default']); - done(); - }); - }); -}); diff --git a/test2/app.toStream.js b/test2/app.toStream.js deleted file mode 100644 index fddfb93..0000000 --- a/test2/app.toStream.js +++ /dev/null @@ -1,64 +0,0 @@ -'use strict'; - -var assemble = require('..'); -var assert = require('assert'); -var should = require('should'); -var app; - -describe('toStream()', function() { - beforeEach(function() { - app = assemble(); - app.create('pages'); - app.page('a', {content: 'this is A'}); - app.page('b', {content: 'this is B'}); - app.page('c', {content: 'this is C'}); - - app.create('posts'); - app.post('x', {content: 'this is X'}); - app.post('y', {content: 'this is Y'}); - app.post('z', {content: 'this is Z'}); - }); - - it('should return a stream', function(cb) { - var stream = app.toStream(); - should.exist(stream); - should.exist(stream.on); - cb(); - }); - - it('should return a stream for a collection', function(cb) { - var stream = app.toStream('pages'); - should.exist(stream); - should.exist(stream.on); - cb(); - }); - - it('should stack handle multiple collections', function(cb) { - var files = []; - app.toStream('pages') - .pipe(app.toStream('posts')) - .on('data', function(file) { - files.push(file); - }) - .on('end', function() { - assert.equal(files.length, 6); - cb(); - }); - }); - - it('should push each item in the collection into the stream', function(cb) { - var files = []; - app.toStream('pages') - .on('error', cb) - .on('data', function(file) { - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - files.push(file.path); - }) - .on('end', function() { - assert.equal(files.length, 3); - cb(); - }); - }); -}); \ No newline at end of file diff --git a/test2/app.use.js b/test2/app.use.js deleted file mode 100644 index 6ca99ac..0000000 --- a/test2/app.use.js +++ /dev/null @@ -1,281 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var Views = App.Views; -var View = App.View; -var app; - -describe('app.use', function() { - beforeEach(function() { - app = new App(); - }); - - it('should expose the instance to `use`:', function(done) { - app.use(function(inst) { - assert(inst instanceof App); - done(); - }); - }); - - it('should be chainable:', function(done) { - app.use(function(inst) { - assert(inst instanceof App); - }) - .use(function(inst) { - assert(inst instanceof App); - }) - .use(function(inst) { - assert(inst instanceof App); - done(); - }); - }); - - it('should pass to collection `use` if a function is returned:', function() { - app.use(function(inst) { - assert(inst instanceof App); - return function(collection) { - collection.foo = collection.addView; - assert(collection instanceof Views); - return collection; - }; - }); - - app.create('pages') - .foo({path: 'a.md', content: '...'}) - .addView({path: 'b.md', content: '...'}) - .addView({path: 'c.md', content: '...'}); - - assert(app.views.pages.hasOwnProperty('a.md')); - assert(app.views.pages.hasOwnProperty('b.md')); - assert(app.views.pages.hasOwnProperty('c.md')); - }); - - it('should be chainable when a collection function is returned:', function() { - app - .use(function(inst) { - assert(inst instanceof App); - return function(collection) { - collection.foo = collection.addView; - assert(collection instanceof Views); - return collection; - }; - }) - .use(function(inst) { - assert(inst instanceof App); - return function(collection) { - collection.bar = collection.addView; - assert(collection instanceof Views); - return collection; - }; - }) - .use(function(inst) { - assert(inst instanceof App); - return function(collection) { - collection.baz = collection.addView; - assert(collection instanceof Views); - return collection; - }; - }); - - var pages = app.create('pages'); - - pages.foo({path: 'a.md', content: '...'}); - pages.bar({path: 'b.md', content: '...'}); - pages.baz({path: 'c.md', content: '...'}); - - assert(app.views.pages.hasOwnProperty('a.md')); - assert(app.views.pages.hasOwnProperty('b.md')); - assert(app.views.pages.hasOwnProperty('c.md')); - }); - - it('should pass to view `use` if collection.use returns a function:', function() { - app.use(function(inst) { - assert(inst instanceof App); - - return function(collection) { - assert(collection instanceof Views); - collection.foo = collection.addView; - - return function(view) { - assert(view instanceof View); - view.foo = collection.addView.bind(collection); - return view; - }; - }; - }); - - app.create('pages') - .foo({path: 'a.md', content: '...'}) - .foo({path: 'b.md', content: '...'}) - .foo({path: 'c.md', content: '...'}); - - assert(app.views.pages.hasOwnProperty('a.md')); - assert(app.views.pages.hasOwnProperty('b.md')); - assert(app.views.pages.hasOwnProperty('c.md')); - }); - - it('should be chainable when a view function is returned:', function() { - app - .use(function(inst) { - assert(inst instanceof App); - - return function(collection) { - assert(collection instanceof Views); - collection.foo = collection.addView; - - return function(view) { - assert(view instanceof View); - view.a = collection.addView.bind(collection); - return view; - }; - }; - }) - .use(function(inst) { - assert(inst instanceof App); - - return function(collection) { - assert(collection instanceof Views); - collection.bar = collection.addView; - - return function(view) { - assert(view instanceof View); - view.b = collection.addView.bind(collection); - return view; - }; - }; - }) - .use(function(inst) { - assert(inst instanceof App); - - return function(collection) { - assert(collection instanceof Views); - collection.baz = collection.addView; - - return function(view) { - assert(view instanceof View); - view.c = collection.addView.bind(collection); - return view; - }; - }; - }); - - var pages = app.create('pages'); - - pages.foo({path: 'a.md', content: '...'}); - pages.bar({path: 'b.md', content: '...'}); - pages.baz({path: 'c.md', content: '...'}) - .a({path: 'x.md', content: '...'}) - .b({path: 'y.md', content: '...'}) - .c({path: 'z.md', content: '...'}); - - assert(app.views.pages.hasOwnProperty('a.md')); - assert(app.views.pages.hasOwnProperty('b.md')); - assert(app.views.pages.hasOwnProperty('c.md')); - - assert(app.views.pages.hasOwnProperty('x.md')); - assert(app.views.pages.hasOwnProperty('y.md')); - assert(app.views.pages.hasOwnProperty('z.md')); - }); - - it('should work with multiple collections:', function() { - app - .use(function(inst) { - assert(inst instanceof App); - - return function(collection) { - assert(collection instanceof Views); - collection.foo = collection.addView; - - return function(view) { - assert(view instanceof View); - view.a = collection.addView.bind(collection); - return view; - }; - }; - }) - .use(function(inst) { - assert(inst instanceof App); - - return function(collection) { - assert(collection instanceof Views); - collection.bar = collection.addView; - - return function(view) { - assert(view instanceof View); - view.b = collection.addView.bind(collection); - return view; - }; - }; - }) - .use(function(inst) { - assert(inst instanceof App); - assert(this instanceof App); - - return function(collection) { - collection.baz = collection.addView; - assert(collection instanceof Views); - assert(this instanceof Views); - - return function(view) { - assert(this instanceof View); - assert(view instanceof View); - view.c = collection.addView.bind(collection); - return view; - }; - }; - }); - - var pages = app.create('pages'); - - pages.foo({path: 'a.md', content: '...'}); - pages.bar({path: 'b.md', content: '...'}); - pages.baz({path: 'c.md', content: '...'}) - .a({path: 'x.md', content: '...'}) - .b({path: 'y.md', content: '...'}) - .c({path: 'z.md', content: '...'}); - - assert(app.views.pages.hasOwnProperty('a.md')); - assert(app.views.pages.hasOwnProperty('b.md')); - assert(app.views.pages.hasOwnProperty('c.md')); - - assert(app.views.pages.hasOwnProperty('x.md')); - assert(app.views.pages.hasOwnProperty('y.md')); - assert(app.views.pages.hasOwnProperty('z.md')); - - var posts = app.create('posts'); - - posts.foo({path: 'a.md', content: '...'}); - posts.bar({path: 'b.md', content: '...'}); - posts.baz({path: 'c.md', content: '...'}) - .a({path: 'x.md', content: '...'}) - .b({path: 'y.md', content: '...'}) - .c({path: 'z.md', content: '...'}); - - assert(app.views.posts.hasOwnProperty('a.md')); - assert(app.views.posts.hasOwnProperty('b.md')); - assert(app.views.posts.hasOwnProperty('c.md')); - - assert(app.views.posts.hasOwnProperty('x.md')); - assert(app.views.posts.hasOwnProperty('y.md')); - assert(app.views.posts.hasOwnProperty('z.md')); - - var docs = app.create('docs'); - - docs.foo({path: 'a.md', content: '...'}); - docs.bar({path: 'b.md', content: '...'}); - docs.baz({path: 'c.md', content: '...'}) - .a({path: 'x.md', content: '...'}) - .b({path: 'y.md', content: '...'}) - .c({path: 'z.md', content: '...'}); - - assert(app.views.docs.hasOwnProperty('a.md')); - assert(app.views.docs.hasOwnProperty('b.md')); - assert(app.views.docs.hasOwnProperty('c.md')); - - assert(app.views.docs.hasOwnProperty('x.md')); - assert(app.views.docs.hasOwnProperty('y.md')); - assert(app.views.docs.hasOwnProperty('z.md')); - }); -}); diff --git a/test2/app.view.compile.js b/test2/app.view.compile.js deleted file mode 100644 index 4dca5cd..0000000 --- a/test2/app.view.compile.js +++ /dev/null @@ -1,38 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('app.view.compile', function() { - describe('compile method', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('page'); - }); - - it('should compile a view:', function() { - var buffer = new Buffer('a b c'); - var view = app.page('a.tmpl', {contents: buffer}) - .compile(); - assert(typeof view.fn === 'function'); - }); - - it('should compile a view with settings:', function() { - var buffer = new Buffer('a b c'); - var view = app.page('a.tmpl', {contents: buffer}) - .compile({foo: 'bar'}); - assert(typeof view.fn === 'function'); - }); - - it('should compile a view with isAsync flag:', function() { - var buffer = new Buffer('a b c'); - var view = app.page('a.tmpl', {contents: buffer}) - .compile(true); - assert(typeof view.fn === 'function'); - }); - }); -}); - diff --git a/test2/app.view.render.js b/test2/app.view.render.js deleted file mode 100644 index 1cfd929..0000000 --- a/test2/app.view.render.js +++ /dev/null @@ -1,92 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('helpers', function() { - describe('rendering', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('page'); - }); - - it('should use helpers to render a view:', function(done) { - var locals = {name: 'Halle'}; - - app.helper('upper', function(str) { - return str.toUpperCase(str); - }); - - var buffer = new Buffer('a <%= upper(name) %> b'); - app.page('a.tmpl', {contents: buffer, locals: locals}) - .render(function(err, res) { - if (err) return done(err); - - assert(res.contents.toString() === 'a HALLE b'); - done(); - }); - }); - - it('should support helpers as an array:', function(done) { - var locals = {name: 'Halle'}; - - app.helpers([ - { - lower: function(str) { - return str.toLowerCase(str); - } - } - ]); - - var buffer = new Buffer('a <%= lower(name) %> b'); - app.page('a.tmpl', {contents: buffer, locals: locals}) - .render(function(err, res) { - if (err) return done(err); - - assert(res.contents.toString() === 'a halle b'); - done(); - }); - }); - - it('should support helpers as an object:', function(done) { - var locals = {name: 'Halle'}; - - app.helpers({ - prepend: function(prefix, str) { - return prefix + str; - } - }); - - var buffer = new Buffer('a <%= prepend("foo ", name) %> b'); - app.page('a.tmpl', {contents: buffer, locals: locals}) - .render(function(err, res) { - if (err) return done(err); - assert(res.contents.toString() === 'a foo Halle b'); - done(); - }); - }); - - it('should use the engine defined on view options:', function(done) { - app.engine('hbs', require('engine-handlebars')); - var locals = {name: 'Halle'}; - - app.helpers({ - prepend: function(prefix, str) { - return prefix + str; - } - }); - - var buffer = new Buffer('a {{prepend "foo " name}} b'); - app.page('a.tmpl', {contents: buffer, locals: locals, options: {engine: 'hbs'}}) - .render(function(err, res) { - if (err) return done(err); - assert(res.contents.toString() === 'a foo Halle b'); - done(); - }); - }); - }); -}); - diff --git a/test2/app.watch.js b/test2/app.watch.js deleted file mode 100644 index 0990e70..0000000 --- a/test2/app.watch.js +++ /dev/null @@ -1,42 +0,0 @@ -'use strict'; - -var assert = require('assert'); -var fs = require('fs'); -var App = require('..'); -var app; - -describe.skip('watch()', function() { - beforeEach(function() { - app = new App({runtimes: false}); - }); - - it('should watch files and run a task when files change', function(done) { - this.timeout(750); - - var count = 0, watch; - app.task('default', function(cb) { - count++; - cb(); - }); - - app.task('close', function(cb) { - watch.close(); - app.emit('close'); - cb(); - }); - - app.task('watch', function(cb) { - watch = app.watch('test/fixtures/watch/*.txt', ['default', 'close']); - fs.writeFile('test/fixtures/watch/test.txt', 'test', function(err) { - if (err) return cb(err); - app.on('close', cb); - }); - }); - - app.build(['watch'], function(err) { - if (err) return done(err); - assert.equal(count, 1); - done(); - }); - }); -}); diff --git a/test2/collection.engines.js b/test2/collection.engines.js deleted file mode 100644 index 3465c9e..0000000 --- a/test2/collection.engines.js +++ /dev/null @@ -1,176 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var Views = App.Views; -var collection, pages; - -describe('collection engines', function() { - beforeEach(function() { - pages = new Views(); - }); - - it('should throw an error when engine name is invalid:', function() { - (function() { - pages.engine(null, {}); - }).should.throw('expected engine ext to be a string or array.'); - }); - - it('should register an engine to the given extension', function() { - pages.engine('hbs', function() {}); - assert(typeof pages.engines['.hbs'] === 'object'); - }); - - it('should set an engine with the given extension', function() { - var hbs = function() {}; - hbs.render = function() {}; - hbs.renderFile = function() {}; - pages.engine('hbs', hbs); - assert(pages.engines['.hbs']); - assert(pages.engines['.hbs'].renderFile); - assert(pages.engines['.hbs'].render); - }); - - it('should get an engine:', function() { - pages.engine('hbs', function() {}); - var hbs = pages.engine('hbs'); - assert(typeof hbs === 'object'); - assert(hbs.hasOwnProperty('render')); - assert(hbs.hasOwnProperty('compile')); - }); - - it('should register multiple engines to the given extension', function() { - pages.engine(['hbs', 'md'], function() {}); - assert(typeof pages.engines['.hbs'] === 'object'); - assert(typeof pages.engines['.md'] === 'object'); - }); -}); - -describe('engines', function() { - beforeEach(function() { - pages = new Views(); - pages.addView('foo.tmpl', {content: 'A <%= letter %> {{= letter }} C'}); - pages.addView('bar.tmpl', {content: 'A <%= letter %> {{ letter }} C'}); - }); - - it('should register an engine:', function() { - pages.engine('a', {render: function() {}}); - pages.engines.should.have.property('.a'); - }); - - it('should use custom delimiters:', function(done) { - pages.engine('tmpl', require('engine-base'), { - delims: ['{{', '}}'] - }); - - pages.render('foo.tmpl', {letter: 'B'}, function(err, res) { - if (err) return done(err); - res.content.should.equal('A <%= letter %> B C'); - done(); - }); - }); - - it('should override individual delims values:', function(done) { - pages.engine('tmpl', require('engine-base'), { - interpolate: /\{{([^}]+)}}/g, - evaluate: /\{{([^}]+)}}/g, - escape: /\{{-([^}]+)}}/g - }); - pages.render('bar.tmpl', {letter: 'B'}, function(err, res) { - if (err) return done(err); - res.content.should.equal('A <%= letter %> B C'); - done(); - }); - }); - - it('should get an engine:', function() { - pages.engine('a', { - render: function() {} - }); - var a = pages.engine('a'); - a.should.have.property('render'); - }); -}); - -describe('engine selection:', function() { - beforeEach(function(done) { - collection = new Views(); - collection.engine('tmpl', require('engine-base')); - collection.engine('hbs', require('engine-handlebars')); - done(); - }); - - it('should get the engine from file extension:', function(done) { - var pages = new Views(); - pages.engine('tmpl', require('engine-base')); - pages.engine('hbs', require('engine-handlebars')); - pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}) - .render(function(err, view) { - if (err) return done(err); - assert(view.content === 'b'); - done(); - }); - }); - - it('should use the engine defined on the collection:', function(done) { - var posts = new Views({engine: 'hbs'}); - posts.engine('tmpl', require('engine-base')); - posts.engine('hbs', require('engine-handlebars')); - - posts.addView('a', {content: '{{a}}', locals: {a: 'b'}}) - .render(function(err, view) { - if (err) return done(err); - assert(view.content === 'b'); - done(); - }); - }); - - it('should use the engine defined on the view:', function(done) { - var posts = new Views(); - posts.engine('tmpl', require('engine-base')); - posts.engine('hbs', require('engine-handlebars')); - posts.addView('a', {content: '{{a}}', engine: 'hbs', locals: {a: 'b'}}) - .render(function(err, view) { - if (err) return done(err); - assert(view.content === 'b'); - done(); - }); - }); - - it('should use the engine defined on view.options:', function(done) { - var posts = new Views(); - posts.engine('tmpl', require('engine-base')); - posts.engine('hbs', require('engine-handlebars')); - posts.addView('a', {content: '{{a}}', data: {a: 'b'}, options: {engine: 'hbs'}}) - .render(function(err, view) { - if (err) return done(err); - assert(view.content === 'b'); - done(); - }); - }); - - it('should use the engine defined on view.data:', function(done) { - var posts = new Views(); - posts.engine('tmpl', require('engine-base')); - posts.engine('hbs', require('engine-handlebars')); - posts.addView('a', {content: '{{a}}', locals: {a: 'b'}, data: {engine: 'hbs'}}) - .render(function(err, view) { - if (err) return done(err); - assert(view.content === 'b'); - done(); - }); - }); - - it('should use the engine defined on render locals:', function(done) { - var posts = new Views(); - posts.engine('tmpl', require('engine-base')); - posts.engine('hbs', require('engine-handlebars')); - posts.addView('a', {content: '{{a}}', locals: {a: 'b'}}) - .render({engine: 'hbs'}, function(err, view) { - if (err) return done(err); - assert(view.content === 'b'); - done(); - }); - }); -}); diff --git a/test2/collection.events.js b/test2/collection.events.js deleted file mode 100644 index bdf9eaf..0000000 --- a/test2/collection.events.js +++ /dev/null @@ -1,27 +0,0 @@ -require('should'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('collection events', function() { - beforeEach(function() { - app = new App(); - app.create('page'); - }); - - it('should emit events:', function() { - app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}); - var events = []; - - app.pages.on('option', function(key) { - events.push(key); - }); - - app.pages.option('a', 'b'); - app.pages.option('c', 'd'); - app.pages.option('e', 'f'); - app.pages.option({g: 'h'}); - - events.should.eql(['a', 'c', 'e', 'g']); - }); -}); diff --git a/test2/collection.getView.js b/test2/collection.getView.js deleted file mode 100644 index fb58723..0000000 --- a/test2/collection.getView.js +++ /dev/null @@ -1,34 +0,0 @@ -'use strict'; - -var path = require('path'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('collection.getView', function() { - beforeEach(function() { - app = new App(); - app.create('page'); - - app.page('foo', {content: 'this is foo'}); - app.page('bar.md', {content: 'this is bar'}); - app.page('a/b/c/baz.md', {content: 'this is baz'}); - app.page('test/fixtures/templates/a.tmpl'); - }); - - it('should get a view by name', function() { - assert(app.pages.getView('foo')); - }); - - it('should get a view with the key modified by the given function', function() { - var view = app.pages.getView('foo.md', function(key) { - return path.basename(key, path.extname(key)); - }); - assert(view); - }); - - it('should get a view by full path', function() { - assert(app.pages.getView('a/b/c/baz.md')); - }); -}); diff --git a/test2/collection.js b/test2/collection.js deleted file mode 100644 index 7c0982c..0000000 --- a/test2/collection.js +++ /dev/null @@ -1,540 +0,0 @@ -require('mocha'); -require('should'); -var path = require('path'); -var assert = require('assert'); -var typeOf = require('kind-of'); -var isBuffer = require('is-buffer'); -var support = require('./support'); -var App = support.resolve(); -var List = App.List; -var Item = App.Item; -var Collection = App.Collection; -var collection; - -describe('collection', function() { - describe('constructor', function() { - it('should create an instance of Collection', function() { - var collection = new Collection(); - assert(collection instanceof Collection); - assert(typeof collection === 'object'); - }); - - it('should instantiate without new', function() { - var collection = Collection(); - assert(collection instanceof Collection); - assert(typeof collection === 'object'); - }); - }); - - describe('static methods', function() { - it('should expose `extend`', function() { - assert(typeof Collection.extend === 'function'); - }); - }); - - describe('prototype methods', function() { - beforeEach(function() { - collection = new Collection(); - }); - - var methods = [ - 'use', - 'setItem', - 'addItem', - 'addItems', - 'addList', - 'getItem', - 'constructor', - 'set', - 'get', - 'del', - 'define', - 'visit', - 'on', - 'once', - 'off', - 'emit', - 'listeners', - 'hasListeners' - ]; - - methods.forEach(function(method) { - it('should expose ' + method + ' method', function() { - assert(typeof collection[method] === 'function'); - }); - }); - - it('should expose isCollection property', function() { - assert(typeof collection.isCollection === 'boolean'); - }); - - it('should expose queue property', function() { - assert(Array.isArray(collection.queue)); - }); - - it('should expose items property', function() { - assert(typeOf(collection.items) === 'object'); - }); - - it('should expose options property', function() { - assert(typeOf(collection.options) === 'object'); - }); - }); -}); - -describe('methods', function() { - beforeEach(function() { - collection = new Collection(); - }); - - describe('chaining', function() { - it('should allow collection methods to be chained', function() { - collection - .addItems({'a.hbs': {path: 'a.hbs'}}) - .addItems({'b.hbs': {path: 'b.hbs'}}) - .addItems({'c.hbs': {path: 'c.hbs'}}); - - collection.items.should.have.properties([ - 'a.hbs', - 'b.hbs', - 'c.hbs' - ]); - }); - }); - - describe('use', function() { - it('should expose the instance to plugins', function() { - collection - .use(function(inst) { - inst.foo = 'bar'; - }); - - assert(collection.foo === 'bar'); - }); - - it('should expose `item` when the plugin returns a function', function() { - collection - .use(function() { - return function(item) { - item.foo = 'bar'; - }; - }); - - collection.addItem('aaa'); - collection.addItem('bbb'); - collection.addItem('ccc'); - - assert(collection.items.aaa.foo === 'bar'); - assert(collection.items.bbb.foo === 'bar'); - assert(collection.items.ccc.foo === 'bar'); - }); - }); - - describe('get / set', function() { - it('should set a value on the instance', function() { - collection.set('a', 'b'); - assert(collection.a === 'b'); - }); - - it('should get a value from the instance', function() { - collection.set('a', 'b'); - assert(collection.get('a') === 'b'); - }); - }); - - describe('adding items', function() { - beforeEach(function() { - collection = new Collection(); - }); - - it('should load a item onto the respective collection', function() { - collection.addItem('a.hbs'); - collection.items.should.have.property('a.hbs'); - }); - }); - - describe('item', function() { - beforeEach(function() { - collection = new Collection(); - }); - - it('should return a single collection item from a key-value pair', function() { - var one = collection.item('one', {content: 'foo'}); - var two = collection.item('two', {content: 'bar'}); - - assert(one instanceof Item); - assert(one instanceof collection.Item); - assert(one.path === 'one'); - assert(two instanceof Item); - assert(two instanceof collection.Item); - assert(two.path === 'two'); - }); - - it('should return a single collection item from an object', function() { - var one = collection.item({path: 'one', content: 'foo'}); - var two = collection.item({path: 'two', content: 'bar'}); - - assert(one instanceof Item); - assert(one.path === 'one'); - assert(two instanceof Item); - assert(two.path === 'two'); - }); - }); - - describe('addItem', function() { - beforeEach(function() { - collection = new Collection(); - }); - - it('should throw an error when args are invalid', function() { - (function() { - collection.addItem(function() {}); - }).should.throw('expected value to be an object.'); - }); - - it('should add a item to `items`', function() { - collection.addItem('foo'); - collection.items.should.have.property('foo'); - - collection.addItem('one', {content: '...'}); - assert(typeof collection.items.one === 'object'); - assert(isBuffer(collection.items.one.contents)); - }); - - it('should create an instance of `Item`', function() { - collection.addItem('one', {content: '...'}); - assert(collection.items.one instanceof collection.Item); - }); - - it('should allow an `Item` constructor to be passed', function() { - Item.prototype.foo = function(key, value) { - this[key] = value; - }; - collection = new Collection({Item: Item}); - collection.addItem('one', {content: '...'}); - collection.items.one.foo('bar', 'baz'); - assert(collection.items.one.bar === 'baz'); - }); - - it('should allow an instance of `Item` to be passed', function() { - var collection = new Collection({Item: Item}); - var item = new Item({content: '...'}); - collection.addItem('one', item); - item.set('abc', 'xyz'); - assert(collection.items.one instanceof collection.Item); - assert(isBuffer(collection.items.one.contents)); - assert(collection.items.one.abc === 'xyz'); - }); - }); - - describe('addItems', function() { - beforeEach(function() { - collection = new Collection(); - }); - - it('should add multiple items', function() { - collection.addItems({ - one: {content: 'foo'}, - two: {content: 'bar'} - }); - assert(isBuffer(collection.items.one.contents)); - assert(isBuffer(collection.items.two.contents)); - }); - - it('should create items from an instance of Collection', function() { - collection.addItems({ - one: {content: 'foo'}, - two: {content: 'bar'} - }); - var pages = new Collection(collection); - assert(isBuffer(pages.items.one.contents)); - assert(isBuffer(pages.items.two.contents)); - }); - - it('should add an array of plain-objects', function() { - collection.addItems([ - {path: 'one', content: 'foo'}, - {path: 'two', content: 'bar'} - ]); - assert(isBuffer(collection.items.one.contents)); - assert(isBuffer(collection.items.two.contents)); - }); - - it('should add an array of items', function() { - var list = new List([ - {path: 'one', content: 'foo'}, - {path: 'two', content: 'bar'} - ]); - - collection.addItems(list.items); - assert(isBuffer(collection.items.one.contents)); - assert(isBuffer(collection.items.two.contents)); - }); - }); - - describe('addList', function() { - beforeEach(function() { - collection = new Collection(); - }); - - it('should add a list of items', function() { - collection.addList([ - {path: 'one', content: 'foo'}, - {path: 'two', content: 'bar'} - ]); - assert(isBuffer(collection.items.one.contents)); - assert(isBuffer(collection.items.two.contents)); - }); - - it('should add a list of items from the constructor', function() { - var list = new List([ - {path: 'one', content: 'foo'}, - {path: 'two', content: 'bar'} - ]); - - collection = new Collection(list); - assert(isBuffer(collection.items.one.contents)); - assert(isBuffer(collection.items.two.contents)); - }); - - it('should throw an error when list is not an array', function() { - var items = new Collection(); - (function() { - items.addList(); - }).should.throw('expected list to be an array.'); - - (function() { - items.addList({}); - }).should.throw('expected list to be an array.'); - - (function() { - items.addList('foo'); - }).should.throw('expected list to be an array.'); - }); - - it('should load an array of items from an event', function() { - var collection = new Collection(); - - collection.on('addList', function(list) { - while (list.length) { - collection.addItem({path: list.pop()}); - } - }); - - collection.addList(['a.txt', 'b.txt', 'c.txt']); - assert(collection.items.hasOwnProperty('a.txt')); - assert(collection.items['a.txt'].path === 'a.txt'); - }); - - it('should load an array of items from the addList callback:', function() { - var collection = new Collection(); - - collection.addList(['a.txt', 'b.txt', 'c.txt'], function(fp) { - return {path: fp}; - }); - assert(collection.items.hasOwnProperty('a.txt')); - assert(collection.items['a.txt'].path === 'a.txt'); - }); - - it('should load an object of items from an event', function() { - var collection = new Collection(); - - collection.on('addItems', function(items) { - for (var key in items) { - collection.addItem('foo/' + key, items[key]); - delete items[key]; - } - }); - - collection.addItems({ - a: {path: 'a.txt'}, - b: {path: 'b.txt'}, - c: {path: 'c.txt'} - }); - - assert(collection.items.hasOwnProperty('foo/a')); - assert(collection.items['foo/a'].path === 'a.txt'); - }); - - it('should signal `loaded` when finished (addItems)', function() { - var collection = new Collection(); - - collection.on('addItems', function(items) { - for (var key in items) { - if (key === 'c') { - collection.loaded = true; - break; - } - collection.addItem('foo/' + key, items[key]); - } - }); - - collection.addItems({ - a: {path: 'a.txt'}, - b: {path: 'b.txt'}, - c: {path: 'c.txt'} - }); - - assert(collection.items.hasOwnProperty('foo/a')); - assert(!collection.items.hasOwnProperty('foo/c')); - assert(collection.items['foo/a'].path === 'a.txt'); - }); - - it('should signal `loaded` when finished (addList)', function() { - var collection = new Collection(); - - collection.on('addList', function(items) { - for (var i = 0; i < items.length; i++) { - var item = items[i]; - if (item.key === 'c') { - collection.loaded = true; - break; - } - item.key = 'foo/' + item.key; - collection.addItem(item.key, item); - } - }); - - collection.addList([ - {key: 'a', path: 'a.txt'}, - {key: 'b', path: 'b.txt'}, - {key: 'c', path: 'c.txt'} - ]); - - assert(collection.items.hasOwnProperty('foo/a')); - assert(collection.items['foo/a'].path === 'a.txt'); - assert(!collection.items.hasOwnProperty('foo/c')); - }); - }); - - describe('getItem', function() { - beforeEach(function() { - collection = new Collection(); - }); - it('should get a item from `items`', function() { - collection.addItem('one', {content: 'aaa'}); - collection.addItem('two', {content: 'zzz'}); - assert(isBuffer(collection.items.one.contents)); - assert(isBuffer(collection.getItem('one').contents)); - assert(collection.getItem('one').contents.toString() === 'aaa'); - assert(collection.getItem('two').contents.toString() === 'zzz'); - }); - }); -}); - -describe('queue', function() { - beforeEach(function() { - collection = new Collection(); - }); - - it('should emit arguments on addItem', function(done) { - collection.on('addItem', function(args) { - assert(args[0] === 'a'); - assert(args[1] === 'b'); - assert(args[2] === 'c'); - assert(args[3] === 'd'); - assert(args[4] === 'e'); - done(); - }); - - collection.addItem('a', 'b', 'c', 'd', 'e'); - }); - - it('should expose the `queue` property for loading items', function() { - collection.queue.push(collection.item('b', {path: 'b'})); - - collection.addItem('a', {path: 'a'}); - assert(collection.items.hasOwnProperty('a')); - assert(collection.items.hasOwnProperty('b')); - }); - - it('should load all items on the queue when addItem is called', function() { - collection.on('addItem', function(args) { - var len = args.length; - var last = args[len - 1]; - if (typeof last === 'string') { - args[len - 1] = { content: last }; - } - }); - - collection.addItem('a.html', 'aaa'); - collection.addItem('b.html', 'bbb'); - collection.addItem('c.html', 'ccc'); - - assert(collection.items.hasOwnProperty('a.html')); - assert(collection.getItem('a.html').content === 'aaa'); - assert(collection.items.hasOwnProperty('b.html')); - assert(collection.getItem('b.html').content === 'bbb'); - assert(collection.items.hasOwnProperty('c.html')); - assert(collection.getItem('c.html').content === 'ccc'); - }); -}); - -describe('options', function() { - describe('option', function() { - beforeEach(function() { - collection = new Collection(); - }); - - it('should expose the `option` method', function() { - collection.option('foo', 'bar'); - collection.options.should.have.property('foo', 'bar'); - }); - - it('should be chainable', function() { - collection.option('foo', 'bar') - .addItems('a.hbs') - .addItems('b.hbs') - .addItems('c.hbs'); - - collection.options.should.have.property('foo', 'bar'); - collection.items.should.have.properties([ - 'a.hbs', - 'b.hbs', - 'c.hbs' - ]); - }); - - it('should set a key/value pair on options', function() { - collection.option('a', 'b'); - assert(collection.options.a === 'b'); - }); - - it('should set an object on options', function() { - collection.option({c: 'd'}); - assert(collection.options.c === 'd'); - }); - - it('should get an option', function() { - collection.option({c: 'd'}); - var c = collection.option('c'); - assert(c === 'd'); - }); - }); - - describe('options.renameKey', function() { - beforeEach(function() { - collection = new Collection({ - renameKey: function(key) { - return path.basename(key); - } - }); - }); - - it('should use a custom rename key function on item keys', function() { - collection.addItem('a/b/c/d.hbs', {content: 'foo bar baz'}); - assert(collection.items['d.hbs'].contents.toString() === 'foo bar baz'); - }); - - it('should get a item with the renamed key', function() { - collection.addItem('a/b/c/d.hbs', {content: 'foo bar baz'}); - assert(collection.getItem('d.hbs').contents.toString() === 'foo bar baz'); - }); - - it('should get a item with the original key', function() { - collection.addItem('a/b/c/d.hbs', {content: 'foo bar baz'}); - assert(collection.getItem('a/b/c/d.hbs').contents.toString() === 'foo bar baz'); - }); - }); -}); - diff --git a/test2/collection.options.js b/test2/collection.options.js deleted file mode 100644 index d1af0af..0000000 --- a/test2/collection.options.js +++ /dev/null @@ -1,25 +0,0 @@ -require('should'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('collection.option()', function() { - beforeEach(function() { - app = new App(); - app.create('page'); - }); - - it('should set an option:', function() { - app.pages.options.should.not.have.property('foo'); - app.pages.option('foo', 'bar'); - app.pages.options.should.have.property('foo'); - }); - - it('should extend options:', function() { - app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}); - app.pages.option('a', 'b'); - app.pages.option('c', 'd'); - app.pages.option('e', 'f'); - app.pages.options.should.have.properties(['a', 'c', 'e']); - }); -}); diff --git a/test2/collection.render.js b/test2/collection.render.js deleted file mode 100644 index 896c805..0000000 --- a/test2/collection.render.js +++ /dev/null @@ -1,138 +0,0 @@ -require('mocha'); -require('should'); -var async = require('async'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var List = App.List; -var Views = App.Views; -var pages; - -describe('render', function() { - describe('rendering', function() { - beforeEach(function() { - pages = new Views(); - pages.engine('tmpl', require('engine-base')); - }); - - it('should throw an error when no callback is given:', function() { - (function() { - pages.render({}); - }).should.throw('Views#render is async and expects a callback function'); - }); - - it('should throw an error when an engine is not defined:', function(done) { - pages.addView('foo.bar', {content: '<%= name %>'}); - var page = pages.getView('foo.bar'); - - pages.render(page, function(err) { - assert(err.message === 'Views#render cannot find an engine for: .bar'); - done(); - }); - }); - - it('should use helpers to render a view:', function(done) { - var locals = {name: 'Halle'}; - - pages.helper('upper', function(str) { - return str.toUpperCase(str); - }); - - pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); - var page = pages.getView('a.tmpl'); - - pages.render(page, function(err, res) { - if (err) return done(err); - - assert(res.content === 'a HALLE b'); - done(); - }); - }); - - it('should use helpers when rendering a view:', function(done) { - var locals = {name: 'Halle'}; - pages.helper('upper', function(str) { - return str.toUpperCase(str); - }); - - pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); - var page = pages.getView('a.tmpl'); - - pages.render(page, function(err, res) { - if (err) return done(err); - assert(res.content === 'a HALLE b'); - done(); - }); - }); - - it('should render a template when contents is a buffer:', function(done) { - pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - var view = pages.getView('a.tmpl'); - - pages.render(view, function(err, view) { - if (err) return done(err); - assert(view.contents.toString() === 'b'); - done(); - }); - }); - - it('should render a template when content is a string:', function(done) { - pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - var view = pages.getView('a.tmpl'); - - pages.render(view, function(err, view) { - if (err) return done(err); - assert(view.contents.toString() === 'b'); - done(); - }); - }); - - it('should render a view from its path:', function(done) { - pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - - pages.render('a.tmpl', function(err, view) { - if (err) return done(err); - assert(view.content === 'b'); - done(); - }); - }); - - it('should use a plugin for rendering:', function(done) { - pages.engine('tmpl', require('engine-base')); - pages.option('engine', 'tmpl'); - - pages.addViews({ - 'a': {content: '<%= title %>', locals: {title: 'aaa'}}, - 'b': {content: '<%= title %>', locals: {title: 'bbb'}}, - 'c': {content: '<%= title %>', locals: {title: 'ccc'}}, - 'd': {content: '<%= title %>', locals: {title: 'ddd'}}, - 'e': {content: '<%= title %>', locals: {title: 'eee'}}, - 'f': {content: '<%= title %>', locals: {title: 'fff'}}, - 'g': {content: '<%= title %>', locals: {title: 'ggg'}}, - 'h': {content: '<%= title %>', locals: {title: 'hhh'}}, - 'i': {content: '<%= title %>', locals: {title: 'iii'}}, - 'j': {content: '<%= title %>', locals: {title: 'jjj'}} - }); - - pages.use(function(collection) { - collection.option('pager', false); - - collection.renderEach = function(cb) { - var list = new List(collection); - - async.map(list.items, function(item, next) { - collection.render(item, next); - }, cb); - }; - }); - - pages.renderEach(function(err, items) { - if (err) return done(err); - assert(items[0].content === 'aaa'); - assert(items[9].content === 'jjj'); - assert(items.length === 10); - done(); - }); - }); - }); -}); diff --git a/test2/collection.use.js b/test2/collection.use.js deleted file mode 100644 index f09bd86..0000000 --- a/test2/collection.use.js +++ /dev/null @@ -1,156 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var Collection = App.Collection; -var Item = App.Item; -var collection; - -describe('collection.use', function() { - beforeEach(function() { - collection = new Collection(); - }); - - it('should expose the instance to `use`:', function(done) { - collection.use(function(inst) { - assert(inst instanceof Collection); - done(); - }); - }); - - it('should be chainable:', function(done) { - collection.use(function(inst) { - assert(inst instanceof Collection); - }) - .use(function(inst) { - assert(inst instanceof Collection); - }) - .use(function(inst) { - assert(inst instanceof Collection); - done(); - }); - }); - - it('should expose the collection to a plugin:', function() { - collection.use(function(items) { - assert(items instanceof Collection); - items.foo = items.addItem.bind(items); - }); - - collection.foo('a', {content: '...'}); - assert(collection.items.hasOwnProperty('a')); - }); - - it('should expose collection when chained:', function() { - collection - .use(function(items) { - assert(items instanceof Collection); - items.foo = items.addItem.bind(items); - }) - .use(function(items) { - assert(items instanceof Collection); - items.bar = items.addItem.bind(items); - }) - .use(function(items) { - assert(items instanceof Collection); - items.baz = items.addItem.bind(items); - }); - - var pages = collection; - - pages.foo({path: 'a', content: '...'}); - pages.bar({path: 'b', content: '...'}); - pages.baz({path: 'c', content: '...'}); - - assert(collection.items.hasOwnProperty('a')); - assert(collection.items.hasOwnProperty('b')); - assert(collection.items.hasOwnProperty('c')); - }); - - it('should work when a custom `Item` constructor is passed:', function() { - collection = new Collection({Item: require('vinyl')}); - collection - .use(function(items) { - assert(items instanceof Collection); - items.foo = items.addItem.bind(items); - }) - .use(function(items) { - assert(items instanceof Collection); - items.bar = items.addItem.bind(items); - }) - .use(function(items) { - assert(items instanceof Collection); - items.baz = items.addItem.bind(items); - }); - - var pages = collection; - - pages.foo({path: 'a', content: '...'}); - pages.bar({path: 'b', content: '...'}); - pages.baz({path: 'c', content: '...'}); - - assert(collection.items.hasOwnProperty('a')); - assert(collection.items.hasOwnProperty('b')); - assert(collection.items.hasOwnProperty('c')); - }); - - it('should pass to item `use` if a function is returned:', function() { - collection.use(function(items) { - assert(items instanceof Collection); - - return function(item) { - item.foo = items.addItem.bind(items); - assert(item instanceof Item); - }; - }); - - collection.addItem('a', {content: '...'}) - .foo({path: 'b', content: '...'}) - .foo({path: 'c', content: '...'}) - .foo({path: 'd', content: '...'}); - - assert(collection.items.hasOwnProperty('a')); - assert(collection.items.hasOwnProperty('b')); - assert(collection.items.hasOwnProperty('c')); - assert(collection.items.hasOwnProperty('d')); - }); - - it('should be chainable when a item function is returned:', function() { - collection - .use(function(items) { - assert(items instanceof Collection); - - return function(item) { - item.foo = items.addItem.bind(items); - assert(item instanceof Item); - }; - }) - .use(function(items) { - assert(items instanceof Collection); - - return function(item) { - item.bar = items.addItem.bind(items); - assert(item instanceof Item); - }; - }) - .use(function(items) { - assert(items instanceof Collection); - - return function(item) { - item.baz = items.addItem.bind(items); - assert(item instanceof Item); - }; - }); - - collection.addItem('a', {content: '...'}) - .foo({path: 'b', content: '...'}) - .bar({path: 'c', content: '...'}) - .baz({path: 'd', content: '...'}); - - assert(collection.items.hasOwnProperty('a')); - assert(collection.items.hasOwnProperty('b')); - assert(collection.items.hasOwnProperty('c')); - assert(collection.items.hasOwnProperty('d')); - }); -}); diff --git a/test2/fixtures/bom-utf16be.txt b/test2/fixtures/bom-utf16be.txt deleted file mode 100644 index b9dce78a5d31af4803acd1a0f0dfc14f064a5de1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 244 zcmX|+I}XAy5Jacu26Qf=0Eq@sM*@i=qJaY#5&}waq&RRn3Xa4Tc-{cbe#Sc=zn?E@ zuZymVayru+l}y7P<@I1MK)hWXxZY@{g_hJzYt4Dvs;8dRDlmE2!LB37&GahJPDg5G zyEjIUb8?Hu>i*d9+Q4pAn^J>j{bf4+Qmn{O;+32Wrj#?&PC0#o+uan?UxFJmPf0ua E03tFf0ssI2 diff --git a/test2/fixtures/bom-utf16le.txt b/test2/fixtures/bom-utf16le.txt deleted file mode 100644 index 07cc600c98675d221bb56d10af38e650538734c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 244 zcmX|+%?`mp6otRFH?W%}3lbZ#mXJt@4G%E1O4?47PI);CkK`4cxy9!GoVn*`-p>~Y zuH1+?F6tGzrhboj9@;Y@-Y$;1UNd3FTy@Kesopkps%IL4CNFld>nNl)y+UZqNwu)u z8>5qRa*M`l|5*Q8iQQ0|QYFpu%XIuwER-RaS8~oYrJPIl?9@kcyPIPAOJL|a#!5Tj E15l{{title}}

-

<%= title() %>

\ No newline at end of file diff --git a/test2/fixtures/pages/b.hbs b/test2/fixtures/pages/b.hbs deleted file mode 100644 index 51320bd..0000000 --- a/test2/fixtures/pages/b.hbs +++ /dev/null @@ -1,2 +0,0 @@ -

{{title}}

-

<%= title() %>

\ No newline at end of file diff --git a/test2/fixtures/pages/c.hbs b/test2/fixtures/pages/c.hbs deleted file mode 100644 index 51320bd..0000000 --- a/test2/fixtures/pages/c.hbs +++ /dev/null @@ -1,2 +0,0 @@ -

{{title}}

-

<%= title() %>

\ No newline at end of file diff --git a/test2/fixtures/pipeline/a.js b/test2/fixtures/pipeline/a.js deleted file mode 100644 index 1085b60..0000000 --- a/test2/fixtures/pipeline/a.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; - -var through = require('through2'); - -module.exports = function(options) { - return through.obj(function(file, enc, cb) { - var str = file.contents.toString(); - str += 'aaa\n'; - - // var err = new Error('foo'); - // err.plugin = 'a'; - // this.emit('error', err); - // return cb(err); - - file.contents = new Buffer(str); - this.push(file); - cb(); - }); -}; diff --git a/test2/fixtures/pipeline/b.js b/test2/fixtures/pipeline/b.js deleted file mode 100644 index e0b0521..0000000 --- a/test2/fixtures/pipeline/b.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -var through = require('through2'); - -module.exports = function(options) { - return through.obj(function(file, enc, cb) { - var str = file.contents.toString(); - str += 'bbb\n'; - - file.contents = new Buffer(str); - this.push(file); - cb(); - }); -}; diff --git a/test2/fixtures/pipeline/c.js b/test2/fixtures/pipeline/c.js deleted file mode 100644 index f5c0a47..0000000 --- a/test2/fixtures/pipeline/c.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -var through = require('through2'); - -module.exports = function(options) { - return through.obj(function(file, enc, cb) { - var str = file.contents.toString(); - str += 'ccc\n'; - - file.contents = new Buffer(str); - this.push(file); - cb(); - }); -}; diff --git a/test2/fixtures/pipeline/d.js b/test2/fixtures/pipeline/d.js deleted file mode 100644 index 636f53e..0000000 --- a/test2/fixtures/pipeline/d.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -var through = require('through2'); - -module.exports = function(options) { - return through.obj(function(file, enc, cb) { - var str = file.contents.toString(); - str += 'ddd\n'; - - file.contents = new Buffer(str); - this.push(file); - cb(); - }); -}; diff --git a/test2/fixtures/posts/a.txt b/test2/fixtures/posts/a.txt deleted file mode 100644 index bca29ee..0000000 --- a/test2/fixtures/posts/a.txt +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: AAA ---- -This is <%= title %> \ No newline at end of file diff --git a/test2/fixtures/posts/b.txt b/test2/fixtures/posts/b.txt deleted file mode 100644 index 1e128c7..0000000 --- a/test2/fixtures/posts/b.txt +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: BBB ---- -This is <%= title %> \ No newline at end of file diff --git a/test2/fixtures/posts/c.txt b/test2/fixtures/posts/c.txt deleted file mode 100644 index 32f9187..0000000 --- a/test2/fixtures/posts/c.txt +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: CCC ---- -This is <%= title %> \ No newline at end of file diff --git a/test2/fixtures/templates/a.tmpl b/test2/fixtures/templates/a.tmpl deleted file mode 100644 index 36f1f1b..0000000 --- a/test2/fixtures/templates/a.tmpl +++ /dev/null @@ -1 +0,0 @@ -<%= name %> \ No newline at end of file diff --git a/test2/fixtures/templates/b.tmpl b/test2/fixtures/templates/b.tmpl deleted file mode 100644 index 36f1f1b..0000000 --- a/test2/fixtures/templates/b.tmpl +++ /dev/null @@ -1 +0,0 @@ -<%= name %> \ No newline at end of file diff --git a/test2/fixtures/templates/c.tmpl b/test2/fixtures/templates/c.tmpl deleted file mode 100644 index 36f1f1b..0000000 --- a/test2/fixtures/templates/c.tmpl +++ /dev/null @@ -1 +0,0 @@ -<%= name %> \ No newline at end of file diff --git a/test2/fixtures/test-symlink b/test2/fixtures/test-symlink deleted file mode 120000 index 3fcfe6c..0000000 --- a/test2/fixtures/test-symlink +++ /dev/null @@ -1 +0,0 @@ -test.coffee \ No newline at end of file diff --git a/test2/fixtures/test-symlink-dir/suchempty b/test2/fixtures/test-symlink-dir/suchempty deleted file mode 100644 index 65bbcaa..0000000 --- a/test2/fixtures/test-symlink-dir/suchempty +++ /dev/null @@ -1 +0,0 @@ -suchempty \ No newline at end of file diff --git a/test2/fixtures/test.coffee b/test2/fixtures/test.coffee deleted file mode 100644 index 6769dd6..0000000 --- a/test2/fixtures/test.coffee +++ /dev/null @@ -1 +0,0 @@ -Hello world! \ No newline at end of file diff --git a/test2/fixtures/updaters/a.txt b/test2/fixtures/updaters/a.txt deleted file mode 100644 index 7c4a013..0000000 --- a/test2/fixtures/updaters/a.txt +++ /dev/null @@ -1 +0,0 @@ -aaa \ No newline at end of file diff --git a/test2/fixtures/updaters/b.txt b/test2/fixtures/updaters/b.txt deleted file mode 100644 index 01f02e3..0000000 --- a/test2/fixtures/updaters/b.txt +++ /dev/null @@ -1 +0,0 @@ -bbb \ No newline at end of file diff --git a/test2/fixtures/updaters/c.txt b/test2/fixtures/updaters/c.txt deleted file mode 100644 index 2383bd5..0000000 --- a/test2/fixtures/updaters/c.txt +++ /dev/null @@ -1 +0,0 @@ -ccc \ No newline at end of file diff --git a/test2/fixtures/vinyl/bom-utf16be.txt b/test2/fixtures/vinyl/bom-utf16be.txt deleted file mode 100644 index b9dce78a5d31af4803acd1a0f0dfc14f064a5de1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 244 zcmX|+I}XAy5Jacu26Qf=0Eq@sM*@i=qJaY#5&}waq&RRn3Xa4Tc-{cbe#Sc=zn?E@ zuZymVayru+l}y7P<@I1MK)hWXxZY@{g_hJzYt4Dvs;8dRDlmE2!LB37&GahJPDg5G zyEjIUb8?Hu>i*d9+Q4pAn^J>j{bf4+Qmn{O;+32Wrj#?&PC0#o+uan?UxFJmPf0ua E03tFf0ssI2 diff --git a/test2/fixtures/vinyl/bom-utf16le.txt b/test2/fixtures/vinyl/bom-utf16le.txt deleted file mode 100644 index 07cc600c98675d221bb56d10af38e650538734c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 244 zcmX|+%?`mp6otRFH?W%}3lbZ#mXJt@4G%E1O4?47PI);CkK`4cxy9!GoVn*`-p>~Y zuH1+?F6tGzrhboj9@;Y@-Y$;1UNd3FTy@Kesopkps%IL4CNFld>nNl)y+UZqNwu)u z8>5qRa*M`l|5*Q8iQQ0|QYFpu%XIuwER-RaS8~oYrJPIl?9@kcyPIPAOJL|a#!5Tj E15l', locals: {a: 'bbb'}}); - app.helper('upper', function(str) { - return str.toUpperCase(); - }); - - var page = app.pages.getView('a.tmpl'); - - app.render(page, function(err, view) { - if (err) return cb(err); - - assert.equal(typeof view.contents.toString(), 'string'); - assert.equal(view.contents.toString(), 'BBB'); - cb(); - }); - }); - - it('should use a namespaced helper:', function(cb) { - app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= foo.upper(a) %>', locals: {a: 'bbb'}}); - - app.helperGroup('foo', { - upper: function(str) { - return str.toUpperCase(); - } - }); - - // console.log(app._.helpers) - - var page = app.pages.getView('a.tmpl'); - app.render(page, function(err, view) { - if (err) return cb(err); - - assert.equal(typeof view.contents.toString(), 'string'); - assert.equal(view.contents.toString(), 'BBB'); - cb(); - }); - }); -}); - -describe('async helpers', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('page'); - }); - - it('should register an async helper:', function() { - app.asyncHelper('a', function() {}); - app.asyncHelper('b', function() {}); - app._.helpers.async.should.have.property('a'); - app._.helpers.async.should.have.property('b'); - }); - - it('should use an async helper:', function(cb) { - app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= lower(a) %>', locals: {a: 'BBB'}}); - app.asyncHelper('lower', function(str, next) { - if (typeof next !== 'function') return str; - next(null, str.toLowerCase()); - }); - - var page = app.pages.getView('a.tmpl'); - app.render(page, function(err, view) { - if (err) return cb(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'bbb'); - cb(); - }); - }); -}); - -describe('built-in helpers:', function() { - describe('automatically generated helpers for default view types:', function() { - beforeEach(function() { - app = new App({rethrow: false}); - app.engine('md', require('engine-base')); - app.engine('tmpl', require('engine-base')); - app.create('partials', { viewType: 'partial' }); - app.create('pages'); - - // parse front matter - app.onLoad(/./, function(view, next) { - matter.parse(view, next); - }); - }); - - it('should expose front matter to the `partial` helper.', function(cb) { - app.partial('a.md', {content: '---\nname: "AAA"\n---\n<%= name %>', locals: {name: 'BBB'}}); - app.page('b.md', {path: 'b.md', content: 'foo <%= partial("a.md") %> bar'}); - - app.render('b.md', function(err, res) { - if (err) return cb(err); - res.content.should.equal('foo AAA bar'); - cb(); - }); - }); - - it('should use helper locals.', function(cb) { - app.partial('abc.md', {content: '<%= name %>', locals: {name: 'BBB'}}); - app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md", { name: "CCC" }) %> bar'}); - - app.render('xyz.md', {name: 'DDD'}, function(err, res) { - if (err) return cb(err); - res.content.should.equal('foo CCC bar'); - cb(); - }); - }); - - it('should use front matter data.', function(cb) { - app.partial('abc.md', {content: '---\nname: "AAA"\n---\n<%= name %>'}); - app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}); - - app.render('xyz.md', {name: 'DDD'}, function(err, res) { - if (err) return cb(err); - res.content.should.equal('foo AAA bar'); - cb(); - }); - }); - - it('should prefer helper locals over front-matter', function(cb) { - app.partial('abc.md', {content: '---\nname: "AAA"\n---\n<%= name %>', locals: {name: 'BBB'}}); - app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md", { name: "CCC" }) %> bar'}); - - app.render('xyz.md', {name: 'DDD'}, function(err, res) { - if (err) return cb(err); - res.content.should.equal('foo CCC bar'); - cb(); - }); - }); - - it('should use partial locals:', function(cb) { - app.partial('abc.md', {content: '<%= name %>', locals: {name: 'EEE'}}); - - app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}) - .render({name: 'DDD'}, function(err, res) { - if (err) return cb(err); - res.content.should.equal('foo EEE bar'); - cb(); - }); - }); - - it('should use locals from the `view.render` method:', function(cb) { - app.partial('abc.md', {content: '<%= name %>', locals: {name: 'EEE'}}); - - app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}) - .render({name: 'DDD'}, function(err, res) { - if (err) return cb(err); - - res.content.should.equal('foo EEE bar'); - cb(); - }); - }); - - it('should use locals from the `app.render` method:', function(cb) { - app.partial('abc.md', {content: '<%= name %>'}); - app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}); - - app.render('xyz.md', {name: 'DDD'}, function(err, res) { - if (err) return cb(err); - res.content.should.equal('foo DDD bar'); - cb(); - }); - }); - - it('should use a `helperContext` function from app.options', function(cb) { - app.option('helperContext', function(view, locals) { - return { name: 'blah' }; - }); - - app.partial('abc.md', {content: '<%= name %>'}); - app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}); - - app.render('xyz.md', {name: 'DDD'}, function(err, res) { - if (err) return cb(err); - res.content.should.equal('foo blah bar'); - cb(); - }); - }); - - it('should return an empty string when the partial is missing.', function(cb) { - app.partial('abc.md', {content: '---\nname: "AAA"\n---\n<%= name %>', locals: {name: 'BBB'}}); - app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("def.md", { name: "CCC" }) %> bar'}); - app.render('xyz.md', {name: 'DDD'}, function(err, res) { - if (err) return cb(err); - res.content.should.equal('foo bar'); - cb(); - }); - }); - }); - - describe('helper context:', function() { - beforeEach(function() { - app = new App({rethrow: false}); - app.engine(['tmpl', 'md'], require('engine-base')); - app.create('partial', { viewType: 'partial' }); - app.create('page'); - - // parse front matter - app.onLoad(/./, function(view, next) { - matter.parse(view, next); - }); - }); - - it('should prefer helper locals over view locals.', function(cb) { - app.partial('abc.md', {content: '<%= name %>', name: 'BBB'}); - app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md", { name: "CCC" }) %> bar'}); - - app.render('xyz.md', {name: 'DDD'}, function(err, res) { - if (err) return cb(err); - res.content.should.equal('foo CCC bar'); - cb(); - }); - }); - - it('should give preference to view locals over render locals.', function(cb) { - app.partial('abc.md', {content: '<%= name %>', locals: {name: 'BBB'}}); - app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}); - - var page = app.pages.getView('xyz.md'); - - app.render(page, {name: 'DDD'}, function(err, res) { - if (err) return cb(err); - res.content.should.equal('foo BBB bar'); - cb(); - }); - }); - - it('should use render locals when other locals are not defined.', function(cb) { - app.partial('abc.md', {content: '<%= name %>'}); - app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}); - - app.render('xyz.md', {name: 'DDD'}, function(err, res) { - if (err) return cb(err); - res.content.should.equal('foo DDD bar'); - cb(); - }); - }); - }); - - describe('user-defined engines:', function() { - beforeEach(function() { - app = new App({rethrow: false}); - app.create('partial', { viewType: 'partial' }); - app.create('page'); - - // parse front matter - app.onLoad(/./, function(view, next) { - matter.parse(view, next); - }); - }); - - it('should use the `partial` helper with handlebars.', function(cb) { - app.engine(['tmpl', 'md'], require('engine-base')); - app.engine('hbs', handlebars); - - app.partial('title.hbs', {content: '{{name}}', locals: {name: 'BBB'}}); - app.page('a.hbs', {path: 'a.hbs', content: 'foo {{{partial "title.hbs" this}}} bar'}); - - app.render('a.hbs', {name: 'Halle Nicole'}, function(err, res) { - if (err) return cb(err); - res.content.should.equal('foo Halle Nicole bar'); - cb(); - }); - }); - - it('should use the `partial` helper with any engine.', function(cb) { - app.engine('hbs', handlebars); - app.engine('md', handlebars); - app.engine('swig', swig); - app.engine('tmpl', require('engine-base')); - - /** - * Partial - */ - - app.partial('a.hbs', { - content: '---\nname: "AAA"\n---\n{{name}}', - locals: { - name: 'BBB' - } - }); - - /** - * Pages - */ - - app.page('a.hbs', { - path: 'a.hbs', - content: '{{author}}', - locals: { - author: 'Halle Nicole' - } - }); - app.page('b.tmpl', { - path: 'b.tmpl', - content: '<%= author %>', - locals: { - author: 'Halle Nicole' - } - }); - app.page('d.swig', { - path: 'd.swig', - content: '{{author}}', - locals: { - author: 'Halle Nicole' - } - }); - app.page('e.swig', { - path: 'e.swig', - content: '{{author}}', - locals: { - author: 'Halle Nicole' - } - }); - app.page('f.hbs', { - content: '{{author}}', - locals: { - author: 'Halle Nicole' - } - }); - app.page('g.md', { - content: '---\nauthor: Brian Woodward\n---\n{{author}}', - locals: { - author: 'Halle Nicole' - } - }); - app.page('with-partial.hbs', { - path: 'with-partial.hbs', - content: '{{{partial "a.hbs" custom.locals}}}' - }); - - app.on('error', function(err) { - cb(err); - }); - - var locals = {custom: {locals: {name: 'Halle Nicole' }}}; - app.render('a.hbs', locals, function(err, res) { - if (err) { - app.emit('error', err); - return; - } - res.content.should.equal('Halle Nicole'); - }); - - app.render('with-partial.hbs', locals, function(err, res) { - if (err) { - app.emit('error', err); - return; - } - res.content.should.equal('Halle Nicole'); - }); - - var page = app.pages.getView('g.md'); - locals.author = page.data.author || locals.author; - page.render(locals, function(err, res) { - if (err) { - app.emit('error', err); - return; - } - res.content.should.equal('Brian Woodward'); - cb(null, res.content); - }); - }); - }); -}); - -describe('helpers integration', function() { - beforeEach(function() { - app = new App(); - app.create('pages'); - app.engine('md', require('engine-base')); - }); - - describe('.helpers()', function() { - it('should add helpers and use them in templates.', function(cb) { - app.helpers({ - upper: function(str) { - return str.toUpperCase(); - } - }); - - app.page('doc.md', {content: 'a <%= upper(name) %> b'}) - .render({name: 'Halle'}, function(err, res) { - if (err) return cb(err); - assert(res.content === 'a HALLE b'); - cb(); - }); - }); - }); - - describe('helper options:', function() { - it('should expose `this.options` to helpers:', function(cb) { - app.helper('cwd', function(fp) { - return path.join(this.options.cwd, fp); - }); - - app.option('one', 'two'); - app.option('cwd', 'foo/bar'); - app.page('doc.md', {content: 'a <%= cwd("baz") %> b'}) - .render(function(err, res) { - if (err) return cb(err); - assert(res.content === 'a foo/bar/baz b'); - cb(); - }); - }); - - it('should pass helper options to helpers:', function(cb) { - app.helper('cwd', function(fp) { - return path.join(this.options.cwd, fp); - }); - - app.option('helper.cwd', 'foo/bar'); - app.option('helper.whatever', '...'); - - app.page('doc.md', {content: 'a <%= cwd("baz") %> b'}) - .render(function(err, res) { - if (err) return cb(err); - assert(res.content === 'a foo/bar/baz b'); - cb(); - }); - }); - }); - - describe('options.helpers', function() { - it('should register helpers passed on the options:', function(cb) { - app.option({ - helpers: { - upper: function(str) { - return str.toUpperCase(); - }, - foo: function(str) { - return 'foo' + str; - } - } - }); - - app.page('doc.md', {content: 'a <%= upper(name) %> <%= foo("bar") %> b'}) - .render({name: 'Halle'}, function(err, res) { - if (err) return cb(err); - assert(res.content === 'a HALLE foobar b'); - cb(); - }); - }); - }); - - describe('options.helpers', function() { - it('should add helpers and use them in templates.', function(cb) { - app.options.helpers = { - upper: function(str) { - return str.toUpperCase(); - }, - foo: function(str) { - return 'foo' + str; - } - }; - - app.page('doc.md', {content: 'a <%= upper(name) %> b'}) - .render({name: 'Halle'}, function(err, res) { - if (err) return cb(err); - assert(res.content === 'a HALLE b'); - cb(); - }); - }); - }); -}); - -describe('collection helpers', function() { - beforeEach(function() { - app = new App(); - app.create('posts'); - app.create('pages', {engine: 'hbs'}); - app.create('partials', {viewType: 'partial', engine: 'hbs'}); - app.create('snippet', {viewType: 'partial'}); - app.engine('hbs', require('engine-handlebars')); - app.helper('log', function(ctx) { - console.log(ctx); - }); - }); - - describe('plural', function() { - it('should get the given collection', function(cb) { - app.post('a.hbs', {content: 'foo'}); - app.post('b.hbs', {content: 'bar'}); - app.post('c.hbs', {content: 'baz'}); - - app.partial('list.hbs', { - content: '{{#posts}}{{#each items}}{{content}}{{/each}}{{/posts}}' - }); - - app.page('index.hbs', { - content: '{{> list.hbs }}' - }) - .render(function(err, res) { - if (err) return cb(err); - assert(res.content === 'foobarbaz'); - cb(); - }); - }); - }); - - describe('single', function() { - it('should get a view from an unspecified collection', function(cb) { - app.post('a.hbs', {content: 'post-a'}); - app.post('b.hbs', {content: 'post-b'}); - - var one = app.page('one', {content: '{{view "a.hbs"}}'}) - .compile() - .fn(); - - var two = app.page('two', {content: '{{view "b.hbs"}}'}) - .compile() - .fn(); - - assert(one === 'post-a'); - assert(two === 'post-b'); - cb(); - }); - - it('should return an empty string if not found', function(cb) { - var one = app.page('one', {content: '{{view "foo.hbs"}}'}) - .compile() - .fn(); - assert(one === ''); - cb(); - }); - - it('should handle engine errors', function(cb) { - app.post('foo.hbs', {content: '{{one "two"}}'}); - app.page('one', {content: '{{posts "foo.hbs"}}'}) - .render(function(err) { - assert(err); - assert(typeof err === 'object'); - assert(typeof err.message === 'string'); - assert(/Missing helper: "one"/.test(err.message)); - cb(); - }); - }); - - it('should handle engine errors2', function(cb) { - app.engine('tmpl', require('engine-base')); - app.create('foo', {engine: 'tmpl'}); - app.create('bar', {engine: 'tmpl'}); - - app.create('foo', {viewType: 'partial'}); - app.foo('foo.tmpl', {path: 'foo.tmpl', content: '<%= blah.bar %>'}); - app.bar('one.tmpl', {content: '<%= foo("foo.tmpl") %>'}) - .render(function(err) { - assert(err); - assert(typeof err === 'object'); - assert(/blah is not defined/.test(err.message)); - cb(); - }); - }); - - it('should work with non-handlebars engine', function(cb) { - app.engine('tmpl', require('engine-base')); - app.create('foo', {engine: 'tmpl'}); - app.create('bar', {engine: 'tmpl'}); - - app.foo('a.tmpl', {content: 'foo-a'}); - app.foo('b.tmpl', {content: 'foo-b'}); - - var one = app.bar('one', {content: '<%= view("a.tmpl") %>'}) - .compile() - .fn(); - - var two = app.bar('two', {content: '<%= view("b.tmpl") %>'}) - .compile() - .fn(); - - assert(one === 'foo-a'); - assert(two === 'foo-b'); - cb(); - }); - - it('should get a specific view from the given collection', function(cb) { - app.post('a.hbs', {content: 'post-a'}); - app.post('b.hbs', {content: 'post-b'}); - app.post('c.hbs', {content: 'post-c'}); - app.page('a.hbs', {content: 'page-a'}); - app.page('b.hbs', {content: 'page-b'}); - app.page('c.hbs', {content: 'page-c'}); - - var one = app.page('one', {content: '{{view "a.hbs" "posts"}}'}) - .compile() - .fn(); - - var two = app.page('two', {content: '{{view "b.hbs" "pages"}}'}) - .compile() - .fn(); - - assert(one === 'post-a'); - assert(two === 'page-b'); - cb(); - }); - }); -}); diff --git a/test2/item.js b/test2/item.js deleted file mode 100644 index ca0f435..0000000 --- a/test2/item.js +++ /dev/null @@ -1,1060 +0,0 @@ -require('mocha'); -var should = require('should'); -var fs = require('fs'); -var path = require('path'); -var assert = require('assert'); -var es = require('event-stream'); -var Stream = require('stream'); -var support = require('./support'); -var App = support.resolve(); -var Item = App.Item; -var item; - -describe('Item', function() { - describe('instance', function() { - it('should create an instance of Item:', function() { - item = new Item(); - assert(item instanceof Item); - }); - - it('should instantiate without new:', function() { - item = Item(); - assert(item instanceof Item); - }); - - it('inspect should not double name `Stream` when ctor is `Stream`', function(done) { - var val = new Stream(); - item = new Item({contents: val}); - done(); - }); - }); - - describe('static methods', function() { - it('should expose `extend`:', function() { - assert(typeof Item.extend === 'function'); - }); - }); - - describe('prototype methods', function() { - beforeEach(function() { - item = new Item(); - }); - - it('should expose `set`:', function() { - assert(typeof item.set === 'function'); - }); - it('should expose `get`:', function() { - assert(typeof item.get === 'function'); - }); - it('should expose `del`:', function() { - assert(typeof item.del === 'function'); - }); - it('should expose `define`:', function() { - assert(typeof item.define === 'function'); - }); - it('should expose `visit`:', function() { - assert(typeof item.visit === 'function'); - }); - }); - - describe('properties', function() { - it('should expose an `options` property', function() { - item = new Item({}); - assert.deepEqual(item.options, {}); - assert(item.hasOwnProperty('options')); - }); - - it('should add `options` when passed on the constructor', function() { - item = new Item({options: {foo: 'bar'}}); - assert(item.options.foo === 'bar'); - }); - - it('should expose a `data` property', function() { - item = new Item({app: {}}); - assert.deepEqual(item.data, {}); - assert(item.hasOwnProperty('data')); - }); - - it('should add `data` when passed on the constructor', function() { - item = new Item({data: {foo: 'bar'}}); - assert(item.data.foo === 'bar'); - }); - - it('should add `locals` when passed on the constructor', function() { - item = new Item({locals: {foo: 'bar'}}); - assert(item.locals.foo === 'bar'); - }); - }); - - describe('set', function() { - it('should set properties on the object', function() { - item = new Item(); - item.set('foo', 'bar'); - assert.equal(item.foo, 'bar'); - }); - }); - - describe('get', function() { - it('should get properties from the object', function() { - item = new Item(); - item.set('foo', 'bar'); - assert.equal(item.get('foo'), 'bar'); - }); - }); - - describe('cwd', function() { - it('should get properties from the object', function() { - item = new Item({cwd: 'test/fixtures'}); - assert(item.cwd === 'test/fixtures'); - }); - }); - - describe('clone', function() { - it('should clone the item:', function() { - item = new Item({content: 'foo'}); - item.set({path: 'foo/bar'}); - item.set('options.one', 'two'); - var clone = item.clone(); - assert(clone.contents); - clone.set('baz', 'quux'); - clone.set('options.three', 'four'); - assert.equal(clone.get('foo'), item.get('foo')); - assert(clone.get('baz') === 'quux'); - assert(!item.get('baz')); - // not deep cloned - assert(clone.get('options.three') === 'four'); - assert(item.get('options.three') === 'four'); - }); - - it('should deep clone the entire object', function() { - item = new Item({content: 'foo'}); - item.set({path: 'foo/bar'}); - item.set('options.one', 'two'); - var clone = item.clone({deep: true}); - clone.set('options.three', 'four'); - assert(item.get('options.one') === 'two'); - assert(clone.get('options.one') === 'two'); - assert(clone.get('options.three') === 'four'); - assert(!item.get('options.three')); - }); - }); - - describe('visit', function() { - it('should visit all properties on an object and call the specified method', function() { - item = new Item(); - var obj = { - foo: 'bar', - bar: 'baz', - baz: 'bang' - }; - item.visit('set', obj); - assert.equal(item.get('foo'), 'bar'); - assert.equal(item.get('bar'), 'baz'); - assert.equal(item.get('baz'), 'bang'); - }); - - it('should visit all properties on all objects in an array and call the specified method', function() { - item = new Item(); - var arr = [{foo: 'bar', bar: 'baz', baz: 'bang'}]; - item.visit('set', arr); - assert.equal(item.get('foo'), 'bar'); - assert.equal(item.get('bar'), 'baz'); - assert.equal(item.get('baz'), 'bang'); - }); - }); -}); - -/** - * The following unit tests are from Vinyl - * Since we inherit vinyl in Item, we need - * to ensure that these still pass. - */ - -describe('Item', function() { - describe('isVinyl()', function() { - it('should return true on a vinyl object', function(done) { - item = new Item(); - assert(Item.isVinyl(item) === true); - done(); - }); - it('should return false on a normal object', function(done) { - assert(Item.isVinyl({}) === false); - done(); - }); - it('should return false on a null object', function(done) { - assert(Item.isVinyl({}) === false); - done(); - }); - }); - - describe('constructor()', function() { - it('should default cwd to process.cwd', function(done) { - item = new Item(); - item.cwd.should.equal(process.cwd()); - done(); - }); - - it('should default base to cwd', function(done) { - var cwd = '/'; - item = new Item({cwd: cwd}); - item.base.should.equal(cwd); - done(); - }); - - it('should default base to cwd even when none is given', function(done) { - item = new Item(); - item.base.should.equal(process.cwd()); - done(); - }); - - it('should default path to null', function(done) { - item = new Item(); - should.not.exist(item.path); - done(); - }); - - it('should default history to []', function(done) { - item = new Item(); - item.history.should.eql([]); - done(); - }); - - it('should default stat to null', function(done) { - item = new Item(); - should.not.exist(item.stat); - done(); - }); - - it('should default contents to null', function(done) { - item = new Item(); - should.not.exist(item.contents); - done(); - }); - - it('should set base to given value', function(done) { - var val = '/'; - item = new Item({base: val}); - item.base.should.equal(val); - done(); - }); - - it('should set cwd to given value', function(done) { - var val = '/'; - item = new Item({cwd: val}); - item.cwd.should.equal(val); - done(); - }); - - it('should set path to given value', function(done) { - var val = '/test.coffee'; - item = new Item({path: val}); - item.path.should.equal(val); - item.history.should.eql([val]); - done(); - }); - - it('should set history to given value', function(done) { - var val = '/test.coffee'; - item = new Item({history: [val]}); - item.path.should.equal(val); - item.history.should.eql([val]); - done(); - }); - - it('should set stat to given value', function(done) { - var val = {}; - item = new Item({stat: val}); - item.stat.should.equal(val); - done(); - }); - - it('should set contents to given value', function(done) { - var val = new Buffer('test'); - item = new Item({contents: val}); - item.contents.should.equal(val); - done(); - }); - }); - - describe('isBuffer()', function() { - it('should return true when the contents are a Buffer', function(done) { - var val = new Buffer('test'); - item = new Item({contents: val}); - item.isBuffer().should.equal(true); - done(); - }); - - it('should return false when the contents are a Stream', function(done) { - var val = new Stream(); - var item = new Item({contents: val}); - item.isBuffer().should.equal(false); - done(); - }); - - it('should return false when the contents are a null', function(done) { - var item = new Item({contents: null}); - item.isBuffer().should.equal(false); - done(); - }); - }); - - describe('isStream()', function() { - it('should return false when the contents are a Buffer', function(done) { - var val = new Buffer('test'); - var item = new Item({contents: val}); - item.isStream().should.equal(false); - done(); - }); - - it('should return true when the contents are a Stream', function(done) { - var val = new Stream(); - var item = new Item({contents: val}); - item.isStream().should.equal(true); - done(); - }); - - it('should return false when the contents are a null', function(done) { - var item = new Item({contents: null}); - item.isStream().should.equal(false); - done(); - }); - }); - - describe('isNull()', function() { - it('should return false when the contents are a Buffer', function(done) { - var val = new Buffer('test'); - var item = new Item({contents: val}); - item.isNull().should.equal(false); - done(); - }); - - it('should return false when the contents are a Stream', function(done) { - var val = new Stream(); - var item = new Item({contents: val}); - item.isNull().should.equal(false); - done(); - }); - - it('should return true when the contents are a null', function(done) { - var item = new Item({contents: null}); - item.isNull().should.equal(true); - done(); - }); - }); - - describe('isDirectory()', function() { - var fakeStat = { - isDirectory: function() { - return true; - } - }; - - it('should return false when the contents are a Buffer', function(done) { - var val = new Buffer('test'); - var item = new Item({contents: val, stat: fakeStat}); - item.isDirectory().should.equal(false); - done(); - }); - - it('should return false when the contents are a Stream', function(done) { - var val = new Stream(); - var item = new Item({contents: val, stat: fakeStat}); - item.isDirectory().should.equal(false); - done(); - }); - - it('should return true when the contents are a null', function(done) { - var item = new Item({contents: null, stat: fakeStat}); - item.isDirectory().should.equal(true); - done(); - }); - }); - - describe('clone()', function() { - it('should copy all attributes over with Buffer', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Buffer('test') - }; - var item = new Item(options); - var item2 = item.clone(); - - item2.should.not.equal(item, 'refs should be different'); - item2.cwd.should.equal(item.cwd); - item2.base.should.equal(item.base); - item2.path.should.equal(item.path); - item2.contents.should.not.equal(item.contents, 'buffer ref should be different'); - item2.contents.toString('utf8').should.equal(item.contents.toString('utf8')); - done(); - }); - - it('should copy buffer\'s reference with option contents: false', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.js', - contents: new Buffer('test') - }; - - var item = new Item(options); - - var copy1 = item.clone({ contents: false }); - copy1.contents.should.equal(item.contents); - - var copy2 = item.clone({}); - copy2.contents.should.not.equal(item.contents); - - var copy3 = item.clone({ contents: 'any string' }); - copy3.contents.should.not.equal(item.contents); - - done(); - }); - - it('should copy all attributes over with Stream', function(done) { - var contents = new Stream.PassThrough(); - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: contents - }; - var item = new Item(options); - var item2 = item.clone(); - - contents.write(new Buffer('wa')); - - process.nextTick(function() { - contents.write(new Buffer('dup')); - contents.end(); - }); - - item2.should.not.equal(item, 'refs should be different'); - item2.cwd.should.equal(item.cwd); - item2.base.should.equal(item.base); - item2.path.should.equal(item.path); - item2.contents.should.not.equal(item.contents, 'stream ref should not be the same'); - item.contents.pipe(es.wait(function(err, data) { - if (err) return done(err); - item2.contents.pipe(es.wait(function(err, data2) { - if (err) return done(err); - data2.should.not.equal(data, 'stream contents ref should not be the same'); - data2.should.eql(data, 'stream contents should be the same'); - })); - })); - done(); - }); - - it('should copy all attributes over with null', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - var item = new Item(options); - var item2 = item.clone(); - - item2.should.not.equal(item, 'refs should be different'); - item2.cwd.should.equal(item.cwd); - item2.base.should.equal(item.base); - item2.path.should.equal(item.path); - should.not.exist(item2.contents); - done(); - }); - - it('should properly clone the `stat` property', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.js', - contents: new Buffer('test'), - stat: fs.statSync(__filename) - }; - - var item = new Item(options); - var copy = item.clone(); - - assert(copy.stat.isFile()); - assert(!copy.stat.isDirectory()); - done(); - }); - - it('should properly clone the `history` property', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.js', - contents: new Buffer('test'), - stat: fs.statSync(__filename) - }; - - var item = new Item(options); - var copy = item.clone(); - - copy.history[0].should.equal(options.path); - copy.path = 'lol'; - item.path.should.not.equal(copy.path); - done(); - }); - - it('should copy custom properties', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - - var item = new Item(options); - item.custom = { a: 'custom property' }; - var item2 = item.clone(); - - item2.should.not.equal(item, 'refs should be different'); - item2.cwd.should.equal(item.cwd); - item2.base.should.equal(item.base); - item2.path.should.equal(item.path); - item2.custom.should.equal(item.custom); - item2.custom.a.should.equal(item.custom.a); - - done(); - }); - - it('should copy history', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - - var item = new Item(options); - item.path = '/test/test.js'; - item.path = '/test/test-938di2s.js'; - var item2 = item.clone(); - - item2.history.should.eql([ - '/test/test.coffee', - '/test/test.js', - '/test/test-938di2s.js' - ]); - item2.history.should.not.equal([ - '/test/test.coffee', - '/test/test.js', - '/test/test-938di2s.js' - ]); - item2.path.should.eql('/test/test-938di2s.js'); - - done(); - }); - - it('should copy all attributes deeply', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - - var item = new Item(options); - item.custom = { a: 'custom property' }; - - var item2 = item.clone(true); - item2.custom.should.eql(item.custom); - item2.custom.should.not.equal(item.custom); - item2.custom.a.should.equal(item.custom.a); - - var item3 = item.clone({ deep: true }); - item3.custom.should.eql(item.custom); - item3.custom.should.not.equal(item.custom); - item3.custom.a.should.equal(item.custom.a); - - var item4 = item.clone(false); - item4.custom.should.eql(item.custom); - item4.custom.should.equal(item.custom); - item4.custom.a.should.equal(item.custom.a); - - var item5 = item.clone({ deep: false }); - item5.custom.should.eql(item.custom); - item5.custom.should.equal(item.custom); - item5.custom.a.should.equal(item.custom.a); - - done(); - }); - }); - - describe('pipe()', function() { - it('should write to stream with Buffer', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Buffer('test') - }; - var item = new Item(options); - var stream = new Stream.PassThrough(); - stream.on('data', function(chunk) { - should.exist(chunk); - (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); - chunk.toString('utf8').should.equal(options.contents.toString('utf8')); - }); - stream.on('end', function() { - done(); - }); - var ret = item.pipe(stream); - ret.should.equal(stream, 'should return the stream'); - }); - - it('should pipe to stream with Stream', function(done) { - var testChunk = new Buffer('test'); - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Stream.PassThrough() - }; - var item = new Item(options); - var stream = new Stream.PassThrough(); - stream.on('data', function(chunk) { - should.exist(chunk); - (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); - chunk.toString('utf8').should.equal(testChunk.toString('utf8')); - done(); - }); - var ret = item.pipe(stream); - ret.should.equal(stream, 'should return the stream'); - - item.contents.write(testChunk); - }); - - it('should do nothing with null', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - var item = new Item(options); - var stream = new Stream.PassThrough(); - stream.on('data', function() { - throw new Error('should not write'); - }); - stream.on('end', function() { - done(); - }); - var ret = item.pipe(stream); - ret.should.equal(stream, 'should return the stream'); - }); - - it('should write to stream with Buffer', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Buffer('test') - }; - var item = new Item(options); - var stream = new Stream.PassThrough(); - stream.on('data', function(chunk) { - should.exist(chunk); - (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); - chunk.toString('utf8').should.equal(options.contents.toString('utf8')); - done(); - }); - stream.on('end', function() { - throw new Error('should not end'); - }); - var ret = item.pipe(stream, {end: false}); - ret.should.equal(stream, 'should return the stream'); - }); - - it('should pipe to stream with Stream', function(done) { - var testChunk = new Buffer('test'); - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Stream.PassThrough() - }; - var item = new Item(options); - var stream = new Stream.PassThrough(); - stream.on('data', function(chunk) { - should.exist(chunk); - (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); - chunk.toString('utf8').should.equal(testChunk.toString('utf8')); - done(); - }); - stream.on('end', function() { - throw new Error('should not end'); - }); - var ret = item.pipe(stream, {end: false}); - ret.should.equal(stream, 'should return the stream'); - - item.contents.write(testChunk); - }); - - it('should do nothing with null', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - var item = new Item(options); - var stream = new Stream.PassThrough(); - stream.on('data', function() { - throw new Error('should not write'); - }); - stream.on('end', function() { - throw new Error('should not end'); - }); - var ret = item.pipe(stream, {end: false}); - ret.should.equal(stream, 'should return the stream'); - process.nextTick(done); - }); - }); - - describe('inspect()', function() { - it('should return correct format when no contents and no path', function(done) { - var item = new Item(); - item.inspect().should.equal(''); - done(); - }); - - it('should return correct format when Buffer and no path', function(done) { - var val = new Buffer('test'); - var item = new Item({ - contents: val - }); - item.inspect().should.equal('>'); - done(); - }); - - it('should return correct format when Buffer and relative path', function(done) { - var val = new Buffer('test'); - var item = new Item({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: val - }); - item.inspect().should.equal('>'); - done(); - }); - - it('should return correct format when Buffer and only path and no base', function(done) { - var val = new Buffer('test'); - var item = new Item({ - cwd: '/', - path: '/test/test.coffee', - contents: val - }); - delete item.base; - item.inspect().should.equal('>'); - done(); - }); - - it('should return correct format when Stream and relative path', function(done) { - var item = new Item({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Stream.PassThrough() - }); - item.inspect().should.equal('>'); - done(); - }); - - it('should return correct format when null and relative path', function(done) { - var item = new Item({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }); - item.inspect().should.equal(''); - done(); - }); - }); - - describe('contents get/set', function() { - it('should work with Buffer', function(done) { - var val = new Buffer('test'); - var item = new Item(); - item.contents = val; - item.contents.should.equal(val); - done(); - }); - - it('should work with Stream', function(done) { - var val = new Stream.PassThrough(); - var item = new Item(); - item.contents = val; - item.contents.should.equal(val); - done(); - }); - - it('should work with null', function(done) { - var val = null; - var item = new Item(); - item.contents = val; - (item.contents === null).should.equal(true); - done(); - }); - - it('should work with string', function(done) { - var val = 'test'; - var item = new Item(); - item.contents = val; - item.contents.should.deepEqual(new Buffer(val)); - done(); - }); - }); - - describe('relative get/set', function() { - it('should error on set', function(done) { - var item = new Item(); - try { - item.relative = 'test'; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should error on get when no base', function(done) { - var item = new Item(); - delete item.base; - try { - item.relative; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should error on get when no path', function(done) { - var item = new Item(); - try { - item.relative; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should return a relative path from base', function(done) { - var item = new Item({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - item.relative.should.equal('test.coffee'); - done(); - }); - - it('should return a relative path from cwd', function(done) { - var item = new Item({ - cwd: '/', - path: '/test/test.coffee' - }); - item.relative.should.equal(path.join('test', 'test.coffee')); - done(); - }); - }); - - describe('dirname get/set', function() { - it('should error on get when no path', function(done) { - var item = new Item(); - try { - item.dirname; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should return the dirname of the path', function(done) { - var item = new Item({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - item.dirname.should.equal('/test'); - done(); - }); - - it('should error on set when no path', function(done) { - var item = new Item(); - try { - item.dirname = '/test'; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should set the dirname of the path', function(done) { - var item = new Item({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - item.dirname = '/test/foo'; - item.path.should.equal('/test/foo/test.coffee'); - done(); - }); - }); - - describe('basename get/set', function() { - it('should error on get when no path', function(done) { - item = new Item(); - try { - item.basename; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should return the basename of the path', function(done) { - item = new Item({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - item.basename.should.equal('test.coffee'); - done(); - }); - - it('should error on set when no path', function(done) { - item = new Item(); - try { - item.basename = 'test.coffee'; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should set the basename of the path', function(done) { - item = new Item({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - item.basename = 'foo.png'; - item.path.should.equal('/test/foo.png'); - done(); - }); - }); - - describe('extname get/set', function() { - it('should error on get when no path', function(done) { - item = new Item(); - try { - item.extname; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should return the extname of the path', function(done) { - item = new Item({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - item.extname.should.equal('.coffee'); - done(); - }); - - it('should error on set when no path', function(done) { - item = new Item(); - try { - item.extname = '.coffee'; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should set the extname of the path', function(done) { - item = new Item({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - item.extname = '.png'; - item.path.should.equal('/test/test.png'); - done(); - }); - }); - - describe('path get/set', function() { - - it('should record history when instantiation', function() { - var item = new Item({ - cwd: '/', - path: '/test/test.coffee' - }); - - item.path.should.eql('/test/test.coffee'); - item.history.should.eql(['/test/test.coffee']); - }); - - it('should record history when path change', function() { - var item = new Item({ - cwd: '/', - path: '/test/test.coffee' - }); - - item.path = '/test/test.js'; - item.path.should.eql('/test/test.js'); - item.history.should.eql(['/test/test.coffee', '/test/test.js']); - - item.path = '/test/test.coffee'; - item.path.should.eql('/test/test.coffee'); - item.history.should.eql(['/test/test.coffee', '/test/test.js', '/test/test.coffee']); - }); - - it('should not record history when set the same path', function() { - var item = new Item({ - cwd: '/', - path: '/test/test.coffee' - }); - - item.path = '/test/test.coffee'; - item.path = '/test/test.coffee'; - item.path.should.eql('/test/test.coffee'); - item.history.should.eql(['/test/test.coffee']); - - // ignore when set empty string - item.path = ''; - item.path.should.eql('/test/test.coffee'); - item.history.should.eql(['/test/test.coffee']); - }); - - it('should throw when set path null in constructor', function() { - (function() { - Item({ - cwd: '/', - path: null - }); - }).should.throw('path should be string'); - }); - - it('should throw when set path null', function() { - item = new Item({ - cwd: '/', - path: 'foo' - }); - - (function() { - item.path = null; - }).should.throw('path should be string'); - }); - }); -}); diff --git a/test2/layouts.js b/test2/layouts.js deleted file mode 100644 index 76f65bc..0000000 --- a/test2/layouts.js +++ /dev/null @@ -1,127 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('layouts', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('layout', { viewType: 'layout' }); - app.create('page'); - }); - - it('should apply a layout to a view:', function(done) { - app.layout('base', {path: 'base.tmpl', content: 'a {% body %} c'}); - app.pages('a.tmpl', {path: 'a.tmpl', content: 'b', layout: 'base'}); - var page = app.pages.getView('a.tmpl'); - - app.render(page, function(err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'a b c'); - done(); - }); - }); - - it('should not apply a layout when `layoutApplied` is set:', function(done) { - app.layout('base', {path: 'base.tmpl', content: 'a {% body %} c'}); - app.pages('a.tmpl', {path: 'a.tmpl', content: 'b', layout: 'base'}); - var page = app.pages.getView('a.tmpl'); - page.option('layoutApplied', true); - - app.render(page, function(err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'b'); - done(); - }); - }); - - it('should not apply a layout to itself:', function(done) { - app.layout('base', {path: 'base.tmpl', content: 'a {% body %} c', layout: 'base'}); - app.pages('a.tmpl', {path: 'a.tmpl', content: 'b', layout: 'base'}); - var page = app.pages.getView('a.tmpl'); - - app.render(page, function(err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'a b c'); - done(); - }); - }); - - it('should apply nested layouts to a view:', function(done) { - app.layout('a', {path: 'a.tmpl', content: 'a {% body %} a', layout: 'b'}); - app.layout('b', {path: 'b.tmpl', content: 'b {% body %} b', layout: 'c'}); - app.layout('c', {path: 'c.tmpl', content: 'c {% body %} c', layout: 'base'}); - app.layout('base', {path: 'base.tmpl', content: 'outter {% body %} outter'}); - - app.pages('z.tmpl', {path: 'a.tmpl', content: 'inner', layout: 'a'}); - var page = app.pages.getView('z.tmpl'); - - app.render(page, function(err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'outter c b a inner a b c outter'); - done(); - }); - }); - - it('should track layout stack history on `layoutStack`:', function(done) { - app.layout('a', {path: 'a.tmpl', content: 'a {% body %} a', layout: 'b'}); - app.layout('b', {path: 'b.tmpl', content: 'b {% body %} b', layout: 'c'}); - app.layout('c', {path: 'c.tmpl', content: 'c {% body %} c', layout: 'base'}); - app.layout('base', {path: 'base.tmpl', content: 'outter {% body %} outter'}); - - app.pages('z.tmpl', {path: 'a.tmpl', content: 'inner', layout: 'a'}); - var page = app.pages.getView('z.tmpl'); - - app.render(page, function(err, view) { - if (err) return done(err); - assert(view.layoutStack.length === 4); - assert(typeof view.layoutStack[0] === 'object'); - assert(typeof view.layoutStack[0].depth === 'number'); - done(); - }); - }); - - it('should track layout stack history on `layoutStack`:', function(done) { - app.layout('a', {path: 'a.tmpl', content: 'a {% body %} a', layout: 'b'}); - app.layout('b', {path: 'b.tmpl', content: 'b {% body %} b', layout: 'c'}); - app.layout('c', {path: 'c.tmpl', content: 'c {% body %} c', layout: 'base'}); - app.layout('base', {path: 'base.tmpl', content: 'outter {% body %} outter'}); - - app.pages('z.tmpl', {path: 'a.tmpl', content: 'inner', layout: 'a'}); - var page = app.pages.getView('z.tmpl'); - - app.render(page, function(err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'outter c b a inner a b c outter'); - done(); - }); - }); - - it('should get layouts from `layout` viewTypes:', function(done) { - app.create('section', { viewType: 'layout' }); - app.create('block', { viewType: 'layout' }); - - app.section('a', {path: 'a.tmpl', content: 'a {% body %} a', layout: 'b'}); - app.block('b', {path: 'b.tmpl', content: 'b {% body %} b', layout: 'c'}); - app.section('c', {path: 'c.tmpl', content: 'c {% body %} c', layout: 'base'}); - app.block('base', {path: 'base.tmpl', content: 'outter {% body %} outter'}); - - app.pages('z.tmpl', {path: 'a.tmpl', content: 'inner', layout: 'a'}); - var page = app.pages.getView('z.tmpl'); - - app.render(page, function(err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'outter c b a inner a b c outter'); - done(); - }); - }); -}); diff --git a/test2/list.js b/test2/list.js deleted file mode 100644 index 8f9cf5d..0000000 --- a/test2/list.js +++ /dev/null @@ -1,689 +0,0 @@ -require('mocha'); -require('should'); -var path = require('path'); -var get = require('get-value'); -var assert = require('assert'); -var typeOf = require('kind-of'); -var support = require('./support/'); -var isBuffer = require('is-buffer'); -assert.containEql = support.containEql; -var App = support.resolve(); -var List = App.List; -var Views = App.Views; -var list, views; - -describe('list', function() { - describe('constructor', function() { - it('should create an instance of List', function() { - var list = new List(); - assert(list instanceof List); - }); - - it('should instaniate without `new`', function() { - var list = List(); - assert(list instanceof List); - }); - }); - - describe('static methods', function() { - it('should expose `extend`', function() { - assert(typeof List.extend === 'function'); - }); - }); - - describe('prototype methods', function() { - beforeEach(function() { - list = new List(); - }); - - var methods = [ - 'use', - 'setItem', - 'addItem', - 'addItems', - 'addList', - 'getItem', - 'constructor', - 'set', - 'get', - 'del', - 'define', - 'visit', - 'on', - 'once', - 'off', - 'emit', - 'listeners', - 'hasListeners' - ]; - - methods.forEach(function(method) { - it('should expose the ' + method + ' method', function() { - assert(typeof list[method] === 'function'); - }); - }); - - it('should expose the isList property', function() { - assert(typeof list.isList === 'boolean'); - }); - - it('should expose the keys property', function() { - assert(Array.isArray(list.keys)); - }); - - it('should expose the queue property', function() { - assert(Array.isArray(list.queue)); - }); - - it('should expose the items property', function() { - assert(Array.isArray(list.items)); - }); - - it('should expose the options property', function() { - assert(typeOf(list.options) === 'object'); - }); - }); - - describe('instance', function() { - beforeEach(function() { - list = new List(); - }); - - it('should set a value on the instance', function() { - list.set('a', 'b'); - assert(list.a === 'b'); - }); - - it('should get a value from the instance', function() { - list.set('a', 'b'); - assert(list.get('a') === 'b'); - }); - }); - - describe('use', function() { - beforeEach(function() { - list = new List(); - }); - - it('should expose the instance to plugins', function() { - list - .use(function(inst) { - inst.foo = 'bar'; - }); - - assert(list.foo === 'bar'); - }); - - it('should expose `item` when the plugin returns a function', function() { - list - .use(function() { - return function(item) { - item.foo = 'bar'; - }; - }); - - list.addItem('aaa'); - list.addItem('bbb'); - list.addItem('ccc'); - - assert(list.items[0].foo === 'bar'); - assert(list.items[1].foo === 'bar'); - assert(list.items[2].foo === 'bar'); - }); - }); - - describe('addItem', function() { - beforeEach(function() { - list = new List(); - }); - - it('should add items to a list', function() { - list.addItem('a', {content: '...'}); - list.addItem('b', {content: '...'}); - list.addItem('c', {content: '...'}); - assert(list.items.length === 3); - }); - }); - - describe('removeItem', function() { - beforeEach(function() { - list = new List(); - }); - - it('should remove an item from `items`', function() { - list.addItem('a', {content: '...'}); - list.addItem('b', {content: '...'}); - list.addItem('c', {content: '...'}); - assert(list.items.length === 3); - var a = list.getItem('a'); - list.removeItem(a); - assert(list.items.length === 2); - var c = list.getItem(c); - list.removeItem(c); - assert(list.items[0].key === 'b'); - }); - - it('should remove an item from `items` by key', function() { - list.addItem('a', {content: '...'}); - list.addItem('b', {content: '...'}); - list.addItem('c', {content: '...'}); - assert(list.items.length === 3); - list.removeItem('c'); - assert(list.items.length === 2); - list.removeItem('b'); - assert(list.items[0].key === 'a'); - }); - }); - - describe('addItems', function() { - beforeEach(function() { - list = new List(); - }); - - it('should add an object with multiple items', function() { - list.addItems({ - one: {content: 'foo'}, - two: {content: 'bar'} - }); - assert(isBuffer(list.items[0].contents)); - assert(isBuffer(list.items[1].contents)); - }); - - it('should signal `loaded` when finished (addItems)', function() { - list.on('addItems', function(items) { - for (var key in items) { - if (key === 'c') { - list.loaded = true; - break; - } - list.addItem('foo/' + key, items[key]); - } - }); - - list.addItems({ - a: {path: 'a.txt'}, - b: {path: 'b.txt'}, - c: {path: 'c.txt'} - }); - - assert.equal(list.items.length, 2); - assert.equal(list.items[0].key, 'foo/a'); - assert.equal(list.items[0].path, 'a.txt'); - }); - }); - - describe('addList', function() { - beforeEach(function() { - list = new List(); - }); - - it('should add an array with multiple items', function() { - list.addList([ - {path: 'one', content: 'foo'}, - {path: 'two', content: 'bar'} - ]); - assert(isBuffer(list.items[0].contents)); - assert(isBuffer(list.items[1].contents)); - }); - - it('should take a callback on `addList`', function() { - function addContents(item) { - item.contents = new Buffer(item.path.charAt(0)); - } - - list.addList([ - { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, - { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, - { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } } - ], addContents); - - assert(isBuffer(list.items[0].contents)); - assert(isBuffer(list.items[1].contents)); - assert(isBuffer(list.items[2].contents)); - }); - - it('should throw an error when the list is not an array', function() { - function addContents(item) { - item.contents = new Buffer(item.path.charAt(0)); - } - - (function() { - list.addList({ - 'a.md': {locals: { date: '2014-01-01', foo: 'zzz', bar: 1 }}, - 'f.md': {locals: { date: '2014-01-01', foo: 'mmm', bar: 2 }}, - 'd.md': {locals: { date: '2014-01-01', foo: 'xxx', bar: 3 }} - }, addContents); - - assert(isBuffer(list.items[0].contents)); - assert(isBuffer(list.items[1].contents)); - assert(isBuffer(list.items[2].contents)); - }).should.throw('expected list to be an array.'); - }); - - it('should signal `loaded` when finished (addList)', function() { - list.on('addList', function(items) { - var len = items.length; - var i = -1; - - while (++i < len) { - if (items[i].path === 'd.md') { - list.loaded = true; - break; - } - list.addItem('foo/' + items[i].path, items[i]); - } - }); - - list.addList([ - { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, - { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, - { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } } - ]); - - assert.equal(list.items.length, 2); - assert.equal(list.keys.indexOf('d.md'), -1); - }); - }); - - describe('queue', function() { - beforeEach(function() { - list = new List(); - }); - - it('should emit arguments on addItem', function(done) { - list.on('addItem', function(args) { - assert(args[0] === 'a'); - assert(args[1] === 'b'); - assert(args[2] === 'c'); - assert(args[3] === 'd'); - assert(args[4] === 'e'); - done(); - }); - - list.addItem('a', 'b', 'c', 'd', 'e'); - }); - - it('should expose the `queue` property for loading items', function() { - list.queue.push(list.item('b', {path: 'b'})); - - list.addItem('a', {path: 'a'}); - assert(list.items[0].key === 'a'); - assert(list.items[1].key === 'b'); - }); - - it('should load all items on the queue when addItem is called', function() { - list.on('addItem', function(args) { - var len = args.length; - var last = args[len - 1]; - if (typeof last === 'string') { - args[len - 1] = { content: last }; - } - }); - - list.addItem('a.html', 'aaa'); - list.addItem('b.html', 'bbb'); - list.addItem('c.html', 'ccc'); - - assert(list.items[0].path === 'a.html'); - assert(list.getItem('a.html').content === 'aaa'); - assert(list.items[1].path === 'b.html'); - assert(list.getItem('b.html').content === 'bbb'); - assert(list.items[2].path === 'c.html'); - assert(list.getItem('c.html').content === 'ccc'); - }); - }); - - describe('sortBy', function() { - var items = [ - { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, - { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, - { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, - { path: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, - { path: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, - { path: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, - { path: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, - { path: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, - { path: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, - { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, - { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, - { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, - { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } } - ]; - - it('should sort a list', function() { - list = new List(); - list.addList(items); - - var compare = function(prop) { - return function(a, b, fn) { - var valA = get(a, prop); - var valB = get(b, prop); - return fn(valA, valB); - }; - }; - - var res = list.sortBy('locals.date', 'doesnt.exist', [ - compare('locals.foo'), - compare('locals.bar') - ]); - - assert.containEql(res.items, [ - { key: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, - { key: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, - { key: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, - { key: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, - { key: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, - { key: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, - { key: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, - { key: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, - { key: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, - { key: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, - { key: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, - { key: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, - { key: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } } - ]); - }); - - it('should not sort the (original) instance list `items`', function() { - list = new List(); - list.addList(items); - - var compare = function(prop) { - return function(a, b, fn) { - var valA = get(a, prop); - var valB = get(b, prop); - return fn(valA, valB); - }; - }; - - var res = list.sortBy('locals.date', 'doesnt.exist', [ - compare('locals.foo'), - compare('locals.bar') - ]); - - // should not be sorted - assert.containEql(list.items, items); - - // should be sorted - assert.containEql(res.items, [ - { key: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, - { key: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, - { key: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, - { key: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, - { key: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, - { key: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, - { key: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, - { key: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, - { key: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, - { key: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, - { key: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, - { key: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, - { key: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } } - ]); - }); - - it('should pass options to array-sort from the constructor', function() { - list = new List({sort: {reverse: true}}); - list.addList(items); - - var compare = function(prop) { - return function(a, b, fn) { - var valA = get(a, prop); - var valB = get(b, prop); - return fn(valA, valB); - }; - }; - - var res = list.sortBy('locals.date', 'doesnt.exist', [ - compare('locals.foo'), - compare('locals.bar') - ]); - - assert.containEql(res.items, [ - { key: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, - { key: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, - { key: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, - { key: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, - { key: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, - { key: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, - { key: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, - { key: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, - { key: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, - { key: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, - { key: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, - { key: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, - { key: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } } - ]); - }); - - it('should pass options to array-sort from the sortBy method', function() { - list = new List(); - list.addList(items); - - var compare = function(prop) { - return function(a, b, fn) { - var valA = get(a, prop); - var valB = get(b, prop); - return fn(valA, valB); - }; - }; - - var res = list.sortBy('locals.date', 'doesnt.exist', [ - compare('locals.foo'), - compare('locals.bar') - ], {reverse: true}); - - assert.containEql(res.items, [ - { key: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, - { key: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, - { key: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, - { key: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, - { key: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, - { key: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, - { key: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, - { key: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, - { key: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, - { key: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, - { key: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, - { key: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, - { key: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } } - ]); - }); - }); - - describe('groupBy', function() { - var items = [ - { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, - { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, - { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, - { path: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, - { path: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, - { path: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, - { path: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, - { path: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, - { path: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, - { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, - { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, - { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, - { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } } - ]; - - it('should group a list by a property', function() { - list = new List(); - list.addList(items); - - var res = list.groupBy('locals.foo'); - var keys = ['zzz', 'mmm', 'xxx', 'aaa', 'ccc', 'rrr', 'ttt', 'yyy']; - assert.deepEqual(Object.keys(res), keys); - }); - }); - - describe('sort and group', function() { - var items = [ - { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, - { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, - { path: 'd.md', locals: { date: '2013-01-01', foo: 'xxx', bar: 3 } }, - { path: 'i.md', locals: { date: '2013-02-01', foo: 'xxx', bar: 5 } }, - { path: 'i.md', locals: { date: '2013-02-01', foo: 'lll', bar: 5 } }, - { path: 'k.md', locals: { date: '2013-03-01', foo: 'xxx', bar: 1 } }, - { path: 'j.md', locals: { date: '2013-02-01', foo: 'xxx', bar: 4 } }, - { path: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, - { path: 'm.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, - { path: 'n.md', locals: { date: '2013-01-01', foo: 'xxx', bar: 7 } }, - { path: 'o.md', locals: { date: '2013-01-01', foo: 'xxx', bar: 7 } }, - { path: 'p.md', locals: { date: '2013-01-01', foo: 'xxx', bar: 7 } }, - { path: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, - { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, - { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, - { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, - { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } } - ]; - - it('should group a list by a property', function() { - list = new List(items); - - var context = list - .sortBy('locals.date') - .groupBy(function(view) { - var date = view.locals.date; - view.locals.year = date.slice(0, 4); - view.locals.month = date.slice(5, 7); - view.locals.day = date.slice(8, 10); - return view.locals.year; - }, 'locals.month'); - - var keys = Object.keys(context); - assert(keys[0] === '2012'); - assert(keys[1] === '2013'); - assert(keys[2] === '2014'); - assert(keys[3] === '2015'); - }); - }); - - describe('paginate', function() { - var items = [ - { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, - { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, - { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, - { path: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, - { path: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, - { path: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, - { path: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, - { path: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, - { path: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, - { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, - { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, - { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, - { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } } - ]; - - it('should paginate a list', function() { - list = new List(items); - - var res = list.paginate(); - assert.equal(res.length, 2); - assert.containEql(res[0].items, items.slice(0, 10)); - assert.containEql(res[1].items, items.slice(10)); - }); - - it('should add pager properties', function() { - list = new List({pager: true}); - list.addList(items); - list.items.forEach(function(item, i) { - assert.equal(item.data.pager.index, i); - }); - }); - - it('should paginate a list with given options', function() { - list = new List(items); - var res = list.paginate({limit: 5}); - - assert.equal(res.length, 3); - assert.containEql(res[0].items, items.slice(0, 5)); - assert.containEql(res[1].items, items.slice(5, 10)); - assert.containEql(res[2].items, items.slice(10)); - }); - }); - - describe('Views instance', function() { - beforeEach(function() { - views = new Views(); - }); - - it('should add views from an instance of Views', function() { - views.addViews({ - one: {content: 'foo'}, - two: {content: 'bar'} - }); - - list = new List(views); - assert(isBuffer(list.items[0].contents)); - assert(isBuffer(list.items[1].contents)); - }); - }); - - describe('getIndex', function() { - beforeEach(function() { - list = new List(); - }); - it('should get the index of a key when key is not renamed', function() { - list.addItem('a/b/c/ddd.hbs', {content: 'ddd'}); - list.addItem('a/b/c/eee.hbs', {content: 'eee'}); - assert(list.getIndex('a/b/c/ddd.hbs') === 0); - assert(list.getIndex('a/b/c/eee.hbs') === 1); - }); - - it('should get the index of a key when key is renamed', function() { - list = new List({ - renameKey: function(key) { - return path.basename(key); - } - }); - list.addItem('a/b/c/ddd.hbs', {content: 'ddd'}); - list.addItem('a/b/c/eee.hbs', {content: 'eee'}); - assert(list.getIndex('a/b/c/ddd.hbs') === 0); - assert(list.getIndex('ddd.hbs') === 0); - assert(list.getIndex('a/b/c/eee.hbs') === 1); - assert(list.getIndex('eee.hbs') === 1); - }); - }); - - describe('getItem', function() { - beforeEach(function() { - list = new List(); - }); - - it('should get an view from `views`', function() { - list.addItem('one', {content: 'aaa'}); - list.addItem('two', {content: 'zzz'}); - assert(list.items.length === 2); - assert(isBuffer(list.items[0].contents)); - assert(isBuffer(list.getItem('one').contents)); - assert(list.getItem('one').contents.toString() === 'aaa'); - assert(list.getItem('two').contents.toString() === 'zzz'); - }); - }); - - describe('use', function() { - beforeEach(function() { - list = new List(); - }); - - it('should use middleware on a list', function() { - list.addItem('one', {content: 'aaa'}); - list.addItem('two', {content: 'zzz'}); - - list - .use(function() { - this.set('foo', 'bar'); - }) - .use(function() { - this.set('one', 'two'); - }); - - assert(list.one === 'two'); - assert(list.foo === 'bar'); - }); - }); -}); - diff --git a/test2/list.render.js b/test2/list.render.js deleted file mode 100644 index 7b226a8..0000000 --- a/test2/list.render.js +++ /dev/null @@ -1,137 +0,0 @@ -require('mocha'); -require('should'); -var async = require('async'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var List = App.List; -var pages; - -describe('render', function() { - describe('rendering', function() { - beforeEach(function() { - pages = new List(); - pages.engine('tmpl', require('engine-base')); - }); - - it('should throw an error when no callback is given:', function() { - (function() { - pages.render({}); - }).should.throw('List#render is async and expects a callback function'); - }); - - it('should throw an error when an engine is not defined:', function(done) { - pages.addItem('foo.bar', {content: '<%= name %>'}); - var page = pages.getItem('foo.bar'); - - pages.render(page, function(err) { - assert(err.message === 'List#render cannot find an engine for: .bar'); - done(); - }); - }); - - it('should use helpers to render a item:', function(done) { - var locals = {name: 'Halle'}; - - pages.helper('upper', function(str) { - return str.toUpperCase(str); - }); - - pages.addItem('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); - var page = pages.getItem('a.tmpl'); - - pages.render(page, function(err, res) { - if (err) return done(err); - - assert(res.content === 'a HALLE b'); - done(); - }); - }); - - it('should use helpers when rendering a item:', function(done) { - var locals = {name: 'Halle'}; - pages.helper('upper', function(str) { - return str.toUpperCase(str); - }); - - pages.addItem('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); - var page = pages.getItem('a.tmpl'); - - pages.render(page, function(err, res) { - if (err) return done(err); - assert(res.content === 'a HALLE b'); - done(); - }); - }); - - it('should render a template when contents is a buffer:', function(done) { - pages.addItem('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - var item = pages.getItem('a.tmpl'); - - pages.render(item, function(err, item) { - if (err) return done(err); - assert(item.contents.toString() === 'b'); - done(); - }); - }); - - it('should render a template when content is a string:', function(done) { - pages.addItem('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - var item = pages.getItem('a.tmpl'); - - pages.render(item, function(err, item) { - if (err) return done(err); - assert(item.contents.toString() === 'b'); - done(); - }); - }); - - it('should render a item from its path:', function(done) { - pages.addItem('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - - pages.render('a.tmpl', function(err, item) { - if (err) return done(err); - assert(item.content === 'b'); - done(); - }); - }); - - it('should use a plugin for rendering:', function(done) { - pages.engine('tmpl', require('engine-base')); - pages.option('engine', 'tmpl'); - - pages.addItems({ - 'a': {content: '<%= title %>', locals: {title: 'aaa'}}, - 'b': {content: '<%= title %>', locals: {title: 'bbb'}}, - 'c': {content: '<%= title %>', locals: {title: 'ccc'}}, - 'd': {content: '<%= title %>', locals: {title: 'ddd'}}, - 'e': {content: '<%= title %>', locals: {title: 'eee'}}, - 'f': {content: '<%= title %>', locals: {title: 'fff'}}, - 'g': {content: '<%= title %>', locals: {title: 'ggg'}}, - 'h': {content: '<%= title %>', locals: {title: 'hhh'}}, - 'i': {content: '<%= title %>', locals: {title: 'iii'}}, - 'j': {content: '<%= title %>', locals: {title: 'jjj'}} - }); - - pages.use(function(collection) { - collection.option('pager', false); - - collection.renderEach = function(cb) { - var list = new List(collection); - - async.map(list.items, function(item, next) { - collection.render(item, next); - }, cb); - }; - }); - - pages.renderEach(function(err, items) { - if (err) return done(err); - assert(items[0].content === 'aaa'); - assert(items[9].content === 'jjj'); - assert(items.length === 10); - done(); - }); - }); - }); -}); diff --git a/test2/list.use.js b/test2/list.use.js deleted file mode 100644 index 5ac2c1b..0000000 --- a/test2/list.use.js +++ /dev/null @@ -1,156 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var List = App.List; -var Item = App.Item; -var list; - -describe('list.use', function() { - beforeEach(function() { - list = new List(); - }); - - it('should expose the instance to `use`:', function(done) { - list.use(function(inst) { - assert(inst instanceof List); - done(); - }); - }); - - it('should be chainable:', function(done) { - list.use(function(inst) { - assert(inst instanceof List); - }) - .use(function(inst) { - assert(inst instanceof List); - }) - .use(function(inst) { - assert(inst instanceof List); - done(); - }); - }); - - it('should expose the list to a plugin:', function() { - list.use(function(items) { - assert(items instanceof List); - items.foo = items.addItem.bind(items); - }); - - list.foo('a', {content: '...'}); - assert(list.hasItem('a')); - }); - - it('should expose list when chained:', function() { - list - .use(function(items) { - assert(items instanceof List); - items.foo = items.addItem.bind(items); - }) - .use(function(items) { - assert(items instanceof List); - items.bar = items.addItem.bind(items); - }) - .use(function(items) { - assert(items instanceof List); - items.baz = items.addItem.bind(items); - }); - - var pages = list; - - pages.foo({path: 'a', content: '...'}); - pages.bar({path: 'b', content: '...'}); - pages.baz({path: 'c', content: '...'}); - - assert(list.hasItem('a')); - assert(list.hasItem('b')); - assert(list.hasItem('c')); - }); - - it('should work when a custom `Item` constructor is passed:', function() { - list = new List({Item: require('vinyl')}); - list - .use(function(items) { - assert(items instanceof List); - items.foo = items.addItem.bind(items); - }) - .use(function(items) { - assert(items instanceof List); - items.bar = items.addItem.bind(items); - }) - .use(function(items) { - assert(items instanceof List); - items.baz = items.addItem.bind(items); - }); - - var pages = list; - - pages.foo({path: 'a', content: '...'}); - pages.bar({path: 'b', content: '...'}); - pages.baz({path: 'c', content: '...'}); - - assert(list.hasItem('a')); - assert(list.hasItem('b')); - assert(list.hasItem('c')); - }); - - it('should pass to item `use` if a function is returned:', function() { - list.use(function(items) { - assert(items instanceof List); - - return function(item) { - item.foo = items.addItem.bind(items); - assert(item.isItem || item.isView); - }; - }); - - list.addItem('a', {content: '...'}) - .foo({path: 'b', content: '...'}) - .foo({path: 'c', content: '...'}) - .foo({path: 'd', content: '...'}); - - assert(list.hasItem('a')); - assert(list.hasItem('b')); - assert(list.hasItem('c')); - assert(list.hasItem('d')); - }); - - it('should be chainable when a item function is returned:', function() { - list - .use(function(items) { - assert(items instanceof List); - - return function(item) { - item.foo = items.addItem.bind(items); - assert(item instanceof Item); - }; - }) - .use(function(items) { - assert(items instanceof List); - - return function(item) { - item.bar = items.addItem.bind(items); - assert(item instanceof Item); - }; - }) - .use(function(items) { - assert(items instanceof List); - - return function(item) { - item.baz = items.addItem.bind(items); - assert(item instanceof Item); - }; - }); - - list.addItem('a', {content: '...'}) - .foo({path: 'b', content: '...'}) - .bar({path: 'c', content: '...'}) - .baz({path: 'd', content: '...'}); - - assert(list.hasItem('a')); - assert(list.hasItem('b')); - assert(list.hasItem('c')); - assert(list.hasItem('d')); - }); -}); diff --git a/test2/mergePartials.js b/test2/mergePartials.js deleted file mode 100644 index 38f23be..0000000 --- a/test2/mergePartials.js +++ /dev/null @@ -1,104 +0,0 @@ -require('should'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('mergePartials', function() { - beforeEach(function() { - app = new App(); - }); - - it('should merge multiple partials collections onto one collection:', function() { - var opts = { viewType: 'partial' }; - app.create('foo', opts); - app.create('bar', opts); - app.create('baz', opts); - - app.foo('a', {path: 'a', content: 'aaa'}); - app.bar('b', {path: 'b', content: 'bbb'}); - app.baz('c', {path: 'c', content: 'ccc'}); - - var actual = app.mergePartials(); - actual.should.have.property('partials'); - actual.partials.should.have.properties(['a', 'b', 'c']); - }); - - it('should keep partials collections on separaet collections:', function() { - var opts = { viewType: 'partial' }; - app.create('foo', opts); - app.create('bar', opts); - app.create('baz', opts); - - app.foo('a', {path: 'a', content: 'aaa'}); - app.bar('b', {path: 'b', content: 'bbb'}); - app.baz('c', {path: 'c', content: 'ccc'}); - - var actual = app.mergePartials({mergePartials: false}); - actual.should.not.have.property('partials'); - actual.should.eql({ foos: { a: 'aaa' }, bars: { b: 'bbb' }, bazs: { c: 'ccc' } }); - }); - - it('should emit `mergePartials`:', function() { - var opts = { viewType: 'partial' }; - app.create('foo', opts); - app.create('bar', opts); - app.create('baz', opts); - var arr = []; - - app.on('onMerge', function(view) { - arr.push(view.content); - }); - - app.foo('a', {path: 'a', content: 'aaa'}); - app.bar('b', {path: 'b', content: 'bbb'}); - app.baz('c', {path: 'c', content: 'ccc'}); - - var actual = app.mergePartials({mergePartials: false}); - actual.should.not.have.property('partials'); - actual.should.eql({ foos: { a: 'aaa' }, bars: { b: 'bbb' }, bazs: { c: 'ccc' } }); - arr.should.eql(['aaa', 'bbb', 'ccc']); - }); - - it('should handle `onMerge` middleware:', function() { - var opts = { viewType: 'partial' }; - app.create('foo', opts); - app.create('bar', opts); - app.create('baz', opts); - - app.onMerge(/./, function(view, next) { - view.content += ' onMerge'; - next(); - }); - - app.foo('a', {path: 'a', content: 'aaa'}); - app.bar('b', {path: 'b', content: 'bbb'}); - app.baz('c', {path: 'c', content: 'ccc'}); - - var actual = app.mergePartials({mergePartials: false}); - actual.should.eql({ - foos: {a: 'aaa onMerge'}, - bars: {b: 'bbb onMerge'}, - bazs: {c: 'ccc onMerge'} - }); - }); - - it('should skip views with `nomerge=true`:', function() { - var opts = { viewType: 'partial' }; - - app.create('foo', opts); - app.create('bar', opts); - app.create('baz', opts); - - app.onMerge(/[ab]/, function(view, next) { - view.options.nomerge = true; - next(); - }); - - app.foo('a', {path: 'a', content: 'aaa'}); - app.bar('b', {path: 'b', content: 'bbb'}); - app.baz('c', {path: 'c', content: 'ccc'}); - - var actual = app.mergePartials({mergePartials: false}); - actual.should.eql({ bazs: { c: 'ccc' } }); - }); -}); diff --git a/test2/partials.js b/test2/partials.js deleted file mode 100644 index b5fbb94..0000000 --- a/test2/partials.js +++ /dev/null @@ -1,202 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app, pages; - -describe('partials', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.engine('hbs', require('engine-handlebars')); - - app.create('partials', { viewType: 'partial' }); - app.create('include', { viewType: 'partial' }); - app.create('layouts', { viewType: 'layout' }); - pages = app.create('page'); - }); - - it('should inject a partial with a helper:', function(done) { - app.include('base', {path: 'base.tmpl', content: 'xyz'}); - app.pages('a.tmpl', {path: 'a.tmpl', content: 'a <%= include("base") %> c'}); - var page = app.pages.getView('a.tmpl'); - - app.render(page, function(err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'a xyz c'); - done(); - }); - }); - - it('should inject a partial with a helper on a collection:', function(done) { - app.include('base', {path: 'base.tmpl', content: 'xyz'}); - pages.engine('.tmpl', require('engine-handlebars')); - pages.helpers(app._.helpers.sync); - pages.asyncHelpers(app._.helpers.async); - pages.addView('a.tmpl', {path: 'a.tmpl', content: 'a {{include "base" }} c'}); - var page = pages.getView('a.tmpl'); - - pages.render(page, function(err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'a xyz c'); - done(); - }); - }); - - it('should use handlebars partial with a helper on a collection:', function(done) { - app.include('base', {path: 'base.tmpl', content: 'xyz'}); - pages.engine('.tmpl', require('engine-handlebars')); - pages.helpers(app._.helpers.sync); - pages.asyncHelpers(app._.helpers.async); - pages.addView('a.tmpl', {path: 'a.tmpl', content: 'a {{> base }} c'}); - - var page = pages.getView('a.tmpl'); - var locals = app.mergePartials(this.options); - - pages.render(page, locals, function(err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'a xyz c'); - done(); - }); - }); - - it('should use layouts with partials:', function(done) { - app.layout('default', {path: 'a.tmpl', content: 'a {% body %} c'}); - app.include('b', {path: 'b.tmpl', content: 'b', layout: 'default'}); - app.pages('a.tmpl', {path: 'c.tmpl', content: '<%= include("b") %>'}); - var page = app.pages.getView('a.tmpl'); - - app.render(page, function(err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'a b c'); - done(); - }); - }); - - it('should add `layoutApplied` after layout is applied:', function(done) { - app.layout('default', {path: 'a.tmpl', content: 'a {% body %} c'}); - app.include('b', {path: 'b.tmpl', content: 'b', layout: 'default'}); - app.pages('a.tmpl', {path: 'c.tmpl', content: '<%= include("b") %>'}); - var page = app.pages.getView('a.tmpl'); - - app.render(page, function(err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(app.layouts.getView('default').options.layoutApplied); - assert.equal(view.content, 'a b c'); - done(); - }); - }); - - it('should pass partials to handlebars:', function(done) { - app.onMerge(/\.hbs$/, function(view, next) { - app.applyLayout(view); - next(); - }); - - app.layout('default', {path: 'a.hbs', content: 'a {% body %} c'}); - app.include('foo', {path: 'foo.hbs', content: 'foo', layout: 'default'}); - app.pages('a.hbs', {path: 'c.hbs', content: '{{> foo }}'}); - var page = app.pages.getView('a.hbs'); - - app.render(page, function(err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'a foo c'); - done(); - }); - }); - - it('should only merge in the specified viewTypes:', function(done) { - app.onMerge(/\.hbs$/, function(view, next) { - app.applyLayout(view); - next(); - }); - - app.layout('default', {path: 'a.hbs', content: 'a {% body %} c'}); - app.option('mergeTypes', ['includes']); - - app.partial('foo', {path: 'bar.hbs', content: 'bar', layout: 'default'}); - app.include('foo', {path: 'foo.hbs', content: 'foo', layout: 'default'}); - - app.pages('a.hbs', {path: 'c.hbs', content: '{{> foo }}'}); - app.pages.getView('a.hbs') - .render(function(err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'a foo c'); - done(); - }); - - }); - - it('should merge the specified viewTypes in the order defined:', function(done) { - app.onMerge(/\.hbs$/, function(view, next) { - app.applyLayout(view); - next(); - }); - - app.layout('default', {path: 'a.hbs', content: 'a {% body %} c'}); - app.option('mergeTypes', ['includes', 'partials']); - - app.partial('foo', {path: 'bar.hbs', content: 'bar', layout: 'default'}); - app.include('foo', {path: 'foo.hbs', content: 'foo', layout: 'default'}); - - app.pages('a.hbs', {path: 'c.hbs', content: '{{> foo }}'}); - app.pages.getView('a.hbs') - .render(function(err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'a bar c'); - done(); - }); - }); - - it('should not merge in partials with `options.nomerge` defined:', function(done) { - app.onMerge(/\.hbs$/, function(view, next) { - app.applyLayout(view); - next(); - }); - - app.layout('default', {path: 'a.hbs', content: 'a {% body %} c'}); - app.option('mergeTypes', ['includes', 'partials']); - - app.partial('foo', {path: 'bar.hbs', content: 'bar', layout: 'default', options: {nomerge: true}}); - app.include('foo', {path: 'foo.hbs', content: 'foo', layout: 'default'}); - - app.pages('a.hbs', {path: 'c.hbs', content: '{{> foo }}'}); - app.pages.getView('a.hbs') - .render(function(err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'a foo c'); - done(); - }); - }); - - it('should emit an `onMerge` event:', function(done) { - app.on('onMerge', function(view) { - app.applyLayout(view); - }); - - app.layout('default', {path: 'a.hbs', content: 'a {% body %} c'}); - app.option('mergeTypes', ['includes', 'partials']); - - app.partial('foo', {path: 'bar.hbs', content: 'bar', layout: 'default'}); - app.include('foo', {path: 'foo.hbs', content: 'foo', layout: 'default'}); - - app.pages('a.hbs', {path: 'c.hbs', content: '{{> foo }}'}); - app.pages.getView('a.hbs') - .render(function(err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'a bar c'); - done(); - }); - }); -}); diff --git a/test2/questions.js b/test2/questions.js deleted file mode 100644 index 4318e64..0000000 --- a/test2/questions.js +++ /dev/null @@ -1,58 +0,0 @@ -require('mocha'); -require('should'); -var fs = require('fs'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe.skip('content', function() { - beforeEach(function() { - app = new App(); - }); - - it('should store a question:', function() { - app.question('a', 'b'); - assert(app.questions); - assert(app.questions.cache); - assert(app.questions.cache.a); - assert(app.questions.cache.a.name === 'a'); - assert(app.questions.cache.a.message === 'b'); - }); - - it('should ask a question and use data value to answer:', function(done) { - app.question('a', 'b'); - app.data('a', 'b'); - - app.ask('a', function(err, answer) { - assert(!err); - assert(answer); - assert(answer === 'b'); - done(); - }) - }); - - it('should ask a question and use store value to answer:', function(done) { - app.question('a', 'b'); - app.store.set('a', 'c'); - - app.ask('a', function(err, answer) { - assert(!err); - assert(answer); - assert(answer === 'c'); - done(); - }) - }); - - it('should ask a question and use config value to answer:', function(done) { - app.question('a', 'b'); - app.store.set('a', 'c'); - - app.ask('a', function(err, answer) { - assert(!err); - assert(answer); - assert(answer === 'c'); - done(); - }) - }); -}); diff --git a/test2/renameKey.js b/test2/renameKey.js deleted file mode 100644 index 94b3ab2..0000000 --- a/test2/renameKey.js +++ /dev/null @@ -1,350 +0,0 @@ -var path = require('path'); -var support = require('./support'); -var App = support.resolve(); -var app; - -function renameKey(key) { - return path.basename(key, path.extname(key)); -} - -describe('renameKey', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('pages'); - app.create('posts'); - }); - - describe('global options:', function() { - it('should use `renameKey` function defined on global opts:', function() { - app.option('renameKey', renameKey); - - app.posts('a/b/c/a.txt', {content: '...'}); - app.posts('a/b/c/b.txt', {content: '...'}); - app.posts('a/b/c/c.txt', {content: '...'}); - app.post('a/b/c/d.txt', {content: '...'}); - app.post('a/b/c/e.txt', {content: '...'}); - - app.views.posts.should.have.property('a'); - app.views.posts.should.have.property('b'); - app.views.posts.should.have.property('c'); - app.views.posts.should.have.property('d'); - app.views.posts.should.have.property('e'); - }); - - it('should not have conflicts when view name is the collection name:', function() { - app.option('renameKey', renameKey); - - app.post('a/b/c/post.txt', {content: 'this is contents'}); - app.page('a/b/c/page.txt', {content: 'this is contents'}); - - app.views.posts.should.have.property('post'); - app.views.pages.should.have.property('page'); - }); - }); - - describe('create method:', function() { - it('should use `renameKey` option chained from the `create` method:', function() { - app.create('post') - .option('renameKey', function(key) { - return 'posts/' + path.basename(key); - }); - - app.posts('a/b/c/a.txt', {content: '...'}); - app.posts('a/b/c/b.txt', {content: '...'}); - app.posts('a/b/c/c.txt', {content: '...'}); - app.post('a/b/c/d.txt', {content: '...'}); - app.post('a/b/c/e.txt', {content: '...'}); - - app.views.posts.should.have.property('posts/a.txt'); - app.views.posts.should.have.property('posts/b.txt'); - app.views.posts.should.have.property('posts/c.txt'); - app.views.posts.should.have.property('posts/d.txt'); - app.views.posts.should.have.property('posts/e.txt'); - }); - }); - - describe('create method:', function() { - it('should use `renameKey` defined on the `create` method:', function() { - app.create('post', { - renameKey: function(key) { - return 'posts/' + path.basename(key); - } - }); - - app.posts('a/b/c/a.txt', {content: '...'}); - app.posts('a/b/c/b.txt', {content: '...'}); - app.posts('a/b/c/c.txt', {content: '...'}); - app.post('a/b/c/d.txt', {content: '...'}); - app.post('a/b/c/e.txt', {content: '...'}); - - app.views.posts.should.have.property('posts/a.txt'); - app.views.posts.should.have.property('posts/b.txt'); - app.views.posts.should.have.property('posts/c.txt'); - app.views.posts.should.have.property('posts/d.txt'); - app.views.posts.should.have.property('posts/e.txt'); - }); - }); - - describe('collections:', function() { - describe('setting:', function() { - it('should get a view with the `renameKey` defined on app.options:', function() { - app.option('renameKey', function(key) { - return 'foo/' + path.basename(key); - }); - - app.posts('a/b/c/a.txt', {content: '...'}); - app.posts('a/b/c/b.txt', {content: '...'}); - app.post('a/b/c/c.txt', {content: '...'}); - - app.views.posts.should.have.property('foo/a.txt'); - app.views.posts.should.have.property('foo/b.txt'); - app.views.posts.should.have.property('foo/c.txt'); - }); - - it('should use `renameKey` defined on collection.options:', function() { - app.pages.option('renameKey', function(key) { - return 'page/' + path.basename(key); - }); - - app.posts.option('renameKey', function(key) { - return 'post/' + path.basename(key); - }); - - app.pages('a/b/c/a.txt', {content: '...'}); - app.pages('a/b/c/b.txt', {content: '...'}); - app.pages('a/b/c/c.txt', {content: '...'}); - app.page('a/b/c/d.txt', {content: '...'}); - app.page('a/b/c/e.txt', {content: '...'}); - - app.posts('a/b/c/a.txt', {content: '...'}); - app.posts('a/b/c/b.txt', {content: '...'}); - app.posts('a/b/c/c.txt', {content: '...'}); - app.post('a/b/c/d.txt', {content: '...'}); - app.post('a/b/c/e.txt', {content: '...'}); - - app.views.pages.should.have.property('page/a.txt'); - app.views.pages.should.have.property('page/b.txt'); - app.views.pages.should.have.property('page/c.txt'); - app.views.pages.should.have.property('page/d.txt'); - app.views.pages.should.have.property('page/e.txt'); - - app.views.posts.should.have.property('post/a.txt'); - app.views.posts.should.have.property('post/b.txt'); - app.views.posts.should.have.property('post/c.txt'); - app.views.posts.should.have.property('post/d.txt'); - app.views.posts.should.have.property('post/e.txt'); - }); - - it('should use the `collection.renameKey()` method:', function() { - app.pages.renameKey(function(key) { - return 'baz/' + path.basename(key); - }); - - app.pages('a/b/c/a.txt', {content: '...'}); - app.pages('a/b/c/b.txt', {content: '...'}); - app.pages('a/b/c/c.txt', {content: '...'}); - app.page('a/b/c/d.txt', {content: '...'}); - app.page('a/b/c/e.txt', {content: '...'}); - - app.views.pages.should.have.property('baz/a.txt'); - app.views.pages.should.have.property('baz/b.txt'); - app.views.pages.should.have.property('baz/c.txt'); - app.views.pages.should.have.property('baz/d.txt'); - app.views.pages.should.have.property('baz/e.txt'); - }); - - it('should use the `app.renameKey()` method:', function() { - app.renameKey(function(key) { - return 'app/' + path.basename(key); - }); - - app.pages('a/b/c/a.txt', {content: '...'}); - app.pages('a/b/c/b.txt', {content: '...'}); - app.pages('a/b/c/c.txt', {content: '...'}); - app.page('a/b/c/d.txt', {content: '...'}); - app.page('a/b/c/e.txt', {content: '...'}); - - app.views.pages.should.have.property('app/a.txt'); - app.views.pages.should.have.property('app/b.txt'); - app.views.pages.should.have.property('app/c.txt'); - app.views.pages.should.have.property('app/d.txt'); - app.views.pages.should.have.property('app/e.txt'); - }); - - it('should prefer collection method over app.options:', function() { - // this works when you switch the order around... - app.pages.renameKey(function pagesRenameKey(key) { - return 'aaa/' + path.basename(key); - }); - app.option('renameKey', function optsRenameKey(key) { - return 'foo/' + path.basename(key); - }); - - app.pages('a/b/c/a.txt', {content: '...'}); - app.pages('a/b/c/b.txt', {content: '...'}); - app.pages('a/b/c/c.txt', {content: '...'}); - app.page('a/b/c/d.txt', {content: '...'}); - app.page('a/b/c/e.txt', {content: '...'}); - - app.views.pages.should.have.property('aaa/a.txt'); - app.views.pages.should.have.property('aaa/b.txt'); - app.views.pages.should.have.property('aaa/c.txt'); - app.views.pages.should.have.property('aaa/d.txt'); - app.views.pages.should.have.property('aaa/e.txt'); - }); - - it('should prefer collection method over app method:', function() { - app.pages.renameKey(function(key) { - return 'aaa/' + path.basename(key); - }); - app.renameKey(function(key) { - return 'zzz/' + path.basename(key); - }); - - app.pages('a/b/c/a.txt', {content: '...'}); - app.pages('a/b/c/b.txt', {content: '...'}); - app.pages('a/b/c/c.txt', {content: '...'}); - app.page('a/b/c/d.txt', {content: '...'}); - app.page('a/b/c/e.txt', {content: '...'}); - - app.views.pages.should.have.property('aaa/a.txt'); - app.views.pages.should.have.property('aaa/b.txt'); - app.views.pages.should.have.property('aaa/c.txt'); - app.views.pages.should.have.property('aaa/d.txt'); - app.views.pages.should.have.property('aaa/e.txt'); - }); - - it('should prefer collection options over app.options:', function() { - app.pages.option('renameKey', function(key) { - return 'collection/' + path.basename(key); - }); - app.option('renameKey', function(key) { - return 'app/' + path.basename(key); - }); - - app.pages('a/b/c/a.txt', {content: '...'}); - app.pages('a/b/c/b.txt', {content: '...'}); - app.pages('a/b/c/c.txt', {content: '...'}); - app.page('a/b/c/d.txt', {content: '...'}); - app.page('a/b/c/e.txt', {content: '...'}); - - app.views.pages.should.have.property('collection/a.txt'); - app.views.pages.should.have.property('collection/b.txt'); - app.views.pages.should.have.property('collection/c.txt'); - app.views.pages.should.have.property('collection/d.txt'); - app.views.pages.should.have.property('collection/e.txt'); - }); - - it('should prefer collection options over app method:', function() { - app.pages.option('renameKey', function(key) { - return 'collection/' + path.basename(key); - }); - app.renameKey(function(key) { - return 'app/' + path.basename(key); - }); - - app.pages('a/b/c/a.txt', {content: '...'}); - app.pages('a/b/c/b.txt', {content: '...'}); - app.pages('a/b/c/c.txt', {content: '...'}); - app.page('a/b/c/d.txt', {content: '...'}); - app.page('a/b/c/e.txt', {content: '...'}); - - app.views.pages.should.have.property('collection/a.txt'); - app.views.pages.should.have.property('collection/b.txt'); - app.views.pages.should.have.property('collection/c.txt'); - app.views.pages.should.have.property('collection/d.txt'); - app.views.pages.should.have.property('collection/e.txt'); - }); - - it('should use renameKey on chained methods:', function() { - app.page('test/fixtures/pages/a.txt', { - options: { - renameKey: function foo(key) { - return 'foo/' + path.basename(key); - } - } - }); - - app.page('test/fixtures/pages/a.hbs', { - options: { - renameKey: function bar(key) { - return 'bar/' + path.basename(key); - } - } - }); - - app.views.pages.should.have.properties([ - 'foo/a.txt', - 'bar/a.hbs' - ]); - }); - }); - - describe('getting', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('post'); - app.create('page'); - }); - - it('should get a view with the `renameKey` defined on the `create` method:', function() { - app.create('post', { - renameKey: function createRenameKey(key) { - return 'posts/' + path.basename(key); - } - }); - - app.posts('a/b/c/a.txt', {content: '...'}); - app.posts('a/b/c/b.txt', {content: '...'}); - app.post('a/b/c/c.txt', {content: '...'}); - - app.posts.getView('a.txt').should.have.property('path', 'a/b/c/a.txt'); - app.posts.getView('posts/a.txt').should.have.property('path', 'a/b/c/a.txt'); - }); - - it('should get a view with `renameKey` on collection.options:', function() { - app.pages.option('renameKey', function(key) { - return 'bar/' + path.basename(key); - }); - - app.pages('a/b/c/a.txt', {content: '...'}); - app.pages('a/b/c/b.txt', {content: '...'}); - app.page('a/b/c/c.txt', {content: '...'}); - - app.views.pages.should.have.property('bar/a.txt'); - app.views.pages.should.have.property('bar/b.txt'); - app.views.pages.should.have.property('bar/c.txt'); - }); - - it('should get a view with the the `app.renameKey()` method:', function() { - app.renameKey(function(key) { - return 'baz/' + path.basename(key); - }); - - app.pages('a/b/c/a.txt', {content: '...'}); - app.pages('a/b/c/b.txt', {content: '...'}); - app.page('a/b/c/c.txt', {content: '...'}); - - app.views.pages.should.have.property('baz/a.txt'); - app.views.pages.should.have.property('baz/b.txt'); - app.views.pages.should.have.property('baz/c.txt'); - }); - - it('should get a view with the the `collection.renameKey()` method:', function() { - app.pages.renameKey(function(key) { - return 'baz/' + path.basename(key); - }); - - app.pages('a/b/c/a.txt', {content: '...'}); - app.pages('a/b/c/b.txt', {content: '...'}); - app.page('a/b/c/c.txt', {content: '...'}); - - app.views.pages.should.have.property('baz/a.txt'); - app.views.pages.should.have.property('baz/b.txt'); - app.views.pages.should.have.property('baz/c.txt'); - }); - }); - }); -}); diff --git a/test2/render.js b/test2/render.js deleted file mode 100644 index 04540b6..0000000 --- a/test2/render.js +++ /dev/null @@ -1,72 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe.skip('render', function() { - describe('engine', function() { - var view; - - beforeEach(function() { - app = new App({silent: true}); - app.engine('tmpl', require('engine-base')); - app.create('page'); - view = {contents: new Buffer('a <%= name %> b'), locals: {name: 'Halle'}}; - }); - - it('should render a view from an object:', function(done) { - app.page('a.tmpl', view) - .render(function(err, res) { - if (err) return done(err); - assert(res.contents.toString() === 'a Halle b'); - done(); - }); - }); - - it('should throw an error when a variable is undefined:', function(done) { - delete view.locals.name; - - app.page('a.tmpl', view) - .render(function(err) { - assert(err.message === 'name is not defined'); - done(); - }); - }); - - it('should re-throw an error when rethrow is true:', function(done) { - delete view.locals.name; - - app = new App({rethrow: true, silent: true}); - app.engine('tmpl', require('engine-base')); - app.create('page'); - - app.page('a.tmpl', view) - .render(function(err) { - assert(err.message === 'name is not defined'); - done(); - }); - }); - - it('should emit a re-thrown error when rethrow is true:', function(done) { - delete view.locals.name; - - app = new App({rethrow: true, silent: false}); - app.engine('tmpl', require('engine-base')); - app.create('page'); - - app.on('error', function(err) { - assert(err.message === 'name is not defined'); - done(); - }); - - app.page('a.tmpl', view) - .render(function(err) { - assert(err.message === 'name is not defined'); - }); - }); - }); -}); diff --git a/test2/routes.js b/test2/routes.js deleted file mode 100644 index af66b93..0000000 --- a/test2/routes.js +++ /dev/null @@ -1,98 +0,0 @@ -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -function append(str) { - return function(view, next) { - var content = view.contents.toString(); - view.contents = new Buffer(content + ' ' + str); - next(); - }; -} -function prepend(str) { - return function(view, next) { - var content = view.contents.toString(); - view.contents = new Buffer(str + ' ' + content); - next(); - }; -} - -describe('routes', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('page'); - }); - - describe('params', function() { - it('should call param function when routing', function(done) { - app.param('id', function(view, next, id) { - assert.equal(id, '123'); - next(); - }); - - app.all('/foo/:id/bar', function(view, next) { - assert.equal(view.options.params.id, '123'); - next(); - }); - - app.router.handle({ path: '/foo/123/bar' }, done); - }); - }); - - describe('onLoad middleware', function() { - it('should run when templates are loaded:', function() { - app.onLoad(/\.tmpl/, prepend('onLoad')); - app.pages('a.tmpl', { path: 'a.tmpl', content: '<%= name %>'}); - - var page = app.pages.getView('a.tmpl'); - page.contents.toString().should.equal('onLoad <%= name %>'); - }); - }); - - describe('preCompile middleware', function() { - it('should run before templates are compiled:', function() { - - }); - }); - - describe('postCompile middleware', function() { - it('should run after templates are compiled:', function() { - - }); - }); - - describe('preRender middleware', function() { - it('should run before templates are rendered:', function(done) { - app.preRender(/\.tmpl/, prepend('preRender')); - app.pages('a.tmpl', { path: 'a.tmpl', content: '<%= name %>', locals: {name: 'aaa'} }); - - var page = app.pages.getView('a.tmpl'); - page.contents.toString().should.equal('<%= name %>'); - - page.render({}, function(err, res) { - if (err) return done(err); - res.contents.toString().should.equal('preRender aaa'); - done(); - }); - }); - }); - - describe('postRender middleware', function() { - it('should run after templates are rendered:', function(done) { - app.postRender(/\.tmpl/, append('postRender')); - app.pages('a.tmpl', { path: 'a.tmpl', content: '<%= name %>', locals: {name: 'aaa' }}); - - var page = app.pages.getView('a.tmpl'); - page.contents.toString().should.equal('<%= name %>'); - - page.render({}, function(err, res) { - if (err) return done(err); - res.contents.toString().should.equal('aaa postRender'); - done(); - }); - }); - }); -}); diff --git a/test2/store.js b/test2/store.js deleted file mode 100644 index e75ec6e..0000000 --- a/test2/store.js +++ /dev/null @@ -1,243 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var fs = require('fs'); -var path = require('path'); -var Store = require('data-store'); -var assert = require('assert'); -var App = require('../'); -var app; - -describe('store', function() { - beforeEach(function() { - app = new App(); - }); - - afterEach(function(cb) { - app.store.del({force: true}); - app.store.data = {}; - cb(); - }); - - it('should create a store at the given `cwd`', function() { - app = new App({store: {cwd: __dirname + '/actual'}}); - app.store.set('foo', 'bar'); - assert(path.basename(app.store.path) === 'update.json'); - assert(app.store.data.hasOwnProperty('foo')); - assert(app.store.data.foo === 'bar'); - assert(fs.existsSync(path.join(__dirname, 'actual', 'update.json'))); - }); - - it('should create a store using the given `indent` value', function() { - app = new App({store: {cwd: __dirname + '/actual', indent: 0}}); - app.store.set('foo', 'bar'); - var contents = fs.readFileSync(path.join(__dirname, 'actual', 'update.json'), 'utf8'); - assert(contents === '{"foo":"bar"}'); - }); - - it('should set a value on the store', function() { - app.store.set('one', 'two'); - app.store.data.one.should.equal('two'); - }); - - it('should set an object', function() { - app.store.set({four: 'five', six: 'seven'}); - app.store.data.four.should.equal('five'); - app.store.data.six.should.equal('seven'); - }); - - it('should set a nested value', function() { - app.store.set('a.b.c.d', {e: 'f'}); - app.store.data.a.b.c.d.e.should.equal('f'); - }); - - it('should union a value onto an array on the store', function() { - app.store.union('one', 'two'); - app.store.data.one.should.eql(['two']); - }); - - it('should not union duplicate values', function() { - app.store.union('one', 'two'); - app.store.data.one.should.eql(['two']); - - app.store.union('one', ['two']); - app.store.data.one.should.eql(['two']); - }); - - it('should concat an existing array:', function() { - app.store.union('one', 'a'); - app.store.data.one.should.eql(['a']); - - app.store.union('one', ['b']); - app.store.data.one.should.eql(['a', 'b']); - - app.store.union('one', ['c', 'd']); - app.store.data.one.should.eql(['a', 'b', 'c', 'd']); - }); - - it('should return true if a key exists on the store', function() { - app.store.set('foo', 'bar'); - assert(app.store.has('foo')); - }); - - it('should return true when the value is null', function() { - app.store.set('baz', null); - assert(app.store.has('baz')); - }); - - it('should return false when the value is undefined', function() { - app.store.set('qux', undefined); - assert(!app.store.has('qux')); - }); - - it('should return true if a nested key exists on the store', function() { - app.store.set('a.b.c.d', {x: 'zzz'}); - app.store.set('a.b.c.e', {f: null}); - app.store.set('a.b.g.j', {k: undefined}); - - assert(!app.store.has('a.b.bar')); - assert(app.store.has('a.b.c.d')); - assert(app.store.has('a.b.c.d.x')); - assert(!app.store.has('a.b.c.d.z')); - assert(app.store.has('a.b.c.e')); - assert(app.store.has('a.b.c.e.f')); - assert(!app.store.has('a.b.c.e.z')); - assert(app.store.has('a.b.g.j')); - assert(!app.store.has('a.b.g.j.k')); - assert(!app.store.has('a.b.g.j.z')); - }); - - it('should return true if a key exists `.hasOwn()` on the store', function() { - app.store.set('foo', 'bar'); - app.store.set('baz', null); - app.store.set('qux', undefined); - - assert(app.store.hasOwn('foo')); - assert(!app.store.hasOwn('bar')); - assert(app.store.hasOwn('baz')); - assert(app.store.hasOwn('qux')); - }); - - it('should return true if a nested key exists `.hasOwn()` on the store', function() { - app.store.set('a.b.c.d', {x: 'zzz'}); - app.store.set('a.b.c.e', {f: null}); - app.store.set('a.b.g.j', {k: undefined}); - - assert(app.store.hasOwn('a.b.c.d')); - assert(app.store.hasOwn('a.b.c.d.x')); - assert(app.store.has('a.b.c.e.f')); - assert(app.store.hasOwn('a.b.c.e.f')); - assert(app.store.hasOwn('a.b.g.j.k')); - - assert(!app.store.hasOwn('a.b.bar')); - assert(!app.store.hasOwn('a.b.c.d.z')); - assert(!app.store.hasOwn('a.b.c.e.bar')); - assert(!app.store.has('a.b.g.j.k')); - assert(!app.store.hasOwn('a.b.g.j.foo')); - }); - - it('should `.get()` a stored value', function() { - app.store.set('three', 'four'); - app.store.get('three').should.equal('four'); - }); - - it('should `.get()` a nested value', function() { - app.store.set({a: {b: {c: 'd'}}}); - app.store.get('a.b.c').should.equal('d'); - }); - - it('should `.del()` a stored value', function() { - app.store.set('a', 'b'); - app.store.set('c', 'd'); - app.store.del('a'); - assert(!app.store.hasOwnProperty('a')); - }); - - it('should `.del()` multiple stored values', function() { - app.store.set('a', 'b'); - app.store.set('c', 'd'); - app.store.set('e', 'f'); - app.store.del(['a', 'c', 'e']); - app.store.data.should.eql({}); - }); -}); - -describe('events', function() { - beforeEach(function() { - app = new App(); - app.store = new Store('update-tests'); - }); - - afterEach(function(cb) { - app.store.del({force: true}); - cb(); - }); - - it('should emit `set` when an object is set:', function() { - var keys = []; - app.store.on('set', function(key) { - keys.push(key); - }); - - app.store.set({a: {b: {c: 'd'}}}); - assert(keys[0] === 'a'); - }); - - it('should emit `set` when a key/value pair is set:', function() { - var keys = []; - app.store.on('set', function(key) { - keys.push(key); - }); - - app.store.set('a', 'b'); - assert(keys[0] === 'a'); - }); - - it('should emit `set` when an object value is set:', function() { - var keys = []; - app.store.on('set', function(key) { - keys.push(key); - }); - - app.store.set('a', {b: 'c'}); - assert(keys[0] === 'a'); - }); - - it('should emit `set` when an array of objects is passed:', function() { - var keys = []; - app.store.on('set', function(key) { - keys.push(key); - }); - - app.store.set([{a: 'b'}, {c: 'd'}]); - assert(keys[0] === 'a'); - assert(keys[1] === 'c'); - }); - - it('should emit `del` when a value is deleted:', function(cb) { - app.store.on('del', function(key) { - assert(key === 'a'); - cb(); - }); - - app.store.set('a', {b: 'c'}); - app.store.get('a').should.eql({b: 'c'}); - app.store.del('a'); - }); - - it('should emit deleted keys on `del`:', function(cb) { - app.store.once('del', function(key) { - console.log(key) - assert(key === 'a'); - cb(); - }); - - app.store.set('a', 'b'); - app.store.set('c', 'd'); - app.store.set('e', 'f'); - assert.deepEqual(Object.keys(app.store.data), ['a', 'c', 'e']); - app.store.del({force: true}); - assert.deepEqual(Object.keys(app.store.data), []); - }); -}); diff --git a/test2/support/ignore.js b/test2/support/ignore.js deleted file mode 100644 index 7dcbb75..0000000 --- a/test2/support/ignore.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = [ - 'addEventListener', - 'removeEventListener', - 'removeAllListeners', - 'removeListener' -]; diff --git a/test2/support/index.js b/test2/support/index.js deleted file mode 100644 index e2194a5..0000000 --- a/test2/support/index.js +++ /dev/null @@ -1,64 +0,0 @@ -'use strict'; - -var path = require('path'); -var loadpkg = require('load-pkg'); -var assert = require('assert'); -var ignore = require('./ignore'); -var cache = {}; - -exports.containEql = function containEql(actual, expected) { - if (Array.isArray(expected)) { - var len = expected.length; - while (len--) { - exports.containEql(actual[len], expected[len]); - } - } else { - for (var key in expected) { - assert.deepEqual(actual[key], expected[key]); - } - } -}; - -exports.keys = function keys(obj) { - var arr = []; - for (var key in obj) { - if (ignore.indexOf(key) === -1) { - arr.push(key); - } - } - return arr; -}; - -exports.resolve = function(filepath) { - filepath = filepath || ''; - var key = 'app:' + filepath; - if (cache.hasOwnProperty(key)) { - return cache[key]; - } - - var pkg = loadpkg.sync(process.cwd()); - var prefix = pkg.name !== 'templates' - ? 'templates' - : ''; - - var base = filepath - ? path.join(prefix, filepath) - : process.cwd(); - - var fp = tryResolve(base); - - if (typeof fp === 'undefined') { - throw new Error('cannot resolve: ' + fp); - } - return (cache[key] = require(fp)); -}; - -function tryResolve(name) { - try { - return require.resolve(name); - } catch (err) {} - - try { - return require.resolve(path.resolve(name)); - } catch (err) {} -} diff --git a/test2/support/spy.js b/test2/support/spy.js deleted file mode 100644 index e14512b..0000000 --- a/test2/support/spy.js +++ /dev/null @@ -1,27 +0,0 @@ -var fs = require('fs'); -var sinon = require('sinon'); - -var errorfn = false; - -function maybeCallAsync(module, func) { - var original = module[func]; - return sinon.stub(module, func, function() { - var args = Array.prototype.slice.call(arguments); - args.unshift(module, func); - var err = typeof errorfn === 'function' && - errorfn.apply(this, args); - if (!err) { - original.apply(this, arguments); - } else { - arguments[arguments.length - 1](err); - } - }); -} - -module.exports = { - setError: function(fn) { - errorfn = fn; - }, - chmodSpy: maybeCallAsync(fs, 'chmod'), - statSpy: maybeCallAsync(fs, 'stat') -}; diff --git a/test2/view.content.js b/test2/view.content.js deleted file mode 100644 index 80ea113..0000000 --- a/test2/view.content.js +++ /dev/null @@ -1,29 +0,0 @@ -require('mocha'); -require('should'); -var fs = require('fs'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('content', function() { - beforeEach(function() { - app = new App(); - app.create('page'); - app.engine('tmpl', require('engine-base')); - }); - - it('should normalize the `content` property on a view to a string:', function(done) { - app.page('abc', {path: 'test/fixtures/templates/a.tmpl'}) - .set('read', function() { - this.contents = fs.readFileSync(this.path); - return this; - }); - - app.views.pages.abc.read(); - - assert('content' in app.views.pages.abc); - assert(typeof app.views.pages.abc.content === 'string'); - done(); - }); -}); diff --git a/test2/view.events.js b/test2/view.events.js deleted file mode 100644 index 3956379..0000000 --- a/test2/view.events.js +++ /dev/null @@ -1,28 +0,0 @@ -require('should'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('view.option()', function() { - beforeEach(function() { - app = new App(); - app.create('page'); - }); - - it('should emit events:', function() { - app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}); - var page = app.pages.getView('a.tmpl'); - var events = []; - - page.on('option', function(key) { - events.push(key); - }); - - page.option('a', 'b'); - page.option('c', 'd'); - page.option('e', 'f'); - page.option({g: 'h'}); - - events.should.eql(['a', 'c', 'e', 'g']); - }); -}); diff --git a/test2/view.js b/test2/view.js deleted file mode 100644 index 92ddf3c..0000000 --- a/test2/view.js +++ /dev/null @@ -1,1148 +0,0 @@ -require('mocha'); -var should = require('should'); -var fs = require('fs'); -var path = require('path'); -var assert = require('assert'); -var Stream = require('stream'); -var es = require('event-stream'); -var support = require('./support'); -var App = support.resolve(); -var View = App.View; -var view; - -describe('View', function() { - describe('instance', function() { - it('should create an instance of View:', function() { - view = new View(); - assert(view instanceof View); - }); - }); - - describe('static methods', function() { - it('should expose `extend`:', function() { - assert(typeof View.extend === 'function'); - }); - }); - - describe('prototype methods', function() { - beforeEach(function() { - view = new View(); - }); - - it('should expose `set`:', function() { - assert(typeof view.set === 'function'); - }); - it('should expose `get`:', function() { - assert(typeof view.get === 'function'); - }); - it('should expose `del`:', function() { - assert(typeof view.del === 'function'); - }); - it('should expose `define`:', function() { - assert(typeof view.define === 'function'); - }); - it('should expose `visit`:', function() { - assert(typeof view.visit === 'function'); - }); - it('should expose `compile`:', function() { - assert(typeof view.compile === 'function'); - }); - it('should expose `render`:', function() { - assert(typeof view.render === 'function'); - }); - it('should expose `isType`:', function() { - assert(typeof view.isType === 'function'); - }); - }); - - describe('properties', function() { - it('should expose an `options` property', function() { - view = new View({}); - assert.deepEqual(view.options, {}); - assert(view.hasOwnProperty('options')); - }); - - it('should add `options` when passed on the constructor', function() { - view = new View({options: {foo: 'bar'}}); - assert(view.options.foo === 'bar'); - }); - - it('should expose a `data` property', function() { - view = new View({app: {}}); - assert.deepEqual(view.data, {}); - assert(view.hasOwnProperty('data')); - }); - - it('should add `data` when passed on the constructor', function() { - view = new View({data: {foo: 'bar'}}); - assert(view.data.foo === 'bar'); - }); - - it('should add `locals` when passed on the constructor', function() { - view = new View({locals: {foo: 'bar'}}); - assert(view.locals.foo === 'bar'); - }); - }); - - describe('set', function() { - it('should set properties on the object', function() { - view = new View(); - view.set('foo', 'bar'); - assert.equal(view.foo, 'bar'); - }); - }); - - describe('get', function() { - it('should get properties from the object', function() { - view = new View(); - view.set('foo', 'bar'); - assert.equal(view.get('foo'), 'bar'); - }); - }); - - describe('cwd', function() { - it('should get properties from the object', function() { - view = new View({cwd: 'test/fixtures'}); - assert(view.cwd === 'test/fixtures'); - }); - }); - - describe('clone', function() { - it('should clone the view:', function() { - view = new View({content: 'foo'}); - view.set({path: 'foo/bar'}); - view.set('options.one', 'two'); - var clone = view.clone(); - assert(clone.contents); - clone.set('baz', 'quux'); - clone.set('options.three', 'four'); - assert.equal(clone.get('foo'), view.get('foo')); - assert(clone.get('baz') === 'quux'); - assert(!view.get('baz')); - // not deep cloned - assert(clone.get('options.three') === 'four'); - assert(view.get('options.three') === 'four'); - }); - - it('should deep clone the entire object', function() { - view = new View({content: 'foo'}); - view.set({path: 'foo/bar'}); - view.set('options.one', 'two'); - var clone = view.clone({deep: true}); - clone.set('options.three', 'four'); - assert(view.get('options.one') === 'two'); - assert(clone.get('options.one') === 'two'); - assert(clone.get('options.three') === 'four'); - assert(!view.get('options.three')); - }); - }); - - describe('visit', function() { - it('should visit all properties on an object and call the specified method', function() { - view = new View(); - var obj = { - foo: 'bar', - bar: 'baz', - baz: 'bang' - }; - view.visit('set', obj); - assert.equal(view.get('foo'), 'bar'); - assert.equal(view.get('bar'), 'baz'); - assert.equal(view.get('baz'), 'bang'); - }); - - it('should visit all properties on all objects in an array and call the specified method', function() { - view = new View(); - var arr = [{foo: 'bar', bar: 'baz', baz: 'bang'}]; - view.visit('set', arr); - assert.equal(view.get('foo'), 'bar'); - assert.equal(view.get('bar'), 'baz'); - assert.equal(view.get('baz'), 'bang'); - }); - }); - - describe('compile', function() { - it('should get view.layout from view.data.layout', function() { - view = new View({path: 'foo', contents: 'a b c', data: {layout: 'default'}}); - assert(view.layout === 'default'); - }); - it('should get view.layout from view.options.layout', function() { - view = new View({path: 'foo', contents: 'a b c', options: {layout: 'default'}}); - assert(view.layout === 'default'); - }); - it('should get view.layout from view.locals.layout', function() { - view = new View({path: 'foo', contents: 'a b c', locals: {layout: 'default'}}); - assert(view.layout === 'default'); - }); - it('should get view.layout from the view', function() { - view = new View({path: 'foo', contents: 'a b c', layout: 'default'}); - assert(view.layout === 'default'); - }); - - it('should add a compiled function to `view.fn`', function() { - view = new View({path: 'foo', contents: 'a <%= name %> z'}); - view.compile(); - assert(typeof view.fn === 'function'); - }); - - it('should render a compiled template', function(done) { - view = new View({path: 'foo', contents: 'a <%= name %> z'}); - view.compile(); - view.render({name: 'Halle'}, function(err, res) { - if (err) return done(err); - assert(res.contents.toString() === 'a Halle z'); - done(); - }); - }); - - it('should render `fn` using data passed on the constructor', function(done) { - view = new View({ - path: 'foo', - contents: 'a <%= name %> z', - data: { - name: 'Brooke' - } - }); - - view.compile(); - view.render(function(err, res) { - if (err) return done(err); - assert(res.contents.toString() === 'a Brooke z'); - done(); - }); - }); - }); - - describe('render', function() { - it('should render a template', function(done) { - view = new View({path: 'foo', contents: 'a <%= name %> z'}); - view.render({name: 'Halle'}, function(err, res) { - if (err) return done(err); - assert(res.contents.toString() === 'a Halle z'); - done(); - }); - }); - - it('should render fn using data passed on the constructor', function(done) { - view = new View({ - path: 'foo', - contents: 'a <%= name %> z', - data: { - name: 'Brooke' - } - }); - - view.render(function(err, res) { - if (err) return done(err); - assert(res.contents.toString() === 'a Brooke z'); - done(); - }); - }); - - it('should pass errors in the callback.', function(done) { - view = new View({ - path: 'foo', - contents: 'a <%= name %> z' - }); - - view.render(function(err) { - assert(err.message === 'name is not defined'); - done(); - }); - }); - }); -}); - -/** - * The following unit tests are from Vinyl - * Since we inherit vinyl in View, we need - * to ensure that these still pass. - */ - -describe('View', function() { - describe('isVinyl()', function() { - it('should return true on a vinyl object', function(done) { - var view = new View(); - assert(View.isVinyl(view) === true); - done(); - }); - it('should return false on a normal object', function(done) { - assert(View.isVinyl({}) === false); - done(); - }); - it('should return false on a null object', function(done) { - assert(View.isVinyl({}) === false); - done(); - }); - }); - - describe('constructor()', function() { - it('should default cwd to process.cwd', function(done) { - var view = new View(); - view.cwd.should.equal(process.cwd()); - done(); - }); - - it('should default base to cwd', function(done) { - var cwd = '/'; - var view = new View({cwd: cwd}); - view.base.should.equal(cwd); - done(); - }); - - it('should default base to cwd even when none is given', function(done) { - var view = new View(); - view.base.should.equal(process.cwd()); - done(); - }); - - it('should default path to null', function(done) { - var view = new View(); - should.not.exist(view.path); - done(); - }); - - it('should default history to []', function(done) { - var view = new View(); - view.history.should.eql([]); - done(); - }); - - it('should default stat to null', function(done) { - var view = new View(); - should.not.exist(view.stat); - done(); - }); - - it('should default contents to null', function(done) { - var view = new View(); - should.not.exist(view.contents); - done(); - }); - - it('should set base to given value', function(done) { - var val = '/'; - var view = new View({base: val}); - view.base.should.equal(val); - done(); - }); - - it('should set cwd to given value', function(done) { - var val = '/'; - var view = new View({cwd: val}); - view.cwd.should.equal(val); - done(); - }); - - it('should set path to given value', function(done) { - var val = '/test.coffee'; - var view = new View({path: val}); - view.path.should.equal(val); - view.history.should.eql([val]); - done(); - }); - - it('should set history to given value', function(done) { - var val = '/test.coffee'; - var view = new View({history: [val]}); - view.path.should.equal(val); - view.history.should.eql([val]); - done(); - }); - - it('should set stat to given value', function(done) { - var val = {}; - var view = new View({stat: val}); - view.stat.should.equal(val); - done(); - }); - - it('should set contents to given value', function(done) { - var val = new Buffer('test'); - var view = new View({contents: val}); - view.contents.should.equal(val); - done(); - }); - }); - - describe('isBuffer()', function() { - it('should return true when the contents are a Buffer', function(done) { - var val = new Buffer('test'); - var view = new View({contents: val}); - view.isBuffer().should.equal(true); - done(); - }); - - it('should return false when the contents are a Stream', function(done) { - var val = new Stream(); - var view = new View({contents: val}); - assert(!view.isBuffer()); - done(); - }); - - it('should return false when the contents are a null', function(done) { - var view = new View({contents: null}); - assert(!view.isBuffer()); - done(); - }); - }); - - describe('isStream()', function() { - it('should return false when the contents are a Buffer', function(done) { - var val = new Buffer('test'); - var view = new View({contents: val}); - assert(!view.isStream()); - done(); - }); - - it('should return true when the contents are a Stream', function(done) { - var val = new Stream(); - var view = new View({contents: val}); - view.isStream().should.equal(true); - done(); - }); - - it('should return false when the contents are a null', function(done) { - var view = new View({contents: null}); - assert(!view.isStream()); - done(); - }); - }); - - describe('isNull()', function() { - it('should return false when the contents are a Buffer', function(done) { - var val = new Buffer('test'); - var view = new View({contents: val}); - assert(!view.isNull()); - done(); - }); - - it('should return false when the contents are a Stream', function(done) { - var val = new Stream(); - var view = new View({contents: val}); - assert(!view.isNull()); - done(); - }); - - it('should return true when the contents are a null', function(done) { - var view = new View({contents: null}); - view.isNull().should.equal(true); - done(); - }); - }); - - describe('isDirectory()', function() { - var fakeStat = { - isDirectory: function() { - return true; - } - }; - - it('should return false when the contents are a Buffer', function(done) { - var val = new Buffer('test'); - var view = new View({contents: val, stat: fakeStat}); - assert(!view.isDirectory()); - done(); - }); - - it('should return false when the contents are a Stream', function(done) { - var val = new Stream(); - var view = new View({contents: val, stat: fakeStat}); - assert(!view.isDirectory()); - done(); - }); - - it('should return true when the contents are a null', function(done) { - var view = new View({contents: null, stat: fakeStat}); - view.isDirectory().should.equal(true); - done(); - }); - }); - - describe('clone()', function() { - it('should copy all attributes over with Buffer', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Buffer('test') - }; - var view = new View(options); - var view2 = view.clone(); - - view2.should.not.equal(view, 'refs should be different'); - view2.cwd.should.equal(view.cwd); - view2.base.should.equal(view.base); - view2.path.should.equal(view.path); - view2.contents.should.not.equal(view.contents, 'buffer ref should be different'); - view2.contents.toString('utf8').should.equal(view.contents.toString('utf8')); - done(); - }); - - it('should copy buffer\'s reference with option contents: false', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.js', - contents: new Buffer('test') - }; - - var view = new View(options); - - var copy1 = view.clone({ contents: false }); - copy1.contents.should.equal(view.contents); - - var copy2 = view.clone({}); - copy2.contents.should.not.equal(view.contents); - - var copy3 = view.clone({ contents: 'any string' }); - copy3.contents.should.not.equal(view.contents); - - done(); - }); - - it('should copy all attributes over with Stream', function(done) { - var contents = new Stream.PassThrough(); - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: contents - }; - var view = new View(options); - var view2 = view.clone(); - - contents.write(new Buffer('wa')); - - process.nextTick(function() { - contents.write(new Buffer('dup')); - contents.end(); - }); - - view2.should.not.equal(view, 'refs should be different'); - view2.cwd.should.equal(view.cwd); - view2.base.should.equal(view.base); - view2.path.should.equal(view.path); - view2.contents.should.not.equal(view.contents, 'stream ref should not be the same'); - view.contents.pipe(es.wait(function(err, data) { - if (err) return done(err); - view2.contents.pipe(es.wait(function(err, data2) { - if (err) return done(err); - data2.should.not.equal(data, 'stream contents ref should not be the same'); - data2.should.eql(data, 'stream contents should be the same'); - })); - })); - done(); - }); - - it('should copy all attributes over with null', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - var view = new View(options); - var view2 = view.clone(); - - view2.should.not.equal(view, 'refs should be different'); - view2.cwd.should.equal(view.cwd); - view2.base.should.equal(view.base); - view2.path.should.equal(view.path); - should.not.exist(view2.contents); - done(); - }); - - it('should properly clone the `stat` property', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.js', - contents: new Buffer('test'), - stat: fs.statSync(__filename) - }; - - var view = new View(options); - var copy = view.clone(); - - assert(copy.stat.isFile()); - assert(!copy.stat.isDirectory()); - done(); - }); - - it('should properly clone the `history` property', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.js', - contents: new Buffer('test'), - stat: fs.statSync(__filename) - }; - - var view = new View(options); - var copy = view.clone(); - - copy.history[0].should.equal(options.path); - copy.path = 'lol'; - view.path.should.not.equal(copy.path); - done(); - }); - - it('should copy custom properties', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - - var view = new View(options); - view.custom = { a: 'custom property' }; - var view2 = view.clone(); - - view2.should.not.equal(view, 'refs should be different'); - view2.cwd.should.equal(view.cwd); - view2.base.should.equal(view.base); - view2.path.should.equal(view.path); - view2.custom.should.equal(view.custom); - view2.custom.a.should.equal(view.custom.a); - - done(); - }); - - it('should copy history', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - - var view = new View(options); - view.path = '/test/test.js'; - view.path = '/test/test-938di2s.js'; - var view2 = view.clone(); - - view2.history.should.eql([ - '/test/test.coffee', - '/test/test.js', - '/test/test-938di2s.js' - ]); - view2.history.should.not.equal([ - '/test/test.coffee', - '/test/test.js', - '/test/test-938di2s.js' - ]); - view2.path.should.eql('/test/test-938di2s.js'); - - done(); - }); - - it('should copy all attributes deeply', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - - var view = new View(options); - view.custom = { a: 'custom property' }; - - var view2 = view.clone(true); - view2.custom.should.eql(view.custom); - view2.custom.should.not.equal(view.custom); - view2.custom.a.should.equal(view.custom.a); - - var view3 = view.clone({ deep: true }); - view3.custom.should.eql(view.custom); - view3.custom.should.not.equal(view.custom); - view3.custom.a.should.equal(view.custom.a); - - var view4 = view.clone(false); - view4.custom.should.eql(view.custom); - view4.custom.should.equal(view.custom); - view4.custom.a.should.equal(view.custom.a); - - var view5 = view.clone({ deep: false }); - view5.custom.should.eql(view.custom); - view5.custom.should.equal(view.custom); - view5.custom.a.should.equal(view.custom.a); - - done(); - }); - }); - - describe('pipe()', function() { - it('should write to stream with Buffer', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Buffer('test') - }; - var view = new View(options); - var stream = new Stream.PassThrough(); - stream.on('data', function(chunk) { - should.exist(chunk); - (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); - chunk.toString('utf8').should.equal(options.contents.toString('utf8')); - }); - stream.on('end', function() { - done(); - }); - var ret = view.pipe(stream); - ret.should.equal(stream, 'should return the stream'); - }); - - it('should pipe to stream with Stream', function(done) { - var testChunk = new Buffer('test'); - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Stream.PassThrough() - }; - var view = new View(options); - var stream = new Stream.PassThrough(); - stream.on('data', function(chunk) { - should.exist(chunk); - (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); - chunk.toString('utf8').should.equal(testChunk.toString('utf8')); - done(); - }); - var ret = view.pipe(stream); - ret.should.equal(stream, 'should return the stream'); - - view.contents.write(testChunk); - }); - - it('should do nothing with null', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - var view = new View(options); - var stream = new Stream.PassThrough(); - stream.on('data', function() { - throw new Error('should not write'); - }); - stream.on('end', function() { - done(); - }); - var ret = view.pipe(stream); - ret.should.equal(stream, 'should return the stream'); - }); - - it('should write to stream with Buffer', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Buffer('test') - }; - var view = new View(options); - var stream = new Stream.PassThrough(); - stream.on('data', function(chunk) { - should.exist(chunk); - (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); - chunk.toString('utf8').should.equal(options.contents.toString('utf8')); - done(); - }); - stream.on('end', function() { - throw new Error('should not end'); - }); - var ret = view.pipe(stream, {end: false}); - ret.should.equal(stream, 'should return the stream'); - }); - - it('should pipe to stream with Stream', function(done) { - var testChunk = new Buffer('test'); - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Stream.PassThrough() - }; - var view = new View(options); - var stream = new Stream.PassThrough(); - stream.on('data', function(chunk) { - should.exist(chunk); - (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); - chunk.toString('utf8').should.equal(testChunk.toString('utf8')); - done(); - }); - stream.on('end', function() { - throw new Error('should not end'); - }); - var ret = view.pipe(stream, {end: false}); - ret.should.equal(stream, 'should return the stream'); - - view.contents.write(testChunk); - }); - - it('should do nothing with null', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - var view = new View(options); - var stream = new Stream.PassThrough(); - stream.on('data', function() { - throw new Error('should not write'); - }); - stream.on('end', function() { - throw new Error('should not end'); - }); - var ret = view.pipe(stream, {end: false}); - ret.should.equal(stream, 'should return the stream'); - process.nextTick(done); - }); - }); - - describe('inspect()', function() { - it('should return correct format when no contents and no path', function(done) { - var view = new View(); - view.inspect().should.equal(''); - done(); - }); - - it('should return correct format when Buffer and no path', function(done) { - var val = new Buffer('test'); - var view = new View({ - contents: val - }); - view.inspect().should.equal('>'); - done(); - }); - - it('should return correct format when Buffer and relative path', function(done) { - var val = new Buffer('test'); - var view = new View({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: val - }); - view.inspect().should.equal('>'); - done(); - }); - - it('should return correct format when Buffer and only path and no base', function(done) { - var val = new Buffer('test'); - var view = new View({ - cwd: '/', - path: '/test/test.coffee', - contents: val - }); - delete view.base; - view.inspect().should.equal('>'); - done(); - }); - - it('should return correct format when Stream and relative path', function(done) { - var view = new View({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Stream.PassThrough() - }); - view.inspect().should.equal('>'); - done(); - }); - - it('should return correct format when null and relative path', function(done) { - var view = new View({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }); - view.inspect().should.equal(''); - done(); - }); - }); - - describe('contents get/set', function() { - it('should work with Buffer', function(done) { - var val = new Buffer('test'); - var view = new View(); - view.contents = val; - view.contents.should.equal(val); - done(); - }); - - it('should work with Stream', function(done) { - var val = new Stream.PassThrough(); - var view = new View(); - view.contents = val; - view.contents.should.equal(val); - done(); - }); - - it('should work with null', function(done) { - var val = null; - var view = new View(); - view.contents = val; - (view.contents === null).should.equal(true); - done(); - }); - - it('should work with string', function(done) { - var val = 'test'; - var view = new View(); - view.contents = val; - view.contents.should.deepEqual(new Buffer(val)); - done(); - }); - }); - - describe('relative get/set', function() { - it('should error on set', function(done) { - var view = new View(); - try { - view.relative = 'test'; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should error on get when no base', function(done) { - var view = new View(); - delete view.base; - try { - view.relative; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should error on get when no path', function(done) { - var view = new View(); - try { - view.relative; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should return a relative path from base', function(done) { - var view = new View({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - view.relative.should.equal('test.coffee'); - done(); - }); - - it('should return a relative path from cwd', function(done) { - var view = new View({ - cwd: '/', - path: '/test/test.coffee' - }); - view.relative.should.equal(path.join('test', 'test.coffee')); - done(); - }); - }); - - describe('dirname get/set', function() { - it('should error on get when no path', function(done) { - var view = new View(); - try { - view.dirname; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should return the dirname of the path', function(done) { - var view = new View({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - view.dirname.should.equal('/test'); - done(); - }); - - it('should error on set when no path', function(done) { - var view = new View(); - try { - view.dirname = '/test'; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should set the dirname of the path', function(done) { - var view = new View({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - view.dirname = '/test/foo'; - view.path.should.equal('/test/foo/test.coffee'); - done(); - }); - }); - - describe('basename get/set', function() { - it('should error on get when no path', function(done) { - var view = new View(); - try { - view.basename; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should return the basename of the path', function(done) { - var view = new View({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - view.basename.should.equal('test.coffee'); - done(); - }); - - it('should error on set when no path', function(done) { - var view = new View(); - try { - view.basename = 'test.coffee'; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should set the basename of the path', function(done) { - var view = new View({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - view.basename = 'foo.png'; - view.path.should.equal('/test/foo.png'); - done(); - }); - }); - - describe('extname get/set', function() { - it('should error on get when no path', function(done) { - var view = new View(); - try { - view.extname; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should return the extname of the path', function(done) { - var view = new View({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - view.extname.should.equal('.coffee'); - done(); - }); - - it('should error on set when no path', function(done) { - var view = new View(); - try { - view.extname = '.coffee'; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should set the extname of the path', function(done) { - var view = new View({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - view.extname = '.png'; - view.path.should.equal('/test/test.png'); - done(); - }); - }); - - describe('path get/set', function() { - it('should record history when instantiation', function() { - var view = new View({ - cwd: '/', - path: '/test/test.coffee' - }); - - view.path.should.eql('/test/test.coffee'); - view.history.should.eql(['/test/test.coffee']); - }); - - it('should record history when path change', function() { - var view = new View({ - cwd: '/', - path: '/test/test.coffee' - }); - - view.path = '/test/test.js'; - view.path.should.eql('/test/test.js'); - view.history.should.eql(['/test/test.coffee', '/test/test.js']); - - view.path = '/test/test.coffee'; - view.path.should.eql('/test/test.coffee'); - view.history.should.eql(['/test/test.coffee', '/test/test.js', '/test/test.coffee']); - }); - - it('should not record history when set the same path', function() { - var view = new View({ - cwd: '/', - path: '/test/test.coffee' - }); - - view.path = '/test/test.coffee'; - view.path = '/test/test.coffee'; - view.path.should.eql('/test/test.coffee'); - view.history.should.eql(['/test/test.coffee']); - - // ignore when set empty string - view.path = ''; - view.path.should.eql('/test/test.coffee'); - view.history.should.eql(['/test/test.coffee']); - }); - - it('should throw when set path null in constructor', function() { - (function() { - View({ - cwd: '/', - path: null - }); - }).should.throw('path should be string'); - }); - - it('should throw when set path null', function() { - var view = new View({ - cwd: '/', - path: 'foo' - }); - - (function() { - view.path = null; - }).should.throw('path should be string'); - }); - }); -}); diff --git a/test2/view.methods.js b/test2/view.methods.js deleted file mode 100644 index 08f064e..0000000 --- a/test2/view.methods.js +++ /dev/null @@ -1,39 +0,0 @@ -require('should'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('view.option()', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('page'); - }); - - describe('.use', function() { - it('should expose `.use` for running plugins on a view:', function() { - app.page('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}) - .use(function() { - this.options.foo = 'bar'; - }) - .use(function() { - this.options.bar = 'baz'; - }); - - var page = app.pages.getView('a.tmpl'); - page.options.should.have.property('foo'); - page.options.should.have.property('bar'); - }); - }); - - describe('.render:', function() { - it('should expose `.render` for rendering a view:', function(done) { - app.page('a.tmpl', {path: 'a.tmpl', content: '<%= a %>', locals: {a: 'bbb'}}) - .render({}, function(err, res) { - if (err) return done(err); - res.contents.toString().should.equal('bbb'); - done(); - }); - }); - }); -}); diff --git a/test2/view.option.js b/test2/view.option.js deleted file mode 100644 index 087399c..0000000 --- a/test2/view.option.js +++ /dev/null @@ -1,29 +0,0 @@ -require('should'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('view.option()', function() { - beforeEach(function() { - app = new App(); - app.create('page'); - }); - - it('should set an option:', function() { - app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}); - var page = app.pages.getView('a.tmpl'); - - page.options.should.not.have.property('foo'); - page.option('foo', 'bar'); - page.options.should.have.property('foo'); - }); - - it('should extend options:', function() { - app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}); - var page = app.pages.getView('a.tmpl'); - page.option('a', 'b'); - page.option('c', 'd'); - page.option('e', 'f'); - page.options.should.have.properties(['a', 'c', 'e']); - }); -}); diff --git a/test2/view.render.js b/test2/view.render.js deleted file mode 100644 index a7d6d71..0000000 --- a/test2/view.render.js +++ /dev/null @@ -1,52 +0,0 @@ -require('mocha'); -require('should'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('helpers', function() { - describe('rendering', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('layouts', {viewType: 'layout'}); - app.create('pages'); - }); - - it('should expose `.render` for rendering a view:', function(done) { - app.page('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}) - .render({a: 'bbb'}, function(err, res) { - if (err) return done(err); - res.content.should.equal('bbb'); - done(); - }); - }); - - it('should render a view with a layout', function(done) { - app.layout('default.tmpl', {content: 'a {% body %} b'}); - app.page('a.tmpl', {content: '<%= title %>', layout: 'default.tmpl'}) - .render({title: 'zzz'}, function(err, res) { - if (err) return done(err); - res.content.should.equal('a zzz b'); - done(); - }); - }); - - it('should render a view with a layout', function(done) { - app.layout('foo.tmpl', {content: 'a {% body %} a'}); - app.layout('bar.tmpl', {content: 'b {% body %} b'}); - app.pages('a.tmpl', {content: '<%= title %>'}); - - app.pages.getView('a.tmpl') - .option('resolveLayout', function() { - return 'bar.tmpl'; - }) - .render({title: 'zzz'}, function(err, res) { - if (err) return done(err); - res.content.should.equal('b zzz b'); - done(); - }); - }); - }); -}); - diff --git a/test2/view.set.js b/test2/view.set.js deleted file mode 100644 index 018df6e..0000000 --- a/test2/view.set.js +++ /dev/null @@ -1,34 +0,0 @@ -require('mocha'); -require('should'); -var fs = require('fs'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('set', function() { - beforeEach(function() { - app = new App(); - app.create('page'); - app.engine('tmpl', require('engine-base')); - }); - - it('should set a property on a view:', function(done) { - app.page('abc', {path: 'test/fixtures/templates/a.tmpl'}) - .set('read', function() { - this.contents = fs.readFileSync(this.path); - return this; - }); - - assert('read' in app.views.pages.abc); - app.views.pages.abc - .read() - .set('data.name', 'Brooke') - .render(function(err, res) { - if (err) return done(err); - - assert(res.content === 'Brooke'); - done(); - }); - }); -}); diff --git a/test2/view.use.js b/test2/view.use.js deleted file mode 100644 index 2883424..0000000 --- a/test2/view.use.js +++ /dev/null @@ -1,60 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var View = App.View; -var view; - -describe('view.use', function() { - beforeEach(function() { - view = new View(); - }); - - it('should expose the instance to `use`:', function(done) { - view.use(function(inst) { - assert(inst instanceof View); - done(); - }); - }); - - it('should be chainable:', function(done) { - view.use(function(inst) { - assert(inst instanceof View); - }) - .use(function(inst) { - assert(inst instanceof View); - }) - .use(function(inst) { - assert(inst instanceof View); - done(); - }); - }); - - it('should expose the view to a plugin:', function() { - view.use(function(view) { - assert(view instanceof View); - view.foo = function(str) { - return str + ' ' + 'bar'; - }; - }); - assert(view.foo('foo') === 'foo bar'); - }); - - it('should be chainable:', function() { - view - .use(function(view) { - view.a = 'aaa'; - }) - .use(function(view) { - view.b = 'bbb'; - }) - .use(function(view) { - view.c = 'ccc'; - }); - - assert(view.a === 'aaa'); - assert(view.b === 'bbb'); - assert(view.c === 'ccc'); - }); -}); diff --git a/test2/viewTypes.js b/test2/viewTypes.js deleted file mode 100644 index b96115e..0000000 --- a/test2/viewTypes.js +++ /dev/null @@ -1,52 +0,0 @@ -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('viewType', function() { - describe('view types', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - }); - - it('should add collection (plural) to the `viewTypes` object', function() { - app.viewTypes = []; // reset - app.create('foo', {viewType: 'layout'}); - app.create('bar', {viewType: 'layout'}); - assert.deepEqual(app.viewTypes.layout, [ 'foos', 'bars' ]); - - app.create('baz', {viewType: 'renderable'}); - assert.deepEqual(app.viewTypes.renderable, [ 'bazs' ]); - }); - - it('should add collection to the given viewType', function() { - app.create('layout', {viewType: 'layout'}); - assert(app.layouts.options.viewType[0] === 'layout'); - }); - - it('should return true if a collection has the given viewType', function() { - app.create('layout', {viewType: 'layout'}); - assert(app.layouts.isType('layout')); - assert(!app.layouts.isType('partial')); - }); - - it('should add types to the collection', function() { - app.create('layout', {viewType: 'layout'}); - app.layouts.viewType('partial'); - assert(app.layouts.options.viewType[0] === 'layout'); - assert(app.layouts.options.viewType[1] === 'partial'); - }); - - it('should add a collection to multiple viewTypes', function() { - app.create('foo', {viewType: ['layout', 'renderable']}); - assert.deepEqual(app.foos.options.viewType, ['layout', 'renderable']); - }); - - it('should expose viewType on `view`', function() { - app.create('foo', {viewType: ['layout', 'renderable']}); - var a = app.foo('a', {content: ''}); - assert.deepEqual(app.foos.options.viewType, a.options.viewType); - }); - }); -}); diff --git a/test2/views.js b/test2/views.js deleted file mode 100644 index f3f9c21..0000000 --- a/test2/views.js +++ /dev/null @@ -1,525 +0,0 @@ -require('mocha'); -require('should'); -var path = require('path'); -var assert = require('assert'); -var typeOf = require('kind-of'); -var support = require('./support'); -var isBuffer = require('is-buffer'); -var App = support.resolve(); -var List = App.List; -var View = App.View; -var Views = App.Views; -var collection; - -describe('views', function() { - describe('constructor', function() { - it('should create an instance of Views:', function() { - var collection = new Views(); - assert(collection instanceof Views); - }); - - it('should instantiate without `new`:', function() { - var collection = Views(); - assert(collection instanceof Views); - }); - }); - - describe('static methods', function() { - it('should expose `extend`:', function() { - assert(typeof Views.extend === 'function'); - }); - }); - - describe('prototype methods', function() { - beforeEach(function() { - collection = new Views(); - }); - - var methods = [ - 'use', - 'setView', - 'addView', - 'addViews', - 'addList', - 'getView', - 'constructor', - 'set', - 'get', - 'del', - 'define', - 'visit', - 'on', - 'once', - 'off', - 'emit', - 'listeners', - 'hasListeners' - ]; - - methods.forEach(function(method) { - it('should expose ' + method + ' method', function() { - assert(typeof collection[method] === 'function'); - }); - }); - - it('should expose isCollection property', function() { - assert(typeof collection.isCollection === 'boolean'); - }); - - it('should expose queue property', function() { - assert(Array.isArray(collection.queue)); - }); - - it('should expose views property', function() { - assert(typeOf(collection.views) === 'object'); - }); - - it('should expose options property', function() { - assert(typeOf(collection.options) === 'object'); - }); - }); - - describe('instance', function() { - beforeEach(function() { - collection = new Views(); - }); - - it('should set a value on the instance:', function() { - collection.set('a', 'b'); - assert(collection.a === 'b'); - }); - - it('should get a value from the instance:', function() { - collection.set('a', 'b'); - assert(collection.get('a') === 'b'); - }); - }); - - describe('option', function() { - beforeEach(function() { - collection = new Views(); - }); - - it('should set a key/value pair on options:', function() { - collection.option('a', 'b'); - assert(collection.options.a === 'b'); - }); - - it('should set an object on options:', function() { - collection.option({c: 'd'}); - assert(collection.options.c === 'd'); - }); - - it('should get an option:', function() { - collection.option({c: 'd'}); - var c = collection.option('c'); - assert(c === 'd'); - }); - }); - - describe('addView', function() { - beforeEach(function() { - collection = new Views(); - }); - - it('should throw an error when args are invalid:', function() { - (function() { - collection.addView(function() {}); - }).should.throw('expected value to be an object.'); - }); - - it('should add a view to `views`:', function() { - collection.addView('foo'); - collection.views.should.have.property('foo'); - - collection.addView('one', {content: '...'}); - assert(typeof collection.views.one === 'object'); - assert(isBuffer(collection.views.one.contents)); - }); - - it('should create an instance of `View`:', function() { - collection.addView('one', {content: '...'}); - assert(collection.views.one instanceof collection.View); - }); - - it('should allow an `View` constructor to be passed:', function() { - View.prototype.foo = function(key, value) { - this[key] = value; - }; - collection = new Views({View: View}); - collection.addView('one', {content: '...'}); - collection.views.one.foo('bar', 'baz'); - assert(collection.views.one.bar === 'baz'); - }); - - it('should allow an instance of `View` to be passed:', function() { - var collection = new Views({View: View}); - var view = new View({content: '...'}); - collection.addView('one', view); - view.set('abc', 'xyz'); - assert(collection.views.one instanceof collection.View); - assert(isBuffer(collection.views.one.contents)); - assert(collection.views.one.abc === 'xyz'); - }); - - it('should expose the `isType` method on items', function() { - var collection = new Views({View: View}); - var view = new View({content: '...'}); - collection.setView('one', view); - - var one = collection.getView('one'); - assert(one.isType('renderable')); - }); - - it('should set viewTypes on a collection', function() { - var collection = new Views({View: View}); - collection.viewType(['partial']); - - var view = new View({content: '...'}); - collection.setView('one', view); - - var one = collection.getView('one'); - assert(!one.isType('renderable')); - assert(one.isType('partial')); - }); - }); - - describe('addViews', function() { - beforeEach(function() { - collection = new Views(); - }); - - it('should emit an error if a string glob pattern is passed', function(done) { - try { - collection.addViews('*.js'); - done(new Error('expected an error')); - } catch (err) { - assert(err); - assert(err.message); - assert(/glob/.test(err.message)); - done(); - } - }); - - it('should emit an error if an array glob pattern is passed', function(done) { - try { - collection.addViews(['*.js']); - done(new Error('expected an error')); - } catch (err) { - assert(err); - assert(err.message); - assert(/glob/.test(err.message)); - done(); - } - }); - - it('should add multiple views:', function() { - collection.addViews({ - one: {content: 'foo'}, - two: {content: 'bar'} - }); - assert(isBuffer(collection.views.one.contents)); - assert(isBuffer(collection.views.two.contents)); - }); - - it('should return the collection instance for chaining:', function() { - var views = collection.addViews({ - one: {content: 'foo'}, - two: {content: 'bar'} - }); - - var view = views.getView('one'); - assert(view); - assert(view.content); - assert(view.content === 'foo'); - }); - - it('should create views from an instance of Views', function() { - collection.addViews({ - one: {content: 'foo'}, - two: {content: 'bar'} - }); - var pages = new Views(collection); - assert(isBuffer(pages.views.one.contents)); - assert(isBuffer(pages.views.two.contents)); - }); - - it('should add an array of views:', function() { - collection.addViews([ - {path: 'one', content: 'foo'}, - {path: 'two', content: 'bar'} - ]); - assert(isBuffer(collection.views.one.contents)); - assert(isBuffer(collection.views.two.contents)); - }); - }); - - describe('view', function() { - beforeEach(function() { - collection = new Views(); - }); - - it('should return a single collection view from a key-value pair', function() { - var one = collection.view('one', {content: 'foo'}); - var two = collection.view('two', {content: 'bar'}); - - assert(one.isView); - assert(one.path === 'one'); - assert(two.isView); - assert(two.path === 'two'); - }); - - it('should return a single collection view from an object', function() { - var one = collection.view({path: 'one', content: 'foo'}); - var two = collection.view({path: 'two', content: 'bar'}); - - assert(one.isView); - assert(one.path === 'one'); - assert(two.isView); - assert(two.path === 'two'); - }); - }); - - describe('addList', function() { - beforeEach(function() { - collection = new Views(); - }); - - it('should emit an error if a string glob pattern is passed', function(done) { - try { - collection.addList('*.js'); - done(new Error('expected an error')); - } catch (err) { - assert(err); - assert(err.message); - assert(/glob/.test(err.message)); - done(); - } - }); - - it('should emit an error if an array glob pattern is passed', function(done) { - try { - collection.addList(['*.js']); - done(new Error('expected an error')); - } catch (err) { - assert(err); - assert(err.message); - assert(/glob/.test(err.message)); - done(); - } - }); - - it('should add a list of views:', function() { - collection.addList([ - {path: 'one', content: 'foo'}, - {path: 'two', content: 'bar'} - ]); - assert(isBuffer(collection.views.one.contents)); - assert(isBuffer(collection.views.two.contents)); - }); - - it('should add a list from the constructor:', function() { - var list = new List([ - {path: 'one', content: 'foo'}, - {path: 'two', content: 'bar'} - ]); - - collection = new Views(list); - assert(isBuffer(collection.views.one.contents)); - assert(isBuffer(collection.views.two.contents)); - }); - - it('should add list items from the constructor:', function() { - var list = new List([ - {path: 'one', content: 'foo'}, - {path: 'two', content: 'bar'} - ]); - - collection = new Views(list.items); - assert(isBuffer(collection.views.one.contents)); - assert(isBuffer(collection.views.two.contents)); - }); - - it('should throw an error when list is not an array:', function() { - var views = new Views(); - (function() { - views.addList(); - }).should.throw('expected list to be an array.'); - - (function() { - views.addList({}); - }).should.throw('expected list to be an array.'); - - (function() { - views.addList('foo'); - }).should.throw('expected list to be an array.'); - }); - - it('should load an array of items from an event:', function() { - var pages = new Views(); - - pages.on('addList', function(list) { - while (list.length) { - pages.addView({path: list.pop()}); - } - this.loaded = true; - }); - - pages.addList(['a.txt', 'b.txt', 'c.txt']); - assert(pages.views.hasOwnProperty('a.txt')); - assert(pages.views['a.txt'].path === 'a.txt'); - }); - - it('should load an array of items from the addList callback:', function() { - var collection = new Views(); - - collection.addList(['a.txt', 'b.txt', 'c.txt'], function(fp) { - return {path: fp}; - }); - assert(collection.views.hasOwnProperty('a.txt')); - assert(collection.views['a.txt'].path === 'a.txt'); - }); - - it('should load an object of views from an event:', function() { - var collection = new Views(); - - collection.on('addViews', function(views) { - for (var key in views) { - collection.addView('foo/' + key, views[key]); - delete views[key]; - } - }); - - collection.addViews({ - a: {path: 'a.txt'}, - b: {path: 'b.txt'}, - c: {path: 'c.txt'} - }); - - assert(collection.views.hasOwnProperty('foo/a')); - assert(collection.views['foo/a'].path === 'a.txt'); - }); - - it('should signal `loaded` when finished:', function() { - var collection = new Views(); - - collection.on('addViews', function(views) { - for (var key in views) { - if (key === 'c') break; - collection.addView('foo/' + key, views[key]); - } - }); - - collection.addViews({ - a: {path: 'a.txt'}, - b: {path: 'b.txt'}, - c: {path: 'c.txt'} - }); - - assert(collection.views.hasOwnProperty('foo/a')); - assert(!collection.views.hasOwnProperty('foo/c')); - assert(collection.views['foo/a'].path === 'a.txt'); - }); - }); - - describe('getView', function() { - beforeEach(function() { - collection = new Views(); - }); - it('should get a view from `views`:', function() { - collection.addView('one', {content: 'aaa'}); - collection.addView('two', {content: 'zzz'}); - assert(isBuffer(collection.views.one.contents)); - assert(isBuffer(collection.getView('one').contents)); - assert(collection.getView('one').contents.toString() === 'aaa'); - assert(collection.getView('two').contents.toString() === 'zzz'); - }); - }); - - describe('count', function() { - beforeEach(function() { - collection = new Views(); - }); - - it('should get the number of views:', function() { - collection.addView('one', {content: 'aaa'}); - collection.addView('two', {content: 'zzz'}); - assert(Object.keys(collection.views).length === 2); - }); - }); -}); - -describe('options', function() { - describe('options.renameKey', function() { - beforeEach(function() { - collection = new Views({ - renameKey: function(key) { - return path.basename(key); - } - }); - }); - - it('should use a custom rename key function on view keys', function() { - collection.addView('a/b/c/d.hbs', {content: 'foo bar baz'}); - assert(collection.views['d.hbs'].contents.toString() === 'foo bar baz'); - }); - - it('should get a view with the renamed key:', function() { - collection.addView('a/b/c/d.hbs', {content: 'foo bar baz'}); - assert(collection.getView('d.hbs').contents.toString() === 'foo bar baz'); - }); - - it('should get a view with the original key:', function() { - collection.addView('a/b/c/d.hbs', {content: 'foo bar baz'}); - assert(collection.getView('a/b/c/d.hbs').contents.toString() === 'foo bar baz'); - }); - }); -}); - -describe('queue', function() { - beforeEach(function() { - collection = new Views(); - }); - - it('should emit arguments on addView', function(done) { - collection.on('addView', function(args) { - assert(args[0] === 'a'); - assert(args[1] === 'b'); - assert(args[2] === 'c'); - assert(args[3] === 'd'); - assert(args[4] === 'e'); - done(); - }); - - collection.addView('a', 'b', 'c', 'd', 'e'); - }); - - it('should expose the `queue` property for loading views', function() { - collection.queue.push(collection.view('b', {path: 'b'})); - - collection.addView('a', {path: 'a'}); - assert(collection.views.hasOwnProperty('a')); - assert(collection.views.hasOwnProperty('b')); - }); - - it('should load all views on the queue when addView is called', function() { - collection.on('addView', function(args) { - var len = args.length; - var last = args[len - 1]; - if (typeof last === 'string') { - args[len - 1] = { content: last }; - } - }); - - collection.addView('a.html', 'aaa'); - collection.addView('b.html', 'bbb'); - collection.addView('c.html', 'ccc'); - - assert(collection.views.hasOwnProperty('a.html')); - assert(collection.getView('a.html').content === 'aaa'); - assert(collection.views.hasOwnProperty('b.html')); - assert(collection.getView('b.html').content === 'bbb'); - assert(collection.views.hasOwnProperty('c.html')); - assert(collection.getView('c.html').content === 'ccc'); - }); -}); diff --git a/test2/views.use.js b/test2/views.use.js deleted file mode 100644 index 4329b31..0000000 --- a/test2/views.use.js +++ /dev/null @@ -1,156 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var Views = App.Views; -var View = App.View; -var collection; - -describe('views.use', function() { - beforeEach(function() { - collection = new Views(); - }); - - it('should expose the instance to `use`:', function(done) { - collection.use(function(inst) { - assert(inst instanceof Views); - done(); - }); - }); - - it('should be chainable:', function(done) { - collection.use(function(inst) { - assert(inst instanceof Views); - }) - .use(function(inst) { - assert(inst instanceof Views); - }) - .use(function(inst) { - assert(inst instanceof Views); - done(); - }); - }); - - it('should expose the collection to a plugin:', function() { - collection.use(function(views) { - assert(views instanceof Views); - views.foo = views.addView.bind(views); - }); - - collection.foo('a', {content: '...'}); - assert(collection.views.hasOwnProperty('a')); - }); - - it('should expose collection when chained:', function() { - collection - .use(function(views) { - assert(views instanceof Views); - views.foo = views.addView.bind(views); - }) - .use(function(views) { - assert(views instanceof Views); - views.bar = views.addView.bind(views); - }) - .use(function(views) { - assert(views instanceof Views); - views.baz = views.addView.bind(views); - }); - - var pages = collection; - - pages.foo({path: 'a', content: '...'}); - pages.bar({path: 'b', content: '...'}); - pages.baz({path: 'c', content: '...'}); - - assert(collection.views.hasOwnProperty('a')); - assert(collection.views.hasOwnProperty('b')); - assert(collection.views.hasOwnProperty('c')); - }); - - it('should work when a custom `View` constructor is passed:', function() { - collection = new Views({View: require('vinyl')}); - collection - .use(function(views) { - assert(views instanceof Views); - views.foo = views.addView.bind(views); - }) - .use(function(views) { - assert(views instanceof Views); - views.bar = views.addView.bind(views); - }) - .use(function(views) { - assert(views instanceof Views); - views.baz = views.addView.bind(views); - }); - - var pages = collection; - - pages.foo({path: 'a', content: '...'}); - pages.bar({path: 'b', content: '...'}); - pages.baz({path: 'c', content: '...'}); - - assert(collection.views.hasOwnProperty('a')); - assert(collection.views.hasOwnProperty('b')); - assert(collection.views.hasOwnProperty('c')); - }); - - it('should pass to view `use` if a function is returned:', function() { - collection.use(function(views) { - assert(views instanceof Views); - - return function(view) { - view.foo = views.addView.bind(views); - assert(view instanceof View); - }; - }); - - collection.addView('a', {content: '...'}) - .foo({path: 'b', content: '...'}) - .foo({path: 'c', content: '...'}) - .foo({path: 'd', content: '...'}); - - assert(collection.views.hasOwnProperty('a')); - assert(collection.views.hasOwnProperty('b')); - assert(collection.views.hasOwnProperty('c')); - assert(collection.views.hasOwnProperty('d')); - }); - - it('should be chainable when a view function is returned:', function() { - collection - .use(function(views) { - assert(views instanceof Views); - - return function(view) { - view.foo = views.addView.bind(views); - assert(view instanceof View); - }; - }) - .use(function(views) { - assert(views instanceof Views); - - return function(view) { - view.bar = views.addView.bind(views); - assert(view instanceof View); - }; - }) - .use(function(views) { - assert(views instanceof Views); - - return function(view) { - view.baz = views.addView.bind(views); - assert(view instanceof View); - }; - }); - - collection.addView('a', {content: '...'}) - .foo({path: 'b', content: '...'}) - .bar({path: 'c', content: '...'}) - .baz({path: 'd', content: '...'}); - - assert(collection.views.hasOwnProperty('a')); - assert(collection.views.hasOwnProperty('b')); - assert(collection.views.hasOwnProperty('c')); - assert(collection.views.hasOwnProperty('d')); - }); -}); From 17238776abcfc93115028680567e4f7b980a3f1a Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sun, 10 Jan 2016 22:36:29 -0500 Subject: [PATCH 173/274] move `renderFile` to plugin in updatefile. adds `rename` function and pipeline --- updatefile.js | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/updatefile.js b/updatefile.js index 93f1917..b9f4e61 100644 --- a/updatefile.js +++ b/updatefile.js @@ -80,6 +80,8 @@ module.exports = function(update, base, env) { }); }); + update.plugin('render', update.renderFile.bind(update, 'text')); + /** * Default configuration settings */ @@ -106,27 +108,43 @@ module.exports = function(update, base, env) { */ update.task('write', function() { + var plugins = update.get('argv.plugins'); + var dest = update.get('argv.dest'); var data = update.get('answers'); + return update.toStream('files') - // .pipe(update.renderFile('text', data)) - // .pipe(update.dest(rename(dest))); + .pipe(base.pipeline(plugins)) + .pipe(update.dest(rename(dest))); }); - /** - * Generate a new project - */ - - update.task('updaters', ['files']); - /** * Default task to be run */ - update.task('default', function(cb) { - update.build('files', cb); - }); + update.task('default', ['files', 'write']); }; +/** + * First init questions to be asked + */ + function forceQuestions(update) { update.questions.options.forceAll = true; } + +/** + * Rename template files + */ + +function rename(dest) { + return function(file) { + if (/\/templates\//.test(file.path)) { + file.basename = file.basename.replace(/^_/, '.'); + file.basename = file.basename.replace(/^\$/, ''); + } + + file.base = file.dest || dest || path.dirname(file.path); + file.path = path.join(file.base, file.basename); + return file.base; + }; +} From b1e9907d56ac16dfa820da7570bc56e693544bf7 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Wed, 13 Jan 2016 09:50:54 -0500 Subject: [PATCH 174/274] lint tests --- test/app.collection.compile.js | 2 + test/app.collection.js | 24 ++++++--- test/app.compile.js | 2 + test/app.copy.js | 2 + test/app.create.js | 2 + test/app.data.js | 2 + test/app.dest.js | 2 +- test/app.events.js | 2 + test/app.get-set.js | 2 + test/app.handle.js | 2 + test/app.handlers.js | 2 + test/app.js | 2 + test/app.list.compile.js | 2 + test/app.list.js | 15 +++++- test/app.lookups.js | 2 + test/app.middleware.js | 2 + test/app.onLoad.js | 2 + test/app.option.js | 2 + test/app.render.js | 2 + test/app.route.js | 2 + test/app.symlink.js | 2 + test/app.use.js | 2 + test/app.view.compile.js | 2 + test/app.view.render.js | 2 + test/collection.engines.js | 2 + test/collection.events.js | 2 + test/collection.js | 2 + test/collection.options.js | 2 + test/collection.render.js | 2 + test/collection.use.js | 2 + test/generator.task.js | 2 + test/generators.js | 2 + test/item.js | 2 + test/layouts.js | 2 + test/list.js | 2 + test/list.render.js | 2 + test/list.use.js | 2 + test/mergePartials.js | 2 + test/partials.js | 2 + test/questions.js | 2 + test/renameKey.js | 2 + test/render.js | 2 + test/routes.js | 2 + test/update.compose.js | 14 +++-- test/update.config.js | 99 ++++++++++++++++++++++++++++++++++ test/update.extendGenerator.js | 10 ++-- test/update.generator.js | 2 + test/update.getGenerator.js | 2 + test/update.js | 12 ++--- test/update.process.js | 2 +- test/update.register.js | 2 + test/update.registerPath.js | 2 + test/view.content.js | 2 + test/view.events.js | 2 + test/view.js | 2 + test/view.methods.js | 2 + test/view.option.js | 2 + test/view.render.js | 2 + test/view.set.js | 2 + test/view.use.js | 2 + test/viewTypes.js | 2 + test/views.js | 2 + test/views.use.js | 2 + 63 files changed, 262 insertions(+), 26 deletions(-) create mode 100644 test/update.config.js diff --git a/test/app.collection.compile.js b/test/app.collection.compile.js index 1ceed4e..12dfe9c 100644 --- a/test/app.collection.compile.js +++ b/test/app.collection.compile.js @@ -1,3 +1,5 @@ +'use strict'; + require('should'); var assert = require('assert'); var support = require('./support'); diff --git a/test/app.collection.js b/test/app.collection.js index db5c65d..4795756 100644 --- a/test/app.collection.js +++ b/test/app.collection.js @@ -3,6 +3,7 @@ require('mocha'); require('should'); var fs = require('fs'); +var path = require('path'); var assert = require('assert'); var define = require('define-property'); var support = require('./support'); @@ -52,8 +53,15 @@ describe('collection', function() { }); it('should load a view onto the respective collection:', function() { + app.pages.use(function() { + return function(file) { + file.content = fs.readFileSync(file.path, 'utf8'); + } + }); + app.pages('test/fixtures/pages/a.hbs'); - app.views.pages.should.have.property('test/fixtures/pages/a.hbs'); + var page = app.pages.getView('test/fixtures/pages/a.hbs'); + assert(app.pages.getView('a.hbs')); }); it('should allow collection methods to be chained:', function() { @@ -63,9 +71,9 @@ describe('collection', function() { .pages('test/fixtures/pages/c.hbs'); app.views.pages.should.have.properties([ - 'test/fixtures/pages/a.hbs', - 'test/fixtures/pages/b.hbs', - 'test/fixtures/pages/c.hbs' + path.resolve('test/fixtures/pages/a.hbs'), + path.resolve('test/fixtures/pages/b.hbs'), + path.resolve('test/fixtures/pages/c.hbs') ]); }); @@ -77,9 +85,9 @@ describe('collection', function() { app.pages.options.should.have.property('foo', 'bar'); app.views.pages.should.have.properties([ - 'test/fixtures/pages/a.hbs', - 'test/fixtures/pages/b.hbs', - 'test/fixtures/pages/c.hbs' + path.resolve('test/fixtures/pages/a.hbs'), + path.resolve('test/fixtures/pages/b.hbs'), + path.resolve('test/fixtures/pages/c.hbs') ]); }); @@ -166,7 +174,7 @@ describe('collection singular method', function() { it('should add a view to the created collection:', function() { app.page('test/fixtures/pages/a.hbs'); - assert(typeof app.views.pages['test/fixtures/pages/a.hbs'] === 'object'); + assert(typeof app.pages.getView('test/fixtures/pages/a.hbs') === 'object'); }); it('should expose the `option` method:', function() { diff --git a/test/app.compile.js b/test/app.compile.js index 090676e..a317fee 100644 --- a/test/app.compile.js +++ b/test/app.compile.js @@ -1,3 +1,5 @@ +'use strict'; + require('should'); var assert = require('assert'); var support = require('./support'); diff --git a/test/app.copy.js b/test/app.copy.js index cf18b8c..9fb0813 100644 --- a/test/app.copy.js +++ b/test/app.copy.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); var path = require('path'); var assert = require('assert'); diff --git a/test/app.create.js b/test/app.create.js index 99b6ed7..e7a2f4e 100644 --- a/test/app.create.js +++ b/test/app.create.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var fs = require('fs'); diff --git a/test/app.data.js b/test/app.data.js index f3a1992..41808b6 100644 --- a/test/app.data.js +++ b/test/app.data.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var path = require('path'); diff --git a/test/app.dest.js b/test/app.dest.js index ff285e8..8e59d82 100644 --- a/test/app.dest.js +++ b/test/app.dest.js @@ -861,6 +861,7 @@ describe('dest stream', function() { var inputRelativeSymlinkPath = 'wow'; var expectedPath = path.join(__dirname, 'actual/test-create-dir-symlink'); + var buffered = []; var inputFile = new File({ base: inputBase, @@ -882,7 +883,6 @@ describe('dest stream', function() { var stream = app.dest('./actual/', {cwd: __dirname}); - var buffered = []; bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); stream.pipe(bufferStream); stream.write(inputFile); diff --git a/test/app.events.js b/test/app.events.js index bae16fe..4086a78 100644 --- a/test/app.events.js +++ b/test/app.events.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var assert = require('assert'); diff --git a/test/app.get-set.js b/test/app.get-set.js index 8e8a597..fd967aa 100644 --- a/test/app.get-set.js +++ b/test/app.get-set.js @@ -1,3 +1,5 @@ +'use strict'; + require('should'); var assert = require('assert'); var support = require('./support'); diff --git a/test/app.handle.js b/test/app.handle.js index d27d82d..bf0d16c 100644 --- a/test/app.handle.js +++ b/test/app.handle.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var assert = require('assert'); diff --git a/test/app.handlers.js b/test/app.handlers.js index f2b94d2..c0c0d2e 100644 --- a/test/app.handlers.js +++ b/test/app.handlers.js @@ -1,3 +1,5 @@ +'use strict'; + require('should'); var fs = require('fs'); var path = require('path'); diff --git a/test/app.js b/test/app.js index 5ff835b..f575266 100644 --- a/test/app.js +++ b/test/app.js @@ -1,3 +1,5 @@ +'use strict'; + /* deps: coveralls istanbul */ require('mocha'); require('should'); diff --git a/test/app.list.compile.js b/test/app.list.compile.js index 1efa791..2efa223 100644 --- a/test/app.list.compile.js +++ b/test/app.list.compile.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var assert = require('assert'); diff --git a/test/app.list.js b/test/app.list.js index 22337b8..8e40af0 100644 --- a/test/app.list.js +++ b/test/app.list.js @@ -3,6 +3,7 @@ require('mocha'); require('should'); var fs = require('fs'); +var path = require('path'); var assert = require('assert'); var support = require('./support'); var App = support.resolve(); @@ -34,14 +35,24 @@ describe('list', function() { beforeEach(function() { app = new App(); app.engine('tmpl', require('engine-base')); - app.create('pages'); + app.create('pages') + .use(function(views) { + var fn = views.getView; + views.getView = function(name) { + var view = fn.apply(this, arguments); + if (!view && fs.existsSync(path.resolve(name))) { + view = this.addView(name, {content: fs.readFileSync(name)}); + } + return view; + }; + }); }); it('should add an item to a list:', function() { app.pages('test/fixtures/pages/a.hbs'); var list = app.list(); list.addItem(app.pages.getView('test/fixtures/pages/a.hbs')); - assert(list.hasItem('test/fixtures/pages/a.hbs')); + assert(list.hasItem('a.hbs')); }); it('should expose the `option` method from a list:', function() { diff --git a/test/app.lookups.js b/test/app.lookups.js index afab53b..b64a593 100644 --- a/test/app.lookups.js +++ b/test/app.lookups.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var fs = require('fs'); diff --git a/test/app.middleware.js b/test/app.middleware.js index e32ca1a..be37e7e 100644 --- a/test/app.middleware.js +++ b/test/app.middleware.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var assert = require('assert'); diff --git a/test/app.onLoad.js b/test/app.onLoad.js index 66bffbe..6788597 100644 --- a/test/app.onLoad.js +++ b/test/app.onLoad.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var assert = require('assert'); diff --git a/test/app.option.js b/test/app.option.js index c4043e7..ec41782 100644 --- a/test/app.option.js +++ b/test/app.option.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var assert = require('assert'); diff --git a/test/app.render.js b/test/app.render.js index 98c194c..7e26e74 100644 --- a/test/app.render.js +++ b/test/app.render.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var assert = require('assert'); diff --git a/test/app.route.js b/test/app.route.js index 213dd56..620a021 100644 --- a/test/app.route.js +++ b/test/app.route.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var assert = require('assert'); diff --git a/test/app.symlink.js b/test/app.symlink.js index 8a63fea..d84e95b 100644 --- a/test/app.symlink.js +++ b/test/app.symlink.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); var should = require('should'); var fs = require('graceful-fs'); diff --git a/test/app.use.js b/test/app.use.js index 6ca99ac..3b6fff8 100644 --- a/test/app.use.js +++ b/test/app.use.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var assert = require('assert'); diff --git a/test/app.view.compile.js b/test/app.view.compile.js index 4dca5cd..7616ce2 100644 --- a/test/app.view.compile.js +++ b/test/app.view.compile.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var assert = require('assert'); diff --git a/test/app.view.render.js b/test/app.view.render.js index 1cfd929..ccf5978 100644 --- a/test/app.view.render.js +++ b/test/app.view.render.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var assert = require('assert'); diff --git a/test/collection.engines.js b/test/collection.engines.js index 3465c9e..3ebaf56 100644 --- a/test/collection.engines.js +++ b/test/collection.engines.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var assert = require('assert'); diff --git a/test/collection.events.js b/test/collection.events.js index bdf9eaf..5abab92 100644 --- a/test/collection.events.js +++ b/test/collection.events.js @@ -1,3 +1,5 @@ +'use strict'; + require('should'); var support = require('./support'); var App = support.resolve(); diff --git a/test/collection.js b/test/collection.js index 7c0982c..d1c8891 100644 --- a/test/collection.js +++ b/test/collection.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var path = require('path'); diff --git a/test/collection.options.js b/test/collection.options.js index d1af0af..17f6b51 100644 --- a/test/collection.options.js +++ b/test/collection.options.js @@ -1,3 +1,5 @@ +'use strict'; + require('should'); var support = require('./support'); var App = support.resolve(); diff --git a/test/collection.render.js b/test/collection.render.js index 896c805..04ed5a9 100644 --- a/test/collection.render.js +++ b/test/collection.render.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var async = require('async'); diff --git a/test/collection.use.js b/test/collection.use.js index f09bd86..9c9400d 100644 --- a/test/collection.use.js +++ b/test/collection.use.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var assert = require('assert'); diff --git a/test/generator.task.js b/test/generator.task.js index 474222f..a40b927 100644 --- a/test/generator.task.js +++ b/test/generator.task.js @@ -1,3 +1,5 @@ +'use strict'; + var assert = require('assert'); var App = require('..'); var app; diff --git a/test/generators.js b/test/generators.js index 5fa8da2..93faef8 100644 --- a/test/generators.js +++ b/test/generators.js @@ -1,3 +1,5 @@ +'use strict'; + var assert = require('assert'); var Update = require('..'); var app; diff --git a/test/item.js b/test/item.js index ca0f435..1cc936d 100644 --- a/test/item.js +++ b/test/item.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); var should = require('should'); var fs = require('fs'); diff --git a/test/layouts.js b/test/layouts.js index 76f65bc..fda29a8 100644 --- a/test/layouts.js +++ b/test/layouts.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var assert = require('assert'); diff --git a/test/list.js b/test/list.js index 8f9cf5d..a814777 100644 --- a/test/list.js +++ b/test/list.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var path = require('path'); diff --git a/test/list.render.js b/test/list.render.js index 7b226a8..c7d41f8 100644 --- a/test/list.render.js +++ b/test/list.render.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var async = require('async'); diff --git a/test/list.use.js b/test/list.use.js index 5ac2c1b..2802a75 100644 --- a/test/list.use.js +++ b/test/list.use.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var assert = require('assert'); diff --git a/test/mergePartials.js b/test/mergePartials.js index 38f23be..b00a14a 100644 --- a/test/mergePartials.js +++ b/test/mergePartials.js @@ -1,3 +1,5 @@ +'use strict'; + require('should'); var support = require('./support'); var App = support.resolve(); diff --git a/test/partials.js b/test/partials.js index b5fbb94..9dea3b1 100644 --- a/test/partials.js +++ b/test/partials.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var assert = require('assert'); diff --git a/test/questions.js b/test/questions.js index 4318e64..e58be5e 100644 --- a/test/questions.js +++ b/test/questions.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var fs = require('fs'); diff --git a/test/renameKey.js b/test/renameKey.js index 94b3ab2..801c1c2 100644 --- a/test/renameKey.js +++ b/test/renameKey.js @@ -1,3 +1,5 @@ +'use strict'; + var path = require('path'); var support = require('./support'); var App = support.resolve(); diff --git a/test/render.js b/test/render.js index 2ab9280..aae8d0e 100644 --- a/test/render.js +++ b/test/render.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var assert = require('assert'); diff --git a/test/routes.js b/test/routes.js index af66b93..e5ff13a 100644 --- a/test/routes.js +++ b/test/routes.js @@ -1,3 +1,5 @@ +'use strict'; + require('should'); var assert = require('assert'); var support = require('./support'); diff --git a/test/update.compose.js b/test/update.compose.js index 2e14954..ec39d79 100644 --- a/test/update.compose.js +++ b/test/update.compose.js @@ -1,3 +1,5 @@ +'use strict'; + /* deps: coveralls istanbul */ require('mocha'); require('should'); @@ -14,8 +16,10 @@ describe('update.compose', function() { it('should throw an error when trying to compose an instance', function(cb) { var foo = new Update({name: 'foo'}); + delete foo.fn; + try { - update.compose(foo); + update.compose(foo, 'foo'); cb(new Error('Expected an error.')); } catch (err) { assert.equal(err.message, 'generators must export a function to extend other generators'); @@ -42,9 +46,9 @@ describe('update.compose', function() { bar.tasks.should.not.have.property('foo'); foo.tasks.should.not.have.property('bar'); - foo.compose(bar); + foo.compose(bar, 'foo'); bar.tasks.should.have.property('foo'); - bar.compose(foo); + bar.compose(foo, 'bar'); foo.tasks.should.have.property('bar'); }); @@ -67,9 +71,9 @@ describe('update.compose', function() { bar.tasks.should.not.have.property('foo'); foo.tasks.should.not.have.property('bar'); - update.compose('foo', bar); + update.compose(bar, 'foo'); bar.tasks.should.have.property('foo'); - update.compose('bar', foo); + update.compose(foo, 'bar'); foo.tasks.should.have.property('bar'); }); }); diff --git a/test/update.config.js b/test/update.config.js new file mode 100644 index 0000000..695c730 --- /dev/null +++ b/test/update.config.js @@ -0,0 +1,99 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var support = require('./support'); +var Update = support.resolve(); +var update; + +describe('.config', function() { + beforeEach(function() { + update = new Update(); + }); + + // describe('loading config', function() { + // it('should map "plugins" when app.config exists', function() { + // app.use(options()); + // app.use(config()); + // app.use(pipeline()); + // assert(app.config.config.hasOwnProperty('plugins')); + // }); + + // it('should register plugin functions from config', function() { + // app.use(options()); + // app.use(config()); + // app.use(pipeline()); + // var args = { + // plugins: { + // a: function() {}, + // b: function() {}, + // c: function() {}, + // } + // }; + // app.config.process(args); + // assert(app.plugins.hasOwnProperty('a')); + // assert(app.plugins.hasOwnProperty('b')); + // assert(app.plugins.hasOwnProperty('c')); + // }); + + // it('should register plugins from config paths', function() { + // app.use(options()); + // app.use(config()); + // app.use(pipeline()); + // var args = { + // plugins: { + // a: 'test/fixtures/plugins/a.js', + // b: 'test/fixtures/plugins/b.js', + // c: 'test/fixtures/plugins/c.js', + // } + // }; + + // app.config.process(args); + // assert(app.plugins.hasOwnProperty('a')); + // assert(app.plugins.hasOwnProperty('b')); + // assert(app.plugins.hasOwnProperty('c')); + + // assert(typeof app.plugins.a === 'function'); + // assert(typeof app.plugins.b === 'function'); + // assert(typeof app.plugins.c === 'function'); + // }); + + // it('should register plugins with keys as paths', function() { + // app.use(options()); + // app.use(config()); + // app.use(pipeline()); + // var args = { + // plugins: { + // 'test/fixtures/plugins/a.js': {aaa: 'bbb'}, + // 'test/fixtures/plugins/b.js': {bbb: 'ccc'}, + // 'test/fixtures/plugins/c.js': {ddd: 'eee'} + // } + // }; + + // app.config.process(args); + // assert(app.plugins.hasOwnProperty('a')); + // assert(app.plugins.hasOwnProperty('b')); + // assert(app.plugins.hasOwnProperty('c')); + + // assert(typeof app.plugins.a === 'function'); + // assert(typeof app.plugins.b === 'function'); + // assert(typeof app.plugins.c === 'function'); + // }); + + // it('should throw an error when invalid config is used', function(cb) { + // app.use(options()); + // app.use(config()); + // app.use(pipeline()); + // var args = {plugins: {'test/fixtures/plugins/a.js': null}}; + // try { + // app.config.process(args); + // cb(new Error('expected an error')); + // } catch (err) { + // assert(err); + // assert(err.message); + // assert(/configuration/.test(err.message)); + // cb(); + // } + // }); + // }); +}); diff --git a/test/update.extendGenerator.js b/test/update.extendGenerator.js index f418bd5..97586e5 100644 --- a/test/update.extendGenerator.js +++ b/test/update.extendGenerator.js @@ -1,3 +1,5 @@ +'use strict'; + /* deps: coveralls istanbul */ require('mocha'); require('should'); @@ -12,14 +14,16 @@ describe('update.extendGenerator', function() { update = new Update(); }); - it('should throw an error when trying to extend an instance', function(done) { + it('should throw an error when trying to extend an instance', function(cb) { var foo = new Update({name: 'foo'}); + delete foo.fn; + try { update.extendGenerator(foo); - done(new Error('Expected an error.')); + cb(new Error('Expected an error.')); } catch (err) { err.message.should.equal('generators must export a function to extend other generators'); - done(); + cb(); } }); diff --git a/test/update.generator.js b/test/update.generator.js index ba213ca..a1d5848 100644 --- a/test/update.generator.js +++ b/test/update.generator.js @@ -1,3 +1,5 @@ +'use strict'; + /* deps: coveralls istanbul */ require('mocha'); require('should'); diff --git a/test/update.getGenerator.js b/test/update.getGenerator.js index 1f868b7..28fa40e 100644 --- a/test/update.getGenerator.js +++ b/test/update.getGenerator.js @@ -1,3 +1,5 @@ +'use strict'; + var assert = require('assert'); var Update = require('..'); var update; diff --git a/test/update.js b/test/update.js index 51b7630..c390a80 100644 --- a/test/update.js +++ b/test/update.js @@ -90,19 +90,19 @@ describe('update', function() { update = new Update(); }); - it('should set `name` to `update` when `_name` is defined', function() { - assert.equal(update.name, 'update'); + it('should set `name` to `base` when `_name` is defined', function() { + assert.equal(update.name, 'base'); }); - it('should set `name` to `update` when `_name` is not defined', function() { + it('should set `name` to `base` when `_name` is not defined', function() { delete update._name; - assert.equal(update.name, 'update'); + assert.equal(update.name, 'base'); }); - it('should set `name` to `update` when `_appname` is not defined', function() { + it('should set `name` to `base` when `_appname` is not defined', function() { delete update._name; delete update._appname; - assert.equal(update.name, 'update'); + assert.equal(update.name, 'base'); }); it('should allow name setter to be set (configurable)', function() { diff --git a/test/update.process.js b/test/update.process.js index 6064edd..6d9c08f 100644 --- a/test/update.process.js +++ b/test/update.process.js @@ -31,7 +31,7 @@ function exists(name) { try { fs.statSync(output(name)); return true; - } catch(err) {} + } catch (err) {} return false; } diff --git a/test/update.register.js b/test/update.register.js index 0386fa4..069bcb8 100644 --- a/test/update.register.js +++ b/test/update.register.js @@ -1,3 +1,5 @@ +'use strict'; + /* deps: coveralls istanbul */ require('mocha'); require('should'); diff --git a/test/update.registerPath.js b/test/update.registerPath.js index bbbe211..835b2ba 100644 --- a/test/update.registerPath.js +++ b/test/update.registerPath.js @@ -1,3 +1,5 @@ +'use strict'; + /* deps: coveralls istanbul */ require('mocha'); require('should'); diff --git a/test/view.content.js b/test/view.content.js index 80ea113..a966e69 100644 --- a/test/view.content.js +++ b/test/view.content.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var fs = require('fs'); diff --git a/test/view.events.js b/test/view.events.js index 3956379..837b955 100644 --- a/test/view.events.js +++ b/test/view.events.js @@ -1,3 +1,5 @@ +'use strict'; + require('should'); var support = require('./support'); var App = support.resolve(); diff --git a/test/view.js b/test/view.js index 92ddf3c..753e4b8 100644 --- a/test/view.js +++ b/test/view.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); var should = require('should'); var fs = require('fs'); diff --git a/test/view.methods.js b/test/view.methods.js index 08f064e..fe49072 100644 --- a/test/view.methods.js +++ b/test/view.methods.js @@ -1,3 +1,5 @@ +'use strict'; + require('should'); var support = require('./support'); var App = support.resolve(); diff --git a/test/view.option.js b/test/view.option.js index 087399c..ebde730 100644 --- a/test/view.option.js +++ b/test/view.option.js @@ -1,3 +1,5 @@ +'use strict'; + require('should'); var support = require('./support'); var App = support.resolve(); diff --git a/test/view.render.js b/test/view.render.js index a7d6d71..cd367c5 100644 --- a/test/view.render.js +++ b/test/view.render.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var support = require('./support'); diff --git a/test/view.set.js b/test/view.set.js index 018df6e..dd5e658 100644 --- a/test/view.set.js +++ b/test/view.set.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var fs = require('fs'); diff --git a/test/view.use.js b/test/view.use.js index 2883424..d6fa379 100644 --- a/test/view.use.js +++ b/test/view.use.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var assert = require('assert'); diff --git a/test/viewTypes.js b/test/viewTypes.js index b96115e..866159b 100644 --- a/test/viewTypes.js +++ b/test/viewTypes.js @@ -1,3 +1,5 @@ +'use strict'; + var assert = require('assert'); var support = require('./support'); var App = support.resolve(); diff --git a/test/views.js b/test/views.js index f3f9c21..04cfd65 100644 --- a/test/views.js +++ b/test/views.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var path = require('path'); diff --git a/test/views.use.js b/test/views.use.js index 4329b31..cc9ef23 100644 --- a/test/views.use.js +++ b/test/views.use.js @@ -1,3 +1,5 @@ +'use strict'; + require('mocha'); require('should'); var assert = require('assert'); From ff85109d340c21ba3d07118619274ee75724921c Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 7 Jun 2016 20:03:27 -0400 Subject: [PATCH 175/274] refactor --- .eslintrc | 125 -- .eslintrc.json | 230 +--- .gitignore | 22 +- .travis.yml | 11 +- .verb.md | 214 +-- README.md | 314 ++--- bin/cli.js | 198 --- bin/errors.js | 5 - bin/update.js | 72 ++ docs/cli.md | 10 - docs/env.md | 10 - examples/eslint/updatefile.js | 19 + gulpfile.js | 22 +- index.js | 286 ++-- lib/cli.js | 238 +++- lib/commands.js | 11 + lib/commands/cwd.js | 14 - lib/commands/help.js | 104 -- lib/commands/init.js | 24 - lib/commands/open.js | 26 - lib/commands/remove.js | 46 + lib/commands/save.js | 11 - lib/commands/show.js | 32 - lib/config.js | 28 - lib/configfiles.js | 2 + lib/generator.js | 8 - lib/ignore.js | 78 -- lib/settings/index.js | 1 - lib/updatefile.js | 124 ++ lib/utils.js | 253 +--- lib2/config.js | 30 - lib2/locals.js | 26 - lib2/middleware/index.js | 1 - lib2/middleware/json.js | 22 - lib2/runner/argv.js | 55 - lib2/runner/decorate.js | 39 - lib2/runner/env.js | 53 - lib2/runner/list.js | 40 - lib2/runner/listen.js | 30 - lib2/runner/run.js | 46 - lib2/runner/runner.js | 107 -- lib2/runner/updater.js | 127 -- lib2/tasks/default.js | 5 - lib2/tasks/del.js | 17 - lib2/tasks/dest.js | 29 - lib2/tasks/files.js | 24 - lib2/tasks/index.js | 1 - lib2/tasks/lint.js | 18 - lib2/tasks/list.js | 10 - lib2/tasks/noop.js | 7 - lib2/tasks/rename.js | 65 - lib2/tasks/tree.js | 10 - lib2/utils.js | 329 ----- package.json | 193 +-- test/app.applyLayout.js | 87 -- test/app.collection.compile.js | 52 - test/app.collection.js | 185 --- test/app.collection.render.js | 157 --- test/app.compile.js | 54 - test/app.copy.js | 33 - test/app.create.js | 242 ---- test/app.data.js | 96 -- test/app.dest.js | 1103 ---------------- test/app.engines.js | 160 --- test/app.events.js | 115 -- test/app.get-set.js | 74 -- test/app.handle.js | 39 - test/app.handlers.js | 74 -- test/app.js | 132 -- test/app.list.compile.js | 46 - test/app.list.js | 117 -- test/app.lookups.js | 114 -- test/app.middleware.js | 62 - test/app.onLoad.js | 50 - test/app.option.js | 100 -- test/app.render.js | 90 -- test/app.renderFile.js | 135 -- test/app.route.js | 95 -- test/app.src.js | 295 ----- test/app.symlink.js | 398 ------ test/app.task.js | 159 --- test/app.toStream.js | 64 - test/app.use.js | 283 ---- test/app.view.compile.js | 40 - test/app.view.render.js | 94 -- test/app.watch.js | 42 - test/collection.engines.js | 178 --- test/collection.events.js | 29 - test/collection.getView.js | 34 - test/collection.js | 542 -------- test/collection.options.js | 27 - test/collection.render.js | 140 -- test/collection.use.js | 158 --- test/fixtures/a.txt | 1 - test/fixtures/b.txt | 1 - test/fixtures/bom-utf16be.txt | Bin 244 -> 0 bytes test/fixtures/bom-utf16le.txt | Bin 244 -> 0 bytes test/fixtures/bom-utf8.txt | 1 - test/fixtures/c.txt | 1 - test/fixtures/copy/example.txt | 1 - test/fixtures/data/a.json | 3 - test/fixtures/data/alert.json | 7 - test/fixtures/data/b.json | 3 - test/fixtures/data/c.json | 3 - test/fixtures/data/data.json | 3 - test/fixtures/data/test.json | 4 - test/fixtures/example.txt | 1 - .../front-matter/autodetect-no-lang.md | 5 - test/fixtures/front-matter/autodetect-yaml.md | 5 - test/fixtures/front-matter/lang-yaml.md | 5 - test/fixtures/generic/run.dmc | 1 - test/fixtures/generic/test.dmc | 1 - test/fixtures/helpers/a.js | 3 - test/fixtures/helpers/b.js | 3 - test/fixtures/helpers/c.js | 3 - test/fixtures/helpers/obj.js | 9 - test/fixtures/noext/license | 21 - test/fixtures/one/generator.js | 19 - test/fixtures/one/package.json | 30 - test/fixtures/one/templates/a.txt | 1 - test/fixtures/one/templates/x.txt | 1 - test/fixtures/one/templates/y.txt | 1 - test/fixtures/one/templates/z.txt | 1 - test/fixtures/pages/a.hbs | 2 - test/fixtures/pages/b.hbs | 2 - test/fixtures/pages/c.hbs | 2 - test/fixtures/posts/a.txt | 4 - test/fixtures/posts/b.txt | 4 - test/fixtures/posts/c.txt | 4 - test/fixtures/templates/a.tmpl | 1 - test/fixtures/templates/b.tmpl | 1 - test/fixtures/templates/c.tmpl | 1 - test/fixtures/test-symlink | 1 - test/fixtures/test-symlink-dir/suchempty | 1 - test/fixtures/test.coffee | 1 - test/fixtures/three/four/five/generator.js | 14 - test/fixtures/three/four/five/package.json | 30 - test/fixtures/three/four/five/templates/a.txt | 0 test/fixtures/three/four/five/templates/b.txt | 0 test/fixtures/three/four/five/templates/c.txt | 0 test/fixtures/three/four/generator.js | 14 - test/fixtures/three/four/package.json | 30 - test/fixtures/three/four/templates/a.txt | 0 test/fixtures/three/four/templates/b.txt | 0 test/fixtures/three/four/templates/c.txt | 0 test/fixtures/three/package.json | 30 - test/fixtures/three/templates/a.txt | 0 test/fixtures/three/templates/b.txt | 0 test/fixtures/three/templates/c.txt | 0 test/fixtures/three/updatefile.js | 14 - test/fixtures/two/package.json | 30 - test/fixtures/two/templates/a.txt | 0 test/fixtures/two/templates/b.txt | 0 test/fixtures/two/templates/c.txt | 0 test/fixtures/two/updatefile.js | 21 - test/fixtures/updaters-array.json | 3 + test/fixtures/updaters-object.json | 6 + test/fixtures/updaters/a.txt | 1 - test/fixtures/updaters/b.txt | 1 - test/fixtures/updaters/c.txt | 1 - test/fixtures/vinyl/bom-utf16be.txt | Bin 244 -> 0 bytes test/fixtures/vinyl/bom-utf16le.txt | Bin 244 -> 0 bytes test/fixtures/vinyl/bom-utf8.txt | 1 - test/fixtures/vinyl/test-symlink | 1 - test/fixtures/vinyl/test-symlink-dir | 1 - test/fixtures/vinyl/test.coffee | 1 - test/fixtures/vinyl/wow/suchempty | 1 - test/fixtures/watch/test.txt | 1 - test/fixtures/wow/suchempty | 1 - test/generator.task.js | 157 --- test/generators.js | 22 - test/group.js | 144 --- test/handlers.js | 46 - test/helpers.js | 800 ------------ test/item.js | 1062 --------------- test/layouts.js | 129 -- test/list.js | 691 ---------- test/list.render.js | 139 -- test/list.use.js | 158 --- test/mergePartials.js | 106 -- test/partials.js | 204 --- test/questions.js | 60 - test/renameKey.js | 352 ----- test/render.js | 72 -- test/routes.js | 100 -- test/store.js | 242 ---- test/support/ignore.js | 6 - test/support/index.js | 75 -- test/support/spy.js | 27 - test/update.compose.js | 79 -- test/update.config.js | 99 -- test/update.extendGenerator.js | 50 - test/update.generator.js | 39 - test/update.getGenerator.js | 47 - test/update.js | 135 -- test/update.pkg.js | 42 - test/update.process.js | 245 ---- test/update.register.js | 52 - test/update.registerPath.js | 30 - test/updaters.js | 10 + test/view.content.js | 31 - test/view.events.js | 30 - test/view.js | 1150 ----------------- test/view.methods.js | 41 - test/view.option.js | 31 - test/view.render.js | 54 - test/view.set.js | 36 - test/view.use.js | 62 - test/viewTypes.js | 54 - test/views.js | 527 -------- test/views.use.js | 158 --- updatefile.js | 150 --- 212 files changed, 1050 insertions(+), 16775 deletions(-) delete mode 100644 .eslintrc delete mode 100755 bin/cli.js delete mode 100644 bin/errors.js create mode 100755 bin/update.js delete mode 100644 docs/cli.md delete mode 100644 docs/env.md create mode 100644 examples/eslint/updatefile.js create mode 100644 lib/commands.js delete mode 100644 lib/commands/cwd.js delete mode 100644 lib/commands/help.js delete mode 100644 lib/commands/init.js delete mode 100644 lib/commands/open.js create mode 100644 lib/commands/remove.js delete mode 100644 lib/commands/save.js delete mode 100644 lib/commands/show.js delete mode 100644 lib/config.js create mode 100644 lib/configfiles.js delete mode 100644 lib/generator.js delete mode 100644 lib/ignore.js delete mode 100644 lib/settings/index.js create mode 100644 lib/updatefile.js delete mode 100644 lib2/config.js delete mode 100644 lib2/locals.js delete mode 100644 lib2/middleware/index.js delete mode 100644 lib2/middleware/json.js delete mode 100644 lib2/runner/argv.js delete mode 100644 lib2/runner/decorate.js delete mode 100644 lib2/runner/env.js delete mode 100644 lib2/runner/list.js delete mode 100644 lib2/runner/listen.js delete mode 100644 lib2/runner/run.js delete mode 100644 lib2/runner/runner.js delete mode 100644 lib2/runner/updater.js delete mode 100644 lib2/tasks/default.js delete mode 100644 lib2/tasks/del.js delete mode 100644 lib2/tasks/dest.js delete mode 100644 lib2/tasks/files.js delete mode 100644 lib2/tasks/index.js delete mode 100644 lib2/tasks/lint.js delete mode 100644 lib2/tasks/list.js delete mode 100644 lib2/tasks/noop.js delete mode 100644 lib2/tasks/rename.js delete mode 100644 lib2/tasks/tree.js delete mode 100644 lib2/utils.js delete mode 100644 test/app.applyLayout.js delete mode 100644 test/app.collection.compile.js delete mode 100644 test/app.collection.js delete mode 100644 test/app.collection.render.js delete mode 100644 test/app.compile.js delete mode 100644 test/app.copy.js delete mode 100644 test/app.create.js delete mode 100644 test/app.data.js delete mode 100644 test/app.dest.js delete mode 100644 test/app.engines.js delete mode 100644 test/app.events.js delete mode 100644 test/app.get-set.js delete mode 100644 test/app.handle.js delete mode 100644 test/app.handlers.js delete mode 100644 test/app.js delete mode 100644 test/app.list.compile.js delete mode 100644 test/app.list.js delete mode 100644 test/app.lookups.js delete mode 100644 test/app.middleware.js delete mode 100644 test/app.onLoad.js delete mode 100644 test/app.option.js delete mode 100644 test/app.render.js delete mode 100644 test/app.renderFile.js delete mode 100644 test/app.route.js delete mode 100644 test/app.src.js delete mode 100644 test/app.symlink.js delete mode 100644 test/app.task.js delete mode 100644 test/app.toStream.js delete mode 100644 test/app.use.js delete mode 100644 test/app.view.compile.js delete mode 100644 test/app.view.render.js delete mode 100644 test/app.watch.js delete mode 100644 test/collection.engines.js delete mode 100644 test/collection.events.js delete mode 100644 test/collection.getView.js delete mode 100644 test/collection.js delete mode 100644 test/collection.options.js delete mode 100644 test/collection.render.js delete mode 100644 test/collection.use.js delete mode 100644 test/fixtures/a.txt delete mode 100644 test/fixtures/b.txt delete mode 100644 test/fixtures/bom-utf16be.txt delete mode 100644 test/fixtures/bom-utf16le.txt delete mode 100644 test/fixtures/bom-utf8.txt delete mode 100644 test/fixtures/c.txt delete mode 100644 test/fixtures/copy/example.txt delete mode 100644 test/fixtures/data/a.json delete mode 100644 test/fixtures/data/alert.json delete mode 100644 test/fixtures/data/b.json delete mode 100644 test/fixtures/data/c.json delete mode 100644 test/fixtures/data/data.json delete mode 100644 test/fixtures/data/test.json delete mode 100644 test/fixtures/example.txt delete mode 100644 test/fixtures/front-matter/autodetect-no-lang.md delete mode 100644 test/fixtures/front-matter/autodetect-yaml.md delete mode 100644 test/fixtures/front-matter/lang-yaml.md delete mode 100644 test/fixtures/generic/run.dmc delete mode 100644 test/fixtures/generic/test.dmc delete mode 100644 test/fixtures/helpers/a.js delete mode 100644 test/fixtures/helpers/b.js delete mode 100644 test/fixtures/helpers/c.js delete mode 100644 test/fixtures/helpers/obj.js delete mode 100644 test/fixtures/noext/license delete mode 100644 test/fixtures/one/generator.js delete mode 100644 test/fixtures/one/package.json delete mode 100644 test/fixtures/one/templates/a.txt delete mode 100644 test/fixtures/one/templates/x.txt delete mode 100644 test/fixtures/one/templates/y.txt delete mode 100644 test/fixtures/one/templates/z.txt delete mode 100644 test/fixtures/pages/a.hbs delete mode 100644 test/fixtures/pages/b.hbs delete mode 100644 test/fixtures/pages/c.hbs delete mode 100644 test/fixtures/posts/a.txt delete mode 100644 test/fixtures/posts/b.txt delete mode 100644 test/fixtures/posts/c.txt delete mode 100644 test/fixtures/templates/a.tmpl delete mode 100644 test/fixtures/templates/b.tmpl delete mode 100644 test/fixtures/templates/c.tmpl delete mode 120000 test/fixtures/test-symlink delete mode 100644 test/fixtures/test-symlink-dir/suchempty delete mode 100644 test/fixtures/test.coffee delete mode 100644 test/fixtures/three/four/five/generator.js delete mode 100644 test/fixtures/three/four/five/package.json delete mode 100644 test/fixtures/three/four/five/templates/a.txt delete mode 100644 test/fixtures/three/four/five/templates/b.txt delete mode 100644 test/fixtures/three/four/five/templates/c.txt delete mode 100644 test/fixtures/three/four/generator.js delete mode 100644 test/fixtures/three/four/package.json delete mode 100644 test/fixtures/three/four/templates/a.txt delete mode 100644 test/fixtures/three/four/templates/b.txt delete mode 100644 test/fixtures/three/four/templates/c.txt delete mode 100644 test/fixtures/three/package.json delete mode 100644 test/fixtures/three/templates/a.txt delete mode 100644 test/fixtures/three/templates/b.txt delete mode 100644 test/fixtures/three/templates/c.txt delete mode 100644 test/fixtures/three/updatefile.js delete mode 100644 test/fixtures/two/package.json delete mode 100644 test/fixtures/two/templates/a.txt delete mode 100644 test/fixtures/two/templates/b.txt delete mode 100644 test/fixtures/two/templates/c.txt delete mode 100644 test/fixtures/two/updatefile.js create mode 100644 test/fixtures/updaters-array.json create mode 100644 test/fixtures/updaters-object.json delete mode 100644 test/fixtures/updaters/a.txt delete mode 100644 test/fixtures/updaters/b.txt delete mode 100644 test/fixtures/updaters/c.txt delete mode 100644 test/fixtures/vinyl/bom-utf16be.txt delete mode 100644 test/fixtures/vinyl/bom-utf16le.txt delete mode 100644 test/fixtures/vinyl/bom-utf8.txt delete mode 120000 test/fixtures/vinyl/test-symlink delete mode 120000 test/fixtures/vinyl/test-symlink-dir delete mode 100644 test/fixtures/vinyl/test.coffee delete mode 100644 test/fixtures/vinyl/wow/suchempty delete mode 100644 test/fixtures/watch/test.txt delete mode 100644 test/fixtures/wow/suchempty delete mode 100644 test/generator.task.js delete mode 100644 test/generators.js delete mode 100644 test/group.js delete mode 100644 test/handlers.js delete mode 100644 test/helpers.js delete mode 100644 test/item.js delete mode 100644 test/layouts.js delete mode 100644 test/list.js delete mode 100644 test/list.render.js delete mode 100644 test/list.use.js delete mode 100644 test/mergePartials.js delete mode 100644 test/partials.js delete mode 100644 test/questions.js delete mode 100644 test/renameKey.js delete mode 100644 test/render.js delete mode 100644 test/routes.js delete mode 100644 test/store.js delete mode 100644 test/support/ignore.js delete mode 100644 test/support/index.js delete mode 100644 test/support/spy.js delete mode 100644 test/update.compose.js delete mode 100644 test/update.config.js delete mode 100644 test/update.extendGenerator.js delete mode 100644 test/update.generator.js delete mode 100644 test/update.getGenerator.js delete mode 100644 test/update.js delete mode 100644 test/update.pkg.js delete mode 100644 test/update.process.js delete mode 100644 test/update.register.js delete mode 100644 test/update.registerPath.js create mode 100644 test/updaters.js delete mode 100644 test/view.content.js delete mode 100644 test/view.events.js delete mode 100644 test/view.js delete mode 100644 test/view.methods.js delete mode 100644 test/view.option.js delete mode 100644 test/view.render.js delete mode 100644 test/view.set.js delete mode 100644 test/view.use.js delete mode 100644 test/viewTypes.js delete mode 100644 test/views.js delete mode 100644 test/views.use.js delete mode 100644 updatefile.js diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 7b5d047..0000000 --- a/.eslintrc +++ /dev/null @@ -1,125 +0,0 @@ -{ - "ecmaFeatures": { - "modules": true, - "experimentalObjectRestSpread": true - }, - - "env": { - "browser": false, - "es6": true, - "node": true, - "mocha": true - }, - - "globals": { - "document": false, - "navigator": false, - "window": false - }, - - "rules": { - "accessor-pairs": 2, - "arrow-spacing": [2, { "before": true, "after": true }], - "block-spacing": [2, "always"], - "brace-style": [2, "1tbs", { "allowSingleLine": true }], - "comma-dangle": [2, "never"], - "comma-spacing": [2, { "before": false, "after": true }], - "comma-style": [2, "last"], - "constructor-super": 2, - "curly": [2, "multi-line"], - "dot-location": [2, "property"], - "eol-last": 2, - "eqeqeq": [2, "allow-null"], - "generator-star-spacing": [2, { "before": true, "after": true }], - "handle-callback-err": [2, "^(err|error)$" ], - "indent": [2, 2, { "SwitchCase": 1 }], - "key-spacing": [2, { "beforeColon": false, "afterColon": true }], - "new-cap": [2, { "newIsCap": true, "capIsNew": false }], - "new-parens": 2, - "no-array-constructor": 2, - "no-caller": 2, - "no-class-assign": 2, - "no-cond-assign": 2, - "no-const-assign": 2, - "no-control-regex": 2, - "no-debugger": 2, - "no-delete-var": 2, - "no-dupe-args": 2, - "no-dupe-class-members": 2, - "no-dupe-keys": 2, - "no-duplicate-case": 2, - "no-empty-character-class": 2, - "no-empty-label": 2, - "no-eval": 2, - "no-ex-assign": 2, - "no-extend-native": 2, - "no-extra-bind": 2, - "no-extra-boolean-cast": 2, - "no-extra-parens": [2, "functions"], - "no-fallthrough": 2, - "no-floating-decimal": 2, - "no-func-assign": 2, - "no-implied-eval": 2, - "no-inner-declarations": [2, "functions"], - "no-invalid-regexp": 2, - "no-irregular-whitespace": 2, - "no-iterator": 2, - "no-label-var": 2, - "no-labels": 2, - "no-lone-blocks": 2, - "no-mixed-spaces-and-tabs": 2, - "no-multi-spaces": 2, - "no-multi-str": 2, - "no-multiple-empty-lines": [2, { "max": 1 }], - "no-native-reassign": 2, - "no-negated-in-lhs": 2, - "no-new": 2, - "no-new-func": 2, - "no-new-object": 2, - "no-new-require": 2, - "no-new-wrappers": 2, - "no-obj-calls": 2, - "no-octal": 2, - "no-octal-escape": 2, - "no-proto": 0, - "no-redeclare": 2, - "no-regex-spaces": 2, - "no-return-assign": 2, - "no-self-compare": 2, - "no-sequences": 2, - "no-shadow-restricted-names": 2, - "no-spaced-func": 2, - "no-sparse-arrays": 2, - "no-this-before-super": 2, - "no-throw-literal": 2, - "no-trailing-spaces": 0, - "no-undef": 2, - "no-undef-init": 2, - "no-unexpected-multiline": 2, - "no-unneeded-ternary": [2, { "defaultAssignment": false }], - "no-unreachable": 2, - "no-unused-vars": [2, { "vars": "all", "args": "none" }], - "no-useless-call": 0, - "no-with": 2, - "one-var": [0, { "initialized": "never" }], - "operator-linebreak": [0, "after", { "overrides": { "?": "before", ":": "before" } }], - "padded-blocks": [0, "never"], - "quotes": [2, "single", "avoid-escape"], - "radix": 2, - "semi": [2, "always"], - "semi-spacing": [2, { "before": false, "after": true }], - "space-after-keywords": [2, "always"], - "space-before-blocks": [2, "always"], - "space-before-function-paren": [2, "never"], - "space-before-keywords": [2, "always"], - "space-in-parens": [2, "never"], - "space-infix-ops": 2, - "space-return-throw-case": 2, - "space-unary-ops": [2, { "words": true, "nonwords": false }], - "spaced-comment": [0, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!", ","] }], - "use-isnan": 2, - "valid-typeof": 2, - "wrap-iife": [2, "any"], - "yoda": [2, "never"] - } -} diff --git a/.eslintrc.json b/.eslintrc.json index cc6a867..948dbdb 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -3,98 +3,39 @@ "modules": true, "experimentalObjectRestSpread": true }, + "env": { "browser": false, "es6": true, "node": true, "mocha": true }, + "globals": { "document": false, "navigator": false, "window": false }, + "rules": { "accessor-pairs": 2, - "arrow-spacing": [ - 2, - { - "before": true, - "after": true - } - ], - "block-spacing": [ - 2, - "always" - ], - "brace-style": [ - 2, - "1tbs", - { - "allowSingleLine": true - } - ], - "comma-dangle": [ - 2, - "never" - ], - "comma-spacing": [ - 2, - { - "before": false, - "after": true - } - ], - "comma-style": [ - 2, - "last" - ], + "arrow-spacing": [2, { "before": true, "after": true }], + "block-spacing": [2, "always"], + "brace-style": [2, "1tbs", { "allowSingleLine": true }], + "comma-dangle": [2, "never"], + "comma-spacing": [2, { "before": false, "after": true }], + "comma-style": [2, "last"], "constructor-super": 2, - "curly": [ - 2, - "multi-line" - ], - "dot-location": [ - 2, - "property" - ], + "curly": [2, "multi-line"], + "dot-location": [2, "property"], "eol-last": 2, - "eqeqeq": [ - 2, - "allow-null" - ], - "generator-star-spacing": [ - 2, - { - "before": true, - "after": true - } - ], - "handle-callback-err": [ - 2, - "^(err|error)$" - ], - "indent": [ - 2, - 2, - { - "SwitchCase": 1 - } - ], - "key-spacing": [ - 2, - { - "beforeColon": false, - "afterColon": true - } - ], - "new-cap": [ - 2, - { - "newIsCap": true, - "capIsNew": false - } - ], + "eqeqeq": [2, "allow-null"], + "generator-star-spacing": [2, { "before": true, "after": true }], + "handle-callback-err": [2, "^(err|error)$" ], + "indent": [2, 2, { "SwitchCase": 1 }], + "key-spacing": [2, { "beforeColon": false, "afterColon": true }], + "keyword-spacing": [2, { "before": true, "after": true }], + "new-cap": [2, { "newIsCap": true, "capIsNew": false }], "new-parens": 2, "no-array-constructor": 2, "no-caller": 2, @@ -109,24 +50,17 @@ "no-dupe-keys": 2, "no-duplicate-case": 2, "no-empty-character-class": 2, - "no-empty-label": 2, "no-eval": 2, "no-ex-assign": 2, "no-extend-native": 2, "no-extra-bind": 2, "no-extra-boolean-cast": 2, - "no-extra-parens": [ - 2, - "functions" - ], + "no-extra-parens": [2, "functions"], "no-fallthrough": 2, "no-floating-decimal": 2, "no-func-assign": 2, "no-implied-eval": 2, - "no-inner-declarations": [ - 2, - "functions" - ], + "no-inner-declarations": [2, "functions"], "no-invalid-regexp": 2, "no-irregular-whitespace": 2, "no-iterator": 2, @@ -136,13 +70,8 @@ "no-mixed-spaces-and-tabs": 2, "no-multi-spaces": 2, "no-multi-str": 2, - "no-multiple-empty-lines": [ - 2, - { - "max": 1 - } - ], - "no-native-reassign": 2, + "no-multiple-empty-lines": [2, { "max": 1 }], + "no-native-reassign": 0, "no-negated-in-lhs": 2, "no-new": 2, "no-new-func": 2, @@ -167,112 +96,27 @@ "no-undef": 2, "no-undef-init": 2, "no-unexpected-multiline": 2, - "no-unneeded-ternary": [ - 2, - { - "defaultAssignment": false - } - ], + "no-unneeded-ternary": [2, { "defaultAssignment": false }], "no-unreachable": 2, - "no-unused-vars": [ - 2, - { - "vars": "all", - "args": "none" - } - ], + "no-unused-vars": [2, { "vars": "all", "args": "none" }], "no-useless-call": 0, "no-with": 2, - "one-var": [ - 0, - { - "initialized": "never" - } - ], - "operator-linebreak": [ - 0, - "after", - { - "overrides": { - "?": "before", - ":": "before" - } - } - ], - "padded-blocks": [ - 0, - "never" - ], - "quotes": [ - 2, - "single", - "avoid-escape" - ], + "one-var": [0, { "initialized": "never" }], + "operator-linebreak": [0, "after", { "overrides": { "?": "before", ":": "before" } }], + "padded-blocks": [0, "never"], + "quotes": [2, "single", "avoid-escape"], "radix": 2, - "semi": [ - 2, - "always" - ], - "semi-spacing": [ - 2, - { - "before": false, - "after": true - } - ], - "space-after-keywords": [ - 2, - "always" - ], - "space-before-blocks": [ - 2, - "always" - ], - "space-before-function-paren": [ - 2, - "never" - ], - "space-before-keywords": [ - 2, - "always" - ], - "space-in-parens": [ - 2, - "never" - ], + "semi": [2, "always"], + "semi-spacing": [2, { "before": false, "after": true }], + "space-before-blocks": [2, "always"], + "space-before-function-paren": [2, "never"], + "space-in-parens": [2, "never"], "space-infix-ops": 2, - "space-return-throw-case": 2, - "space-unary-ops": [ - 2, - { - "words": true, - "nonwords": false - } - ], - "spaced-comment": [ - 0, - "always", - { - "markers": [ - "global", - "globals", - "eslint", - "eslint-disable", - "*package", - "!", - "," - ] - } - ], + "space-unary-ops": [2, { "words": true, "nonwords": false }], + "spaced-comment": [0, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!", ","] }], "use-isnan": 2, "valid-typeof": 2, - "wrap-iife": [ - 2, - "any" - ], - "yoda": [ - 2, - "never" - ] + "wrap-iife": [2, "any"], + "yoda": [2, "never"] } } diff --git a/.gitignore b/.gitignore index 7988154..80a228c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,15 @@ -# always ignore files *.DS_Store *.sublime-* - -# test related, or directories generated by tests -test/actual -actual -coverage - -# npm -node_modules -npm-debug.log - -# misc _gh_pages -benchmark bower_components -vendor +node_modules +npm-debug.log +actual +test/actual temp tmp TODO.md +vendor +.idea +benchmark +coverage diff --git a/.travis.yml b/.travis.yml index d6e658e..e9f3361 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,13 @@ sudo: false language: node_js node_js: - - "stable" - - "0.12" - - "0.10" + - '5' + - '4' + - '0.12' + - '0.10' matrix: fast_finish: true allow_failures: - - node_js: "0.10" + - node_js: '4' + - node_js: '0.10' + - node_js: '0.12' diff --git a/.verb.md b/.verb.md index fad0eb2..f2852e2 100644 --- a/.verb.md +++ b/.verb.md @@ -1,147 +1,195 @@ -# {%= name %} {%= badge("fury") %} {%= badge("travis") %} +## What is update? -> {%= description %} +Update is a new, open-source developer framework for automating updates of any kind to code projects. ## CLI -### Install +**Installing the CLI** -{%= include("install-global") %} - - -### Commands +To run update from the command line, you'll need to install it globally first. You can do that now with the following command: ```sh -$ update [options] +$ npm i -g update ``` -**List updaters** +This adds the `update` command to your system path, allowing it to be run from any directory. -Choose from a list of updaters and tasks to run: +You should now be able to use the `update` command to execute code in a local `updatefile.js` file, or to execute globally installed updaters by their [aliases](#aliases). -```sh -$ update list -``` +**Init** -**Run a specific updater** +If it's your first time using update, run `update init` to set your global defaults. -The following would run updater `foo`: -```sh -$ update foo +**CLI help** -# run updater "foo" with options -$ update foo --bar=baz ``` +Usage: update [options] +Command: Generator or tasks to run -### tasks +Examples: -_(TODO)_ + # run the "foo" updater + $ update foo -### plugins + # run the "bar" task on updater "foo" + $ update foo:bar -_(TODO)_ + # run multiple tasks on updater "foo" + $ update foo:bar,baz,qux -#### pipeline plugins + # run a sub-updater on updater "foo" + $ update foo.abc -_(TODO)_ + # run task "xyz" on sub-updater "foo.abc" + $ update foo.abc:xyz -#### instance plugins + Update attempts to automatically determine if "foo" is a task or updater. + If there is a conflict, you can force update to run updater "foo" + by specifying a task on the updater. Example: `update foo:default` +``` -_(TODO)_ +## Quickstart -### middleware +The following intro only skims the surface of what update has to offer. For a more in-depth introduction, we highly recommend visiting the [getting started guide][getting-started]. -A middleware is a function that exposes the following parameters: +**Create a updater** -- `file`: **{Object}** [vinyl][] file object -- `next`: **{Function}** must be called to continue on to the next file. +Add a `updatefile.js` to the current working directory with the following code: ```js -function rename(file, next) { - file.path = 'foo/' + file.path; - next(); -} - -// example usage: prefix all `.js` file paths with `foo/` -app.onLoad(/\.js/, rename); +module.exports = function(app) { + console.log('success!'); +}; ``` -The `onStream` method is a custom [middleware](docs/middleware.md) handler that the `update` +**Run a updater** -```js -app.onStream(/lib\//, rename); -``` +Enter the following command: +```sh +update +``` -## API +If successful, you should see `success!` in the terminal. -### Install +**Create a task** -{%= include("install-npm", {save: true}) %} +Now, add a task to your updater. ```js -var update = require('{%= name %}'); +module.exports = function(app) { + app.task('default', function(cb) { + console.log('success!'); + cb(); + }); +}; ``` -## API -{%= apidocs("index.js") %} +Now, in the command line, run: -## Related projects -{%= related(verb.related.list) %} +```sh +$ update +# then try +$ update default +``` -## Authoring +When a local `updatefile.js` exists, the `update` command is aliased to automatically run the `default` task if one exists. But you can also run the task with `update default`. -### Updaters +**Run a task** -_(TODO)_ +Let's try adding more tasks to your updater: -#### Tasks +```js +module.exports = function(app) { + app.task('default', function(cb) { + console.log('default > success!'); + cb(); + }); + + app.task('foo', function(cb) { + console.log('foo > success!'); + cb(); + }); -_(TODO)_ + app.task('bar', function(cb) { + console.log('bar > success!'); + cb(); + }); +}; +``` -#### Middleware +Now, in the command line, run: -_(TODO)_ +```sh +$ update +# then try +$ update foo +# then try +$ update foo bar +``` -#### Plugins +**Run task dependencies** -> Updater plugins follow the same signature as gulp plugins +Now update your code to the following: -**Example** ```js -function myPlugin(options) { - return through.obj(function(file, enc, next) { - var str = file.contents.toString(); - // do stuff to `file` - file.contents = new Buffer(file.contents); - next(null, file); +module.exports = function(app) { + app.task('default', ['foo', 'bar']); + + app.task('foo', function(cb) { + console.log('foo > success!'); + cb(); }); -} + + app.task('bar', function(cb) { + console.log('bar > success!'); + cb(); + }); +}; +``` + +And run: + +```sh +$ update ``` -### Publish +You're now a master at running tasks with update! You can do anything with update tasks that you can do with [gulp][] tasks (we use and support gulp libraries after all!). + +**Next steps** + +But update does much more than this. For a more in-depth introduction, we highly recommend visiting the [getting started guide][getting-started]. + +## More info + +### Feature highlights + +Update offers an elegant and robust suite of methods, carefully organized to help you accomplish common activities in less time, including: + +- **unparalleled flow control**: through the use of [updaters][getting-started], [sub-updaters][getting-started] and [tasks][getting-started] +- **templates, scaffolds and boilerplates**: update a single file, initialize an entire project, or provide ad-hoc "components" throughout the duration of a project using any combination of [templates, scaffolds and boilerplates](#templates-scaffolds-and-boilerplates). +- **any engine**: use any template engine to render templates, including [handlebars][], [lodash][], [swig][] and [pug][] +- **prompts**: asks you for data when it can't find what it needs, and it's easy to customize prompts for any data you want. +- **data**: gathers data from the user's environment to populate "hints" in user prompts and render templates +- **streams**: interact with the file system, with full support for [gulp][] and [assemble][] plugins +- **smart plugins**: Update is built on [base][], so any "smart" plugin can be used +- **stores**: persist configuration settings, global defaults, project-specific defaults, answers to prompts, and so on. + +Visit the [getting started guide][getting-started] to learn more. -1. Name your project following the convention: `updater-*` -2. Don't use dots in the name (e.g `.js`) -3. Make sure you add `updater` to the keywords in `package.json` -4. Tweet about your updater! -## Running tests -{%= include("tests") %} +### FAQ -## Contributing -{%= include("contributing") %} + +**What's an alias, and what do they do?** -## Author -{%= include("author") %} +Update tries to find globally installed updaters using an "alias" first, falling back on the updater's full name if not found by its alias. -## License -{%= copyright() %} -{%= license %} +A updater's alias is created by stripping the substring `update-` from the _full name_ of updater. Thus, when publishing a updater the naming convention `update-foo` should be used (where `foo` is the alias, and `update-foo` is the full name). -*** +Note that **no dots may be used in published updater names**. Aside from that, any characters considered valid by npm are fine. -{%= include("footer") %} +[getting-started]: https://github.com/update/getting-started diff --git a/README.md b/README.md index 566deef..2bcae1c 100644 --- a/README.md +++ b/README.md @@ -1,260 +1,262 @@ -# update [![NPM version](https://img.shields.io/npm/v/update.svg)](https://www.npmjs.com/package/update) [![Build Status](https://img.shields.io/travis/update/update.svg)](https://travis-ci.org/update/update) +# update [![NPM version](https://img.shields.io/npm/v/update.svg?style=flat)](https://www.npmjs.com/package/update) [![NPM downloads](https://img.shields.io/npm/dm/update.svg?style=flat)](https://npmjs.org/package/update) [![Build Status](https://img.shields.io/travis/jonschlinkert/update.svg?style=flat)](https://travis-ci.org/jonschlinkert/update) -> Easily keep anything in your project up-to-date by installing the updaters you want to use and running `update` in the command line! Update the copyright date, licence type, ensure that a project uses your latest eslint or jshint configuration, remove deprecated package.json fields, or anything you can think of! +Update your projects. -## CLI - -### Install +## Install -Install globally with [npm](https://www.npmjs.com/) +Install with [npm](https://www.npmjs.com/): ```sh -$ npm i -g update +$ npm install --save update ``` -### Commands +## What is update? -```sh -$ update [options] -``` +Update is a new, open-source developer framework for automating updates of any kind to code projects. Using a combination of hrough the use of [updaters](#updaters) and [tasks](#tasks). intuitive CLI, and a powerful and expressive API, Update is easy to learn, and enjoyable to use. -**List updaters** +## tldr -Choose from a list of updaters and tasks to run: +Install `update` globally with the following command: -```sh -$ update list +```js +$ npm install --global update ``` -**Run a specific updater** +Update is installed globally and run with the `update` command. -The following would run updater `foo`: +* download +* git commit! +* run `update` -```sh -$ update foo - -# run updater "foo" with options -$ update foo --bar=baz +```js +var updater = require('update'); ``` -### tasks - -_(TODO)_ - -### plugins +**Example** -_(TODO)_ +This example shows two updaters working together seamlessly. -#### pipeline plugins +![Example of how to update a gulpfile.js](demo.gif) -_(TODO)_ +**Want to know more?** -#### instance plugins +You can use update from the command line, or as a node.js library as a part of your own application. -_(TODO)_ +* Jump to [feature highlights](#feature-highlights) +* Visit the [getting started guide](https://github.com/update/getting-started) -### middleware +Continue on, and start updating! -A middleware is a function that exposes the following parameters: +## CLI -* `file`: **{Object}** [vinyl](http://github.com/gulpjs/vinyl) file object -* `next`: **{Function}** must be called to continue on to the next file. +**Installing the CLI** -```js -function rename(file, next) { - file.path = 'foo/' + file.path; - next(); -} +To run update from the command line, you'll need to install it globally first. You can do that now with the following command: -// example usage: prefix all `.js` file paths with `foo/` -app.onLoad(/\.js/, rename); +```sh +$ npm i -g update ``` -The `onStream` method is a custom [middleware](docs/middleware.md) handler that the `update` +This adds the `update` command to your system path, allowing it to be run from any directory. -```js -app.onStream(/lib\//, rename); -``` +You should now be able to use the `update` command to execute code in a local `updatefile.js` file, or to execute globally installed updaters by their [aliases](#aliases). -## API +**Init** -### Install +If it's your first time using update, run `update init` to set your global defaults. -Install with [npm](https://www.npmjs.com/): +**Usage** -```sh -$ npm i update --save ``` +Usage: update [options] -```js -var update = require('update'); -``` +Command: Generator or tasks to run -## API +Examples: -### [Update](index.js#L26) + # run the "foo" updater + $ update foo -Create an instance of `Update`. This is the main function exported by the update module, used for creating `updaters`. + # run the "bar" task on updater "foo" + $ update foo:bar -**Params** + # run multiple tasks on updater "foo" + $ update foo:bar,baz,qux -* `options` **{Object}**: Optionally pass default options to use. + # run a sub-updater on updater "foo" + $ update foo.abc -**Example** + # run task "xyz" on sub-updater "foo.abc" + $ update foo.abc:xyz -```js -var Update = require('update'); -var update = new Update(); + Update attempts to automatically determine if "foo" is a task or updater. + If there is a conflict, you can force update to run updater "foo" + by specifying a task on the updater. Example: `update foo:default` ``` -### [.ignore](index.js#L121) - -Add ignore patterns to the `update.cache.ignores` array. This array is initially populated with patterns from `gitignore` +## Quickstart -**Params** +The following intro only skims the surface of what update has to offer. For a more in-depth introduction, we highly recommend visiting the [getting started guide](https://github.com/update/getting-started). -* `patterns` **{String|Array}** -* `returns` **{Object}**: returns the instance for chaining +**Create a updater** -**Example** +Add a `updatefile.js` to the current working directory with the following code: ```js -update.ignore(['foo', 'bar']); +module.exports = function(app) { + console.log('success!'); +}; ``` -### [.fillin](index.js#L143) +**Run a updater** -Set `prop` with the given `value`, but only if `prop` is not already defined. +Enter the following command: -**Params** +```sh +update +``` -* `prop` **{String}**: The name of the property to define -* `val` **{any}**: The value to use if a value is _not already defined_ +If successful, you should see `success!` in the terminal. -* `returns` **{Object}**: Returns the instance for chaining +**Create a task** -**Example** +Now, add a task to your updater. ```js -app.set('cwd', 'foo'); -app.fillin('cwd', process.cwd()); -console.log(app.get('cwd')); -//=> 'foo' +module.exports = function(app) { + app.task('default', function(cb) { + console.log('success!'); + cb(); + }); +}; ``` -### [.getFile](index.js#L162) +Now, in the command line, run: -Get a file from the `update.files` collection. +```sh +$ update +# then try +$ update default +``` -**Params** +When a local `updatefile.js` exists, the `update` command is aliased to automatically run the `default` task if one exists. But you can also run the task with `update default`. -* `pattern` **{String}**: Pattern to use for matching. Checks against -* `returns` **{Object}**: If successful, a `file` object is returned, otherwise `null` +**Run a task** -**Example** +Let's try adding more tasks to your updater: ```js -update.getFile('LICENSE'); -``` +module.exports = function(app) { + app.task('default', function(cb) { + console.log('default > success!'); + cb(); + }); + + app.task('foo', function(cb) { + console.log('foo > success!'); + cb(); + }); -### [.getTemplate](index.js#L177) + app.task('bar', function(cb) { + console.log('bar > success!'); + cb(); + }); +}; +``` -Get a template from the `update.templates` collection. +Now, in the command line, run: -**Params** +```sh +$ update +# then try +$ update foo +# then try +$ update foo bar +``` -* `pattern` **{String}**: Pattern to use for matching. Checks against -* `returns` **{Object}**: If successful, a `file` object is returned, otherwise `null` +**Run task dependencies** -**Example** +Now update your code to the following: ```js -update.getTemplate('foo.tmpl'); +module.exports = function(app) { + app.task('default', ['foo', 'bar']); + + app.task('foo', function(cb) { + console.log('foo > success!'); + cb(); + }); + + app.task('bar', function(cb) { + console.log('bar > success!'); + cb(); + }); +}; ``` -### [.union](index.js#L201) +And run: -Create or append array `name` on `update.cache` with the given (uniqueified) `items`. Supports setting deeply nested properties using using object-paths/dot notation. +```sh +$ update +``` -**Params** +You're now a master at running tasks with update! You can do anything with update tasks that you can do with [gulp](http://gulpjs.com) tasks (we use and support gulp libraries after all!). -* `name` **{String}** -* `items` **{any}** -* `returns` **{Object}**: returns the instance for chaining +**Next steps** -**Example** +But update does much more than this. For a more in-depth introduction, we highly recommend visiting the [getting started guide](https://github.com/update/getting-started). -```js -update.union('foo', 'bar'); -update.union('foo', 'baz'); -update.union('foo', 'qux'); -update.union('foo', 'qux'); -update.union('foo', 'qux'); -console.log(update.cache.foo); -//=> ['bar', 'baz', 'qux']; -``` +## More info -## Related projects +### Feature highlights -* [assemble](https://www.npmjs.com/package/assemble): Assemble is a powerful, extendable and easy to use static site generator for node.js. Used… [more](https://www.npmjs.com/package/assemble) | [homepage](https://github.com/assemble/assemble) -* [boilerplate](https://www.npmjs.com/package/boilerplate): Tools and conventions for authoring and publishing boilerplates that can be generated by any build… [more](https://www.npmjs.com/package/boilerplate) | [homepage](http://boilerplates.io) -* [composer](https://www.npmjs.com/package/composer): API-first task runner with three methods: task, run and watch. | [homepage](https://github.com/jonschlinkert/composer) -* [generate](https://www.npmjs.com/package/generate): Fast, composable, highly extendable project generator with a user-friendly and expressive API. | [homepage](https://github.com/generate/generate) -* [scaffold](https://www.npmjs.com/package/scaffold): Conventions and API for creating declarative configuration objects for project scaffolds - similar in format… [more](https://www.npmjs.com/package/scaffold) | [homepage](https://github.com/jonschlinkert/scaffold) -* [templates](https://www.npmjs.com/package/templates): System for creating and managing template collections, and rendering templates with any node.js template engine.… [more](https://www.npmjs.com/package/templates) | [homepage](https://github.com/jonschlinkert/templates) -* [update](https://www.npmjs.com/package/update): Easily keep anything in your project up-to-date by installing the updaters you want to use… [more](https://www.npmjs.com/package/update) | [homepage](https://github.com/update/update) -* [verb](https://www.npmjs.com/package/verb): Documentation generator for GitHub projects. Verb is extremely powerful, easy to use, and is used… [more](https://www.npmjs.com/package/verb) | [homepage](https://github.com/verbose/verb) +Update offers an elegant and robust suite of methods, carefully organized to help you accomplish common activities in less time, including: -## Authoring +* **unparalleled flow control**: through the use of [updaters](https://github.com/update/getting-started), [sub-updaters](https://github.com/update/getting-started) and [tasks](https://github.com/update/getting-started) +* **templates, scaffolds and boilerplates**: update a single file, initialize an entire project, or provide ad-hoc "components" throughout the duration of a project using any combination of [templates, scaffolds and boilerplates](#templates-scaffolds-and-boilerplates). +* **any engine**: use any template engine to render templates, including [handlebars](http://www.handlebarsjs.com/), [lodash](https://lodash.com/), [swig](https://github.com/paularmstrong/swig) and [pug](http://jade-lang.com) +* **prompts**: asks you for data when it can't find what it needs, and it's easy to customize prompts for any data you want. +* **data**: gathers data from the user's environment to populate "hints" in user prompts and render templates +* **streams**: interact with the file system, with full support for [gulp](http://gulpjs.com) and [assemble](https://github.com/assemble/assemble) plugins +* **smart plugins**: Update is built on [base](https://github.com/node-base/base), so any "smart" plugin can be used +* **stores**: persist configuration settings, global defaults, project-specific defaults, answers to prompts, and so on. -### Updaters +Visit the [getting started guide](https://github.com/update/getting-started) to learn more. -_(TODO)_ +### FAQ -#### Tasks + -_(TODO)_ +**What's an alias, and what do they do?** -#### Middleware +Update tries to find globally installed updaters using an "alias" first, falling back on the updater's full name if not found by its alias. -_(TODO)_ +A updater's alias is created by stripping the substring `update-` from the _full name_ of updater. Thus, when publishing a updater the naming convention `update-foo` should be used (where `foo` is the alias, and `update-foo` is the full name). -#### Plugins +Note that **no dots may be used in published updater names**. Aside from that, any characters considered valid by npm are fine. -> Updater plugins follow the same signature as gulp plugins +## Contributing -**Example** +This document was generated by [verb-readme-generator](https://github.com/verbose/verb-readme-generator) (a [verb](https://github.com/verbose/verb) generator), please don't edit directly. Any changes to the readme must be made in [.verb.md](.verb.md). See [Building Docs](#building-docs). -```js -function myPlugin(options) { - return through.obj(function(file, enc, next) { - var str = file.contents.toString(); - // do stuff to `file` - file.contents = new Buffer(file.contents); - next(null, file); - }); -} -``` +Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new). Or visit the [verb-readme-generator](https://github.com/verbose/verb-readme-generator) project to submit bug reports or pull requests for the readme layout template. -### Publish +## Building docs -1. Name your project following the convention: `updater-*` -2. Don't use dots in the name (e.g `.js`) -3. Make sure you add `updater` to the keywords in `package.json` -4. Tweet about your updater! +Generate readme and API documentation with [verb](https://github.com/verbose/verb): + +```sh +$ npm install -g verb verb-readme-generator && verb +``` ## Running tests Install dev dependencies: ```sh -$ npm i -d && npm test +$ npm install -d && npm test ``` -## Contributing - -Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](https://github.com/jonschlinkert/update/issues/new). - ## Author **Jon Schlinkert** @@ -264,9 +266,9 @@ Pull requests and stars are always welcome. For bugs and feature requests, [plea ## License -Copyright © 2016 [Jon Schlinkert](https://github.com/jonschlinkert) -Released under the MIT license. +Copyright © 2016, [Jon Schlinkert](https://github.com/jonschlinkert). +Released under the [MIT license](LICENSE). *** -_This file was generated by [verb](https://github.com/verbose/verb) on January 10, 2016._ \ No newline at end of file +_This file was generated by [verb](https://github.com/verbose/verb), v0.9.0, on June 13, 2016._ \ No newline at end of file diff --git a/bin/cli.js b/bin/cli.js deleted file mode 100755 index c997d81..0000000 --- a/bin/cli.js +++ /dev/null @@ -1,198 +0,0 @@ -#!/usr/bin/env node - -var path = require('path'); -var utils = require('../lib/utils'); -var errors = require('./errors'); -var update = require('..'); -var Env = update.Env; - -var argv = require('minimist')(process.argv.slice(2), { - alias: { - help: 'h', - verbose: 'v' - } -}); - -function run(cb) { - var cwd = process.cwd(); - var root = cwd; - - /** - * Set the working directory - */ - - if (argv.cwd && cwd !== path.resolve(argv.cwd)) { - process.chdir(argv.cwd); - cwd = process.cwd(); - utils.timestamp('cwd changed to ' + utils.colors.yellow('~/' + argv.cwd)); - } - - /** - * Create the base "update" instance - */ - - var baseDir = path.resolve(__dirname, '..'); - var baseEnv = createEnv('updatefile.js', baseDir); - - // instantiate - var base = update(); - base.env = baseEnv; - - // set the updater function on the instance - base.fn = require('../updatefile.js'); - - /** - * Get the updatefile.js to use - */ - - var updatefile = path.resolve(process.cwd(), 'updatefile.js'); - if (!utils.exists(updatefile)) { - if (utils.isEmpty(process.cwd())) { - argv._.unshift('defaults:init'); - utils.logConfigfile(baseDir, 'updatefile.js'); - base.fn.call(base, base, base, base.env); - cb(null, base); - return; - } - - updatefile = path.resolve(__dirname, '../updatefile.js'); - cwd = path.dirname(updatefile); - } - - /** - * Create the user's "update" instance - */ - - var fn = require(updatefile); - var env = createEnv('updatefile.js', cwd); - var app; - - function register(app, env, fn) { - app.option(argv); - app.register('base', base.fn, base.env); - app.env = env; - if (fn) app.fn = fn; - } - - if (typeof fn === 'function') { - app = update(); - register(app, env, fn); - fn.call(app, app, base, env); - - } else { - app = fn; - register(app, env); - } - - /** - * Process command line arguments - */ - - var args = utils.processArgv(app, argv); - app.set('argv', args); - - /** - * Process configuration settings defined on the - * `update` property in package.json - */ - - app.config.process(app.get('env.user.pkg.update')); - - /** - * Show path to updatefile - */ - - utils.logConfigfile(root, updatefile); - - /** - * Support `--emit` for debugging - * - * Example: - * $ --emit data - */ - - if (argv.emit && typeof argv.emit === 'string') { - app.on(argv.emit, console.error.bind(console)); - } - - /** - * Listen for generator configs, and register them - * as they're emitted - */ - - app.env.on('config', function(name, env) { - app.register(name, env.config.fn, env); - }); - - /** - * Resolve update generators - */ - - app.env - .resolve('updater-*/updatefile.js', { - configfile: 'updatefile.js', - cwd: utils.gm - }) - .resolve('generate-*/generator.js', { - configfile: 'generator.js', - cwd: utils.gm - }); - - cb(null, app); -} - -/** - * Run - */ - -run(function(err, app) { - if (err) handleError(err); - - if (!app) { - process.exit(0); - } - - /** - * Listen for errors - */ - - app.on('error', function(err) { - console.log(err); - }); - - /** - * Run tasks - */ - - app.build(argv, function(err) { - if (err) { - console.error(err.stack); - process.exit(1); - } - utils.timestamp('finished ' + utils.success()); - process.exit(0); - }); -}); - -/** - * Creat a new `env` object - */ - -function createEnv(configfile, cwd) { - var env = new Env(configfile, 'update', cwd);; - env.module.path = utils.tryResolve('update'); - return env; -} - -/** - * Handle CLI errors - */ - -function handleError(err) { - if (typeof err === 'string' && errors[err]) { - console.error(errors[err]); - } else { - console.error(err); - } - process.exit(1); -} diff --git a/bin/errors.js b/bin/errors.js deleted file mode 100644 index bae4786..0000000 --- a/bin/errors.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict'; - -module.exports = { - updatefile: 'Cannot find updatefile.js in the current working directory. Use `--cwd` to specify a working directory.' -}; diff --git a/bin/update.js b/bin/update.js new file mode 100755 index 0000000..fc7fd2d --- /dev/null +++ b/bin/update.js @@ -0,0 +1,72 @@ +#!/usr/bin/env node + +require('set-blocking')(true); + +var Update = require('..'); +var commands = require('../lib/commands'); +var utils = require('../lib/utils'); +var argv = require('yargs-parser')(process.argv.slice(2), { + alias: { + add: 'a', + config: 'c', + configfile: 'f', + global: 'g', + remove: 'r' + } +}); + +/** + * Listen for errors + */ + +Update.on('update.preInit', function(app) { + app.on('error', function(err) { + console.log(err.stack); + process.exit(1); + }); +}); + +Update.on('update.postInit', function(app) { + commands(app); +}); + +/** + * Init CLI + */ + +Update.cli(Update, argv, function(err, app) { + if (err) return console.log(err); + var tasks = resolveTasks(app, argv); + + app.cli.process(argv, function(err) { + if (err) app.emit('error', err); + + app.update(tasks, function(err) { + if (err) return console.log(err); + app.emit('done'); + process.exit(); + }); + }); +}); + +/** + * Get the updaters to run from user config + */ + +function resolveTasks(app, argv) { + var tasks = utils.arrayify(argv._); + if (tasks.length && utils.contains(['help', 'new', 'default'], tasks)) { + app.enable('silent'); + return tasks; + } + + if (tasks.length && !utils.contains(['help', 'new', 'default'], tasks)) { + return tasks; + } + + tasks = app.getUpdaters(argv.add, argv); + if (!tasks || !tasks.length) { + return ['init']; + } + return tasks; +}; diff --git a/docs/cli.md b/docs/cli.md deleted file mode 100644 index 4fdfa72..0000000 --- a/docs/cli.md +++ /dev/null @@ -1,10 +0,0 @@ -# CLI - -## Resolving updaters - -1. Resolve the CWD (Current Working Directory) -2. Create a `base` instance -3. Try to resolve a "configfile" - - if configfile is a function - - if configfile is an instance - - if configfile does not exist \ No newline at end of file diff --git a/docs/env.md b/docs/env.md deleted file mode 100644 index 871422b..0000000 --- a/docs/env.md +++ /dev/null @@ -1,10 +0,0 @@ -# Environment - -At a high level, the `env` object provides easy-access to information about certain "environment" details, like the user's working directory, filepaths to generators or updatefiles, and so on. - -There are three main properties on the `env` object, each is an object with additional properties: - -- [env.user](#env-user) -- [env.config](#env-config) -- [env.module](#env-module) - diff --git a/examples/eslint/updatefile.js b/examples/eslint/updatefile.js new file mode 100644 index 0000000..a2b175b --- /dev/null +++ b/examples/eslint/updatefile.js @@ -0,0 +1,19 @@ +'use strict'; + +module.exports = function(app) { + app.task('foo', function(cb) { + app.update('bar', cb); + }); + + app.register('foo', function(gen) { + gen.task('default', function(cb) { + console.log(gen.name, 'task >', this.name); + cb(); + }); + }); + + app.task('default', function(cb) { + console.log(app.name, 'task >', this.name); + cb(); + }); +}; diff --git a/gulpfile.js b/gulpfile.js index 7d0c54a..fa32719 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -3,25 +3,33 @@ var gulp = require('gulp'); var mocha = require('gulp-mocha'); var istanbul = require('gulp-istanbul'); +var unused = require('gulp-unused'); var eslint = require('gulp-eslint'); -var lint = ['index.js', 'lib/*.js', 'test/*.js']; +var lib = ['index.js', 'lib/*.js', 'lib/commands/*.js', 'bin/*.js']; -gulp.task('coverage', function () { - return gulp.src(lint) +gulp.task('coverage', function() { + return gulp.src(lib) .pipe(istanbul()) .pipe(istanbul.hookRequire()); }); -gulp.task('mocha', ['coverage'], function () { +gulp.task('test', ['coverage'], function() { return gulp.src('test/*.js') .pipe(mocha({reporter: 'spec'})) .pipe(istanbul.writeReports()); }); -gulp.task('eslint', function () { - return gulp.src(lint) +gulp.task('lint', function() { + return gulp.src(['*.js', 'test/*.js', 'lib/**/*.js']) .pipe(eslint()) + .pipe(eslint.format()); }); -gulp.task('default', ['mocha', 'eslint']); +gulp.task('unused', function() { + var keys = Object.keys(require('./lib/utils.js')); + return gulp.src(lib) + .pipe(unused({keys: keys})); +}); + +gulp.task('default', ['test', 'lint']); diff --git a/index.js b/index.js index 2123f58..46e7313 100644 --- a/index.js +++ b/index.js @@ -1,239 +1,193 @@ -'use strict'; - -/** - * module dependencies +/*! + * update + * + * Copyright (c) 2016, Jon Schlinkert. + * Licensed under the MIT License. */ -var path = require('path'); -var Generate = require('generate'); -var config = require('./lib/config'); -var ignore = require('./lib/ignore'); +'use strict'; + +var Base = require('base-app'); +var resolve = require('resolve-file'); var utils = require('./lib/utils'); var cli = require('./lib/cli'); /** - * Create an instance of `Update`. This is the main function exported - * by the update module, used for creating `updaters`. + * Create a update application with `options`. * * ```js - * var Update = require('update'); - * var update = new Update(); + * var update = require('update'); + * var app = update(); * ``` - * @param {Object} `options` Optionally pass default options to use. + * @param {Object} `options` Settings to initialize with. * @api public */ function Update(options) { - if (!(this instanceof Update)) { - return new Update(options); - } - - Generate.apply(this, arguments); - this.updaters = this.generators; - this.isUpdate = true; - - this.initPlugins(); - this.lazyCollections(); + Base.call(this, options); + this.is('update'); this.initUpdate(this); + this.initDefaults(); } /** - * Inherit Generate + * Inherit `Base` */ -Generate.extend(Update); +Base.extend(Update); /** - * Lazily initialize default collections + * Initialize defaults, emit events before and after */ -Update.prototype.lazyCollections = function() { - if (!this.templates) { - this.create('files'); - this.create('templates'); - } +Update.prototype.initUpdate = function() { + Update.emit('update.preInit', this); + Update.plugins(this); + Update.emit('update.postInit', this); }; /** - * Load default plugins. Built-in plugins can be disabled - * on the `update` options. - * - * ```js - * var app = update({ - * plugins: { - * loader: false, - * store: false - * } - * }); - * ``` + * Initialize `Update` defaults */ -Update.prototype.initPlugins = function() { - this.use(utils.middleware()) - .use(utils.loader()) - .use(utils.pkg()) - .use(config()) - .use(cli()); -}; +Update.prototype.initDefaults = function() { + this.define('generators', this.generators); + this.updaters = this.generators; -/** - * Lazily add gitignore patterns to `update.cache.ignores` - */ + this.option('help', { + configname: 'updater', + appname: 'update' + }); + + this.define('update', function() { + return this.generate.apply(this, arguments); + }); + + this.option('toAlias', function(name) { + return name.replace(/^updater?-(.*)$/, '$1'); + }); -Update.prototype.lazyIgnores = function() { - if (!this.cache.ignores) { - this.union('ignores', ignore.gitignore(this.cwd)); + function isUpdater(name) { + return /^updater?-/.test(name); } -}; -/** - * Initialize `update` defaults - */ + this.option('lookup', function(name) { + var patterns = []; + if (!isUpdater(name)) { + patterns.push(`updater-${name}`); + } + return patterns; + }); -Update.prototype.initUpdate = function(app) { - this.on('build', function(app, env) { - var ignores = app.get('cache.ignores'); - var cwd = env.config.cwd; - - app.templates('templates/*', { - ignore: ignores, - cwd: cwd, - renameKey: function(key, view) { - var cwd = path.resolve(env.config.cwd); - return path.relative(cwd, path.resolve(cwd, key)); - } - }); + this.on('unresolved', function(search, app) { + if (!isUpdater(search.name)) return; + var resolved = resolve.file(search.name) || resolve.file(search.name, {cwd: utils.gm}); + if (resolved) { + search.app = app.generator(search.name, require(resolved.path)); + } }); }; /** - * Add ignore patterns to the `update.cache.ignores` array. This - * array is initially populated with patterns from `gitignore` - * - * ```js - * update.ignore(['foo', 'bar']); - * ``` - * @param {String|Array} `patterns` - * @return {Object} returns the instance for chaining - * @api public + * Expose plugins on the constructor to allow other `base` + * apps to use the plugins before instantiating. */ -Update.prototype.ignore = function(patterns) { - this.lazyIgnores(); - this.union('ignores', ignore.toGlobs(patterns)); - return this; +Update.prototype.configfile = function(cwd) { + return utils.configfile(cwd) }; /** - * Set `prop` with the given `value`, but only if `prop` is - * not already defined. - * - * ```js - * app.set('cwd', 'foo'); - * app.fillin('cwd', process.cwd()); - * console.log(app.get('cwd')); - * //=> 'foo' - * ``` - * @param {String} `prop` The name of the property to define - * @param {any} `val` The value to use if a value is _not already defined_ - * @return {Object} Returns the instance for chaining - * @api public + * Get the list of updaters to run */ -Update.prototype.fillin = function(prop, val) { - var current = this.get(prop); - if (typeof current === 'undefined') { - this.set(prop, val); +Update.prototype.getUpdaters = function(names, options) { + var updaters = this.option('updaters'); + this.addUpdaters(names, options); + if (utils.isEmpty(updaters)) { + updaters = this.pkg.get('update.updaters'); + } + if (utils.isEmpty(updaters)) { + updaters = this.store.get('updaters'); } - return this; + if (options.remove) { + updaters = utils.remove(updaters, utils.toArray(options.remove)); + } + if (options.add) { + updaters = utils.union([], updaters, utils.toArray(options.add)); + } + return updaters; }; /** - * Get a file from the `update.files` collection. - * - * ```js - * update.getFile('LICENSE'); - * ``` - * @param {String} `pattern` Pattern to use for matching. Checks against - * @return {Object} If successful, a `file` object is returned, otherwise `null` - * @api public + * Get the list of updaters to run */ -Update.prototype.getFile = function(pattern) { - return utils.getFile(this, 'files', pattern); -}; - -/** - * Get a template from the `update.templates` collection. - * - * ```js - * update.getTemplate('foo.tmpl'); - * ``` - * @param {String} `pattern` Pattern to use for matching. Checks against - * @return {Object} If successful, a `file` object is returned, otherwise `null` - * @api public - */ - -Update.prototype.getTemplate = function(pattern) { - return utils.getFile(this, 'templates', pattern); +Update.prototype.addUpdaters = function(names, options) { + options = options || {}; + if (typeof names === 'string') { + names = utils.toArray(names); + } + if (options.config) { + this.pkg.union('update.updaters', names); + } + if (options.global) { + this.store.union('updaters', names); + } }; /** - * Create or append array `name` on `update.cache` with the - * given (uniqueified) `items`. Supports setting deeply nested - * properties using using object-paths/dot notation. - * - * ```js - * update.union('foo', 'bar'); - * update.union('foo', 'baz'); - * update.union('foo', 'qux'); - * update.union('foo', 'qux'); - * update.union('foo', 'qux'); - * console.log(update.cache.foo); - * //=> ['bar', 'baz', 'qux']; - * ``` - * @param {String} `name` - * @param {any} `items` - * @return {Object} returns the instance for chaining - * @api public + * Expose plugins on the constructor to allow other `base` + * apps to use the plugins before instantiating. */ -Update.prototype.union = function(name, items) { - utils.union(this.cache, name, utils.arrayify(items)); - return this; +Update.plugins = function(app) { + app.use(utils.store('update')); + app.use(utils.runtimes()); + app.use(utils.questions()); + app.use(utils.config()); + app.use(utils.cli()); }; /** - * Ensure `name` is set on the instance for lookups. + * Expose logging methods */ -Object.defineProperty(Update.prototype, 'name', { +Object.defineProperty(Update.prototype, 'log', { configurable: true, - set: function(name) { - this.options.name = name; - }, get: function() { - return this.options.name || 'update'; + function log() { + return console.log.apply(console, arguments); + } + log.warn = function(msg) { + return utils.logger('warning', 'yellow').apply(null, arguments); + }; + + log.success = function() { + return utils.logger('success', 'green').apply(null, arguments); + }; + + log.info = function() { + return utils.logger('info', 'cyan').apply(null, arguments); + }; + + log.error = function() { + return utils.logger('error', 'red').apply(null, arguments); + }; + log.__proto__ = utils.log; + return log; } }); + /** - * Ensure `name` is set on the instance for lookups. + * Expose static `cli` method */ -Object.defineProperty(Update.prototype, 'cwd', { - configurable: true, - set: function(cwd) { - this.options.cwd = path.resolve(cwd); - }, - get: function() { - var cwd = this.get('env.user.cwd') || this.options.cwd || process.cwd(); - return path.resolve(cwd); - } -}); +Update.cli = cli; /** - * Expose `Update` + * Expose `update` */ module.exports = Update; diff --git a/lib/cli.js b/lib/cli.js index 70bd2a0..a0deeba 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -1,67 +1,181 @@ 'use strict'; -var commands = require('./commands'); -var utils = require('./utils'); - -/** - * Update CLI - * - * Custom extensions to the built-in mappings - * provided by the `base-cli` plugin. - */ - -module.exports = function(options) { - return function(app) { - if (!app.cli) { - app.use(utils.cli()); +process.ORIGINAL_CWD = process.cwd(); + +var path = require('path'); +var find = require('find-pkg'); +var log = require('log-utils'); +var exists = require('fs-exists-sync'); +var extend = require('extend-shallow'); +var argv = require('yargs-parser')(process.argv.slice(2), { + alias: {configfile: 'f', template: 't'} +}); + +module.exports = function(Update, config, cb) { + if (typeof cb !== 'function') { + throw new TypeError('expected a callback function'); + } + if (!config || typeof config !== 'object') { + throw new TypeError('expected config to be an object'); + } + + var opts = extend({}, config, argv); + + function logger() { + if (opts.silent) return; + console.log.apply(console, arguments); + } + + /** + * Resolve `package.json` filepath and use it for `cwd` + */ + + var pkgPath = find.sync(process.cwd()); + var cwd = path.dirname(pkgPath); + + /** + * Set `cwd` + */ + + if (cwd !== process.cwd()) { + logger(log.timestamp, 'using cwd', log.yellow('~' + cwd)); + process.chdir(cwd); + } + + /** + * Check for config file + */ + + var configfile = path.resolve(__dirname, 'updatefile.js'); + + /** + * Log configfile + */ + + logger(log.timestamp, 'using file', log.green('~' + configfile)); + + /** + * Get the Base ctor and instance to use + */ + + var base = resolveBase(); + if (!(base instanceof Update)) { + base = new Update(opts); + } + + /** + * Require the config file (instance or function) + */ + + base.set('cache.argv', opts); + var app = require(configfile); + + /** + * Invoke `app` + */ + + if (typeof app === 'function' && typeof base.register === 'function') { + base.register('default', app); + } else if (typeof app === 'function') { + base.use(app); + } else if (app) { + base = app; + } + + /** + * Handle errors + */ + + if (!base) { + handleError(base, new Error('cannot run config file: ' + configfile)); + } + if (typeof base !== 'function' && Object.keys(base).length === 0) { + handleError(base, new Error('expected a function or instance of Base to be exported')); + } + + /** + * Merge `argv` onto options + */ + + base.option(argv); + + /** + * Setup listeners + */ + + // base.on('build', function(event, build) { + // var prefix = event === 'finished' ? log.success + ' ' : ''; + // logger(log.timestamp, event, build.key, prefix + log.red(build.time)); + // }); + + // base.on('task', function(event, task) { + // logger(log.timestamp, event, task.key, log.red(task.time)); + // }); + + // base.on('error', function(err) { + // logger(err.stack); + // }); + + /** + * Resolve the "app" to use + */ + + function resolveBase() { + var paths = [ + { name: 'updater', path: path.resolve(cwd, 'node_modules/updater')} + ]; + + var len = paths.length; + var idx = -1; + + while (++idx < len) { + var file = paths[idx]; + if (opts.app && file.name !== opts.app) { + continue; + } + if ((base = resolveApp(file))) { + return base; + } + } + + if (opts.app) { + base = resolveApp({name: opts.app, path: path.resolve(cwd, 'node_modules', opts.app)}); + } + return base; + } + + /** + * Try to resolve the path to the given module, require it, + * and create an instance + */ + + function resolveApp(file) { + if (exists(file.path)) { + var Base = require(file.path); + var base = new Base({isApp: true}, opts); + base.define('log', log); + + if (typeof base.name === 'undefined') { + base.name = file.name; + } + + // if this is not an instance of base-app, load + // app-base plugins onto the instance + if (file.name !== 'core') { + Update.plugins(base); + } + return base; + } + } + + function handleError(app, err) { + if (app && app.hasListeners && app.hasListeners('error')) { + app.emit('error', err); + } else { + console.error(err.stack); + process.exit(1); } + } - /** - * Help and information-related - */ - - app.cli - .map('init', commands.init(app)) - .map('help', commands.help(app)) - .map('show', commands.show(app)) - .map('open', commands.open(app)) - .map('diff', function(val) { - app.option('diff', val); - }); - - /** - * Options, settings and context related - */ - - app.cli - .map('ask', commands.init(app)) - .map('cwd', function(val) { - app.option('cwd', val); - }) - .map('save', commands.save(app)) - .map('data', function(val) { - app.data(val); - }) - .map('option', function(val) { - app.option(val); - }) - .map('config', function(val) { - app.config.process({ - update: val - }); - }); - - /** - * Task-related - */ - - app.cli - .map('choose', function(key) { - app.enable('tasks.choose'); - }) - .map('tasks', function(key) { - app.enable('tasks.display'); - }); - - }; + cb(null, base); }; diff --git a/lib/commands.js b/lib/commands.js new file mode 100644 index 0000000..d53bfef --- /dev/null +++ b/lib/commands.js @@ -0,0 +1,11 @@ +'use strict'; + +var commands = require('./commands/'); + +module.exports = function(app, options) { + for (var key in commands) { + if (commands.hasOwnProperty(key)) { + app.cli.map(key, commands[key](app, options)); + } + } +}; diff --git a/lib/commands/cwd.js b/lib/commands/cwd.js deleted file mode 100644 index 1ac6a7f..0000000 --- a/lib/commands/cwd.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -var path = require('path'); - -module.exports = function(app) { - return function(dir) { - var cwd = path.resolve(dir); - if (cwd !== process.cwd()) { - process.chdir(cwd); - cwd = process.cwd(); - } - app.option('cwd', cwd); - }; -}; diff --git a/lib/commands/help.js b/lib/commands/help.js deleted file mode 100644 index 1aa501a..0000000 --- a/lib/commands/help.js +++ /dev/null @@ -1,104 +0,0 @@ -'use strict'; - -var utils = require('../utils'); - -module.exports = function(app) { - return function(key) { - // do help stuff... - }; -}; - -/** - * Create `help` documentation - */ - -function help() { - return { - heading: '', - options: { - init: { - description: 'Force initialization questions to be re-asked.', - example: '', - short: 'i' - }, - help: { - description: '', - example: '', - short: 'h' - }, - show: { - description: '', - example: '', - short: null - }, - ask: { - description: '', - example: '', - short: null - }, - open: { - description: '', - example: '', - short: 'o' - }, - config: { - description: '', - example: '', - short: 'c' - }, - diff: { - description: '', - example: '', - short: null - }, - cwd: { - description: '', - example: '', - short: null - }, - data: { - description: '', - example: '', - short: 'd' - }, - choose: { - description: '', - example: '', - short: null - }, - tasks: { - description: '', - example: '', - short: null - } - }, - footer: '' - }; -} - -function format(obj) { - var heading = obj.heading || ''; - var optsList = ''; - for (var key in obj) { - if (obj.hasOwnProperty(key)) { - var val = obj[key]; - - optsList += toFlag(key, val.short); - optsList += utils.wrap(val.description); - } - } - - return heading + '\n' - + optsList + '\n' - + obj.footer || ''; -} - -function toFlag(key, short) { - return shortKey(short) + '--' + key + ' '; -} - -function shortKey(sh) { - return sh ? ('-' + sh + ', ') : ' '; -} - -// console.log(format(help())) diff --git a/lib/commands/init.js b/lib/commands/init.js deleted file mode 100644 index 1dc7933..0000000 --- a/lib/commands/init.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict'; - -var utils = require('../utils'); - -module.exports = function(app) { - return function(val) { - if (val === true) { - app.enable('questions.init'); - return; - } - - var keys; - if (utils.isObject(val)) { - keys = Object.keys(utils.tableize(val)); - app.questions.enqueue(keys); - app.option('questions.init', keys); - return; - } - - keys = utils.arrayify(val); - app.questions.enqueue(keys); - app.option('questions.init', keys); - }; -}; diff --git a/lib/commands/open.js b/lib/commands/open.js deleted file mode 100644 index cf940c0..0000000 --- a/lib/commands/open.js +++ /dev/null @@ -1,26 +0,0 @@ -'use strict'; - -var path = require('path'); -var utils = require('../utils'); - -module.exports = function(app) { - return function(val) { - if (val === 'answers') { - var dest = app.get('questions.dest'); - if (dest) { - console.log('opening answers data directory >', '"' + dest + '"'); - utils.opn(dest); - process.exit(0); - } - } - - if (val === 'store') { - var dir = path.dirname(app.get('store.path')); - if (dir) { - console.log('opening store data directory >', '"' + dir + '"'); - utils.opn(dir); - process.exit(0); - } - } - }; -}; diff --git a/lib/commands/remove.js b/lib/commands/remove.js new file mode 100644 index 0000000..b86ad5c --- /dev/null +++ b/lib/commands/remove.js @@ -0,0 +1,46 @@ +'use strict'; + +var utils = require('../utils'); + +/** + * Remove names from the list of locally or globally stored updaters. + * + * ```sh + * # remove updater `foo` + * $ update -r foo + * # or + * $ update -rc foo + * # sugar for + * $ update --remove --config foo + * # remove globally stored updaters + * $ update -rg foo + * # sugar for + * $ update --remove --global foo + * ``` + * @name tasks + * @api public + * @cli public + */ + +module.exports = function(app, options) { + return function(names, key, config, next) { + var updaters = []; + + if (typeof names === 'string') { + names = utils.toArray(names); + } + + if (config.global) { + updaters = app.store.get('updaters') || []; + app.store.del('updaters'); + app.store.set('updaters', utils.remove(updaters, names)); + } + + if (config.config || !config.global) { + updaters = app.pkg.get('update.updaters') || []; + app.pkg.del('update.updaters'); + app.pkg.set('update.updaters', utils.remove(updaters, names)); + } + next(); + }; +}; diff --git a/lib/commands/save.js b/lib/commands/save.js deleted file mode 100644 index 0a59249..0000000 --- a/lib/commands/save.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -var utils = require('../utils'); - -module.exports = function(app) { - return function(val) { - app.store.config.set(val); - val = utils.tableize(val); - console.log('saved > "%j" %s', val, 'in global config store.'); - }; -}; diff --git a/lib/commands/show.js b/lib/commands/show.js deleted file mode 100644 index 7e01c94..0000000 --- a/lib/commands/show.js +++ /dev/null @@ -1,32 +0,0 @@ -'use strict'; - -var utils = require('../utils'); - -module.exports = function(app) { - return function(key) { - if (typeof key === 'string') { - var val = this.get(key) - || this.get(['cache', key]) - || this.get(['cache.data', key]) - || this.get(['options', key]); - - if (val) { - console.log('showing >', key, val); - } - } - - if (utils.isObject(key)) { - key = utils.tableize(key); - } - - if (key === 'answers') { - app.on('answers', console.log); - return; - } - - if (key === 'commands') { - console.log(app.commands.sort()); - return; - } - }; -}; diff --git a/lib/config.js b/lib/config.js deleted file mode 100644 index afb3831..0000000 --- a/lib/config.js +++ /dev/null @@ -1,28 +0,0 @@ -'use strict'; - -var settings = require('./settings'); -var utils = require('./utils'); - -/** - * Custom extensions to the built-in mappings - * provided by the `base-config` plugin. - */ - -module.exports = function(options) { - return function(app) { - if (!app.config) { - app.use(utils.config()); - } - - app.config - .map('data', function(val) { - app.data(val); - }) - .map('options', function(val) { - app.option(val); - }) - .map('ignore', function(val) { - app.ignore(val); - }) - }; -}; diff --git a/lib/configfiles.js b/lib/configfiles.js new file mode 100644 index 0000000..eb109ab --- /dev/null +++ b/lib/configfiles.js @@ -0,0 +1,2 @@ +'use strict'; + diff --git a/lib/generator.js b/lib/generator.js deleted file mode 100644 index 75a3d71..0000000 --- a/lib/generator.js +++ /dev/null @@ -1,8 +0,0 @@ -'use strict'; - -module.exports = function(app, base, env) { - app.task('default', function(cb) { - console.log('lib/updater > default'); - cb(); - }); -}; diff --git a/lib/ignore.js b/lib/ignore.js deleted file mode 100644 index 789bd9d..0000000 --- a/lib/ignore.js +++ /dev/null @@ -1,78 +0,0 @@ -'use strict'; - -var path = require('path'); -var utils = require('./utils'); - -/** - * Common directories to ignore - */ - -var exclusions = [ - '.git', - 'actual', - 'coverage', - 'expected', - 'fixtures', - 'node_modules', - 'temp', - 'templates', - 'test/actual', - 'test/fixtures', - 'tmp', - 'vendor', - 'wip' -]; - -/** - * Convert gitignore to an array of glob patterns - */ - -exports.gitignore = function(cwd) { - var arr = ignores(cwd).map(function(pattern) { - return exports.toGlob(pattern); - }); - return utils.unique(arr); -}; - -/** - * Convert an array of `gitignore` patterns (git uses wildmatch) to - * close-enough approximations of glob-style patterns. - */ - -exports.toGlobs = function(patterns) { - return utils.arrayify(patterns).map(exports.toGlob); -}; - -/** - * Convert a `gitignore` pattern (git uses wildmatch) to - * a close-enough approximation of a glob-style pattern. - */ - -exports.toGlob = function(pattern) { - pattern = pattern.replace(/^[*]{2}|[*]{2}$/, ''); - pattern = pattern.replace(/^\/|\/$/, ''); - return '**/' + pattern + '/**'; -}; - -/** - * Directories to exclude in the search - */ - -function ignores(cwd) { - return gitignore('.gitignore', cwd) - .concat(exclusions) - .sort(); -} - -/** - * Parse the local `.gitignore` file and add - * the resulting ignore patterns. - */ - -function gitignore(fp, cwd) { - fp = path.resolve(cwd, fp); - if (!utils.exists(fp)) { - return []; - } - return utils.parseGitignore(fp); -} diff --git a/lib/settings/index.js b/lib/settings/index.js deleted file mode 100644 index 23b2930..0000000 --- a/lib/settings/index.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('export-files')(__dirname); diff --git a/lib/updatefile.js b/lib/updatefile.js new file mode 100644 index 0000000..749456c --- /dev/null +++ b/lib/updatefile.js @@ -0,0 +1,124 @@ +'use strict'; + +var util = require('util'); +var wrap = require('word-wrap'); +var size = require('window-size'); +var choose = require('gulp-choose-files'); +var through = require('through2'); +var gm = require('global-modules'); + +module.exports = function(app, base) { + + /** + * Prompts the user to select the updaters to run with the `update` command. Use `--add` to + * add addtional updaters, and `--remove` to remove them. + * + * ```sh + * $ update defaults:help + * # aliased as + * $ update help + * ``` + * @name help + * @api public + */ + + app.task('init', { silent: true }, function() { + var updaters = []; + return app.src('updater-*', {cwd: gm}) + .pipe(through.obj(function(file, enc, next) { + file.basename = file.basename.split('updater-').join(''); + next(null, file); + })) + .pipe(choose({message: 'Choose the updaters to run with the `update` command:'})) + .pipe(through.obj(function(file, enc, next) { + updaters.push(file.basename); + next(); + }, function(next) { + save(app, updaters); + next(); + })); + }); + + /** + * Display a help menu of available commands and flags. + * + * ```sh + * $ update defaults:help + * # aliased as + * $ update help + * ``` + * @name help + * @api public + */ + + app.task('help', { silent: true }, function(cb) { + base.cli.process({ help: true }, cb); + }); + + /** + * Show the list of updaters that are registered to run on the current project. + * + * ```sh + * $ update defaults:show + * # aliased as + * $ update show + * ``` + * @name show + * @api public + */ + + app.task('show', { silent: true }, function(cb) { + base.cli.process({ help: true }, cb); + }); + + /** + * Default task for the built-in `defaults` generator. + * + * ```sh + * $ update help + * ``` + * @name help + * @api public + */ + + app.task('default', { silent: true }, ['help']); +}; + +/** + * Save answers to `init` prompts + */ + +function save(app, list) { + if (!list.length) { + console.log(' no updaters were saved.'); + return; + } + + if (app.options.c || app.options.config) { + app.pkg.set('update.updaters', list); + } else { + app.store.set('updaters', list); + } + + var suffix = list.length > 1 ? 'updaters are' : 'updater is'; + var gray = app.log.gray; + var cyan = app.log.cyan; + var bold = app.log.bold; + var configname = 'updater'; + var appname = 'update'; + var command = 'update'; + + var msg = gray('\n ---') + + '\n' + + `\n ${app.log.green(app.log.check)} Done, your default ${suffix}:` + + '\n' + + cyan(`\n · ` + list.join('\n · ')) + + '\n' + + '\n' + + gray(' ---') + + '\n' + + '\n' + + ' To make changes, run: ' + bold(`$ ${command} init`) + console.log(msg.split('\n').join('\n')); + console.log(); +} diff --git a/lib/utils.js b/lib/utils.js index ba4b324..1a49a05 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,257 +1,80 @@ 'use strict'; -var fs = require('fs'); -var path = require('path'); var utils = require('lazy-cache')(require); var fn = require; -require = utils; +require = utils; // eslint-disable-line /** - * Lazily required module dependencies + * Utils */ -// plugins, middleware or helpers -require('assemble-loader', 'loader'); -require('base-config', 'config'); -require('common-middleware', 'middleware'); - -// other -require('array-unique', 'unique'); -require('async'); -require('find-pkg'); -require('get-value', 'get'); +require('arr-union', 'union'); +require('base-cli-process', 'cli'); +require('base-config-process', 'config'); +require('base-questions', 'questions'); +require('base-runtimes', 'runtimes'); +require('base-store', 'store'); require('global-modules', 'gm'); -require('isobject', 'isObject'); -require('matched', 'glob'); -require('micromatch', 'mm'); -require('mixin-deep', 'merge'); -require('namify'); -require('opn'); -require('parse-gitignore'); -require('pkg-store'); -require('project-name', 'project'); -require('resolve-dir'); -require('try-open'); -require('word-wrap', 'wrap'); -require('union-value', 'union'); - -// CLI -require('ansi-colors', 'colors'); -require('expand-args'); -require('success-symbol'); -require('time-stamp', 'stamp'); -require = fn; - -/** - * Add `pkg-store` methods to the instance - */ - -utils.pkg = function() { - return function(app) { - app.define('pkg', utils.pkgStore(app.cwd)); - }; -}; +require('log-utils', 'log'); +require('os-homedir', 'home'); +require = fn; // eslint-disable-line -/** - * Green checkmark - * - * @return {String} - */ - -utils.processArgv = function(app, argv) { - var args = app.processArgv(argv); - var opts = utils.merge({}, args, args.options, args.commands); - app.option(opts); - return opts; -}; - -/** - * Get a home-relative filepath - */ - -utils.homeRelative = function(fp) { - var home = utils.resolveDir('~/'); - var res = path.relative(home, fp); - return utils.colors.green('~/' + res); -}; - -/** - * Log out the path to the given config file, - * relative to the given `root` directory - */ - -utils.logConfigfile = function(root, configfile) { - var configPath = path.resolve(root, configfile); - var fp = utils.homeRelative(configPath); - var name = path.basename(configfile, path.extname(configfile)); - utils.timestamp('using ' + name + ' ' + fp); -}; - -/** - * Green checkmark - * - * @return {String} - */ - -utils.success = function() { - return utils.colors.green(utils.successSymbol); +utils.remove = function(arr, names) { + return arr.filter(function(ele) { + return names.indexOf(ele) === -1; + }); }; /** - * Create a formatted timestamp - * - * @param {String} msg - * @return {String} + * Return true if any of the given `tasks` are in the specified `list` */ -utils.timestamp = function(msg, stamp) { - var time = '[' + utils.colors.gray(utils.stamp('HH:mm:ss', new Date())) + ']'; - console.log(time, msg); +utils.contains = function(list, tasks) { + return utils.arrayify(list).some(function(ele) { + return ~tasks.indexOf(ele); + }); }; /** - * Log out a custom, time-stamped message to indicate that a task is starting. + * Return true if the given array is empty */ -utils.logTask = function(appname, taskname) { - utils.timestamp('starting ' + utils.colors.cyan(appname + taskname)); +utils.isEmpty = function(val) { + return utils.arrayify(val).length === 0; }; /** - * Create a view lookup method + * Cast `val` to an array */ -utils.getFile = function(app, collectionName, pattern) { - // "views" are "template objects", but we're - // exposing them as `files` - var file = app[collectionName].getView(pattern); - if (file) return file; - - var collection = app.getViews(collectionName); - for (var key in collection) { - var file = collection[key]; - if (file.basename === pattern) return file; - if (file.filename === pattern) return file; - if (file.path === pattern) return file; - if (file.key === pattern) return file; - if (utils.mm.isMatch(key, pattern)) { - return file; - } - } - return null; +utils.toArray = function(val) { + return typeof val === 'string' ? val.split(',') : val; }; /** - * Return true if a directory exists and is empty. - * - * @param {*} val - * @return {Array} + * Cast `val` to an array */ -utils.isEmpty = function(dir) { - var files; - try { - if (!utils.exists(dir)) { - return false; - } - files = fs.readdirSync(dir); - files = files.filter(function(fp) { - return !/\.DS_Store/i.test(fp) - }); - return files.length === 0; - } catch (err) {}; - return true; -}; - -utils.exists = function(fp) { - return !!utils.tryOpen(fp, 'r'); -}; - utils.arrayify = function(val) { - if (!val) return []; - return Array.isArray(val) ? val : [val]; -}; - -utils.extRegex = function(exts) { - return new RegExp('\\.(' + utils.arrayify(exts).join('|') + ')$'); + return val ? (Array.isArray(val) ? val : [val]) : []; }; /** - * Try to require a file + * Placeholder logger for updaters */ -utils.tryRequire = function(name) { - try { - return require(name); - } catch (err) {} - - try { - return require(path.resolve(name)); - } catch (err) {} - return {}; -}; - -utils.tryResolve = function(name) { - try { - return require.resolve(name); - } catch (err) {} - - try { - return require.resolve(path.resolve(name)); - } catch (err) {} - - try { - return require.resolve(path.resolve(process.cwd(), name)); - } catch (err) {} - - try { - return require.resolve(path.resolve(utils.gm, name)); - } catch (err) {} -}; - -/** - * Modified from the `tableize` lib, which replaces - * dashes with underscores, and we don't want that behavior. - * Tableize `obj` by flattening and normalizing the keys. - * - * @param {Object} obj - * @return {Object} - * @api public - */ - -utils.tableize = function(obj, opts) { - var ret = {}; - opts = opts || {}; - type(ret, obj, '', opts); - return ret; +utils.logger = function(prop, color) { + color = color || 'dim'; + return function(msg) { + var rest = [].slice.call(arguments, 1); + return console.log + .bind(console, utils.log.timestamp, utils.log[prop]) + .apply(console, [utils.log[color](msg), ...rest]); + } }; /** - * Type `obj` recursively. - * - * @param {Object} schema - * @param {Object} obj - * @param {String} prefix - * @api private - */ - -function type(schema, obj, prefix, opts) { - Object.keys(obj).forEach(function(key) { - var val = obj[key]; - - key = prefix + key; - if (opts.lowercase) key = key.toLowerCase(); - - if (utils.isObject(val)) { - type(schema, val, key + '.', opts); - } else { - schema[key] = val; - } - }); -} - -/** - * Expose `utils` + * Expose utils */ module.exports = utils; diff --git a/lib2/config.js b/lib2/config.js deleted file mode 100644 index c829c9b..0000000 --- a/lib2/config.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict'; - - -module.exports = function(app) { - if (!app.isUpdate) return; - - var config = require('base-config'); - app.use(config()); - - app.config - .map('addViews') - .map('addView') - .map('helpers') - .map('asyncHelpers') - .map('plugins', function(val) { - app.visit('plugin', val); - }) - .map('data', function(val) { - app.visit('data', val); - }) - .map('collections', function(val) { - app.visit('create', val); - }) - .map('reflinks', function(val) { - app.data({reflinks: val}); - }) - .map('related', function(val) { - app.data({related: val}); - }); -}; diff --git a/lib2/locals.js b/lib2/locals.js deleted file mode 100644 index 4d0097b..0000000 --- a/lib2/locals.js +++ /dev/null @@ -1,26 +0,0 @@ -'use strict'; - -var get = require('get-value'); -var set = require('set-value'); -var utils = require('./utils'); - -module.exports = function(name) { - name = name || utils.project(process.cwd()); - - return function(app) { - app.define('locals', new Locals(name, this)); - }; -}; - -function Locals(name, app) { - this.cache = get(app, ['cache.data', name]) || {}; -} - -Locals.prototype.get = function(key) { - return get(this.cache, key); -}; - -Locals.prototype.set = function(key, value) { - set(this.cache, key, value); - return this; -}; diff --git a/lib2/middleware/index.js b/lib2/middleware/index.js deleted file mode 100644 index 23b2930..0000000 --- a/lib2/middleware/index.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('export-files')(__dirname); diff --git a/lib2/middleware/json.js b/lib2/middleware/json.js deleted file mode 100644 index b7cbd14..0000000 --- a/lib2/middleware/json.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; - -var sortArrays = require('sort-object-arrays'); - -module.exports = function(app, base, env) { - base.onLoad(/(\.eslintrc|\.js(on|hintrc))$/, function(file, next) { - file.json = JSON.parse(file.content); - next(); - }); - - base.preWrite(/\.js(on|hintrc)$/, function(file, next) { - if (typeof file.json === 'undefined') { - next(new Error('json middleware expects `file.json` to be an object')); - return; - } - - file.json = sortArrays(file.json); - file.content = JSON.stringify(file.json, null, 2); - file.content += '\n'; - next(); - }); -}; diff --git a/lib2/runner/argv.js b/lib2/runner/argv.js deleted file mode 100644 index 95dd750..0000000 --- a/lib2/runner/argv.js +++ /dev/null @@ -1,55 +0,0 @@ -'use strict'; - -var utils = require('../utils'); - -module.exports = function(options) { - return function(app) { - - this.define('argv', function(argv, commands, fn) { - var args = {}; - args.argv = argv; - args.commands = []; - args.updaters = {}; - - args.flags = utils.expandArgs(utils.omit(argv, ['_', 'files'])); - args.flagskeys = Object.keys(args.flags); - - var files = argv.files ? utils.pick(argv, 'files') : null; - if (files) args.flags.files = files; - - var arr = argv._; - var len = arr.length, i = -1; - - while (++i < len) { - var key = arr[i]; - - if (/\W/.test(key)) { - var obj = utils.expand(key); - - for (var prop in obj) { - if (obj.hasOwnProperty(prop)) { - var val = obj[prop]; - utils.union(args, 'updaters.' + prop, val); - } - } - continue; - } - - if (utils.contains(commands, key)) { - args.commands.push(key); - continue; - } - // fn(key, args); - var updaters = this.base.updaters; - if (key in updaters) { - utils.union(args, 'updaters.' + key, 'default'); - - } else if (key !== 'base') { - utils.union(args, 'updaters.base', key); - } - } - return args; - }); - }; -}; - diff --git a/lib2/runner/decorate.js b/lib2/runner/decorate.js deleted file mode 100644 index e62a2f4..0000000 --- a/lib2/runner/decorate.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict'; - -var path = require('path'); -var utils = require('../utils'); - -module.exports = function(options) { - return function(app) { - - this.define('decorate', function(name, app, options) { - app.option('name', name) - .option('fullname', options.fullname || name) - .option('path', options.path || ''); - - app.create('templates', { - cwd: path.resolve(options.path, 'templates'), - renameKey: function(key) { - return path.basename(key); - } - }); - - var base = this.base; - - app.define('getFile', function(name) { - var view = base.files.getView.apply(base.files, arguments); - if (!view) { - view = app.templates.getView.apply(app.templates, arguments); - } - if (!view) return null; - view.basename = view.basename.replace(/^_/, '.'); - view.basename = view.basename.replace(/^$/, ''); - return view; - }); - - base.define('getFile', app.getFile); - base.files.getFile = base.files.getView.bind(base.files); - return this; - }); - }; -}; diff --git a/lib2/runner/env.js b/lib2/runner/env.js deleted file mode 100644 index 4d431b7..0000000 --- a/lib2/runner/env.js +++ /dev/null @@ -1,53 +0,0 @@ -'use strict'; - -var path = require('path'); -var define = require('define-property'); -var extend = require('extend-shallow'); -var get = require('get-value'); -var set = require('set-value'); - -function Env(options) { - this.options = options || {}; - define(this, 'cache', {}); -} - -Env.prototype.set = function(key, value) { - set(this, key, value); - return this; -}; - -Env.prototype.get = function(key) { - return get(this, key); -}; - -Object.defineProperty(Env.prototype, 'cwd', { - set: function(dir) { - this.cache.cwd = dir; - }, - get: function() { - return this.cache.cwd || process.cwd(); - } -}); - -Object.defineProperty(Env.prototype, 'pkg', { - set: function() { - throw new Error('env.pkg is a getter and cannot be set directly.'); - }, - get: function() { - if (!this.cache.pkg) { - this.cache.pkg = require(path.resolve(this.cwd, 'package.json')); - } - return this.cache.pkg; - } -}); - -/** - * Expose `Env` - */ - -module.exports = function(options) { - return function(app) { - var opts = extend({}, this.options, options); - app.define('env', new Env(opts)); - }; -}; diff --git a/lib2/runner/list.js b/lib2/runner/list.js deleted file mode 100644 index 15122ee..0000000 --- a/lib2/runner/list.js +++ /dev/null @@ -1,40 +0,0 @@ -'use strict'; - -var utils = require('../utils'); - -module.exports = function(options) { - return function(app) { - - this.define('list', function(cb) { - var questions = utils.questions(this.base.options); - var choices = utils.list(this.base.updaters); - if (!choices.length) { - console.log(utils.cyan(' No updater tasks found.')); - return cb(null, { - updaters: {} - }); - } - - var question = { - updaters: { - message: 'pick an updater to run', - type: 'checkbox', - choices: choices - } - }; - - questions.ask(question, function(err, answers) { - if (err) return cb(err); - var args = { - updaters: {} - }; - answers.updaters.forEach(function(answer) { - var segs = answer.split(':'); - if (segs.length === 1) return; - utils.union(args.updaters, segs[0], (segs[1] || 'default').split(',')); - }); - return cb(null, args); - }); - }); - }; -}; diff --git a/lib2/runner/listen.js b/lib2/runner/listen.js deleted file mode 100644 index dc48f2b..0000000 --- a/lib2/runner/listen.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict'; - -module.exports = function(options) { - return function(app) { - this.define('_listen', function() { - if (this.base.disabled('verbose')) return; - var store = ['store.set', 'store.has', 'store.get', 'store.del']; - var methods = ['set', 'has', 'get', 'del', 'option', 'data']; - - var names = store.concat(methods); - var len = names.length, i = -1; - var multi = this; - - while (++i < len) { - var method = names[i]; - var prop = method.split('.'); - - if (prop.length === 2) { - this.base[prop[0]].on(prop[1], multi.emit.bind(multi, method)); - this.base[prop[0]].on(prop[1], multi.emit.bind(multi, '*', method)); - } else { - this.base.on(method, function(key, val) { - multi.emit(method, key, val); - multi.emit('*', method, key, val); - }); - } - } - }); - }; -}; diff --git a/lib2/runner/run.js b/lib2/runner/run.js deleted file mode 100644 index 864451b..0000000 --- a/lib2/runner/run.js +++ /dev/null @@ -1,46 +0,0 @@ -'use strict'; - -var utils = require('../utils'); - -module.exports = function(options) { - return function(app) { - - this.define('getApp', function(name) { - return name !== 'base' - ? this.base.updater(name) - : this.base; - }); - - this.define('run', function(args, cb) { - if (typeof args === 'function') { - cb = args; - args = null; - } - - if (!args) { - var commands = this.options.commands || this.commands; - args = this.argv(this.base.get('argv'), commands); - } - - if (args.commands && args.commands.length > 1) { - var cmd = '"' + args.commands.join(', ') + '"'; - return cb(new Error('Error: only one root level command may be given: ' + cmd)); - } - - this.base.cli.process(args.flags); - var self = this; - - utils.async.eachOf(args.updaters, function(tasks, name, next) { - var app = self.getApp(name); - - tasks = tasks.filter(Boolean); - if (!tasks.length) return next(); - - self.emit('task', name, tasks); - app.build(tasks, next); - }, cb); - - return this; - }); - }; -}; diff --git a/lib2/runner/runner.js b/lib2/runner/runner.js deleted file mode 100644 index 7d458e7..0000000 --- a/lib2/runner/runner.js +++ /dev/null @@ -1,107 +0,0 @@ -'use strict'; - -var path = require('path'); -var Base = require('base'); -var option = require('base-options'); -var store = require('base-store'); -var fns = require('../middleware'); -var Updater = require('./updater'); -var tasks = require('../tasks'); -var utils = require('../utils'); -var decorate = require('./decorate'); -var listen = require('./listen'); -var args = require('./argv'); -var list = require('./list'); -var run = require('./run'); -var Update = require('../..'); - -module.exports = function(namespace, config) { - function Runner(argv, options) { - if (!(this instanceof Runner)) { - return new Runner(argv, options); - } - - Base.call(this); - this.use(option()); - this.use(store()); - this.use(decorate()); - this.use(listen()); - this.use(args()); - this.use(list()); - this.use(run()); - - this.options = options || {}; - this.commands = ['set', 'get', 'del', 'store', 'init', 'option', 'data', 'list']; - - this.base = new Update() - .on('error', console.error) - .set('argv', argv) - - // register middleware - for (var fn in fns) { - fns[fn](this.base, this.base, this); - } - - // register tasks - for (var key in tasks) { - this.base.task(key, tasks[key](this.base, this.base, this)); - } - - this._listen(); - } - - Base.extend(Runner); - - Runner.prototype.updater = function(name) { - return this.base.updater(name); - }; - - Runner.prototype.build = function() { - this.base.build.apply(this.base, arguments); - return this; - }; - - Runner.prototype.register = function(name, options, updater) { - if (arguments.length === 2) { - updater = options; - options = {}; - } - - var Ctor = options.Update || Update; - var app = new Ctor(this.base.options); - this.decorate(name, app, options); - - updater.call(app, app, this.base, this); - this.base.updater(name, app); - - this.emit('register', name, app); - return this; - }; - - Runner.prototype.registerEach = function(patterns, options) { - utils.matchFiles(patterns, options).forEach(function(fp) { - var filepath = path.resolve(fp, 'updatefile.js'); - var updater = require(filepath); - - // get the full project name ('updater-foo') - var fullname = utils.project(fp); - // get the updater name ('foo') - var name = utils.renameFn(fullname, options); - var opts = {}; - - // get the constructor to use (node_modules or our 'Update') - opts.Update = utils.resolveModule(fp); - opts.fullname = fullname; - opts.path = fp; - - this.register(name, opts, updater); - }.bind(this)); - return this; - }; - - /** - * Expose `Runner` - */ - - return Runner; -}; diff --git a/lib2/runner/updater.js b/lib2/runner/updater.js deleted file mode 100644 index 222dcfa..0000000 --- a/lib2/runner/updater.js +++ /dev/null @@ -1,127 +0,0 @@ -'use strict'; - -var path = require('path'); -var set = require('set-value'); -var define = require('define-property'); -var use = require('use'); - -/** - * Create an instance of `Updater`, optionally passing - * a default object to initialize with. - * - * ```js - * var app = new Updater({ - * path: 'foo.html' - * }); - * ``` - * @param {Object} `app` - * @api public - */ - -function Updater(name, config, fn) { - if (!(this instanceof Updater)) { - return new Updater(config); - } - - if (typeof config === 'function') { - fn = config; - config = {}; - } - - this.isUpdater = true; - define(this, 'cache', {}); - config = config || {}; - config.fn = fn; - - for (var key in config) { - if (!(key in this)) { - this.set(key, config[key]); - } - } - use(this); -} - -/** - * Set `key` on the instance with the given `value`. - * - * @param {String} `key` - * @param {Object} `value` - * @return {Object} Returns the instance for chaining - */ - -Updater.prototype.set = function(key, value) { - set(this, key, value); - return this; -}; - -/** - * Custom `inspect` method. - */ - -// Updater.prototype.inspect = function() { -// var name = this.name || 'Updater'; -// var inspect = []; - -// if (this.alias) { -// inspect.push('"' + this.alias + '"'); -// } -// return '<' + name + ' ' + inspect.join(' ') + '>'; -// }; - -/** - * Get the `cwd` (current working directory) for the updater. - */ - -define(Updater.prototype, 'cwd', { - set: function(dir) { - this.cache.cwd = dir; - }, - get: function() { - return this.cache.cwd || (this.cache.cwd = process.cwd()); - } -}); - -/** - * Get the `dirname` for the updater. - */ - -define(Updater.prototype, 'dirname', { - set: function(dir) { - this.path = path.join(dir, path.basename(this.path)); - }, - get: function() { - return path.dirname(this.path); - } -}); - -/** - * Get the `basename` for the updater. - */ - -define(Updater.prototype, 'basename', { - set: function(basename) { - this.path = path.join(path.dirname(this.path), basename); - }, - get: function() { - return path.basename(this.path); - } -}); - -/** - * Get the `filename` for the updater. - */ - -define(Updater.prototype, 'filename', { - set: function(filename) { - this.path = path.join(path.dirname(this.path), filename + this.extname); - }, - get: function() { - return path.basename(this.path, this.extname); - } -}); - -/** - * Expose `Updater` - */ - -module.exports = Updater; diff --git a/lib2/tasks/default.js b/lib2/tasks/default.js deleted file mode 100644 index 54bcf12..0000000 --- a/lib2/tasks/default.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict'; - -module.exports = function(app, base, env) { - return ['del', 'files', 'run', 'dest']; -}; diff --git a/lib2/tasks/del.js b/lib2/tasks/del.js deleted file mode 100644 index 98b46cd..0000000 --- a/lib2/tasks/del.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict'; - -var path = require('path'); -var async = require('async'); -var rimraf = require('rimraf'); - -var list = ['.npmignore', '.jshintrc', '.eslintrc']; - -module.exports = function(app, base, env) { - var files = base.option('delete') || list; - - return function(cb) { - async.each(files, function(fp, next) { - rimraf(path.resolve(process.cwd(), fp), next); - }, cb); - }; -}; diff --git a/lib2/tasks/dest.js b/lib2/tasks/dest.js deleted file mode 100644 index dc354e0..0000000 --- a/lib2/tasks/dest.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict'; - -var utils = require('../utils'); - -module.exports = function(app, base, env) { - var plugins = base.get('argv.plugins'); - - function handle(stage) { - return utils.through.obj(function(file, enc, next) { - if (file.isNull()) return next(); - app.handle(stage, file, next); - }); - } - - return function(cb) { - app.toStream('files') - .on('error', cb) - .pipe(handle('onStream')) - .on('error', cb) - .pipe(app.pipeline(plugins)) - .on('error', cb) - .pipe(handle('preWrite')) - .on('error', cb) - .pipe(app.dest('.')) - .pipe(utils.exhaust(handle('postWrite'))) - .on('error', cb) - .on('end', cb); - }; -}; diff --git a/lib2/tasks/files.js b/lib2/tasks/files.js deleted file mode 100644 index 4c958ac..0000000 --- a/lib2/tasks/files.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict'; - -var path = require('path'); - -module.exports = function(app, base, env) { - base.create('files', { - renameKey: function(key) { - return path.basename(key); - } - }); - - var glob = base.get('argv.files'); - if (glob) { - glob = glob.split(','); - } else { - glob = ['*', 'lib/*', 'bin/*']; - } - - return function(cb) { - base.files(glob, {dot: true, ignore: ['.DS_Store']}); - base.emit('loaded', base.files); - cb(); - } -}; diff --git a/lib2/tasks/index.js b/lib2/tasks/index.js deleted file mode 100644 index 23b2930..0000000 --- a/lib2/tasks/index.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('export-files')(__dirname); diff --git a/lib2/tasks/lint.js b/lib2/tasks/lint.js deleted file mode 100644 index 3c4fe17..0000000 --- a/lib2/tasks/lint.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -var through = require('through2'); -var eslint = require('gulp-eslint'); - -module.exports = function(app, base, env) { - return function() { - return base.toStream('files') - .pipe(through.obj(function(file, enc, cb) { - if (/\.js$/.test(file.path)) { - this.push(file); - } - cb(); - })) - .pipe(eslint()) - .pipe(eslint.format()) - }; -}; diff --git a/lib2/tasks/list.js b/lib2/tasks/list.js deleted file mode 100644 index 92ffe1d..0000000 --- a/lib2/tasks/list.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; - -module.exports = function(app, base, env) { - return function(cb) { - env.list(function(err, args) { - if (err) return cb(err); - env.run(args, cb); - }); - }; -}; diff --git a/lib2/tasks/noop.js b/lib2/tasks/noop.js deleted file mode 100644 index 0386d7e..0000000 --- a/lib2/tasks/noop.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -module.exports = function(app, base, env) { - return function(cb) { - return cb(); - }; -}; diff --git a/lib2/tasks/rename.js b/lib2/tasks/rename.js deleted file mode 100644 index ad419c1..0000000 --- a/lib2/tasks/rename.js +++ /dev/null @@ -1,65 +0,0 @@ -'use strict'; - -var path = require('path'); -var rimraf = require('rimraf'); -var through = require('through2'); - -var mapping = { - 'LICENSE': 'LICENSE-MIT', - 'readme.md': 'README.md' -}; - -module.exports = function(app, base, env) { - var config = base.option('rename') || mapping; - - app.task('undo', function() { - return base.toStream('files') - .pipe(rename(config, {invert: true})); - }); - - return function() { - return base.toStream('files') - .pipe(rename(config)); - }; -}; - -function rename(mapping, options) { - options = options || {}; - if (options.invert === true) { - mapping = invert(mapping); - } - return through.obj(function(file, enc, next) { - if (file.isNull()) return next(null, file); - var fp = file.path; - function del(err) { - if (err) return next(err); - next(null, file); - } - for (var key in mapping) { - if (isMatch(file, mapping[key])) { - file.path = path.resolve(file.base, key); - rimraf(fp, del); - return; - } - } - next(null, file); - }); -} - -function isMatch(file, src) { - file.basename = path.basename(file.path); - if (src instanceof RegExp) { - return src.test(file.basename) || src.test(file.path); - } - if (typeof src === 'string') { - return src === file.path || src === file.basename; - } -} - -function invert(obj) { - var res = {}; - for (var key in obj) { - res[obj[key]] = key; - } - return res; -} diff --git a/lib2/tasks/tree.js b/lib2/tasks/tree.js deleted file mode 100644 index 06e6cc8..0000000 --- a/lib2/tasks/tree.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; - -var utils = require('../utils'); - -module.exports = function(app, base, env) { - return function(cb) { - console.log(utils.tree(base.updaters)); - cb(); - }; -}; diff --git a/lib2/utils.js b/lib2/utils.js deleted file mode 100644 index 122ee60..0000000 --- a/lib2/utils.js +++ /dev/null @@ -1,329 +0,0 @@ -'use strict'; - -var fs = require('fs'); -var path = require('path'); -var pkg = require(path.resolve(__dirname, '../package')); - -/** - * Module dependencies - */ - -var utils = require('lazy-cache')(require); -var fn = require; -require = utils; - -require('assemble-loader', 'loader'); -require('async'); -require('base-cli', 'cli'); -require('base-pipeline', 'pipeline'); -require('base-store', 'store'); -require('composer-runtimes', 'runtimes'); -require('expand-args'); -require('expand-object', 'expand'); -require('extend-shallow', 'extend'); -require('for-own'); -require('get-value', 'get'); -require('load-pkg', 'pkg'); -require('matched', 'glob'); -require('micromatch', 'mm'); -require('object.omit', 'omit'); -require('object.pick', 'pick'); -require('parser-front-matter', 'matter'); -require('project-name', 'project'); -require('question-cache', 'questions'); -require('set-value', 'set'); -require('stream-exhaust', 'exhaust'); -require('through2', 'through'); -require('union-value', 'union'); - -require('success-symbol'); -require('ansi-yellow', 'yellow'); -require('ansi-green', 'green'); -require('ansi-gray', 'gray'); -require('ansi-cyan', 'cyan'); -require('ansi-red', 'red'); -require('time-stamp', 'stamp'); -require = fn; - -/** - * Logging utils - */ - -utils.timestamp = function(msg) { - var time = '[' + utils.gray(utils.stamp('HH:mm:ss', new Date())) + ']'; - return console.log(time, msg); -}; - -function Status(status) { - status = status || {}; - this.err = status.err || null; - this.code = status.code || null; - this.name = status.name || ''; - this.msg = status.msg || ''; -} - -utils.ok = function() { - var args = utils.toArray(arguments) || []; - args.unshift(' ' + utils.green(utils.successSymbol)); - console.log.apply(console, args); -}; -utils.success = function() { - var args = utils.toArray(arguments) || []; - args[0] = utils.green(args[0] || ''); - console.log.apply(console, args); -}; -utils.error = function() { - var args = utils.toArray(arguments); - args.unshift(utils.red('Error:')); - console.error.apply(console, args); -}; - -/** - * CLI utils - */ - -utils.commands = function(argv) { - argv._ = argv._ || []; - var commands = {}; - - argv._.forEach(function(key) { - commands[key] = true; - }); - return commands; -}; - -utils.identity = function(val) { - return val; -}; - -utils.arrayify = function(val) { - return Array.isArray(val) ? val : [val]; -}; - -utils.toArgv = function(args) { - var argv = args.flags; - argv._ = args.commands; - return argv; -}; - -utils.toArray = function(val) { - if (Array.isArray(val)) return val; - if (val && val.length) { - return [].slice.call(val); - } -}; - -utils.contains = function(arr, key) { - return arr.indexOf(key) > -1; -}; - -utils.npm = function(name) { - return utils.tryRequire(name) || utils.tryRequire(path.resolve(name)); -}; - -utils.exists = function(fp) { - return fs.existsSync(fp); -}; - -/** - * Rename a filepath to the "nickname" of the project. - * - * ```js - * renameFn('updater-foo'); - * //=> 'foo' - * ``` - */ - -utils.renameFn = function(filename, options) { - if (options && typeof options.renameFn === 'function') { - return options.renameFn(filename); - } - return filename.slice(filename.indexOf('-') + 1); -}; - -/** - * Return a glob of file paths - */ - -utils.matchFiles = function(pattern, options) { - options = options || {}; - var isMatch = utils.mm.matcher(pattern); - var files = fs.readdirSync(options.cwd); - var len = files.length, i = -1; - var res = []; - while (++i < len) { - var name = files[i]; - if (name === 'update') continue; - var fp = path.join(options.cwd, name); - if (isMatch(fp) || isMatch(name)) { - res.push(fp); - } - } - return res; -}; - -/** - * Resolve the correct updater module to instantiate. - * If `update` exists in `node_modules` of the cwd, - * then that will be used to create the instance, - * otherwise this module will be used. - */ - -utils.resolveModule = function(dir) { - dir = path.join(dir, 'node_modules/', pkg.name); - if (utils.exists(dir)) { - return require(path.resolve(dir)); - } - return null; -}; - -/** - * Print a tree of "updaters" and their tasks - * - * ```js - * utils.tree(updaters); - * ``` - */ - -utils.tree = function(updaters) { - var res = ''; - for (var key in updaters) { - res += utils.cyan(key) + '\n'; - for (var task in updaters[key].tasks) { - res += ' - ' + task + '\n'; - } - } - return res; -}; - -/** - * Return a list of "updaters" and their tasks - * - * ```js - * utils.list(updaters); - * ``` - */ - -utils.list = function(updaters) { - var list = []; - for (var key in updaters) { - var updater = updaters[key]; - if (!Object.keys(updater.tasks).length) { - continue; - } - - var hasDefault = updater.tasks['default']; - var name = updater.option('name'); - var item = { - name: name + (hasDefault ? ' (default)' : ''), - value: key, - short: name + (hasDefault ? ':default' : '') - }; - list.push(item); - for (var task in updater.tasks) { - if (task === 'default') continue; - list.push({ - name: ' - ' + task, - value: key + ':' + task, - short: key + ':' + task - }); - } - } - return list; -}; - -/** - * Try to require a file - */ - -utils.tryRequire = function(name) { - try { - return require(name); - } catch (err) { - console.log(err); - } - return null; -}; - -/** - * Try to read a file - */ - -utils.tryRead = function(fp) { - try { - return fs.readFileSync(fp); - } catch (err) {} - return null; -}; - -utils.tryParse = function(str) { - try { - return JSON.parse(str); - } catch (err) {} - return {}; -}; - -utils.register = function(pattern, base, update, options) { - utils.matchFiles(pattern, options).forEach(function(fp) { - var name = utils.project(fp); - var mod = utils.resolveModule(fp) || update; - var app = mod(base.options) - .option('name', name) - .set('path', fp); - - require(utils.updatefile(fp))(app, base); - base.updater(name, app); - }); -}; - -utils.opts = function(key) { - key = key || 'opts'; - return function(app) { - var name = this.options.name || 'base'; - this.define(key, function() { - var config = this.defaults.apply(this, arguments); - return function(key, opts) { - var args = [].concat.apply([], [].slice.call(arguments)); - var prop = typeof key === 'string' ? args.shift() : null; - var val; - - if (prop && !args.length) { - val = utils.get(config, prop); - if (val) return val; - } - - var options = utils.extend.apply(utils.extend, [config].concat(args)); - return prop ? utils.get(options, prop) : options; - }; - }); - }; -}; - -utils.defaults = function(key) { - key = key || 'defaults'; - return function(app) { - this.define(key, function() { - var args = [].concat.apply([], [].slice.call(arguments)); - args.unshift({}, this.options); - return utils.extend.apply(utils.extend, args); - }); - }; -}; - -/** - * Restore `require` - */ - -require = fn; - -/** - * Expose `utils` - */ - -module.exports = utils; - -/** - * Expose utils - */ - -module.exports = utils; diff --git a/package.json b/package.json index 9c35151..badc184 100644 --- a/package.json +++ b/package.json @@ -1,163 +1,104 @@ { "name": "update", - "description": "Easily keep anything in your project up-to-date by installing the updaters you want to use and running `update` in the command line! Update the copyright date, licence type, ensure that a project uses your latest eslint or jshint configuration, remove deprecated package.json fields, or anything you can think of!", - "version": "0.4.2", - "homepage": "https://github.com/update/update", + "description": "Update your projects.", + "version": "0.1.0", + "homepage": "https://github.com/jonschlinkert/update", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", - "authors": [ - "Brian Woodward (https://github.com/doowb)", - "Jon Schlinkert (https://github.com/jonschlinkert)" - ], - "contributors": [ - "Brian Woodward (https://github.com/doowb)", - "Jon Schlinkert (https://github.com/jonschlinkert)" - ], - "repository": "update/update", + "repository": "jonschlinkert/update", "bugs": { - "url": "https://github.com/update/update/issues" + "url": "https://github.com/jonschlinkert/update/issues" }, "license": "MIT", "files": [ "bin", "index.js", - "lib/" + "lib" ], "main": "index.js", + "preferGlobal": true, + "bin": { + "update": "bin/update.js" + }, "engines": { - "node": ">=0.10.0" + "node": ">=5.0" }, "scripts": { "test": "mocha" }, - "preferGlobal": true, - "bin": { - "update": "bin/cli.js" - }, "dependencies": { - "ansi-colors": "^0.1.0", - "array-unique": "^0.2.1", - "assemble-loader": "^0.2.6", - "async": "^1.5.2", - "base-config": "^0.3.3", - "common-middleware": "^0.2.2", - "define-property": "^0.2.5", - "engine-base": "^0.1.2", - "expand-args": "^0.3.1", - "export-files": "^2.1.0", - "find-pkg": "^0.1.1", - "generate": "^0.3.6", - "get-value": "^2.0.2", - "global-modules": "^0.2.0", - "gulp-eslint": "^1.1.1", - "isobject": "^2.0.0", - "lazy-cache": "^1.0.3", - "load-pkg": "^3.0.1", - "matched": "^0.4.1", - "micromatch": "^2.3.7", - "minimist": "^1.2.0", - "mixin-deep": "^1.1.3", - "namify": "^0.1.3", - "opn": "^3.0.3", - "parse-gitignore": "^0.2.0", - "parser-front-matter": "^1.3.0", - "pkg-store": "^0.1.0", - "project-name": "^0.2.3", - "resolve-dir": "^0.1.0", - "rimraf": "^2.5.0", - "success-symbol": "^0.1.0", - "through2": "^2.0.0", - "time-stamp": "^0.1.3", - "try-open": "^0.1.0", - "word-wrap": "^1.1.0" + "base-app": "^0.2.5", + "base-cli-process": "^0.1.11", + "base-config-process": "^0.1.6", + "base-questions": "^0.6.6", + "base-runtimes": "^0.1.11", + "base-store": "^0.4.4", + "extend-shallow": "^2.0.1", + "find-pkg": "^0.1.2", + "fs-exists-sync": "^0.1.0", + "global-modules": "^0.2.2", + "gulp-choose-files": "^0.1.0", + "lazy-cache": "^2.0.1", + "log-utils": "^0.1.4", + "os-homedir": "^1.0.1", + "resolve-file": "^0.2.0", + "set-blocking": "^2.0.0", + "through2": "^2.0.1", + "window-size": "^0.2.0", + "word-wrap": "^1.1.0", + "yargs-parser": "^2.4.0" }, "devDependencies": { - "base-methods": "^0.6.2", - "buffer-equal": "^1.0.0", - "consolidate": "^0.13.1", - "coveralls": "^2.11.6", - "data-store": "^0.12.1", - "engine-handlebars": "^0.8.0", - "event-stream": "^3.3.2", - "expand-files": "^0.7.1", - "graceful-fs": "^4.1.2", - "gulp": "^3.9.0", - "gulp-format-md": "^0.1.5", - "gulp-istanbul": "^0.10.3", + "gulp": "^3.9.1", + "gulp-eslint": "^2.0.0", + "gulp-format-md": "^0.1.9", + "gulp-istanbul": "^0.10.4", "gulp-mocha": "^2.2.0", - "is-buffer": "^1.1.1", - "istanbul": "^0.4.1", - "kind-of": "^3.0.2", - "mocha": "^2.3.4", - "resolve-glob": "^0.1.8", - "should": "^8.0.2", - "sinon": "^1.17.2", - "swig": "^1.4.2", - "vinyl": "^1.1.0" + "gulp-unused": "^0.1.2", + "mocha": "^2.5.3" }, "keywords": [ + "convention", + "dev", + "develop", + "fix", "lint", - "next", - "repo", + "maintain", + "manage", + "standard", + "tools", + "up-to-date", "update" ], - "lint-deps": { - "devDpendencies": [ - "coveralls", - "istanbul" - ], - "ignore": [ - "bin2", - "lib2", - "test2" + "update": { + "add": [ + "keywords" ] }, "verb": { "related": { - "list": [ - "assemble", - "boilerplate", - "composer", - "generate", - "scaffold", - "templates", - "update", - "verb" - ], - "description": "" + "list": [] }, - "reflinks": [ - "boilerplate", - "scaffold", - "template", - "verb", - "vinyl" + "toc": false, + "layout": "default", + "tasks": [ + "readme" ], "plugins": [ "gulp-format-md" ], - "data": { - "author": { - "username": "jonschlinkert" - } - } - }, - "update": { - "note": "this is just pseudo data for tests. I'll update with real stuff soon.", - "cwd": "lib/pipeline", - "helpers": { - "related": "@/helper-related" + "lint": { + "reflinks": true }, - "ignore": [ - "lib2", - "test2" - ], - "plugins": { - "./lib/pipeline/a": {}, - "./lib/pipeline/b": {}, - "./lib/pipeline/c": {} - }, - "set": { - "aaa": "yyy" - } + "reflinks": [ + "assemble", + "base", + "gulp", + "handlebars", + "lodash", + "pug", + "swig", + "verb", + "verb-readme-generator" + ] } } diff --git a/test/app.applyLayout.js b/test/app.applyLayout.js deleted file mode 100644 index 7c8d782..0000000 --- a/test/app.applyLayout.js +++ /dev/null @@ -1,87 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; -var page = { - content: '<%= name %>', - layout: 'default.tmpl', - locals: { - name: 'Halle' - } -}; - -describe('helpers', function() { - describe('rendering', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('layout', { viewType: 'layout' }); - app.create('page'); - }); - - it('should throw an error when a layout cannot be found:', function(cb) { - app.layout('fofof.tmpl', {content: '..'}); - app.page('a.tmpl', page) - .render(function(err) { - assert.equal(err.message, 'Templates#layouts layout "default.tmpl" was defined on view "a.tmpl"\nbut cannot be not found (common causes are incorrect glob patterns,\nrenameKey function modifying the key, and typos in search pattern)'); - cb(); - }); - }); - - it('should emit an error when a layout cannot be found:', function(cb) { - app.layout('fofof.tmpl', {content: '..'}); - app.on('error', function(err) { - assert.equal(err.message, 'Templates#layouts layout "default.tmpl" was defined on view "a.tmpl"\nbut cannot be not found (common causes are incorrect glob patterns,\nrenameKey function modifying the key, and typos in search pattern)'); - cb(); - }); - - app.page('a.tmpl', page) - .render(function() { - }); - }); - - it('should throw an error - layout defined but no layouts registered:', function(cb) { - app.page('a.tmpl', page) - .render(function(err) { - assert.equal(err.message, 'Templates#layouts layout "default.tmpl" was defined on view "a.tmpl"\nbut cannot be not found (common causes are incorrect glob patterns,\nrenameKey function modifying the key, and typos in search pattern)'); - cb(); - }); - }); - - it('should emit an error - layout defined but no layouts registered:', function(cb) { - app.on('error', function(err) { - assert.equal(err.message, 'Templates#layouts layout "default.tmpl" was defined on view "a.tmpl"\nbut cannot be not found (common causes are incorrect glob patterns,\nrenameKey function modifying the key, and typos in search pattern)'); - cb(); - }); - app.page('a.tmpl', page) - .render(function() { - }); - }); - - it('should wrap a view with a layout (view.render):', function(cb) { - app.layout('default.tmpl', {content: 'before {% body %} after'}); - app.page('a.tmpl', page) - .render(function(err) { - if (err) return cb(err); - cb(); - }); - }); - - it('should wrap a view with a layout (app.render):', function(cb) { - app.layout('default.tmpl', {content: 'before {% body %} after'}); - app.page('a.tmpl', page); - - var view = app.pages.getView('a.tmpl'); - app.render(view, function(err, res) { - if (err) return cb(err); - assert(res.contents.toString() === 'before Halle after'); - cb(); - }); - }); - }); -}); - diff --git a/test/app.collection.compile.js b/test/app.collection.compile.js deleted file mode 100644 index 12dfe9c..0000000 --- a/test/app.collection.compile.js +++ /dev/null @@ -1,52 +0,0 @@ -'use strict'; - -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var Views = App.Views; -var views; - -describe('compile', function() { - beforeEach(function() { - views = new Views(); - }); - - it('should throw an error when an engine cannot be found:', function() { - views.addView('foo.bar', {content: '<%= name %>'}); - var page = views.getView('foo.bar'); - (function() { - views.compile(page); - }).should.throw('Views#compile cannot find an engine for: .bar'); - }); - - it('should compile a template:', function() { - views.engine('tmpl', require('engine-base')); - views.addView('a.tmpl', {path: 'a.tmpl', content: '<%= a %>', a: 'b'}); - - var page = views.getView('a.tmpl'); - var view = views.compile(page); - assert.equal(typeof view.fn, 'function'); - }); - - it('should compile a template by name:', function() { - views.engine('tmpl', require('engine-base')); - views.addView('a.tmpl', {path: 'a.tmpl', content: '<%= a %>', a: 'b'}); - - var view = views.compile('a.tmpl'); - assert.equal(typeof view.fn, 'function'); - }); - - it('should throw an error when a callback is given:', function() { - views.engine('md', require('engine-base')); - views.addView('foo.md', {content: '<%= name %>'}); - var page = views.getView('foo.md'); - (function() { - views.compile(page, function() {}); - }).should.throw('Views#compile is sync and does not take a callback function'); - - (function() { - views.compile(page, {}, function() {}); - }).should.throw('Views#compile is sync and does not take a callback function'); - }); -}); diff --git a/test/app.collection.js b/test/app.collection.js deleted file mode 100644 index 4795756..0000000 --- a/test/app.collection.js +++ /dev/null @@ -1,185 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var fs = require('fs'); -var path = require('path'); -var assert = require('assert'); -var define = require('define-property'); -var support = require('./support'); -var App = support.resolve(); -var Collection = App.Collection; -var app; - -describe('collection', function() { - describe('method', function() { - beforeEach(function() { - app = new App(); - }); - - it('should expose the collection method', function() { - assert(typeof app.collection === 'function'); - }); - - it('should return a new collection', function() { - var collection = app.collection(); - assert(typeof collection === 'object'); - }); - - it('should have isCollection property', function() { - var collection = app.collection(); - assert(collection.isCollection === true); - }); - }); - - describe('adding views', function() { - beforeEach(function() { - app = new App() - .use(function() { - return function() { - define(this, 'count', { - get: function() { - return Object.keys(this.views).length; - }, - set: function() { - throw new Error('count is a read-only getter and cannot be defined.'); - } - }); - }; - }); - - app.engine('tmpl', require('engine-base')); - app.create('pages'); - }); - - it('should load a view onto the respective collection:', function() { - app.pages.use(function() { - return function(file) { - file.content = fs.readFileSync(file.path, 'utf8'); - } - }); - - app.pages('test/fixtures/pages/a.hbs'); - var page = app.pages.getView('test/fixtures/pages/a.hbs'); - assert(app.pages.getView('a.hbs')); - }); - - it('should allow collection methods to be chained:', function() { - app - .pages('test/fixtures/pages/a.hbs') - .pages('test/fixtures/pages/b.hbs') - .pages('test/fixtures/pages/c.hbs'); - - app.views.pages.should.have.properties([ - path.resolve('test/fixtures/pages/a.hbs'), - path.resolve('test/fixtures/pages/b.hbs'), - path.resolve('test/fixtures/pages/c.hbs') - ]); - }); - - it('should expose the `option` method:', function() { - app.pages.option('foo', 'bar') - .pages('test/fixtures/pages/a.hbs') - .pages('test/fixtures/pages/b.hbs') - .pages('test/fixtures/pages/c.hbs'); - - app.pages.options.should.have.property('foo', 'bar'); - app.views.pages.should.have.properties([ - path.resolve('test/fixtures/pages/a.hbs'), - path.resolve('test/fixtures/pages/b.hbs'), - path.resolve('test/fixtures/pages/c.hbs') - ]); - }); - - it('should expose the `option` method:', function() { - app.pages.option('foo', 'bar') - .pages('test/fixtures/pages/a.hbs') - .pages('test/fixtures/pages/b.hbs') - .pages('test/fixtures/pages/c.hbs'); - - assert(app.pages.count === 3); - }); - }); - - describe('addItem', function() { - beforeEach(function() { - app = new App(); - }); - - it('should add items to a collection', function() { - var pages = app.collection({Collection: Collection}); - pages.addItem('foo'); - pages.addItem('bar'); - pages.addItem('baz'); - - pages.items.hasOwnProperty('foo'); - pages.items.hasOwnProperty('bar'); - pages.items.hasOwnProperty('baz'); - }); - - it('should create a collection from an existing collection:', function() { - var pages = app.collection({Collection: Collection}); - pages.addItem('foo'); - pages.addItem('bar'); - pages.addItem('baz'); - - var posts = app.collection(pages); - posts.items.hasOwnProperty('foo'); - posts.items.hasOwnProperty('bar'); - posts.items.hasOwnProperty('baz'); - }); - }); - - describe('rendering views', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('pages'); - app.cache.data = {}; - }); - - it('should render a view with inherited app.render', function(cb) { - app.page('test/fixtures/templates/a.tmpl') - .use(function(view) { - view.contents = fs.readFileSync(view.path); - }) - .set('data.name', 'Brian') - .render(function(err, res) { - if (err) return cb(err); - assert(res.content === 'Brian'); - cb(); - }); - }); - }); -}); - -describe('collection singular method', function() { - describe('create', function() { - beforeEach(function() { - app = new App(); - }); - - it('should add a pluralized collection from singular name', function() { - app.create('page'); - assert(typeof app.views.pages === 'object'); - }); - }); - - describe('adding views', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('page'); - }); - - it('should add a view to the created collection:', function() { - app.page('test/fixtures/pages/a.hbs'); - assert(typeof app.pages.getView('test/fixtures/pages/a.hbs') === 'object'); - }); - - it('should expose the `option` method:', function() { - app.pages.option('foo', 'bar'); - app.pages.options.should.have.property('foo', 'bar'); - }); - }); -}); diff --git a/test/app.collection.render.js b/test/app.collection.render.js deleted file mode 100644 index 2d23152..0000000 --- a/test/app.collection.render.js +++ /dev/null @@ -1,157 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var async = require('async'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var List = App.List; -var pages, app; - -describe('render', function() { - describe('rendering', function() { - beforeEach(function() { - app = App(); - pages = app.create('pages'); - app.engine('tmpl', require('engine-base')); - pages.engine('tmpl', require('engine-base')); - }); - - it('should throw an error when no callback is given:', function() { - (function() { - app.pages.render({}); - }).should.throw('Views#render is async and expects a callback function'); - }); - - it('should throw an error when an engine is not defined:', function(done) { - pages.addView('foo.bar', { content: '<%= name %>' }); - var page = pages.getView('foo.bar'); - - app.pages.render(page, function(err) { - assert(err.message === 'Views#render cannot find an engine for: .bar'); - done(); - }); - }); - - it('should use helpers defined on app to render a view:', function(done) { - var locals = {name: 'Halle'}; - app.helper('upper', function(str) { - return str.toUpperCase(str) + 'app'; - }); - - pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); - var page = pages.getView('a.tmpl'); - - app.render(page, function(err, res) { - if (err) return done(err); - - assert(res.content === 'a HALLEapp b'); - done(); - }); - }); - - it('should use helpers defined on app to render a view with collection.render:', function(done) { - var locals = {name: 'Halle'}; - app.helper('upper', function(str) { - return str.toUpperCase(str) + 'app'; - }); - - pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); - pages.helper('upper', app._.helpers.sync.upper); - var page = pages.getView('a.tmpl'); - - pages.render(page, function(err, res) { - if (err) return done(err); - - assert(res.content === 'a HALLEapp b'); - done(); - }); - }); - - it('should use helpers when rendering a view:', function(done) { - var locals = {name: 'Halle'}; - pages.helper('upper', function(str) { - return str.toUpperCase(str); - }); - - pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); - var page = pages.getView('a.tmpl'); - - pages.render(page, function(err, res) { - if (err) return done(err); - assert(res.content === 'a HALLE b'); - done(); - }); - }); - - it('should render a template when contents is a buffer:', function(done) { - pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - var view = pages.getView('a.tmpl'); - - pages.render(view, function(err, view) { - if (err) return done(err); - assert(view.contents.toString() === 'b'); - done(); - }); - }); - - it('should render a template when content is a string:', function(done) { - pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - var view = pages.getView('a.tmpl'); - - pages.render(view, function(err, view) { - if (err) return done(err); - assert(view.contents.toString() === 'b'); - done(); - }); - }); - - it('should render a view from its path:', function(done) { - pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - - pages.render('a.tmpl', function(err, view) { - if (err) return done(err); - assert(view.content === 'b'); - done(); - }); - }); - - it('should use a plugin for rendering:', function(done) { - pages.engine('tmpl', require('engine-base')); - pages.option('engine', 'tmpl'); - - pages.addViews({ - 'a': {content: '<%= title %>', locals: {title: 'aaa'}}, - 'b': {content: '<%= title %>', locals: {title: 'bbb'}}, - 'c': {content: '<%= title %>', locals: {title: 'ccc'}}, - 'd': {content: '<%= title %>', locals: {title: 'ddd'}}, - 'e': {content: '<%= title %>', locals: {title: 'eee'}}, - 'f': {content: '<%= title %>', locals: {title: 'fff'}}, - 'g': {content: '<%= title %>', locals: {title: 'ggg'}}, - 'h': {content: '<%= title %>', locals: {title: 'hhh'}}, - 'i': {content: '<%= title %>', locals: {title: 'iii'}}, - 'j': {content: '<%= title %>', locals: {title: 'jjj'}} - }); - - pages.use(function(collection) { - collection.option('pager', false); - - collection.renderEach = function(cb) { - var list = new List(collection); - async.map(list.items, function(item, next) { - collection.render(item, next); - }, cb); - }; - }); - - pages.renderEach(function(err, items) { - if (err) return done(err); - assert(items[0].content === 'aaa'); - assert(items[9].content === 'jjj'); - assert(items.length === 10); - done(); - }); - }); - }); -}); diff --git a/test/app.compile.js b/test/app.compile.js deleted file mode 100644 index a317fee..0000000 --- a/test/app.compile.js +++ /dev/null @@ -1,54 +0,0 @@ -'use strict'; - -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('compile', function() { - beforeEach(function() { - app = new App(); - app.create('page'); - }); - - it('should throw an error when an engine cannot be found:', function() { - app.page('foo.bar', {content: '<%= name %>'}); - var page = app.pages.getView('foo.bar'); - (function() { - app.compile(page); - }).should.throw('Templates#compile cannot find an engine for: .bar'); - }); - - it('should compile a template:', function() { - app.engine('tmpl', require('engine-base')); - app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>', a: 'b'}); - - var page = app.pages.getView('a.tmpl'); - var view = app.compile(page); - assert.equal(typeof view.fn, 'function'); - }); - - it('should compile a template by name:', function() { - app.engine('tmpl', require('engine-base')); - app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>', a: 'b'}); - - var view = app.compile('a.tmpl'); - assert.equal(typeof view.fn, 'function'); - }); - - it('should throw an error when a callback is given:', function() { - app.engine('md', require('engine-base')); - app.page('foo.md', {content: '<%= name %>'}); - var page = app.pages.getView('foo.md'); - (function() { - app.compile(page, function() { - }); - }).should.throw('Templates#compile is sync and does not take a callback function'); - - (function() { - app.compile(page, {}, function() { - }); - }).should.throw('Templates#compile is sync and does not take a callback function'); - }); -}); diff --git a/test/app.copy.js b/test/app.copy.js deleted file mode 100644 index 9fb0813..0000000 --- a/test/app.copy.js +++ /dev/null @@ -1,33 +0,0 @@ -'use strict'; - -require('mocha'); -var path = require('path'); -var assert = require('assert'); -var rimraf = require('rimraf'); -var App = require('..'); -var app; - -var fixtures = path.join(__dirname, 'fixtures/copy/*.txt'); -var actual = path.join(__dirname, 'actual'); - -describe('copy()', function() { - beforeEach(function(done) { - rimraf(actual, done); - app = new App(); - }); - - afterEach(function(done) { - rimraf(actual, done); - }); - - describe('streams', function() { - it('should copy files', function(done) { - app.copy(fixtures, path.join(__dirname, 'actual')) - .on('error', done) - .on('data', function(file) { - assert.equal(typeof file, 'object'); - }) - .on('end', done); - }); - }); -}); diff --git a/test/app.create.js b/test/app.create.js deleted file mode 100644 index e7a2f4e..0000000 --- a/test/app.create.js +++ /dev/null @@ -1,242 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var fs = require('fs'); -var path = require('path'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('create', function() { - describe('inflections', function() { - beforeEach(function() { - app = new App(); - }); - - it('should expose the create method', function() { - assert(typeof app.create === 'function'); - }); - - it('should add a collection to `views`', function() { - app.create('pages'); - assert(typeof app.views.pages === 'object'); - assert(typeof app.pages === 'function'); - }); - - it('should add a pluralized collection to `views`', function() { - app.create('page'); - assert(typeof app.views.pages === 'object'); - assert(typeof app.page === 'function'); - }); - }); - - describe('renderable views', function() { - beforeEach(function() { - app = new App(); - app.create('pages'); - app.create('partials', {viewType: 'partial'}); - app.create('layout', {viewType: 'layout'}); - }); - - it('should add renderable views when no type is defined', function() { - app.pages.addView('foo', {content: 'bar'}); - assert(app.views.pages.hasOwnProperty('foo')); - }); - - it('should add view Ctor names to views', function() { - app.pages.addView('foo', {content: 'bar'}); - assert(app.views.pages.foo._name === 'Page'); - }); - - it('should add partial views when partial type is defined', function() { - app.partials.addView('abc', {content: 'xyz'}); - assert(app.views.partials.hasOwnProperty('abc')); - }); - - it('should add layout views when layout type is defined', function() { - app.layouts.addView('foo', {content: 'bar'}); - assert(app.views.layouts.hasOwnProperty('foo')); - }); - - it('should set viewType on renderable views', function() { - app.pages.addView('foo', {content: 'bar'}); - var view = app.pages.getView('foo'); - assert(view.isType('renderable')); - assert(!view.isType('layout')); - assert(!view.isType('partial')); - }); - - it('should set viewType on partial views', function() { - app.partials.addView('foo', {content: 'bar'}); - var view = app.partials.getView('foo'); - assert(view.isType('partial')); - assert(!view.isType('layout')); - assert(!view.isType('renderable')); - }); - - it('should set viewType on layout views', function() { - app.layouts.addView('foo', {content: 'bar'}); - var view = app.layouts.getView('foo'); - assert(view.isType('layout')); - assert(!view.isType('renderable')); - assert(!view.isType('partial')); - }); - }); - - describe('custom constructors', function() { - beforeEach(function() { - var Vinyl = require('vinyl'); - Vinyl.prototype.custom = function(key) { - this[key] = 'nonsense'; - return this; - }; - app = new App({View: Vinyl}); - app.create('pages'); - }); - - it('should create views from key-value pairs:', function() { - app.page('a.hbs', {path: 'a.hbs', content: 'a'}); - app.page('b.hbs', {path: 'b.hbs', content: 'b'}); - app.page('c.hbs', {path: 'c.hbs', content: 'c'}); - var a = app.pages.getView('a.hbs'); - a.custom('foo'); - a.foo.should.equal('nonsense'); - }); - }); - - describe('custom instances', function() { - it('should create views from custom `View` and `Views` instance/ctor:', function() { - var Vinyl = require('vinyl'); - Vinyl.prototype.read = function(file) { - return fs.readFileSync(file.path); - }; - - var Views = App.Views; - var views = new Views({View: Vinyl}); - - views.addView('a.hbs', {path: 'a.hbs', content: 'a'}); - views.addView('b.hbs', {path: 'b.hbs', content: 'b'}); - views.addView('c.hbs', {path: 'c.hbs', content: 'c'}); - - app = new App(); - app.create('pages', views); - - var a = app.pages.getView('a.hbs'); - assert(a instanceof Vinyl); - assert(Vinyl.isVinyl(a)); - assert(typeof a.read === 'function'); - - views.addView('d.hbs', {path: 'd.hbs', content: 'd'}); - var d = app.pages.getView('d.hbs'); - assert(d instanceof Vinyl); - assert(Vinyl.isVinyl(d)); - }); - }); - - describe('chaining', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('page'); - }); - - it('should create views from key-value pairs:', function() { - app.page('a.hbs', {content: 'a'}); - app.page('b.hbs', {content: 'b'}); - app.page('c.hbs', {content: 'c'}); - app.views.pages.should.have.properties(['a.hbs', 'b.hbs', 'c.hbs']); - assert(app.views.pages['a.hbs'].contents.toString() === 'a'); - }); - - it('should create views from file paths:', function() { - app.page('test/fixtures/pages/a.hbs'); - app.page('test/fixtures/pages/b.hbs'); - app.page('test/fixtures/pages/c.hbs'); - - app.views.pages.should.have.properties([ - 'test/fixtures/pages/a.hbs', - 'test/fixtures/pages/b.hbs', - 'test/fixtures/pages/c.hbs' - ]); - }); - }); - - describe('instance', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - }); - - it('should return the collection instance', function() { - var collection = app.create('pages'); - assert(collection instanceof App.Views); - - collection.option('renameKey', function(key) { - return path.basename(key); - }); - collection - .use(function(views) { - views.read = function(name) { - var view = this.getView(name); - view.contents = fs.readFileSync(view.path); - }; - }); - - collection.addView('test/fixtures/templates/a.tmpl'); - collection.read('a.tmpl'); - assert(collection.getView('a.tmpl').contents.toString() === '<%= name %>'); - }); - }); - - describe('viewType', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - }); - - it('should add collection to the given viewType', function() { - app.create('layout', {viewType: 'layout'}); - assert(app.layouts.options.viewType[0] === 'layout'); - }); - - it('should add a collection to multiple viewTypes', function() { - app.create('foo', {viewType: ['layout', 'renderable']}); - assert.deepEqual(app.foos.options.viewType, ['layout', 'renderable']); - }); - }); - - describe('events', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - }); - - it('should emit `create` when a collection is created:', function() { - app.on('create', function(collection) { - if (collection.options.plural === 'layouts') { - collection.options.foo = 'bar'; - } - }); - - app.create('layout'); - app.layout('one', {path: 'two', contents: '...'}); - assert(app.layouts.options.foo === 'bar'); - }); - }); - - describe('collection instantiation', function() { - it('should expose collection instance methods that are created after instantiation on the app collection loader', function() { - app.create('pages'); - app.pages.use(function(collection) { - collection.define('foo', function(msg) { - return 'foo ' + msg; - }); - }); - - assert(app.pages.foo); - assert(typeof app.pages.foo === 'function'); - }); - }); -}); diff --git a/test/app.data.js b/test/app.data.js deleted file mode 100644 index 41808b6..0000000 --- a/test/app.data.js +++ /dev/null @@ -1,96 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var path = require('path'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('app.data', function() { - beforeEach(function() { - app = new App(); - }); - - it('should set a key-value pair on cache.data:', function() { - app.data('a', 'b'); - assert(app.cache.data.a === 'b'); - }); - - it('should set an object on cache.data:', function() { - app.data({c: 'd'}); - assert(app.cache.data.c === 'd'); - }); - - it('should load data from a file onto cache.data:', function() { - app.data('test/fixtures/data/a.json'); - assert(app.cache.data.a.one.a === 'aaa'); - }); - - it('should load a glob of data onto cache.data:', function() { - app.data('test/fixtures/data/*.json'); - assert(app.cache.data.a.one.a === 'aaa'); - assert(app.cache.data.b.two.b === 'bbb'); - assert(app.cache.data.c.three.c === 'ccc'); - }); - - it('should use `namespace` defined on global opts:', function() { - app.option('namespace', function(key) { - return 'prefix_' + path.basename(key, path.extname(key)); - }); - app.data('test/fixtures/data/*.json'); - assert(app.cache.data.prefix_a.one.a === 'aaa'); - assert(app.cache.data.prefix_b.two.b === 'bbb'); - assert(app.cache.data.prefix_c.three.c === 'ccc'); - }); - - it('should use `namespace` defined on data opts:', function() { - app.data('test/fixtures/data/*.json', { - namespace: function(key) { - return 'prefix_' + path.basename(key, path.extname(key)); - } - }); - assert(app.cache.data.prefix_a.one.a === 'aaa'); - assert(app.cache.data.prefix_b.two.b === 'bbb'); - assert(app.cache.data.prefix_c.three.c === 'ccc'); - }); - - it('should use `renameKey` defined on data opts:', function() { - app.data('test/fixtures/data/*.json', { - renameKey: function(key) { - return 'prefix_' + path.basename(key, path.extname(key)); - } - }); - assert(app.cache.data.prefix_a.one.a === 'aaa'); - assert(app.cache.data.prefix_b.two.b === 'bbb'); - assert(app.cache.data.prefix_c.three.c === 'ccc'); - }); - - it('should extend `cache.data`', function() { - app.data({a: 'aaa', b: 'bbb', c: 'ccc'}); - app.data({x: 'xxx', y: 'yyy', z: 'zzz'}); - assert(app.cache.data.a === 'aaa'); - assert(app.cache.data.b === 'bbb'); - assert(app.cache.data.c === 'ccc'); - assert(app.cache.data.x === 'xxx'); - assert(app.cache.data.y === 'yyy'); - assert(app.cache.data.z === 'zzz'); - }); - - it('should extend the `cache.data` object when the first param is a string.', function() { - app.data('foo', {x: 'xxx', y: 'yyy', z: 'zzz'}); - app.data('bar', {a: 'aaa', b: 'bbb', c: 'ccc'}); - assert(app.cache.data.foo.x === 'xxx'); - assert(app.cache.data.bar.a === 'aaa'); - }); - - it('should be chainable.', function() { - app - .data({x: 'xxx', y: 'yyy', z: 'zzz'}) - .data({a: 'aaa', b: 'bbb', c: 'ccc'}); - - assert(app.cache.data.x === 'xxx'); - assert(app.cache.data.a === 'aaa'); - }); -}); diff --git a/test/app.dest.js b/test/app.dest.js deleted file mode 100644 index 8e59d82..0000000 --- a/test/app.dest.js +++ /dev/null @@ -1,1103 +0,0 @@ -var spies = require('./support/spy'); -var chmodSpy = spies.chmodSpy; -var statSpy = spies.statSpy; - -require('mocha'); -var should = require('should'); -var assert = require('assert'); -var App = require('..'); -var app; - -var path = require('path'); -var fs = require('graceful-fs'); -var rimraf = require('rimraf'); - -var bufferStream; -var bufEqual = require('buffer-equal'); -var through = require('through2'); -var File = require('vinyl'); - -var actual = path.join(__dirname, 'actual'); - -var wipeOut = function(cb) { - app = new App(); - rimraf(path.join(__dirname, 'actual/'), cb); - spies.setError('false'); - statSpy.reset(); - chmodSpy.reset(); -}; - -var dataWrap = function(fn) { - return function(data, enc, cb) { - fn(data); - cb(); - }; -}; - -var realMode = function(n) { - return n & 07777; -}; - -describe('dest stream', function() { - beforeEach(wipeOut); - afterEach(wipeOut); - - it('should explode on invalid folder (empty)', function(done) { - var stream; - try { - stream = app.dest(); - } catch (err) { - assert(err && typeof err === 'object'); - should.not.exist(stream); - done(); - } - }); - - it('should explode on invalid folder (empty string)', function(done) { - var stream; - try { - stream = app.dest(''); - } catch (err) { - assert(err && typeof err === 'object'); - should.not.exist(stream); - done(); - } - }); - - it('should pass through writes with cwd', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - - var expectedFile = new File({ - base: __dirname, - cwd: __dirname, - path: inputPath, - contents: null - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - done(); - }; - - var stream = app.dest('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should pass through writes with default cwd', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - - var expectedFile = new File({ - base: __dirname, - cwd: __dirname, - path: inputPath, - contents: null - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - done(); - }; - - var stream = app.dest(path.join(__dirname, 'actual/')); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should not write null files', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedCwd = __dirname; - var expectedBase = path.join(__dirname, 'actual'); - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: null - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(false); - done(); - }; - - var stream = app.dest('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should write buffer files to the right folder with relative cwd', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedCwd = __dirname; - var expectedBase = path.join(__dirname, 'actual'); - var expectedContents = fs.readFileSync(inputPath); - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(true); - bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); - done(); - }; - - var stream = app.dest('./actual/', {cwd: path.relative(process.cwd(), __dirname)}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should write buffer files to the right folder with function and relative cwd', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedCwd = __dirname; - var expectedBase = path.join(__dirname, 'actual'); - var expectedContents = fs.readFileSync(inputPath); - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(true); - bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); - done(); - }; - - var stream = app.dest(function(file){ - should.exist(file); - file.should.equal(expectedFile); - return './actual'; - }, {cwd: path.relative(process.cwd(), __dirname)}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should write buffer files to the right folder', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedCwd = __dirname; - var expectedBase = path.join(__dirname, 'actual'); - var expectedMode = 0655; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: expectedMode - } - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(true); - bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); - realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); - done(); - }; - - var stream = app.dest('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should write streaming files to the right folder', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedCwd = __dirname; - var expectedBase = path.join(__dirname, 'actual'); - var expectedMode = 0655; - - var contentStream = through.obj(); - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: contentStream, - stat: { - mode: expectedMode - } - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(true); - bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); - realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); - done(); - }; - - var stream = app.dest('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - setTimeout(function(){ - contentStream.write(expectedContents); - contentStream.end(); - }, 100); - stream.end(); - }); - - it('should write directories to the right folder', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test'); - var expectedCwd = __dirname; - var expectedBase = path.join(__dirname, 'actual'); - var expectedMode = 0655; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: null, - stat: { - isDirectory: function(){ - return true; - }, - mode: expectedMode - } - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(true); - fs.lstatSync(expectedPath).isDirectory().should.equal(true); - realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); - done(); - }; - - var stream = app.dest('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should allow piping multiple dests in streaming mode', function(done) { - var inputPath1 = path.join(__dirname, 'actual/multiple-first'); - var inputPath2 = path.join(__dirname, 'actual/multiple-second'); - var inputBase = path.join(__dirname, 'actual/'); - var srcPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var stream1 = app.dest('./actual/', {cwd: __dirname}); - var stream2 = app.dest('./actual/', {cwd: __dirname}); - var content = fs.readFileSync(srcPath); - var rename = through.obj(function(file, _, next) { - file.path = inputPath2; - this.push(file); - next(); - }); - - stream1.on('data', function(file) { - file.path.should.equal(inputPath1); - }); - - stream1.pipe(rename).pipe(stream2); - stream2.on('data', function(file) { - file.path.should.equal(inputPath2); - }).once('end', function() { - fs.readFileSync(inputPath1, 'utf8').should.equal(content.toString()); - fs.readFileSync(inputPath2, 'utf8').should.equal(content.toString()); - done(); - }); - - var file = new File({ - base: inputBase, - path: inputPath1, - cwd: __dirname, - contents: content - }); - - stream1.write(file); - stream1.end(); - }); - - it('should write new files with the default user mode', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedMode = 0666 & (~process.umask()); - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - fs.existsSync(expectedPath).should.equal(true); - realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); - done(); - }; - - chmodSpy.reset(); - var stream = app.dest('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should write new files with the specified mode', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedMode = 0744; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - fs.existsSync(expectedPath).should.equal(true); - realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); - done(); - }; - - chmodSpy.reset(); - var stream = app.dest('./actual/', {cwd: __dirname, mode:expectedMode}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should update file mode to match the vinyl mode', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, 'actual'); - var startMode = 0655; - var expectedMode = 0722; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: expectedMode - } - }); - - var onEnd = function(){ - assert(chmodSpy.called); - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - fs.existsSync(expectedPath).should.equal(true); - realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); - done(); - }; - - fs.mkdirSync(expectedBase); - fs.closeSync(fs.openSync(expectedPath, 'w')); - fs.chmodSync(expectedPath, startMode); - - chmodSpy.reset(); - var stream = app.dest('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should use different modes for files and directories', function(done) { - var inputBase = path.join(__dirname, 'fixtures/vinyl'); - var inputPath = path.join(__dirname, 'fixtures/vinyl/wow/suchempty'); - var expectedBase = path.join(__dirname, 'actual/wow'); - var expectedDirMode = 0755; - var expectedFileMode = 0655; - - var firstFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - stat: fs.statSync(inputPath) - }); - - var onEnd = function(){ - realMode(fs.lstatSync(expectedBase).mode).should.equal(expectedDirMode); - realMode(buffered[0].stat.mode).should.equal(expectedFileMode); - done(); - }; - - var stream = app.dest('./actual/', { - cwd: __dirname, - mode: expectedFileMode, - dirMode: expectedDirMode - }); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(firstFile); - stream.end(); - }); - - it('should change to the specified base as string', function(done) { - var inputBase = path.join(__dirname, 'fixtures/vinyl'); - var inputPath = path.join(__dirname, 'fixtures/vinyl/wow/suchempty'); - - var firstFile = new File({ - cwd: __dirname, - path: inputPath, - stat: fs.statSync(inputPath) - }); - - var onEnd = function(){ - buffered[0].base.should.equal(inputBase); - done(); - }; - - var stream = app.dest('./actual/', { - cwd: __dirname, - base: inputBase - }); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(firstFile); - stream.end(); - }); - - it('should change to the specified base as function', function(done) { - var inputBase = path.join(__dirname, 'fixtures/vinyl'); - var inputPath = path.join(__dirname, 'fixtures/vinyl/wow/suchempty'); - - var firstFile = new File({ - cwd: __dirname, - path: inputPath, - stat: fs.statSync(inputPath) - }); - - var onEnd = function() { - buffered[0].base.should.equal(inputBase); - done(); - }; - - var stream = app.dest('./actual/', { - cwd: __dirname, - base: function(file){ - should.exist(file); - file.path.should.equal(inputPath); - return inputBase; - } - }); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(firstFile); - stream.end(); - }); - - it('should report IO errors', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, 'actual'); - var expectedMode = 0722; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: expectedMode - } - }); - - fs.mkdirSync(expectedBase); - fs.closeSync(fs.openSync(expectedPath, 'w')); - fs.chmodSync(expectedPath, 0); - - var stream = app.dest('./actual/', {cwd: __dirname}); - stream.on('error', function(err) { - err.code.should.equal('EACCES'); - done(); - }); - stream.write(expectedFile); - }); - - it('should report stat errors', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, 'actual'); - var expectedMode = 0722; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: expectedMode - } - }); - - fs.mkdirSync(expectedBase); - fs.closeSync(fs.openSync(expectedPath, 'w')); - - spies.setError(function(mod, fn) { - if (fn === 'stat' && arguments[2] === expectedPath) { - return new Error('stat error'); - } - }); - - var stream = app.dest('./actual/', {cwd: __dirname}); - stream.on('error', function(err) { - err.message.should.equal('stat error'); - done(); - }); - stream.write(expectedFile); - }); - - it('should report chmod errors', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, 'actual'); - var expectedMode = 0722; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: expectedMode - } - }); - - fs.mkdirSync(expectedBase); - fs.closeSync(fs.openSync(expectedPath, 'w')); - - spies.setError(function(mod, fn) { - if (fn === 'chmod' && arguments[2] === expectedPath) { - return new Error('chmod error'); - } - }); - - var stream = app.dest('./actual/', {cwd: __dirname}); - stream.on('error', function(err) { - err.message.should.equal('chmod error'); - done(); - }); - stream.write(expectedFile); - }); - - it('should not chmod a matching file', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, 'actual'); - var expectedMode = 0722; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: expectedMode - } - }); - - var expectedCount = 0; - spies.setError(function(mod, fn) { - if (fn === 'stat' && arguments[2] === expectedPath) { - expectedCount++; - } - }); - - var onEnd = function(){ - expectedCount.should.equal(1); - assert(!chmodSpy.called); - realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); - done(); - }; - - fs.mkdirSync(expectedBase); - fs.closeSync(fs.openSync(expectedPath, 'w')); - fs.chmodSync(expectedPath, expectedMode); - - statSpy.reset(); - chmodSpy.reset(); - var stream = app.dest('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should see a file with special chmod (setuid/setgid/sticky) as matching', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, 'actual'); - var expectedMode = 03722; - var normalMode = 0722; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: normalMode - } - }); - - var expectedCount = 0; - spies.setError(function(mod, fn) { - if (fn === 'stat' && arguments[2] === expectedPath) { - expectedCount++; - } - }); - - var onEnd = function(){ - expectedCount.should.equal(1); - assert(!chmodSpy.called); - done(); - }; - - fs.mkdirSync(expectedBase); - fs.closeSync(fs.openSync(expectedPath, 'w')); - fs.chmodSync(expectedPath, expectedMode); - - statSpy.reset(); - chmodSpy.reset(); - var stream = app.dest('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should not overwrite files with overwrite option set to false', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var inputContents = fs.readFileSync(inputPath); - - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedBase = path.join(__dirname, 'actual'); - var existingContents = 'Lorem Ipsum'; - - var inputFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: inputContents - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - bufEqual(fs.readFileSync(expectedPath), new Buffer(existingContents)).should.equal(true); - done(); - }; - - // Write expected file which should not be overwritten - fs.mkdirSync(expectedBase); - fs.writeFileSync(expectedPath, existingContents); - - var stream = app.dest('./actual/', {cwd: __dirname, overwrite: false}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(inputFile); - stream.end(); - }); - - it('should overwrite files with overwrite option set to true', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var inputContents = fs.readFileSync(inputPath); - - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedBase = path.join(__dirname, 'actual'); - var existingContents = 'Lorem Ipsum'; - - var inputFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: inputContents - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - bufEqual(fs.readFileSync(expectedPath), new Buffer(inputContents)).should.equal(true); - done(); - }; - - // This should be overwritten - fs.mkdirSync(expectedBase); - fs.writeFileSync(expectedPath, existingContents); - - var stream = app.dest('./actual/', {cwd: __dirname, overwrite: true}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(inputFile); - stream.end(); - }); - - it('should create symlinks when the `symlink` attribute is set on the file', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test-create-dir-symlink'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var inputRelativeSymlinkPath = 'wow'; - - var expectedPath = path.join(__dirname, 'actual/test-create-dir-symlink'); - var buffered = []; - - var inputFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: null, //'' - }); - - // `src()` adds this side-effect with `keepSymlinks` option set to false - inputFile.symlink = inputRelativeSymlinkPath; - - var onEnd = function(){ - fs.readlink(buffered[0].path, function() { - buffered[0].symlink.should.equal(inputFile.symlink); - buffered[0].path.should.equal(expectedPath); - done(); - }); - }; - - var stream = app.dest('./actual/', {cwd: __dirname}); - - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(inputFile); - stream.end(); - }); - - it('should emit finish event', function(done) { - var srcPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var stream = app.dest('./actual/', {cwd: __dirname}); - - stream.once('finish', function() { - done(); - }); - - var file = new File({ - path: srcPath, - cwd: __dirname, - contents: new Buffer("1234567890") - }); - - stream.write(file); - stream.end(); - }); -}); - -describe('dest', function() { - beforeEach(function(done) { - rimraf(actual, done); - app = new App(); - }); - - afterEach(function(done) { - rimraf(actual, done); - }); - - describe('streams', function() { - it('should return a stream', function(done) { - var stream = app.dest(path.join(__dirname, 'fixtures/')); - should.exist(stream); - should.exist(stream.on); - done(); - }); - - it('should return an output stream that writes files', function(done) { - var instream = app.src(path.join(__dirname, 'fixtures/copy/e*.txt')); - var outstream = app.dest(actual); - instream.pipe(outstream); - - outstream.on('error', done); - outstream.on('data', function(file) { - // data should be re-emitted correctly - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - path.join(file.path, '').should.equal(path.join(actual, 'example.txt')); - String(file.contents).should.equal('Hello world!'); - }); - outstream.on('end', function() { - fs.readFile(path.join(actual, 'example.txt'), function(err, contents) { - should.not.exist(err); - should.exist(contents); - String(contents).should.equal('Hello world!'); - done(); - }); - }); - }); - - it('should return an output stream that does not write non-read files', function(done) { - var instream = app.src(path.join(__dirname, 'fixtures/copy/e*.txt'), {read: false}); - var outstream = app.dest(actual); - instream.pipe(outstream); - - outstream.on('error', done); - outstream.on('data', function(file) { - // data should be re-emitted correctly - should.exist(file); - should.exist(file.path); - should.not.exist(file.contents); - path.join(file.path, '').should.equal(path.join(actual, 'example.txt')); - }); - - outstream.on('end', function() { - fs.readFile(path.join(actual, 'example.txt'), function(err, contents) { - should.exist(err); - should.not.exist(contents); - done(); - }); - }); - }); - - it('should return an output stream that writes streaming files', function(done) { - var instream = app.src(path.join(__dirname, 'fixtures/copy/e*.txt'), {buffer: false}); - var outstream = instream.pipe(app.dest(actual)); - - outstream.on('error', done); - outstream.on('data', function(file) { - // data should be re-emitted correctly - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - path.join(file.path, '').should.equal(path.join(actual, 'example.txt')); - }); - outstream.on('end', function() { - fs.readFile(path.join(actual, 'example.txt'), function(err, contents) { - should.not.exist(err); - should.exist(contents); - String(contents).should.equal('Hello world!'); - done(); - }); - }); - }); - - it('should return an output stream that writes streaming files to new directories', function(done) { - testWriteDir({}, done); - }); - - it('should return an output stream that writes streaming files to new directories (buffer: false)', function(done) { - testWriteDir({buffer: false}, done); - }); - - it('should return an output stream that writes streaming files to new directories (read: false)', function(done) { - testWriteDir({read: false}, done); - }); - - it('should return an output stream that writes streaming files to new directories (read: false, buffer: false)', function(done) { - testWriteDir({buffer: false, read: false}, done); - }); - - }); - - describe('ext', function() { - beforeEach(function() { - app = new App(); - app.set('ext', '.txt'); - }); - - afterEach(function() { - app.set('ext', '.html'); - }); - - it('should return a stream', function(done) { - var stream = app.dest(path.join(__dirname, 'fixtures/')); - should.exist(stream); - should.exist(stream.on); - done(); - }); - - it('should return an output stream that writes files', function(done) { - var instream = app.src(path.join(__dirname, 'fixtures/copy/e*.txt')); - var outstream = app.dest(actual); - instream.pipe(outstream); - - outstream.on('error', done); - outstream.on('data', function(file) { - // data should be re-emitted correctly - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - path.join(file.path, '').should.equal(path.join(actual, 'example.txt')); - String(file.contents).should.equal('Hello world!'); - }); - outstream.on('end', function() { - fs.readFile(path.join(actual, 'example.txt'), function(err, contents) { - should.not.exist(err); - should.exist(contents); - String(contents).should.equal('Hello world!'); - done(); - }); - }); - }); - - it('should return an output stream that does not write non-read files', function(done) { - var instream = app.src(path.join(__dirname, 'fixtures/dest/*.txt'), {read: false}); - var outstream = app.dest(actual); - instream.pipe(outstream); - - outstream.on('error', done); - outstream.on('data', function(file) { - // data should be re-emitted correctly - should.exist(file); - should.exist(file.path); - should.not.exist(file.contents); - path.join(file.path, '').should.equal(path.join(actual, 'example.txt')); - }); - - outstream.on('end', function() { - fs.readFile(path.join(actual, 'example.txt'), function(err, contents) { - should.exist(err); - should.not.exist(contents); - done(); - }); - }); - }); - }); - - function testWriteDir(srcOptions, done) { - var instream = app.src(path.join(__dirname, 'fixtures/generic'), srcOptions); - var outstream = instream.pipe(app.dest(actual)); - - outstream.on('error', done); - outstream.on('data', function(file) { - // data should be re-emitted correctly - should.exist(file); - should.exist(file.path); - path.join(file.path,'').should.equal(path.join(actual, 'generic')); - }); - - outstream.on('end', function() { - fs.exists(path.join(actual, 'generic'), function(exists) { - /* jshint expr: true */ - should(exists).be.ok; - /* jshint expr: false */ - done(); - }); - }); - } -}); - diff --git a/test/app.engines.js b/test/app.engines.js deleted file mode 100644 index 16bd062..0000000 --- a/test/app.engines.js +++ /dev/null @@ -1,160 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('engine support', function() { - beforeEach(function() { - app = new App(); - }); - - it('should throw an error when engine name is invalid:', function() { - (function() { - app.engine(null, {}); - }).should.throw('expected engine ext to be a string or array.'); - }); - - it('should register an engine to the given extension', function() { - app.engine('hbs', function() {}); - assert(typeof app.engines['.hbs'] === 'object'); - }); - - it('should set an engine with the given extension', function() { - var hbs = function() {}; - hbs.render = function() {}; - hbs.renderFile = function() {}; - app.engine('hbs', hbs); - assert(app.engines['.hbs']); - assert(app.engines['.hbs'].renderFile); - assert(app.engines['.hbs'].render); - }); - - it('should get an engine:', function() { - app.engine('hbs', function() {}); - var hbs = app.engine('hbs'); - assert(typeof hbs === 'object'); - assert(hbs.hasOwnProperty('render')); - assert(hbs.hasOwnProperty('compile')); - }); - - it('should return undefined if no engine is found:', function() { - var hbs = app.getEngine(); - assert.equal(typeof hbs, 'undefined'); - }); - - it('should register multiple engines to the given extension', function() { - app.engine(['hbs', 'md'], function() {}); - assert(typeof app.engines['.hbs'] === 'object'); - assert(typeof app.engines['.md'] === 'object'); - }); -}); - -describe('engines', function() { - beforeEach(function() { - app = new App(); - app.create('pages'); - app.pages('foo.tmpl', {content: 'A <%= letter %> {{= letter }} C'}); - app.pages('bar.tmpl', {content: 'A <%= letter %> {{ letter }} C'}); - }); - - it('should register an engine:', function() { - app.engine('a', {render: function() {}}); - app.engines.should.have.property('.a'); - }); - - it('should use custom delimiters:', function(cb) { - app.engine('tmpl', require('engine-base'), { - delims: ['{{', '}}'] - }); - app.render('foo.tmpl', {letter: 'B'}, function(err, res) { - if (err) return cb(err); - res.contents.toString().should.equal('A <%= letter %> B C'); - cb(); - }); - }); - - it('should override individual delims values:', function(cb) { - app.engine('tmpl', require('engine-base'), { - interpolate: /\{{([^}]+)}}/g, - evaluate: /\{{([^}]+)}}/g, - escape: /\{{-([^}]+)}}/g - }); - app.render('bar.tmpl', {letter: 'B'}, function(err, res) { - if (err) return cb(err); - res.contents.toString().should.equal('A <%= letter %> B C'); - cb(); - }); - }); - - it('should get an engine:', function() { - app.engine('a', { - render: function() {} - }); - var a = app.engine('a'); - a.should.have.property('render'); - }); -}); - -describe('engine selection:', function() { - beforeEach(function(cb) { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.engine('hbs', require('engine-handlebars')); - app.create('pages'); - cb(); - }); - - it('should get the engine from file extension:', function(cb) { - app.page('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}) - .render(function(err, view) { - if (err) return cb(err); - assert(view.content === 'b'); - cb(); - }); - }); - - it('should use the engine defined on the collection:', function(cb) { - app.create('posts', {engine: 'hbs'}); - - app.post('a', {content: '{{a}}', locals: {a: 'b'}}) - .render(function(err, view) { - if (err) return cb(err); - assert(view.content === 'b'); - cb(); - }); - }); - - it('should use the engine defined on the view:', function(cb) { - app.create('posts'); - app.post('a', {content: '{{a}}', engine: 'hbs', locals: {a: 'b'}}) - .render(function(err, view) { - if (err) return cb(err); - assert(view.content === 'b'); - cb(); - }); - }); - - it('should use the engine defined on `view.data`:', function(cb) { - app.create('posts'); - app.post('a', {content: '{{a}}', locals: {a: 'b'}, data: {engine: 'hbs'}}) - .render(function(err, view) { - if (err) return cb(err); - assert(view.content === 'b'); - cb(); - }); - }); - - it('should use the engine defined on render locals:', function(cb) { - app.create('posts'); - app.post('a', {content: '{{a}}', locals: {a: 'b'}}) - .render({engine: 'hbs'}, function(err, view) { - if (err) return cb(err); - assert(view.content === 'b'); - cb(); - }); - }); -}); diff --git a/test/app.events.js b/test/app.events.js deleted file mode 100644 index 4086a78..0000000 --- a/test/app.events.js +++ /dev/null @@ -1,115 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('events', function() { - beforeEach(function() { - app = new App(); - }); - - it('should listen for an event:', function() { - var app = new App(); - app.on('foo', function() {}); - assert(Array.isArray(app._callbacks['$foo'])); - }); - - it('should emit an event:', function(done) { - var app = new App(); - app.on('foo', function(val) { - assert(val === 'bar'); - done(); - }); - assert(Array.isArray(app._callbacks['$foo'])); - app.emit('foo', 'bar'); - }); - - it('should listen for `view` events:', function() { - app = new App(); - - app.on('view', function(view) { - view.foo = 'bar'; - }); - - var view = app.view({path: 'a', content: 'b'}); - assert(view.foo === 'bar'); - }); -}); - -describe('onLoad', function() { - beforeEach(function() { - app = new App(); - }); - - describe('app.collection', function() { - it('should emit a `view` event when view is created', function(done) { - var collection = app.collection(); - - app.on('view', function(view) { - assert(view.path === 'blog/foo.js'); - done(); - }); - - app.onLoad('blog/:title', function(view, next) { - assert(view.path === 'blog/foo.js'); - next(); - }); - - collection.addView('whatever', {path: 'blog/foo.js', content: 'bar baz'}); - }); - - it('should emit an onLoad event when view is created', function(done) { - var collection = app.collection(); - - app.on('onLoad', function(view) { - assert(view.path === 'blog/foo.js'); - done(); - }); - - app.onLoad('blog/:title', function(view, next) { - assert(view.path === 'blog/foo.js'); - next(); - }); - - collection.addView('whatever', {path: 'blog/foo.js', content: 'bar baz'}); - }); - }); - - describe('view collections', function() { - it('should emit a view event when view is created', function(done) { - app.create('posts'); - - app.on('view', function(view) { - assert(view.path === 'blog/foo.js'); - done(); - }); - - app.onLoad('blog/:title', function(view, next) { - assert(view.path === 'blog/foo.js'); - next(); - }); - - app.post('whatever', {path: 'blog/foo.js', content: 'bar baz'}); - }); - - it('should emit an onLoad event when view is created', function(done) { - app.create('posts'); - - app.on('onLoad', function(view) { - assert(view.path === 'blog/foo.js'); - done(); - }); - - app.onLoad('blog/:title', function(view, next) { - assert(view.path === 'blog/foo.js'); - next(); - }); - - app.post('whatever', {path: 'blog/foo.js', content: 'bar baz'}); - }); - }); -}); diff --git a/test/app.get-set.js b/test/app.get-set.js deleted file mode 100644 index fd967aa..0000000 --- a/test/app.get-set.js +++ /dev/null @@ -1,74 +0,0 @@ -'use strict'; - -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('app.set()', function() { - beforeEach(function() { - app = new App(); - }); - - it('should set a value', function() { - app.set('a', 'b'); - app.get('a').should.equal('b'); - }); - - it('should set properties on the instance.', function() { - app.set('a', 'b'); - app.a.should.equal('b'); - }); - - it('should allow an object to be set directly.', function() { - app.set({x: 'y'}); - app.x.should.equal('y'); - app.get('x').should.equal('y'); - }); - - it('should set nested properties on the instance.', function() { - app.set('c', {d: 'e'}); - app.get('c').d.should.equal('e'); - }); - - it('should use dot notation to `set` values.', function() { - app.set('h.i', 'j'); - app.get('h').should.eql({i: 'j'}); - }); - - it('should use dot notation to `get` values.', function() { - app.set('h', {i: 'j'}); - app.get('h.i').should.equal('j'); - }); - - it('should return `this` for chaining', function() { - app.set('a', 'b').should.equal(app); - app - .set('aa', 'bb') - .set('bb', 'cc') - .set('cc', 'dd'); - app.get('aa').should.equal('bb'); - app.get('bb').should.equal('cc'); - app.get('cc').should.equal('dd'); - }); - - it('should return undefined when not set', function() { - app.set('a', undefined).should.equal(app); - }); -}); - -describe('app.get()', function() { - beforeEach(function() { - app = new App(); - }); - - it('should return undefined when no set', function() { - assert(app.get('a') === undefined); - }); - - it('should otherwise return the value', function() { - app.set('a', 'b'); - app.get('a').should.equal('b'); - }); -}); diff --git a/test/app.handle.js b/test/app.handle.js deleted file mode 100644 index bf0d16c..0000000 --- a/test/app.handle.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('handler', function() { - beforeEach(function() { - app = new App(); - app.create('pages'); - app.handlers(['foo']); - }); - - it('should support custom handle methods:', function(done) { - var page = app.page('foo', {contents: null}); - - app.handle('foo', page, function(err, view) { - if (err) return done(err); - - assert(typeof view.path === 'string'); - done(); - }); - }); - - it('should not blow up if `options.handled` does not exist:', function(done) { - var page = app.page('foo', {contents: null}); - delete page.options.handled; - - app.handle('foo', page, function(err, view) { - if (err) return done(err); - - assert(typeof view.path === 'string'); - done(); - }); - }); -}); diff --git a/test/app.handlers.js b/test/app.handlers.js deleted file mode 100644 index c0c0d2e..0000000 --- a/test/app.handlers.js +++ /dev/null @@ -1,74 +0,0 @@ -'use strict'; - -require('should'); -var fs = require('fs'); -var path = require('path'); -var assert = require('assert'); -var resolve = require('resolve-glob'); -var support = require('./support'); -var App = support.resolve(); -var app; - -function decorateViews(views) { - var fn = views.decorateView; - views.decorateView = function() { - var view = fn.apply(fn, arguments); - view.read = function() { - if (!this.contents) { - this.contents = fs.readFileSync(this.path); - } - }; - return view; - }; - views.loader = function(pattern) { - var files = resolve.sync(pattern); - return files.reduce(function(acc, fp) { - acc[fp] = {path: fp}; - return acc; - }, {}); - }; - return views; -} - -describe('handlers', function() { - describe('custom handlers', function() { - beforeEach(function() { - app = new App(); - app.create('pages') - .use(decorateViews) - .option('renameKey', function(key) { - return path.basename(key); - }); - }); - - it('should add custom middleware handlers:', function() { - app.handler('foo'); - app.router.should.have.property('foo'); - assert.equal(typeof app.router.foo, 'function'); - }); - - it('should add custom middleware handlers:', function() { - app.handler('foo'); - app.handler('bar'); - - app.foo(/./, function(view, next) { - view.one = 'aaa'; - next(); - }); - - app.bar(/./, function(view, next) { - view.two = 'zzz'; - next(); - }); - - app.page('abc', {content: '...'}) - .use(function(view) { - app.handleView('foo', view); - app.handleView('bar', view); - }); - - app.views.pages.abc.should.have.property('one', 'aaa'); - app.views.pages.abc.should.have.property('two', 'zzz'); - }); - }); -}); diff --git a/test/app.js b/test/app.js deleted file mode 100644 index f575266..0000000 --- a/test/app.js +++ /dev/null @@ -1,132 +0,0 @@ -'use strict'; - -/* deps: coveralls istanbul */ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var Base = App.Base; -var app; - -describe('app', function() { - describe('constructor', function() { - it('should create an instance of App:', function() { - app = new App(); - assert(app instanceof App); - }); - - it('should new up without new:', function() { - app = App(); - assert(app instanceof App); - }); - }); - - describe('static methods', function() { - it('should expose `extend`:', function() { - assert(typeof App.extend === 'function'); - }); - }); - - describe('prototype methods', function() { - beforeEach(function() { - app = new App(); - }); - - it('should expose `set`', function() { - assert(typeof app.set === 'function'); - }); - it('should expose `get`', function() { - assert(typeof app.get === 'function'); - }); - it('should expose `visit`', function() { - assert(typeof app.visit === 'function'); - }); - it('should expose `define`', function() { - assert(typeof app.define === 'function'); - }); - it('should expose `views`', function() { - assert(typeof app.views === 'object'); - }); - }); - - describe('instance', function() { - beforeEach(function() { - app = new App(); - }); - - it('should set a value on the instance:', function() { - app.set('a', 'b'); - assert(app.a === 'b'); - }); - - it('should get a value from the instance:', function() { - app.set('a', 'b'); - assert(app.get('a') === 'b'); - }); - }); - - describe('initialization', function() { - it('should listen for errors:', function(done) { - app = new App(); - app.on('error', function(err) { - assert(err.message === 'foo'); - done(); - }); - app.emit('error', new Error('foo')); - }); - - it('should mixin methods after init:', function() { - app = new App(); - app.option({ - mixins: { - foo: function() {} - } - }); - assert(typeof app.foo === 'function'); - }); - - it('should expose constructors from `lib`:', function() { - app = new App(); - app.expose('Collection'); - assert(typeof app.Collection === 'function'); - }); - - it('should update constructors after init:', function() { - var Group = App.Group; - function MyGroup() { - Base.call(this); - } - Base.extend(MyGroup); - - app = new App(); - assert.equal(app.Group, Group); - assert.equal(app.get('Group'), Group); - app.option('Group', MyGroup); - assert.equal(app.Group, MyGroup); - assert.equal(app.get('Group'), MyGroup); - }); - - it('should mixin prototype methods defined on options:', function() { - app = new App({ - mixins: { - foo: function() {} - } - }); - assert(typeof app.foo === 'function'); - delete App.prototype.foo; - }); - - it('should expose `_` on app:', function() { - app = new App(); - assert(typeof app._ === 'object'); - }); - - it('should not re-add `_` in init:', function() { - app = new App(); - app._.foo = 'bar'; - app.defaultConfig(); - assert(app._.foo === 'bar'); - }); - }); -}); diff --git a/test/app.list.compile.js b/test/app.list.compile.js deleted file mode 100644 index 2efa223..0000000 --- a/test/app.list.compile.js +++ /dev/null @@ -1,46 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var List = App.List; -var list; - -describe('app.list.compile', function() { - beforeEach(function() { - list = new List(); - list.engine('tmpl', require('engine-base')); - }); - - it('should compile an item:', function() { - var buffer = new Buffer('a b c'); - var item = list.addItem('a.tmpl', {contents: buffer}) - .compile(); - - assert(typeof item.fn === 'function'); - }); - - it('should use the compiled function to render:', function() { - var buffer = new Buffer('a <%= title %> c'); - var item = list.addItem('a.tmpl', {contents: buffer}) - .compile(); - - assert(item.fn({title: 'z'})); - assert(typeof item.fn({title: 'z'}) === 'string'); - assert(item.fn({title: 'z'}) === 'a z c'); - }); - - it('should compile a view by name:', function() { - var buffer = new Buffer('a <%= title %> c'); - list.addItem('a.tmpl', {contents: buffer}); - - var item = list.compile('a.tmpl'); - - assert(item.fn({title: 'z'})); - assert(typeof item.fn({title: 'z'}) === 'string'); - assert(item.fn({title: 'z'}) === 'a z c'); - }); -}); - diff --git a/test/app.list.js b/test/app.list.js deleted file mode 100644 index 8e40af0..0000000 --- a/test/app.list.js +++ /dev/null @@ -1,117 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var fs = require('fs'); -var path = require('path'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var List = App.List; -var app; - -describe('list', function() { - describe('method', function() { - beforeEach(function() { - app = new App(); - }); - - it('should expose the list method', function() { - assert(typeof app.list === 'function'); - }); - - it('should return a new list', function() { - var list = app.list(); - assert(typeof list === 'object'); - }); - - it('should have isList property', function() { - var list = app.list(); - assert(list.isList === true); - }); - }); - - describe('adding items', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('pages') - .use(function(views) { - var fn = views.getView; - views.getView = function(name) { - var view = fn.apply(this, arguments); - if (!view && fs.existsSync(path.resolve(name))) { - view = this.addView(name, {content: fs.readFileSync(name)}); - } - return view; - }; - }); - }); - - it('should add an item to a list:', function() { - app.pages('test/fixtures/pages/a.hbs'); - var list = app.list(); - list.addItem(app.pages.getView('test/fixtures/pages/a.hbs')); - assert(list.hasItem('a.hbs')); - }); - - it('should expose the `option` method from a list:', function() { - var list = app.list(); - list.option('a', 'b'); - assert(list.options); - assert(list.options.a === 'b'); - }); - }); - - describe('addItem', function() { - beforeEach(function() { - app = new App(); - }); - - it('should add items to a list', function() { - var pages = app.list({List: List}); - pages.addItem('foo'); - pages.addItem('bar'); - pages.addItem('baz'); - - pages.items.hasOwnProperty('foo'); - pages.items.hasOwnProperty('bar'); - pages.items.hasOwnProperty('baz'); - }); - - it('should create a list from an existing list:', function() { - var pages = app.list({List: List}); - pages.addItem('foo'); - pages.addItem('bar'); - pages.addItem('baz'); - - var posts = app.list(pages); - posts.items.hasOwnProperty('foo'); - posts.items.hasOwnProperty('bar'); - posts.items.hasOwnProperty('baz'); - }); - }); - - describe('rendering items', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('pages'); - }); - - it('should render a item with inherited app.render', function(done) { - app.page('test/fixtures/templates/a.tmpl') - .use(function(item) { - if (!item.content) { - item.contents = fs.readFileSync(item.path); - } - }) - .set('data.name', 'Brian') - .render(function(err, res) { - if (err) return done(err); - assert(res.contents.toString() === 'Brian'); - done(); - }); - }); - }); -}); diff --git a/test/app.lookups.js b/test/app.lookups.js deleted file mode 100644 index b64a593..0000000 --- a/test/app.lookups.js +++ /dev/null @@ -1,114 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var fs = require('fs'); -var path = require('path'); -var assert = require('assert'); -var resolve = require('resolve-glob'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('lookups', function() { - beforeEach(function() { - app = new App(); - app.option('renameKey', function(key) { - return path.basename(key); - }); - app.create('pages') - .use(function(pages) { - pages.on('addViews', function(glob) { - var files = resolve.sync(glob); - files.forEach(function(fp) { - pages.addView(fp, {path: fp}); - }); - pages.loaded = true; - }); - return function(view) { - view.read = function() { - this.contents = fs.readFileSync(this.path); - }; - return view; - }; - }); - - app.pages('test/fixtures/templates/*.tmpl'); - }); - - describe('getView', function() { - it('should find a view', function() { - var view = app.getView('pages', 'a.tmpl'); - assert(typeof view.path === 'string'); - }); - - it('should find a view using the renameKey function', function() { - var view = app.getView('pages', 'test/fixtures/templates/a.tmpl'); - assert(typeof view.path === 'string'); - }); - - it('should return null when nothing is found', function() { - var view = app.getView('pages', 'test/fixtures/templates/foo.tmpl'); - assert(view === null); - }); - - it('should find a view using a glob pattern', function() { - var view = app.getView('pages', 'a', function(key) { - return key + '.tmpl'; - }); - assert(typeof view.path === 'string'); - }); - }); - - describe('getViews', function() { - it('should return the collection object if passed:', function() { - var views = app.getViews(app.views.pages); - assert(Object.keys(views).length > 1); - }); - - it('should return the specified collection with the plural name:', function() { - var views = app.getViews('pages'); - assert(Object.keys(views).length > 1); - }); - - it('should return the specified collection with the singular name:', function() { - var views = app.getViews('page'); - assert(Object.keys(views).length > 1); - }); - - it('should return null when the collection is not found:', function() { - (function() { - app.getViews('nada'); - }).should.throw('getViews cannot find collection: nada'); - }); - }); - - describe('find', function() { - it('should return null when a view is not found:', function() { - (function() { - app.find({}); - }).should.throw('expected name to be a string.'); - }); - - it('should find a view by collection name:', function() { - var view = app.find('a.tmpl', 'pages'); - assert(typeof view.path === 'string'); - }); - - it('should find a view by collection name:', function() { - app = new App(); - app.option('renameKey', function(key) { - return path.basename(key); - }); - app.create('pages'); - app.page('a/b/c.md', {content: '...'}); - var view = app.find('a/b/c.md'); - assert(typeof view.path === 'string'); - }); - - it('should find a view without a collection name:', function() { - var view = app.find('a.tmpl'); - assert(typeof view.path === 'string'); - }); - }); -}); diff --git a/test/app.middleware.js b/test/app.middleware.js deleted file mode 100644 index be37e7e..0000000 --- a/test/app.middleware.js +++ /dev/null @@ -1,62 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('middleware', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('pages'); - }); - - it('should call the all method for every middleware method:', function() { - var i = 0; - app.all(/./, function(view, next) { - assert(typeof view.path === 'string'); - i++; - next(); - }); - - assert(i === 0); - app.page('foo.tmpl', {content: 'foo'}); - assert(i === 1); - }); - - it('should call the onLoad method when a view is loaded:', function() { - var i = 0; - app.onLoad(/./, function(view, next) { - assert(typeof view.path === 'string'); - i++; - next(); - }); - - assert(i === 0); - app.page('foo.tmpl', {content: 'foo'}); - assert(i === 1); - }); - - it('should emit an event when a handler is called:', function(done) { - var i = 0; - app.on('onLoad', function() { - i++; - }); - app.on('preRender', function() { - i++; - }); - app.on('preCompile', function() { - i++; - }); - - app.page('foo.tmpl', {content: 'foo'}) - .render(function(err) { - if (err) return done(err); - assert(i === 3); - done(); - }); - }); -}); diff --git a/test/app.onLoad.js b/test/app.onLoad.js deleted file mode 100644 index 6788597..0000000 --- a/test/app.onLoad.js +++ /dev/null @@ -1,50 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('onLoad', function() { - beforeEach(function() { - app = new App(); - }); - - describe('app.collection', function() { - it('should emit an onLoad when view is created', function(done) { - var collection = app.collection(); - - app.on('onLoad', function(view) { - assert(view.path === 'blog/foo.js'); - done(); - }); - - app.onLoad('blog/:title', function(view, next) { - assert(view.path === 'blog/foo.js'); - next(); - }); - - collection.addView('whatever', {path: 'blog/foo.js', content: 'bar baz'}); - }); - }); - - describe('view collections', function() { - it('should emit an onLoad when view is created', function(done) { - app.create('posts'); - - app.on('onLoad', function(view) { - assert(view.path === 'blog/foo.js'); - done(); - }); - - app.onLoad('blog/:title', function(view, next) { - assert(view.path === 'blog/foo.js'); - next(); - }); - - app.post('whatever', {path: 'blog/foo.js', content: 'bar baz'}); - }); - }); -}); diff --git a/test/app.option.js b/test/app.option.js deleted file mode 100644 index ec41782..0000000 --- a/test/app.option.js +++ /dev/null @@ -1,100 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('app.option', function() { - beforeEach(function() { - app = new App(); - }); - - it('should set a key-value pair on options:', function() { - app.option('a', 'b'); - assert(app.options.a === 'b'); - }); - - it('should set an object on options:', function() { - app.option({c: 'd'}); - assert(app.options.c === 'd'); - }); - - it('should set an option.', function() { - app.option('a', 'b'); - app.options.should.have.property('a'); - }); - - it('should get an option.', function() { - app.option('a', 'b'); - app.option('a').should.equal('b'); - }); - - it('should extend the `options` object.', function() { - app.option({x: 'xxx', y: 'yyy', z: 'zzz'}); - app.option('x').should.equal('xxx'); - app.option('y').should.equal('yyy'); - app.option('z').should.equal('zzz'); - }); - - it('options should be on the `options` object.', function() { - app.option({x: 'xxx', y: 'yyy', z: 'zzz'}); - app.options.x.should.equal('xxx'); - app.options.y.should.equal('yyy'); - app.options.z.should.equal('zzz'); - }); - - it('should be chainable.', function() { - app.option({x: 'xxx', y: 'yyy', z: 'zzz'}); - app.option({a: 'aaa', b: 'bbb', c: 'ccc'}); - - app.option('x').should.equal('xxx'); - app.option('a').should.equal('aaa'); - }); - - it('should extend the `options` object when the first param is a string.', function() { - app.option('foo', {x: 'xxx', y: 'yyy', z: 'zzz'}); - app.option('bar', {a: 'aaa', b: 'bbb', c: 'ccc'}); - - app.option('foo').should.have.property('x'); - app.option('bar').should.have.property('a'); - - app.options.foo.should.have.property('x'); - app.options.bar.should.have.property('a'); - }); - - it('should set an option.', function() { - app.option('a', 'b'); - app.options.should.have.property('a'); - }); - - it('should get an option.', function() { - app.option('a', 'b'); - app.option('a').should.equal('b'); - }); - - it('should extend the `options` object.', function() { - app.option({x: 'xxx', y: 'yyy', z: 'zzz'}); - app.option('x').should.equal('xxx'); - app.option('y').should.equal('yyy'); - app.option('z').should.equal('zzz'); - }); - - it('options should be on the `options` object.', function() { - app.option({x: 'xxx', y: 'yyy', z: 'zzz'}); - app.options.x.should.equal('xxx'); - app.options.y.should.equal('yyy'); - app.options.z.should.equal('zzz'); - }); - - it('should be chainable.', function() { - app - .option({x: 'xxx', y: 'yyy', z: 'zzz'}) - .option({a: 'aaa', b: 'bbb', c: 'ccc'}); - - app.option('x').should.equal('xxx'); - app.option('a').should.equal('aaa'); - }); -}); diff --git a/test/app.render.js b/test/app.render.js deleted file mode 100644 index 7e26e74..0000000 --- a/test/app.render.js +++ /dev/null @@ -1,90 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('render', function() { - describe('rendering', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('page'); - }); - - it('should throw an error when no callback is given:', function() { - (function() { - app.render({}); - }).should.throw('Templates#render is async and expects a callback function'); - }); - - it('should throw an error when an engine is not defined:', function(done) { - app.page('foo.bar', {content: '<%= name %>'}); - var page = app.pages.getView('foo.bar'); - - app.render(page, function(err) { - assert(err.message === 'Templates#render cannot find an engine for: .bar'); - done(); - }); - }); - - it('should use helpers to render a view:', function(done) { - var locals = {name: 'Halle'}; - - app.helper('upper', function(str) { - return str.toUpperCase(str); - }); - - app.page('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); - var page = app.pages.getView('a.tmpl'); - - app.render(page, function(err, res) { - if (err) return done(err); - - assert(res.contents.toString() === 'a HALLE b'); - done(); - }); - }); - - it('should use helpers when rendering a view:', function(done) { - var locals = {name: 'Halle'}; - app.helper('upper', function(str) { - return str.toUpperCase(str); - }); - - app.page('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); - var page = app.pages.getView('a.tmpl'); - - app.render(page, function(err, res) { - if (err) return done(err); - assert(res.contents.toString() === 'a HALLE b'); - done(); - }); - }); - - it('should render a template when contents is a buffer:', function(done) { - app.pages('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - var view = app.pages.getView('a.tmpl'); - - app.render(view, function(err, view) { - if (err) return done(err); - assert(view.contents.toString() === 'b'); - done(); - }); - }); - - it('should render a template when content is a string:', function(done) { - app.pages('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - var view = app.pages.getView('a.tmpl'); - - app.render(view, function(err, view) { - if (err) return done(err); - assert(view.contents.toString() === 'b'); - done(); - }); - }); - }); -}); diff --git a/test/app.renderFile.js b/test/app.renderFile.js deleted file mode 100644 index 95ef60c..0000000 --- a/test/app.renderFile.js +++ /dev/null @@ -1,135 +0,0 @@ -'use strict'; - -var update = require('..'); -var assert = require('assert'); -var should = require('should'); -var path = require('path'); -var app; - -describe('app.renderFile()', function() { - beforeEach(function() { - app = update(); - app.engine('hbs', require('engine-handlebars')); - app.engine('*', require('engine-base')); - - app.create('files', {engine: '*'}); - app.file('a', {content: 'this is <%= title() %>'}); - app.file('b', {content: 'this is <%= title() %>'}); - app.file('c', {content: 'this is <%= title() %>'}); - - app.option('renameKey', function(key) { - return path.basename(key, path.extname(key)); - }); - - app.helper('title', function() { - var view = this.context.view; - var key = view.key; - var ctx = this.context[key]; - if (ctx && ctx.title) return ctx.title; - return key; - }); - }); - - it('should render views from src', function(done) { - var stream = app.src(path.join(__dirname, 'fixtures/pages/*.hbs')); - var files = []; - - stream.pipe(app.renderFile()) - .on('error', done) - .on('data', function(file) { - files.push(file); - }) - .on('end', function() { - assert.equal(files[0].basename, 'a.hbs'); - assert.equal(files[1].basename, 'b.hbs'); - assert.equal(files[2].basename, 'c.hbs'); - done(); - }); - }); - - it('should render views with the engine that matches the file extension', function(done) { - var stream = app.src(path.join(__dirname, 'fixtures/pages/*.hbs')); - var files = []; - - stream.pipe(app.renderFile()) - .on('error', done) - .on('data', function(file) { - files.push(file); - }) - .on('end', function() { - assert(/

a<\/h1>/.test(files[0].content)); - assert(/

b<\/h1>/.test(files[1].content)); - assert(/

c<\/h1>/.test(files[2].content)); - done(); - }); - }); - - it('should render views from src with the engine passed on the opts', function(done) { - var stream = app.src(path.join(__dirname, 'fixtures/pages/*.hbs')); - var files = []; - - stream.pipe(app.renderFile({engine: '*'})) - .on('error', done) - .on('data', function(file) { - files.push(file); - }) - .on('end', function() { - assert(/

a<\/h2>/.test(files[0].content)); - assert(/

b<\/h2>/.test(files[1].content)); - assert(/

c<\/h2>/.test(files[2].content)); - done(); - }); - }); - - it('should use the context passed on the opts', function(done) { - var stream = app.src(path.join(__dirname, 'fixtures/pages/*.hbs')); - var files = []; - - stream.pipe(app.renderFile({a: {title: 'foo'}})) - .on('error', done) - .on('data', function(file) { - files.push(file); - }) - .on('end', function() { - assert(/

foo<\/h1>/.test(files[0].content)); - assert(/

b<\/h1>/.test(files[1].content)); - assert(/

c<\/h1>/.test(files[2].content)); - done(); - }); - }); - - it('should render the files in a collection', function(cb) { - var files = []; - app.toStream('files') - .pipe(app.renderFile()) - .on('error', cb) - .on('data', function(file) { - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - files.push(file); - }) - .on('end', function() { - assert(/this is a/.test(files[0].content)); - assert(/this is b/.test(files[1].content)); - assert(/this is c/.test(files[2].content)); - assert.equal(files.length, 3); - cb(); - }); - }); - - it('should handle engine errors', function(cb) { - app.create('notdefined', {engine: '*'}); - app.notdefined('foo', {content: '<%= bar %>'}); - app.toStream('notdefined') - .pipe(app.renderFile()) - .on('error', function(err) { - assert.equal(typeof err, 'object'); - assert.equal(err.message, 'bar is not defined'); - cb(); - }) - .on('end', function() { - cb(new Error('expected renderFile to handle the error.')); - }); - }); -}); diff --git a/test/app.route.js b/test/app.route.js deleted file mode 100644 index 620a021..0000000 --- a/test/app.route.js +++ /dev/null @@ -1,95 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('routes', function() { - beforeEach(function() { - app = new App(); - }); - - describe('routes', function() { - it('should create a route for the given path:', function(done) { - app = new App(); - app.create('posts'); - - app.on('all', function(msg) { - assert(msg === 'done'); - done(); - }); - - app.route('blog/:title') - .all(function(view, next) { - app.emit('all', 'done'); - next(); - }); - - app.post('whatever', {path: 'blog/foo.js', content: 'bar baz'}); - }); - - it('should emit events when a route method is called:', function(done) { - app = new App(); - app.create('posts'); - - app.on('onLoad', function(view) { - assert(view.path === 'blog/foo.js'); - done(); - }); - - app.param('title', function(view, next, title) { - assert(title === 'foo.js'); - next(); - }); - - app.onLoad('blog/:title', function(view, next) { - assert(view.path === 'blog/foo.js'); - next(); - }); - - app.post('whatever', {path: 'blog/foo.js', content: 'bar baz'}); - }); - - it('should emit errors', function(done) { - app = new App(); - app.create('posts'); - - app.on('error', function(err) { - assert(err.message === 'false == true'); - done(); - }); - - // wrong... - app.param('title', function(view, next, title) { - assert(title === 'fo.js'); - next(); - }); - - app.onLoad('/blog/:title', function(view, next) { - assert(view.path === '/blog/foo.js'); - next(); - }); - - app.post('whatever', {path: '/blog/foo.js', content: 'bar baz'}); - }); - - it('should have path property', function() { - var route = new app.Route('/blog/:year/:month/:day/:slug').all([ - function() {} - ]); - route.path.should.equal('/blog/:year/:month/:day/:slug'); - }); - - it('should have stack property', function() { - var route = new app.Route('/blog/:year/:month/:day/:slug').all([ - function() {} - ]); - - route.stack.should.be.instanceof(Array); - route.stack.should.have.length(1); - }); - }); -}); diff --git a/test/app.src.js b/test/app.src.js deleted file mode 100644 index 034917b..0000000 --- a/test/app.src.js +++ /dev/null @@ -1,295 +0,0 @@ -'use strict'; - -var App = require('..'); -var assert = require('assert'); -var should = require('should'); -var join = require('path').join; -var app; - -describe('src()', function() { - beforeEach(function() { - app = new App(); - }); - - it('should return a stream', function(done) { - var stream = app.src(join(__dirname, './fixtures/*.coffee')); - assert(stream); - assert.equal(typeof stream.on, 'function'); - assert.equal(typeof stream.pipe, 'function'); - done(); - }); - - it('should return an input stream from a flat glob', function(done) { - var stream = app.src(join(__dirname, './fixtures/*.coffee')); - stream.on('error', done); - stream.on('data', function(file) { - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); - String(file.contents).should.equal('Hello world!'); - }); - stream.on('end', function() { - done(); - }); - }); - - it('should return an input stream for multiple globs', function(done) { - var globArray = [ - join(__dirname, './fixtures/generic/run.dmc'), - join(__dirname, './fixtures/generic/test.dmc') - ]; - var stream = app.src(globArray); - - var files = []; - stream.on('error', done); - stream.on('data', function(file) { - should.exist(file); - should.exist(file.path); - files.push(file); - }); - stream.on('end', function() { - files.length.should.equal(2); - files[0].path.should.equal(globArray[0]); - files[1].path.should.equal(globArray[1]); - done(); - }); - }); - - it('should return an input stream for multiple globs with negation', function(done) { - var expectedPath = join(__dirname, './fixtures/generic/run.dmc'); - var globArray = [ - join(__dirname, './fixtures/generic/*.dmc'), - '!' + join(__dirname, './fixtures/generic/test.dmc'), - ]; - var stream = app.src(globArray); - - var files = []; - stream.on('error', done); - stream.on('data', function(file) { - should.exist(file); - should.exist(file.path); - files.push(file); - }); - stream.on('end', function() { - files.length.should.equal(1); - files[0].path.should.equal(expectedPath); - done(); - }); - }); - - it('should return an input stream with no contents when read is false', function(done) { - var stream = app.src(join(__dirname, './fixtures/*.coffee'), {read: false}); - stream.on('error', done); - stream.on('data', function(file) { - should.exist(file); - should.exist(file.path); - should.not.exist(file.contents); - join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); - }); - stream.on('end', function() { - done(); - }); - }); - - it('should return an input stream with contents as stream when buffer is false', function(done) { - var stream = app.src(join(__dirname, './fixtures/*.coffee'), {buffer: false}); - stream.on('error', done); - stream.on('data', function(file) { - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - var buf = ''; - file.contents.on('data', function(d) { - buf += d; - }); - file.contents.on('end', function() { - buf.should.equal('Hello world!'); - done(); - }); - join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); - }); - }); - - it('should return an input stream from a deep glob', function(done) { - var stream = app.src(join(__dirname, './fixtures/**/*.jade')); - stream.on('error', done); - stream.on('data', function(file) { - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - join(file.path, '').should.equal(join(__dirname, './fixtures/test/run.jade')); - String(file.contents).should.equal('test template'); - }); - stream.on('end', function() { - done(); - }); - }); - - it('should return an input stream from a deeper glob', function(done) { - var stream = app.src(join(__dirname, './fixtures/**/*.dmc')); - var a = 0; - stream.on('error', done); - stream.on('data', function() { - ++a; - }); - stream.on('end', function() { - a.should.equal(2); - done(); - }); - }); - - it('should return a file stream from a flat path', function(done) { - var a = 0; - var stream = app.src(join(__dirname, './fixtures/test.coffee')); - stream.on('error', done); - stream.on('data', function(file) { - ++a; - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); - String(file.contents).should.equal('Hello world!'); - }); - stream.on('end', function() { - a.should.equal(1); - done(); - }); - }); - - it('should return a stream', function(done) { - var stream = app.src(join(__dirname, './fixtures/*.coffee')); - should.exist(stream); - should.exist(stream.on); - done(); - }); - - it('should return an input stream from a flat glob', function(done) { - var stream = app.src(join(__dirname, './fixtures/*.coffee')); - stream.on('error', done); - stream.on('data', function(file) { - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); - String(file.contents).should.equal('Hello world!'); - }); - stream.on('end', function() { - done(); - }); - }); - - it('should return an input stream for multiple globs', function(done) { - var globArray = [ - join(__dirname, './fixtures/generic/run.dmc'), - join(__dirname, './fixtures/generic/test.dmc') - ]; - var stream = app.src(globArray); - - var files = []; - stream.on('error', done); - stream.on('data', function(file) { - should.exist(file); - should.exist(file.path); - files.push(file); - }); - stream.on('end', function() { - files.length.should.equal(2); - files[0].path.should.equal(globArray[0]); - files[1].path.should.equal(globArray[1]); - done(); - }); - }); - - it('should return an input stream for multiple globs, with negation', function(done) { - var expectedPath = join(__dirname, './fixtures/generic/run.dmc'); - var globArray = [ - join(__dirname, './fixtures/generic/*.dmc'), - '!' + join(__dirname, './fixtures/generic/test.dmc'), - ]; - var stream = app.src(globArray); - - var files = []; - stream.on('error', done); - stream.on('data', function(file) { - should.exist(file); - should.exist(file.path); - files.push(file); - }); - stream.on('end', function() { - files.length.should.equal(1); - files[0].path.should.equal(expectedPath); - done(); - }); - }); - - it('should return an input stream with no contents when read is false', function(done) { - var stream = app.src(join(__dirname, './fixtures/*.coffee'), {read: false}); - stream.on('error', done); - stream.on('data', function(file) { - should.exist(file); - should.exist(file.path); - should.not.exist(file.contents); - join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); - }); - stream.on('end', function() { - done(); - }); - }); - - it.skip('should throw an error when buffer is false', function(done) { - app.src(join(__dirname, './fixtures/*.coffee'), {buffer: false}) - .on('error', function() { - done(); - }) - .on('data', function() { - done(new Error('should have thrown an error')); - }); - }); - - it('should return an input stream from a deep glob', function(done) { - app.src(join(__dirname, './fixtures/**/*.jade')) - .on('error', done) - .on('data', function(file) { - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - join(file.path, '').should.equal(join(__dirname, './fixtures/test/run.jade')); - String(file.contents).should.equal('test template'); - }) - .on('end', function() { - done(); - }); - }); - - it('should return an input stream from a deeper glob', function(done) { - var stream = app.src(join(__dirname, './fixtures/**/*.dmc')); - var a = 0; - stream.on('error', done); - stream.on('data', function() { - ++a; - }); - stream.on('end', function() { - a.should.equal(2); - done(); - }); - }); - - it('should return a file stream from a flat path', function(done) { - var a = 0; - var stream = app.src(join(__dirname, './fixtures/test.coffee')); - stream.on('error', done); - stream.on('data', function(file) { - ++a; - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); - String(file.contents).should.equal('Hello world!'); - }); - stream.on('end', function() { - a.should.equal(1); - done(); - }); - }); -}); diff --git a/test/app.symlink.js b/test/app.symlink.js deleted file mode 100644 index d84e95b..0000000 --- a/test/app.symlink.js +++ /dev/null @@ -1,398 +0,0 @@ -'use strict'; - -require('mocha'); -var should = require('should'); -var fs = require('graceful-fs'); -var path = require('path'); -var rimraf = require('rimraf'); -var bufEqual = require('buffer-equal'); -var through = require('through2'); -var File = require('vinyl'); -var update = require('..'); -var spies = require('./support/spy'); -var chmodSpy = spies.chmodSpy; -var statSpy = spies.statSpy; -var app, bufferStream; - -var wipeOut = function(cb) { - rimraf(path.join(__dirname, './actual/'), cb); - spies.setError('false'); - statSpy.reset(); - chmodSpy.reset(); - app = update(); -}; - -var dataWrap = function(fn) { - return function(data, enc, cb) { - fn(data); - cb(); - }; -}; - -var realMode = function(n) { - return n & 07777; -}; - -describe('symlink stream', function() { - beforeEach(wipeOut); - afterEach(wipeOut); - - it('should pass through writes with cwd', function(done) { - var inputPath = path.join(__dirname, './fixtures/test.coffee'); - - var expectedFile = new File({ - base: __dirname, - cwd: __dirname, - path: inputPath, - contents: null - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - done(); - }; - - var stream = app.symlink('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should pass through writes with default cwd', function(done) { - var inputPath = path.join(__dirname, './fixtures/test.coffee'); - - var expectedFile = new File({ - base: __dirname, - cwd: __dirname, - path: inputPath, - contents: null - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - done(); - }; - - var stream = app.symlink(path.join(__dirname, './actual/')); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should make link to the right folder with relative cwd', function(done) { - var inputPath = path.join(__dirname, './fixtures/test.coffee'); - var inputBase = path.join(__dirname, './fixtures/'); - var expectedPath = path.join(__dirname, './actual/test.coffee'); - var expectedBase = path.join(__dirname, './actual'); - var expectedContents = fs.readFileSync(inputPath); - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(__dirname, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(true); - bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); - fs.readlinkSync(expectedPath).should.equal(inputPath); - done(); - }; - - var stream = app.symlink('./actual/', {cwd: path.relative(process.cwd(), __dirname)}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should write buffer files to the right folder with function and relative cwd', function(done) { - var inputPath = path.join(__dirname, './fixtures/test.coffee'); - var inputBase = path.join(__dirname, './fixtures/'); - var expectedPath = path.join(__dirname, './actual/test.coffee'); - var expectedBase = path.join(__dirname, './actual'); - var expectedContents = fs.readFileSync(inputPath); - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(__dirname, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(true); - bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); - fs.readlinkSync(expectedPath).should.equal(inputPath); - done(); - }; - - var stream = app.symlink(function(file){ - should.exist(file); - file.should.equal(expectedFile); - return './actual'; - }, {cwd: path.relative(process.cwd(), __dirname)}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should write buffer files to the right folder', function(done) { - var inputPath = path.join(__dirname, './fixtures/test.coffee'); - var inputBase = path.join(__dirname, './fixtures/'); - var expectedPath = path.join(__dirname, './actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, './actual'); - var expectedMode = 0655; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: expectedMode - } - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(__dirname, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(true); - bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); - fs.readlinkSync(expectedPath).should.equal(inputPath); - done(); - }; - - var stream = app.symlink('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should write streaming files to the right folder', function(done) { - var inputPath = path.join(__dirname, './fixtures/test.coffee'); - var inputBase = path.join(__dirname, './fixtures/'); - var expectedPath = path.join(__dirname, './actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, './actual'); - var expectedMode = 0655; - - var contentStream = through.obj(); - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: contentStream, - stat: { - mode: expectedMode - } - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(__dirname, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(true); - bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); - fs.readlinkSync(expectedPath).should.equal(inputPath); - done(); - }; - - var stream = app.symlink('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - setTimeout(function(){ - contentStream.write(expectedContents); - contentStream.end(); - }, 100); - stream.end(); - }); - - it('should write directories to the right folder', function(done) { - var inputPath = path.join(__dirname, './fixtures/wow'); - var inputBase = path.join(__dirname, './fixtures/'); - var expectedPath = path.join(__dirname, './actual/wow'); - var expectedBase = path.join(__dirname, './actual'); - var expectedMode = 0655; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: null, - stat: { - isDirectory: function(){ - return true; - }, - mode: expectedMode - } - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(__dirname, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.readlinkSync(expectedPath).should.equal(inputPath); - fs.lstatSync(expectedPath).isDirectory().should.equal(false); - fs.statSync(expectedPath).isDirectory().should.equal(true); - done(); - }; - - var stream = app.symlink('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should use different modes for files and directories', function(done) { - var inputBase = path.join(__dirname, './fixtures'); - var inputPath = path.join(__dirname, './fixtures/wow/suchempty'); - var expectedBase = path.join(__dirname, './actual/wow'); - var expectedDirMode = 0755; - var expectedFileMode = 0655; - - var firstFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - stat: fs.statSync(inputPath) - }); - - var onEnd = function(){ - realMode(fs.lstatSync(expectedBase).mode).should.equal(expectedDirMode); - realMode(buffered[0].stat.mode).should.equal(expectedFileMode); - done(); - }; - - var stream = app.symlink('./actual/', { - cwd: __dirname, - mode: expectedFileMode, - dirMode: expectedDirMode - }); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(firstFile); - stream.end(); - }); - - it('should change to the specified base', function(done) { - var inputBase = path.join(__dirname, './fixtures'); - var inputPath = path.join(__dirname, './fixtures/wow/suchempty'); - - var firstFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - stat: fs.statSync(inputPath) - }); - - var onEnd = function(){ - buffered[0].base.should.equal(inputBase); - done(); - }; - - var stream = app.symlink('./actual/', { - cwd: __dirname, - base: inputBase - }); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(firstFile); - stream.end(); - }); - - it('should report IO errors', function(done) { - var inputPath = path.join(__dirname, './fixtures/test.coffee'); - var inputBase = path.join(__dirname, './fixtures/'); - var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, './actual'); - var expectedMode = 0722; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: expectedMode - } - }); - - fs.mkdirSync(expectedBase); - fs.chmodSync(expectedBase, 0); - - var stream = app.symlink('./actual/', {cwd: __dirname}); - stream.on('error', function(err) { - err.code.should.equal('EACCES'); - done(); - }); - stream.write(expectedFile); - }); - - ['end', 'finish'].forEach(function(eventName) { - it('should emit ' + eventName + ' event', function(done) { - var srcPath = path.join(__dirname, './fixtures/test.coffee'); - var stream = app.symlink('./actual/', {cwd: __dirname}); - - stream.on(eventName, function() { - done(); - }); - - var file = new File({ - path: srcPath, - cwd: __dirname, - contents: new Buffer("1234567890") - }); - - stream.write(file); - stream.end(); - }); - }); -}); diff --git a/test/app.task.js b/test/app.task.js deleted file mode 100644 index 333f4fd..0000000 --- a/test/app.task.js +++ /dev/null @@ -1,159 +0,0 @@ -'use strict'; - -var assert = require('assert'); -var App = require('..'); -var app; - -describe('task()', function() { - beforeEach(function() { - app = new App(); - app.tasks = {}; - }); - - it('should register a task', function() { - var fn = function(done) { - done(); - }; - app.task('default', fn); - assert.equal(typeof app.tasks.default, 'object'); - assert.equal(app.tasks.default.fn, fn); - }); - - it('should register a task with an array of dependencies', function() { - app.task('default', ['foo', 'bar'], function(done) { - done(); - }); - assert.equal(typeof app.tasks.default, 'object'); - assert.deepEqual(app.tasks.default.deps, ['foo', 'bar']); - }); - - it('should register a task with a list of strings as dependencies', function() { - app.task('default', 'foo', 'bar', function(done) { - done(); - }); - assert.equal(typeof app.tasks.default, 'object'); - assert.deepEqual(app.tasks.default.deps, ['foo', 'bar']); - }); - - it('should run a task', function(done) { - var count = 0; - app.task('default', function(cb) { - count++; - cb(); - }); - - app.build('default', function(err) { - if (err) return done(err); - assert.equal(count, 1); - done(); - }); - }); - - it('should throw an error when a task with unregistered dependencies is run', function(done) { - var count = 0; - app.task('default', ['foo', 'bar'], function(cb) { - count++; - cb(); - }); - - app.build('default', function(err) { - if (!err) return done(new Error('Expected an error to be thrown.')); - assert.equal(count, 0); - done(); - }); - }); - - it('should throw an error when `.build` is called without a callback function.', function() { - try { - app.build('default'); - throw new Error('Expected an error to be thrown.'); - } catch (err) { - } - }); - - it('should emit task events', function(done) { - var events = []; - app.on('task:starting', function(task) { - events.push('starting.' + task.name); - }); - app.on('task:finished', function(task) { - events.push('finished.' + task.name); - }); - app.on('task:error', function(err, task) { - events.push('error.' + task.name); - }); - - app.task('foo', function(cb) { - cb(); - }); - app.task('bar', ['foo'], function(cb) { - cb(); - }); - app.task('default', ['bar']); - app.build('default', function(err) { - if (err) return done(err); - assert.deepEqual(events, [ - 'starting.default', - 'starting.bar', - 'starting.foo', - 'finished.foo', - 'finished.bar', - 'finished.default' - ]); - done(); - }); - }); - - it('should emit an error event when an error is passed back in a task', function(done) { - app.on('error', function(err) { - assert(err); - assert.equal(err.message, 'This is an error'); - }); - app.task('default', function(cb) { - return cb(new Error('This is an error')); - }); - app.build('default', function(err) { - if (err) return done(); - done(new Error('Expected an error')); - }); - }); - - it('should emit an error event when an error is thrown in a task', function(done) { - var errors = 0; - app.on('error', function(err) { - errors++; - assert(err); - assert.equal(err.message, 'This is an error'); - }); - app.task('default', function(cb) { - cb(new Error('This is an error')); - }); - app.build('default', function(err) { - assert.equal(errors, 1); - if (err) return done(); - done(new Error('Expected an error')); - }); - }); - - it('should run dependencies before running the dependent task.', function(done) { - var seq = []; - app.task('foo', function(cb) { - seq.push('foo'); - cb(); - }); - app.task('bar', function(cb) { - seq.push('bar'); - cb(); - }); - app.task('default', ['foo', 'bar'], function(cb) { - seq.push('default'); - cb(); - }); - - app.build('default', function(err) { - if (err) return done(err); - assert.deepEqual(seq, ['foo', 'bar', 'default']); - done(); - }); - }); -}); diff --git a/test/app.toStream.js b/test/app.toStream.js deleted file mode 100644 index 8eefb1a..0000000 --- a/test/app.toStream.js +++ /dev/null @@ -1,64 +0,0 @@ -'use strict'; - -var update = require('..'); -var assert = require('assert'); -var should = require('should'); -var app; - -describe('toStream()', function() { - beforeEach(function() { - app = update(); - app.create('pages'); - app.page('a', {content: 'this is A'}); - app.page('b', {content: 'this is B'}); - app.page('c', {content: 'this is C'}); - - app.create('posts'); - app.post('x', {content: 'this is X'}); - app.post('y', {content: 'this is Y'}); - app.post('z', {content: 'this is Z'}); - }); - - it('should return a stream', function(cb) { - var stream = app.toStream(); - should.exist(stream); - should.exist(stream.on); - cb(); - }); - - it('should return a stream for a collection', function(cb) { - var stream = app.toStream('pages'); - should.exist(stream); - should.exist(stream.on); - cb(); - }); - - it('should stack handle multiple collections', function(cb) { - var files = []; - app.toStream('pages') - .pipe(app.toStream('posts')) - .on('data', function(file) { - files.push(file); - }) - .on('end', function() { - assert.equal(files.length, 6); - cb(); - }); - }); - - it('should push each item in the collection into the stream', function(cb) { - var files = []; - app.toStream('pages') - .on('error', cb) - .on('data', function(file) { - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - files.push(file.path); - }) - .on('end', function() { - assert.equal(files.length, 3); - cb(); - }); - }); -}); \ No newline at end of file diff --git a/test/app.use.js b/test/app.use.js deleted file mode 100644 index 3b6fff8..0000000 --- a/test/app.use.js +++ /dev/null @@ -1,283 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var Views = App.Views; -var View = App.View; -var app; - -describe('app.use', function() { - beforeEach(function() { - app = new App(); - }); - - it('should expose the instance to `use`:', function(done) { - app.use(function(inst) { - assert(inst instanceof App); - done(); - }); - }); - - it('should be chainable:', function(done) { - app.use(function(inst) { - assert(inst instanceof App); - }) - .use(function(inst) { - assert(inst instanceof App); - }) - .use(function(inst) { - assert(inst instanceof App); - done(); - }); - }); - - it('should pass to collection `use` if a function is returned:', function() { - app.use(function(inst) { - assert(inst instanceof App); - return function(collection) { - collection.foo = collection.addView; - assert(collection instanceof Views); - return collection; - }; - }); - - app.create('pages') - .foo({path: 'a.md', content: '...'}) - .addView({path: 'b.md', content: '...'}) - .addView({path: 'c.md', content: '...'}); - - assert(app.views.pages.hasOwnProperty('a.md')); - assert(app.views.pages.hasOwnProperty('b.md')); - assert(app.views.pages.hasOwnProperty('c.md')); - }); - - it('should be chainable when a collection function is returned:', function() { - app - .use(function(inst) { - assert(inst instanceof App); - return function(collection) { - collection.foo = collection.addView; - assert(collection instanceof Views); - return collection; - }; - }) - .use(function(inst) { - assert(inst instanceof App); - return function(collection) { - collection.bar = collection.addView; - assert(collection instanceof Views); - return collection; - }; - }) - .use(function(inst) { - assert(inst instanceof App); - return function(collection) { - collection.baz = collection.addView; - assert(collection instanceof Views); - return collection; - }; - }); - - var pages = app.create('pages'); - - pages.foo({path: 'a.md', content: '...'}); - pages.bar({path: 'b.md', content: '...'}); - pages.baz({path: 'c.md', content: '...'}); - - assert(app.views.pages.hasOwnProperty('a.md')); - assert(app.views.pages.hasOwnProperty('b.md')); - assert(app.views.pages.hasOwnProperty('c.md')); - }); - - it('should pass to view `use` if collection.use returns a function:', function() { - app.use(function(inst) { - assert(inst instanceof App); - - return function(collection) { - assert(collection instanceof Views); - collection.foo = collection.addView; - - return function(view) { - assert(view instanceof View); - view.foo = collection.addView.bind(collection); - return view; - }; - }; - }); - - app.create('pages') - .foo({path: 'a.md', content: '...'}) - .foo({path: 'b.md', content: '...'}) - .foo({path: 'c.md', content: '...'}); - - assert(app.views.pages.hasOwnProperty('a.md')); - assert(app.views.pages.hasOwnProperty('b.md')); - assert(app.views.pages.hasOwnProperty('c.md')); - }); - - it('should be chainable when a view function is returned:', function() { - app - .use(function(inst) { - assert(inst instanceof App); - - return function(collection) { - assert(collection instanceof Views); - collection.foo = collection.addView; - - return function(view) { - assert(view instanceof View); - view.a = collection.addView.bind(collection); - return view; - }; - }; - }) - .use(function(inst) { - assert(inst instanceof App); - - return function(collection) { - assert(collection instanceof Views); - collection.bar = collection.addView; - - return function(view) { - assert(view instanceof View); - view.b = collection.addView.bind(collection); - return view; - }; - }; - }) - .use(function(inst) { - assert(inst instanceof App); - - return function(collection) { - assert(collection instanceof Views); - collection.baz = collection.addView; - - return function(view) { - assert(view instanceof View); - view.c = collection.addView.bind(collection); - return view; - }; - }; - }); - - var pages = app.create('pages'); - - pages.foo({path: 'a.md', content: '...'}); - pages.bar({path: 'b.md', content: '...'}); - pages.baz({path: 'c.md', content: '...'}) - .a({path: 'x.md', content: '...'}) - .b({path: 'y.md', content: '...'}) - .c({path: 'z.md', content: '...'}); - - assert(app.views.pages.hasOwnProperty('a.md')); - assert(app.views.pages.hasOwnProperty('b.md')); - assert(app.views.pages.hasOwnProperty('c.md')); - - assert(app.views.pages.hasOwnProperty('x.md')); - assert(app.views.pages.hasOwnProperty('y.md')); - assert(app.views.pages.hasOwnProperty('z.md')); - }); - - it('should work with multiple collections:', function() { - app - .use(function(inst) { - assert(inst instanceof App); - - return function(collection) { - assert(collection instanceof Views); - collection.foo = collection.addView; - - return function(view) { - assert(view instanceof View); - view.a = collection.addView.bind(collection); - return view; - }; - }; - }) - .use(function(inst) { - assert(inst instanceof App); - - return function(collection) { - assert(collection instanceof Views); - collection.bar = collection.addView; - - return function(view) { - assert(view instanceof View); - view.b = collection.addView.bind(collection); - return view; - }; - }; - }) - .use(function(inst) { - assert(inst instanceof App); - assert(this instanceof App); - - return function(collection) { - collection.baz = collection.addView; - assert(collection instanceof Views); - assert(this instanceof Views); - - return function(view) { - assert(this instanceof View); - assert(view instanceof View); - view.c = collection.addView.bind(collection); - return view; - }; - }; - }); - - var pages = app.create('pages'); - - pages.foo({path: 'a.md', content: '...'}); - pages.bar({path: 'b.md', content: '...'}); - pages.baz({path: 'c.md', content: '...'}) - .a({path: 'x.md', content: '...'}) - .b({path: 'y.md', content: '...'}) - .c({path: 'z.md', content: '...'}); - - assert(app.views.pages.hasOwnProperty('a.md')); - assert(app.views.pages.hasOwnProperty('b.md')); - assert(app.views.pages.hasOwnProperty('c.md')); - - assert(app.views.pages.hasOwnProperty('x.md')); - assert(app.views.pages.hasOwnProperty('y.md')); - assert(app.views.pages.hasOwnProperty('z.md')); - - var posts = app.create('posts'); - - posts.foo({path: 'a.md', content: '...'}); - posts.bar({path: 'b.md', content: '...'}); - posts.baz({path: 'c.md', content: '...'}) - .a({path: 'x.md', content: '...'}) - .b({path: 'y.md', content: '...'}) - .c({path: 'z.md', content: '...'}); - - assert(app.views.posts.hasOwnProperty('a.md')); - assert(app.views.posts.hasOwnProperty('b.md')); - assert(app.views.posts.hasOwnProperty('c.md')); - - assert(app.views.posts.hasOwnProperty('x.md')); - assert(app.views.posts.hasOwnProperty('y.md')); - assert(app.views.posts.hasOwnProperty('z.md')); - - var docs = app.create('docs'); - - docs.foo({path: 'a.md', content: '...'}); - docs.bar({path: 'b.md', content: '...'}); - docs.baz({path: 'c.md', content: '...'}) - .a({path: 'x.md', content: '...'}) - .b({path: 'y.md', content: '...'}) - .c({path: 'z.md', content: '...'}); - - assert(app.views.docs.hasOwnProperty('a.md')); - assert(app.views.docs.hasOwnProperty('b.md')); - assert(app.views.docs.hasOwnProperty('c.md')); - - assert(app.views.docs.hasOwnProperty('x.md')); - assert(app.views.docs.hasOwnProperty('y.md')); - assert(app.views.docs.hasOwnProperty('z.md')); - }); -}); diff --git a/test/app.view.compile.js b/test/app.view.compile.js deleted file mode 100644 index 7616ce2..0000000 --- a/test/app.view.compile.js +++ /dev/null @@ -1,40 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('app.view.compile', function() { - describe('compile method', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('page'); - }); - - it('should compile a view:', function() { - var buffer = new Buffer('a b c'); - var view = app.page('a.tmpl', {contents: buffer}) - .compile(); - assert(typeof view.fn === 'function'); - }); - - it('should compile a view with settings:', function() { - var buffer = new Buffer('a b c'); - var view = app.page('a.tmpl', {contents: buffer}) - .compile({foo: 'bar'}); - assert(typeof view.fn === 'function'); - }); - - it('should compile a view with isAsync flag:', function() { - var buffer = new Buffer('a b c'); - var view = app.page('a.tmpl', {contents: buffer}) - .compile(true); - assert(typeof view.fn === 'function'); - }); - }); -}); - diff --git a/test/app.view.render.js b/test/app.view.render.js deleted file mode 100644 index ccf5978..0000000 --- a/test/app.view.render.js +++ /dev/null @@ -1,94 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('helpers', function() { - describe('rendering', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('page'); - }); - - it('should use helpers to render a view:', function(done) { - var locals = {name: 'Halle'}; - - app.helper('upper', function(str) { - return str.toUpperCase(str); - }); - - var buffer = new Buffer('a <%= upper(name) %> b'); - app.page('a.tmpl', {contents: buffer, locals: locals}) - .render(function(err, res) { - if (err) return done(err); - - assert(res.contents.toString() === 'a HALLE b'); - done(); - }); - }); - - it('should support helpers as an array:', function(done) { - var locals = {name: 'Halle'}; - - app.helpers([ - { - lower: function(str) { - return str.toLowerCase(str); - } - } - ]); - - var buffer = new Buffer('a <%= lower(name) %> b'); - app.page('a.tmpl', {contents: buffer, locals: locals}) - .render(function(err, res) { - if (err) return done(err); - - assert(res.contents.toString() === 'a halle b'); - done(); - }); - }); - - it('should support helpers as an object:', function(done) { - var locals = {name: 'Halle'}; - - app.helpers({ - prepend: function(prefix, str) { - return prefix + str; - } - }); - - var buffer = new Buffer('a <%= prepend("foo ", name) %> b'); - app.page('a.tmpl', {contents: buffer, locals: locals}) - .render(function(err, res) { - if (err) return done(err); - assert(res.contents.toString() === 'a foo Halle b'); - done(); - }); - }); - - it('should use the engine defined on view options:', function(done) { - app.engine('hbs', require('engine-handlebars')); - var locals = {name: 'Halle'}; - - app.helpers({ - prepend: function(prefix, str) { - return prefix + str; - } - }); - - var buffer = new Buffer('a {{prepend "foo " name}} b'); - app.page('a.tmpl', {contents: buffer, locals: locals, options: {engine: 'hbs'}}) - .render(function(err, res) { - if (err) return done(err); - assert(res.contents.toString() === 'a foo Halle b'); - done(); - }); - }); - }); -}); - diff --git a/test/app.watch.js b/test/app.watch.js deleted file mode 100644 index 0990e70..0000000 --- a/test/app.watch.js +++ /dev/null @@ -1,42 +0,0 @@ -'use strict'; - -var assert = require('assert'); -var fs = require('fs'); -var App = require('..'); -var app; - -describe.skip('watch()', function() { - beforeEach(function() { - app = new App({runtimes: false}); - }); - - it('should watch files and run a task when files change', function(done) { - this.timeout(750); - - var count = 0, watch; - app.task('default', function(cb) { - count++; - cb(); - }); - - app.task('close', function(cb) { - watch.close(); - app.emit('close'); - cb(); - }); - - app.task('watch', function(cb) { - watch = app.watch('test/fixtures/watch/*.txt', ['default', 'close']); - fs.writeFile('test/fixtures/watch/test.txt', 'test', function(err) { - if (err) return cb(err); - app.on('close', cb); - }); - }); - - app.build(['watch'], function(err) { - if (err) return done(err); - assert.equal(count, 1); - done(); - }); - }); -}); diff --git a/test/collection.engines.js b/test/collection.engines.js deleted file mode 100644 index 3ebaf56..0000000 --- a/test/collection.engines.js +++ /dev/null @@ -1,178 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var Views = App.Views; -var collection, pages; - -describe('collection engines', function() { - beforeEach(function() { - pages = new Views(); - }); - - it('should throw an error when engine name is invalid:', function() { - (function() { - pages.engine(null, {}); - }).should.throw('expected engine ext to be a string or array.'); - }); - - it('should register an engine to the given extension', function() { - pages.engine('hbs', function() {}); - assert(typeof pages.engines['.hbs'] === 'object'); - }); - - it('should set an engine with the given extension', function() { - var hbs = function() {}; - hbs.render = function() {}; - hbs.renderFile = function() {}; - pages.engine('hbs', hbs); - assert(pages.engines['.hbs']); - assert(pages.engines['.hbs'].renderFile); - assert(pages.engines['.hbs'].render); - }); - - it('should get an engine:', function() { - pages.engine('hbs', function() {}); - var hbs = pages.engine('hbs'); - assert(typeof hbs === 'object'); - assert(hbs.hasOwnProperty('render')); - assert(hbs.hasOwnProperty('compile')); - }); - - it('should register multiple engines to the given extension', function() { - pages.engine(['hbs', 'md'], function() {}); - assert(typeof pages.engines['.hbs'] === 'object'); - assert(typeof pages.engines['.md'] === 'object'); - }); -}); - -describe('engines', function() { - beforeEach(function() { - pages = new Views(); - pages.addView('foo.tmpl', {content: 'A <%= letter %> {{= letter }} C'}); - pages.addView('bar.tmpl', {content: 'A <%= letter %> {{ letter }} C'}); - }); - - it('should register an engine:', function() { - pages.engine('a', {render: function() {}}); - pages.engines.should.have.property('.a'); - }); - - it('should use custom delimiters:', function(done) { - pages.engine('tmpl', require('engine-base'), { - delims: ['{{', '}}'] - }); - - pages.render('foo.tmpl', {letter: 'B'}, function(err, res) { - if (err) return done(err); - res.content.should.equal('A <%= letter %> B C'); - done(); - }); - }); - - it('should override individual delims values:', function(done) { - pages.engine('tmpl', require('engine-base'), { - interpolate: /\{{([^}]+)}}/g, - evaluate: /\{{([^}]+)}}/g, - escape: /\{{-([^}]+)}}/g - }); - pages.render('bar.tmpl', {letter: 'B'}, function(err, res) { - if (err) return done(err); - res.content.should.equal('A <%= letter %> B C'); - done(); - }); - }); - - it('should get an engine:', function() { - pages.engine('a', { - render: function() {} - }); - var a = pages.engine('a'); - a.should.have.property('render'); - }); -}); - -describe('engine selection:', function() { - beforeEach(function(done) { - collection = new Views(); - collection.engine('tmpl', require('engine-base')); - collection.engine('hbs', require('engine-handlebars')); - done(); - }); - - it('should get the engine from file extension:', function(done) { - var pages = new Views(); - pages.engine('tmpl', require('engine-base')); - pages.engine('hbs', require('engine-handlebars')); - pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}) - .render(function(err, view) { - if (err) return done(err); - assert(view.content === 'b'); - done(); - }); - }); - - it('should use the engine defined on the collection:', function(done) { - var posts = new Views({engine: 'hbs'}); - posts.engine('tmpl', require('engine-base')); - posts.engine('hbs', require('engine-handlebars')); - - posts.addView('a', {content: '{{a}}', locals: {a: 'b'}}) - .render(function(err, view) { - if (err) return done(err); - assert(view.content === 'b'); - done(); - }); - }); - - it('should use the engine defined on the view:', function(done) { - var posts = new Views(); - posts.engine('tmpl', require('engine-base')); - posts.engine('hbs', require('engine-handlebars')); - posts.addView('a', {content: '{{a}}', engine: 'hbs', locals: {a: 'b'}}) - .render(function(err, view) { - if (err) return done(err); - assert(view.content === 'b'); - done(); - }); - }); - - it('should use the engine defined on view.options:', function(done) { - var posts = new Views(); - posts.engine('tmpl', require('engine-base')); - posts.engine('hbs', require('engine-handlebars')); - posts.addView('a', {content: '{{a}}', data: {a: 'b'}, options: {engine: 'hbs'}}) - .render(function(err, view) { - if (err) return done(err); - assert(view.content === 'b'); - done(); - }); - }); - - it('should use the engine defined on view.data:', function(done) { - var posts = new Views(); - posts.engine('tmpl', require('engine-base')); - posts.engine('hbs', require('engine-handlebars')); - posts.addView('a', {content: '{{a}}', locals: {a: 'b'}, data: {engine: 'hbs'}}) - .render(function(err, view) { - if (err) return done(err); - assert(view.content === 'b'); - done(); - }); - }); - - it('should use the engine defined on render locals:', function(done) { - var posts = new Views(); - posts.engine('tmpl', require('engine-base')); - posts.engine('hbs', require('engine-handlebars')); - posts.addView('a', {content: '{{a}}', locals: {a: 'b'}}) - .render({engine: 'hbs'}, function(err, view) { - if (err) return done(err); - assert(view.content === 'b'); - done(); - }); - }); -}); diff --git a/test/collection.events.js b/test/collection.events.js deleted file mode 100644 index 5abab92..0000000 --- a/test/collection.events.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict'; - -require('should'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('collection events', function() { - beforeEach(function() { - app = new App(); - app.create('page'); - }); - - it('should emit events:', function() { - app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}); - var events = []; - - app.pages.on('option', function(key) { - events.push(key); - }); - - app.pages.option('a', 'b'); - app.pages.option('c', 'd'); - app.pages.option('e', 'f'); - app.pages.option({g: 'h'}); - - events.should.eql(['a', 'c', 'e', 'g']); - }); -}); diff --git a/test/collection.getView.js b/test/collection.getView.js deleted file mode 100644 index fb58723..0000000 --- a/test/collection.getView.js +++ /dev/null @@ -1,34 +0,0 @@ -'use strict'; - -var path = require('path'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('collection.getView', function() { - beforeEach(function() { - app = new App(); - app.create('page'); - - app.page('foo', {content: 'this is foo'}); - app.page('bar.md', {content: 'this is bar'}); - app.page('a/b/c/baz.md', {content: 'this is baz'}); - app.page('test/fixtures/templates/a.tmpl'); - }); - - it('should get a view by name', function() { - assert(app.pages.getView('foo')); - }); - - it('should get a view with the key modified by the given function', function() { - var view = app.pages.getView('foo.md', function(key) { - return path.basename(key, path.extname(key)); - }); - assert(view); - }); - - it('should get a view by full path', function() { - assert(app.pages.getView('a/b/c/baz.md')); - }); -}); diff --git a/test/collection.js b/test/collection.js deleted file mode 100644 index d1c8891..0000000 --- a/test/collection.js +++ /dev/null @@ -1,542 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var path = require('path'); -var assert = require('assert'); -var typeOf = require('kind-of'); -var isBuffer = require('is-buffer'); -var support = require('./support'); -var App = support.resolve(); -var List = App.List; -var Item = App.Item; -var Collection = App.Collection; -var collection; - -describe('collection', function() { - describe('constructor', function() { - it('should create an instance of Collection', function() { - var collection = new Collection(); - assert(collection instanceof Collection); - assert(typeof collection === 'object'); - }); - - it('should instantiate without new', function() { - var collection = Collection(); - assert(collection instanceof Collection); - assert(typeof collection === 'object'); - }); - }); - - describe('static methods', function() { - it('should expose `extend`', function() { - assert(typeof Collection.extend === 'function'); - }); - }); - - describe('prototype methods', function() { - beforeEach(function() { - collection = new Collection(); - }); - - var methods = [ - 'use', - 'setItem', - 'addItem', - 'addItems', - 'addList', - 'getItem', - 'constructor', - 'set', - 'get', - 'del', - 'define', - 'visit', - 'on', - 'once', - 'off', - 'emit', - 'listeners', - 'hasListeners' - ]; - - methods.forEach(function(method) { - it('should expose ' + method + ' method', function() { - assert(typeof collection[method] === 'function'); - }); - }); - - it('should expose isCollection property', function() { - assert(typeof collection.isCollection === 'boolean'); - }); - - it('should expose queue property', function() { - assert(Array.isArray(collection.queue)); - }); - - it('should expose items property', function() { - assert(typeOf(collection.items) === 'object'); - }); - - it('should expose options property', function() { - assert(typeOf(collection.options) === 'object'); - }); - }); -}); - -describe('methods', function() { - beforeEach(function() { - collection = new Collection(); - }); - - describe('chaining', function() { - it('should allow collection methods to be chained', function() { - collection - .addItems({'a.hbs': {path: 'a.hbs'}}) - .addItems({'b.hbs': {path: 'b.hbs'}}) - .addItems({'c.hbs': {path: 'c.hbs'}}); - - collection.items.should.have.properties([ - 'a.hbs', - 'b.hbs', - 'c.hbs' - ]); - }); - }); - - describe('use', function() { - it('should expose the instance to plugins', function() { - collection - .use(function(inst) { - inst.foo = 'bar'; - }); - - assert(collection.foo === 'bar'); - }); - - it('should expose `item` when the plugin returns a function', function() { - collection - .use(function() { - return function(item) { - item.foo = 'bar'; - }; - }); - - collection.addItem('aaa'); - collection.addItem('bbb'); - collection.addItem('ccc'); - - assert(collection.items.aaa.foo === 'bar'); - assert(collection.items.bbb.foo === 'bar'); - assert(collection.items.ccc.foo === 'bar'); - }); - }); - - describe('get / set', function() { - it('should set a value on the instance', function() { - collection.set('a', 'b'); - assert(collection.a === 'b'); - }); - - it('should get a value from the instance', function() { - collection.set('a', 'b'); - assert(collection.get('a') === 'b'); - }); - }); - - describe('adding items', function() { - beforeEach(function() { - collection = new Collection(); - }); - - it('should load a item onto the respective collection', function() { - collection.addItem('a.hbs'); - collection.items.should.have.property('a.hbs'); - }); - }); - - describe('item', function() { - beforeEach(function() { - collection = new Collection(); - }); - - it('should return a single collection item from a key-value pair', function() { - var one = collection.item('one', {content: 'foo'}); - var two = collection.item('two', {content: 'bar'}); - - assert(one instanceof Item); - assert(one instanceof collection.Item); - assert(one.path === 'one'); - assert(two instanceof Item); - assert(two instanceof collection.Item); - assert(two.path === 'two'); - }); - - it('should return a single collection item from an object', function() { - var one = collection.item({path: 'one', content: 'foo'}); - var two = collection.item({path: 'two', content: 'bar'}); - - assert(one instanceof Item); - assert(one.path === 'one'); - assert(two instanceof Item); - assert(two.path === 'two'); - }); - }); - - describe('addItem', function() { - beforeEach(function() { - collection = new Collection(); - }); - - it('should throw an error when args are invalid', function() { - (function() { - collection.addItem(function() {}); - }).should.throw('expected value to be an object.'); - }); - - it('should add a item to `items`', function() { - collection.addItem('foo'); - collection.items.should.have.property('foo'); - - collection.addItem('one', {content: '...'}); - assert(typeof collection.items.one === 'object'); - assert(isBuffer(collection.items.one.contents)); - }); - - it('should create an instance of `Item`', function() { - collection.addItem('one', {content: '...'}); - assert(collection.items.one instanceof collection.Item); - }); - - it('should allow an `Item` constructor to be passed', function() { - Item.prototype.foo = function(key, value) { - this[key] = value; - }; - collection = new Collection({Item: Item}); - collection.addItem('one', {content: '...'}); - collection.items.one.foo('bar', 'baz'); - assert(collection.items.one.bar === 'baz'); - }); - - it('should allow an instance of `Item` to be passed', function() { - var collection = new Collection({Item: Item}); - var item = new Item({content: '...'}); - collection.addItem('one', item); - item.set('abc', 'xyz'); - assert(collection.items.one instanceof collection.Item); - assert(isBuffer(collection.items.one.contents)); - assert(collection.items.one.abc === 'xyz'); - }); - }); - - describe('addItems', function() { - beforeEach(function() { - collection = new Collection(); - }); - - it('should add multiple items', function() { - collection.addItems({ - one: {content: 'foo'}, - two: {content: 'bar'} - }); - assert(isBuffer(collection.items.one.contents)); - assert(isBuffer(collection.items.two.contents)); - }); - - it('should create items from an instance of Collection', function() { - collection.addItems({ - one: {content: 'foo'}, - two: {content: 'bar'} - }); - var pages = new Collection(collection); - assert(isBuffer(pages.items.one.contents)); - assert(isBuffer(pages.items.two.contents)); - }); - - it('should add an array of plain-objects', function() { - collection.addItems([ - {path: 'one', content: 'foo'}, - {path: 'two', content: 'bar'} - ]); - assert(isBuffer(collection.items.one.contents)); - assert(isBuffer(collection.items.two.contents)); - }); - - it('should add an array of items', function() { - var list = new List([ - {path: 'one', content: 'foo'}, - {path: 'two', content: 'bar'} - ]); - - collection.addItems(list.items); - assert(isBuffer(collection.items.one.contents)); - assert(isBuffer(collection.items.two.contents)); - }); - }); - - describe('addList', function() { - beforeEach(function() { - collection = new Collection(); - }); - - it('should add a list of items', function() { - collection.addList([ - {path: 'one', content: 'foo'}, - {path: 'two', content: 'bar'} - ]); - assert(isBuffer(collection.items.one.contents)); - assert(isBuffer(collection.items.two.contents)); - }); - - it('should add a list of items from the constructor', function() { - var list = new List([ - {path: 'one', content: 'foo'}, - {path: 'two', content: 'bar'} - ]); - - collection = new Collection(list); - assert(isBuffer(collection.items.one.contents)); - assert(isBuffer(collection.items.two.contents)); - }); - - it('should throw an error when list is not an array', function() { - var items = new Collection(); - (function() { - items.addList(); - }).should.throw('expected list to be an array.'); - - (function() { - items.addList({}); - }).should.throw('expected list to be an array.'); - - (function() { - items.addList('foo'); - }).should.throw('expected list to be an array.'); - }); - - it('should load an array of items from an event', function() { - var collection = new Collection(); - - collection.on('addList', function(list) { - while (list.length) { - collection.addItem({path: list.pop()}); - } - }); - - collection.addList(['a.txt', 'b.txt', 'c.txt']); - assert(collection.items.hasOwnProperty('a.txt')); - assert(collection.items['a.txt'].path === 'a.txt'); - }); - - it('should load an array of items from the addList callback:', function() { - var collection = new Collection(); - - collection.addList(['a.txt', 'b.txt', 'c.txt'], function(fp) { - return {path: fp}; - }); - assert(collection.items.hasOwnProperty('a.txt')); - assert(collection.items['a.txt'].path === 'a.txt'); - }); - - it('should load an object of items from an event', function() { - var collection = new Collection(); - - collection.on('addItems', function(items) { - for (var key in items) { - collection.addItem('foo/' + key, items[key]); - delete items[key]; - } - }); - - collection.addItems({ - a: {path: 'a.txt'}, - b: {path: 'b.txt'}, - c: {path: 'c.txt'} - }); - - assert(collection.items.hasOwnProperty('foo/a')); - assert(collection.items['foo/a'].path === 'a.txt'); - }); - - it('should signal `loaded` when finished (addItems)', function() { - var collection = new Collection(); - - collection.on('addItems', function(items) { - for (var key in items) { - if (key === 'c') { - collection.loaded = true; - break; - } - collection.addItem('foo/' + key, items[key]); - } - }); - - collection.addItems({ - a: {path: 'a.txt'}, - b: {path: 'b.txt'}, - c: {path: 'c.txt'} - }); - - assert(collection.items.hasOwnProperty('foo/a')); - assert(!collection.items.hasOwnProperty('foo/c')); - assert(collection.items['foo/a'].path === 'a.txt'); - }); - - it('should signal `loaded` when finished (addList)', function() { - var collection = new Collection(); - - collection.on('addList', function(items) { - for (var i = 0; i < items.length; i++) { - var item = items[i]; - if (item.key === 'c') { - collection.loaded = true; - break; - } - item.key = 'foo/' + item.key; - collection.addItem(item.key, item); - } - }); - - collection.addList([ - {key: 'a', path: 'a.txt'}, - {key: 'b', path: 'b.txt'}, - {key: 'c', path: 'c.txt'} - ]); - - assert(collection.items.hasOwnProperty('foo/a')); - assert(collection.items['foo/a'].path === 'a.txt'); - assert(!collection.items.hasOwnProperty('foo/c')); - }); - }); - - describe('getItem', function() { - beforeEach(function() { - collection = new Collection(); - }); - it('should get a item from `items`', function() { - collection.addItem('one', {content: 'aaa'}); - collection.addItem('two', {content: 'zzz'}); - assert(isBuffer(collection.items.one.contents)); - assert(isBuffer(collection.getItem('one').contents)); - assert(collection.getItem('one').contents.toString() === 'aaa'); - assert(collection.getItem('two').contents.toString() === 'zzz'); - }); - }); -}); - -describe('queue', function() { - beforeEach(function() { - collection = new Collection(); - }); - - it('should emit arguments on addItem', function(done) { - collection.on('addItem', function(args) { - assert(args[0] === 'a'); - assert(args[1] === 'b'); - assert(args[2] === 'c'); - assert(args[3] === 'd'); - assert(args[4] === 'e'); - done(); - }); - - collection.addItem('a', 'b', 'c', 'd', 'e'); - }); - - it('should expose the `queue` property for loading items', function() { - collection.queue.push(collection.item('b', {path: 'b'})); - - collection.addItem('a', {path: 'a'}); - assert(collection.items.hasOwnProperty('a')); - assert(collection.items.hasOwnProperty('b')); - }); - - it('should load all items on the queue when addItem is called', function() { - collection.on('addItem', function(args) { - var len = args.length; - var last = args[len - 1]; - if (typeof last === 'string') { - args[len - 1] = { content: last }; - } - }); - - collection.addItem('a.html', 'aaa'); - collection.addItem('b.html', 'bbb'); - collection.addItem('c.html', 'ccc'); - - assert(collection.items.hasOwnProperty('a.html')); - assert(collection.getItem('a.html').content === 'aaa'); - assert(collection.items.hasOwnProperty('b.html')); - assert(collection.getItem('b.html').content === 'bbb'); - assert(collection.items.hasOwnProperty('c.html')); - assert(collection.getItem('c.html').content === 'ccc'); - }); -}); - -describe('options', function() { - describe('option', function() { - beforeEach(function() { - collection = new Collection(); - }); - - it('should expose the `option` method', function() { - collection.option('foo', 'bar'); - collection.options.should.have.property('foo', 'bar'); - }); - - it('should be chainable', function() { - collection.option('foo', 'bar') - .addItems('a.hbs') - .addItems('b.hbs') - .addItems('c.hbs'); - - collection.options.should.have.property('foo', 'bar'); - collection.items.should.have.properties([ - 'a.hbs', - 'b.hbs', - 'c.hbs' - ]); - }); - - it('should set a key/value pair on options', function() { - collection.option('a', 'b'); - assert(collection.options.a === 'b'); - }); - - it('should set an object on options', function() { - collection.option({c: 'd'}); - assert(collection.options.c === 'd'); - }); - - it('should get an option', function() { - collection.option({c: 'd'}); - var c = collection.option('c'); - assert(c === 'd'); - }); - }); - - describe('options.renameKey', function() { - beforeEach(function() { - collection = new Collection({ - renameKey: function(key) { - return path.basename(key); - } - }); - }); - - it('should use a custom rename key function on item keys', function() { - collection.addItem('a/b/c/d.hbs', {content: 'foo bar baz'}); - assert(collection.items['d.hbs'].contents.toString() === 'foo bar baz'); - }); - - it('should get a item with the renamed key', function() { - collection.addItem('a/b/c/d.hbs', {content: 'foo bar baz'}); - assert(collection.getItem('d.hbs').contents.toString() === 'foo bar baz'); - }); - - it('should get a item with the original key', function() { - collection.addItem('a/b/c/d.hbs', {content: 'foo bar baz'}); - assert(collection.getItem('a/b/c/d.hbs').contents.toString() === 'foo bar baz'); - }); - }); -}); - diff --git a/test/collection.options.js b/test/collection.options.js deleted file mode 100644 index 17f6b51..0000000 --- a/test/collection.options.js +++ /dev/null @@ -1,27 +0,0 @@ -'use strict'; - -require('should'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('collection.option()', function() { - beforeEach(function() { - app = new App(); - app.create('page'); - }); - - it('should set an option:', function() { - app.pages.options.should.not.have.property('foo'); - app.pages.option('foo', 'bar'); - app.pages.options.should.have.property('foo'); - }); - - it('should extend options:', function() { - app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}); - app.pages.option('a', 'b'); - app.pages.option('c', 'd'); - app.pages.option('e', 'f'); - app.pages.options.should.have.properties(['a', 'c', 'e']); - }); -}); diff --git a/test/collection.render.js b/test/collection.render.js deleted file mode 100644 index 04ed5a9..0000000 --- a/test/collection.render.js +++ /dev/null @@ -1,140 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var async = require('async'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var List = App.List; -var Views = App.Views; -var pages; - -describe('render', function() { - describe('rendering', function() { - beforeEach(function() { - pages = new Views(); - pages.engine('tmpl', require('engine-base')); - }); - - it('should throw an error when no callback is given:', function() { - (function() { - pages.render({}); - }).should.throw('Views#render is async and expects a callback function'); - }); - - it('should throw an error when an engine is not defined:', function(done) { - pages.addView('foo.bar', {content: '<%= name %>'}); - var page = pages.getView('foo.bar'); - - pages.render(page, function(err) { - assert(err.message === 'Views#render cannot find an engine for: .bar'); - done(); - }); - }); - - it('should use helpers to render a view:', function(done) { - var locals = {name: 'Halle'}; - - pages.helper('upper', function(str) { - return str.toUpperCase(str); - }); - - pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); - var page = pages.getView('a.tmpl'); - - pages.render(page, function(err, res) { - if (err) return done(err); - - assert(res.content === 'a HALLE b'); - done(); - }); - }); - - it('should use helpers when rendering a view:', function(done) { - var locals = {name: 'Halle'}; - pages.helper('upper', function(str) { - return str.toUpperCase(str); - }); - - pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); - var page = pages.getView('a.tmpl'); - - pages.render(page, function(err, res) { - if (err) return done(err); - assert(res.content === 'a HALLE b'); - done(); - }); - }); - - it('should render a template when contents is a buffer:', function(done) { - pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - var view = pages.getView('a.tmpl'); - - pages.render(view, function(err, view) { - if (err) return done(err); - assert(view.contents.toString() === 'b'); - done(); - }); - }); - - it('should render a template when content is a string:', function(done) { - pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - var view = pages.getView('a.tmpl'); - - pages.render(view, function(err, view) { - if (err) return done(err); - assert(view.contents.toString() === 'b'); - done(); - }); - }); - - it('should render a view from its path:', function(done) { - pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - - pages.render('a.tmpl', function(err, view) { - if (err) return done(err); - assert(view.content === 'b'); - done(); - }); - }); - - it('should use a plugin for rendering:', function(done) { - pages.engine('tmpl', require('engine-base')); - pages.option('engine', 'tmpl'); - - pages.addViews({ - 'a': {content: '<%= title %>', locals: {title: 'aaa'}}, - 'b': {content: '<%= title %>', locals: {title: 'bbb'}}, - 'c': {content: '<%= title %>', locals: {title: 'ccc'}}, - 'd': {content: '<%= title %>', locals: {title: 'ddd'}}, - 'e': {content: '<%= title %>', locals: {title: 'eee'}}, - 'f': {content: '<%= title %>', locals: {title: 'fff'}}, - 'g': {content: '<%= title %>', locals: {title: 'ggg'}}, - 'h': {content: '<%= title %>', locals: {title: 'hhh'}}, - 'i': {content: '<%= title %>', locals: {title: 'iii'}}, - 'j': {content: '<%= title %>', locals: {title: 'jjj'}} - }); - - pages.use(function(collection) { - collection.option('pager', false); - - collection.renderEach = function(cb) { - var list = new List(collection); - - async.map(list.items, function(item, next) { - collection.render(item, next); - }, cb); - }; - }); - - pages.renderEach(function(err, items) { - if (err) return done(err); - assert(items[0].content === 'aaa'); - assert(items[9].content === 'jjj'); - assert(items.length === 10); - done(); - }); - }); - }); -}); diff --git a/test/collection.use.js b/test/collection.use.js deleted file mode 100644 index 9c9400d..0000000 --- a/test/collection.use.js +++ /dev/null @@ -1,158 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var Collection = App.Collection; -var Item = App.Item; -var collection; - -describe('collection.use', function() { - beforeEach(function() { - collection = new Collection(); - }); - - it('should expose the instance to `use`:', function(done) { - collection.use(function(inst) { - assert(inst instanceof Collection); - done(); - }); - }); - - it('should be chainable:', function(done) { - collection.use(function(inst) { - assert(inst instanceof Collection); - }) - .use(function(inst) { - assert(inst instanceof Collection); - }) - .use(function(inst) { - assert(inst instanceof Collection); - done(); - }); - }); - - it('should expose the collection to a plugin:', function() { - collection.use(function(items) { - assert(items instanceof Collection); - items.foo = items.addItem.bind(items); - }); - - collection.foo('a', {content: '...'}); - assert(collection.items.hasOwnProperty('a')); - }); - - it('should expose collection when chained:', function() { - collection - .use(function(items) { - assert(items instanceof Collection); - items.foo = items.addItem.bind(items); - }) - .use(function(items) { - assert(items instanceof Collection); - items.bar = items.addItem.bind(items); - }) - .use(function(items) { - assert(items instanceof Collection); - items.baz = items.addItem.bind(items); - }); - - var pages = collection; - - pages.foo({path: 'a', content: '...'}); - pages.bar({path: 'b', content: '...'}); - pages.baz({path: 'c', content: '...'}); - - assert(collection.items.hasOwnProperty('a')); - assert(collection.items.hasOwnProperty('b')); - assert(collection.items.hasOwnProperty('c')); - }); - - it('should work when a custom `Item` constructor is passed:', function() { - collection = new Collection({Item: require('vinyl')}); - collection - .use(function(items) { - assert(items instanceof Collection); - items.foo = items.addItem.bind(items); - }) - .use(function(items) { - assert(items instanceof Collection); - items.bar = items.addItem.bind(items); - }) - .use(function(items) { - assert(items instanceof Collection); - items.baz = items.addItem.bind(items); - }); - - var pages = collection; - - pages.foo({path: 'a', content: '...'}); - pages.bar({path: 'b', content: '...'}); - pages.baz({path: 'c', content: '...'}); - - assert(collection.items.hasOwnProperty('a')); - assert(collection.items.hasOwnProperty('b')); - assert(collection.items.hasOwnProperty('c')); - }); - - it('should pass to item `use` if a function is returned:', function() { - collection.use(function(items) { - assert(items instanceof Collection); - - return function(item) { - item.foo = items.addItem.bind(items); - assert(item instanceof Item); - }; - }); - - collection.addItem('a', {content: '...'}) - .foo({path: 'b', content: '...'}) - .foo({path: 'c', content: '...'}) - .foo({path: 'd', content: '...'}); - - assert(collection.items.hasOwnProperty('a')); - assert(collection.items.hasOwnProperty('b')); - assert(collection.items.hasOwnProperty('c')); - assert(collection.items.hasOwnProperty('d')); - }); - - it('should be chainable when a item function is returned:', function() { - collection - .use(function(items) { - assert(items instanceof Collection); - - return function(item) { - item.foo = items.addItem.bind(items); - assert(item instanceof Item); - }; - }) - .use(function(items) { - assert(items instanceof Collection); - - return function(item) { - item.bar = items.addItem.bind(items); - assert(item instanceof Item); - }; - }) - .use(function(items) { - assert(items instanceof Collection); - - return function(item) { - item.baz = items.addItem.bind(items); - assert(item instanceof Item); - }; - }); - - collection.addItem('a', {content: '...'}) - .foo({path: 'b', content: '...'}) - .bar({path: 'c', content: '...'}) - .baz({path: 'd', content: '...'}); - - assert(collection.items.hasOwnProperty('a')); - assert(collection.items.hasOwnProperty('b')); - assert(collection.items.hasOwnProperty('c')); - assert(collection.items.hasOwnProperty('d')); - }); -}); diff --git a/test/fixtures/a.txt b/test/fixtures/a.txt deleted file mode 100644 index a8a9406..0000000 --- a/test/fixtures/a.txt +++ /dev/null @@ -1 +0,0 @@ -this is a test \ No newline at end of file diff --git a/test/fixtures/b.txt b/test/fixtures/b.txt deleted file mode 100644 index a8a9406..0000000 --- a/test/fixtures/b.txt +++ /dev/null @@ -1 +0,0 @@ -this is a test \ No newline at end of file diff --git a/test/fixtures/bom-utf16be.txt b/test/fixtures/bom-utf16be.txt deleted file mode 100644 index b9dce78a5d31af4803acd1a0f0dfc14f064a5de1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 244 zcmX|+I}XAy5Jacu26Qf=0Eq@sM*@i=qJaY#5&}waq&RRn3Xa4Tc-{cbe#Sc=zn?E@ zuZymVayru+l}y7P<@I1MK)hWXxZY@{g_hJzYt4Dvs;8dRDlmE2!LB37&GahJPDg5G zyEjIUb8?Hu>i*d9+Q4pAn^J>j{bf4+Qmn{O;+32Wrj#?&PC0#o+uan?UxFJmPf0ua E03tFf0ssI2 diff --git a/test/fixtures/bom-utf16le.txt b/test/fixtures/bom-utf16le.txt deleted file mode 100644 index 07cc600c98675d221bb56d10af38e650538734c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 244 zcmX|+%?`mp6otRFH?W%}3lbZ#mXJt@4G%E1O4?47PI);CkK`4cxy9!GoVn*`-p>~Y zuH1+?F6tGzrhboj9@;Y@-Y$;1UNd3FTy@Kesopkps%IL4CNFld>nNl)y+UZqNwu)u z8>5qRa*M`l|5*Q8iQQ0|QYFpu%XIuwER-RaS8~oYrJPIl?9@kcyPIPAOJL|a#!5Tj E15l default'); - cb(); - }); - - app.task('a', function() {}); - app.task('b', function() {}); - app.task('c', function() {}); - - app.register('foo', function(app) { - app.task('x', function() {}); - app.task('y', function() {}); - app.task('z', function() {}); - }); -}; \ No newline at end of file diff --git a/test/fixtures/one/package.json b/test/fixtures/one/package.json deleted file mode 100644 index d1d4676..0000000 --- a/test/fixtures/one/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "one", - "description": "The most interesting project in the world > Verb", - "version": "0.1.0", - "homepage": "https://github.com/jonschlinkert/one", - "author": "Jon Schlinkert (https://github.com/jonschlinkert)", - "repository": "jonschlinkert/one", - "bugs": { - "url": "https://github.com/jonschlinkert/one/issues" - }, - "license": "MIT", - "files": [ - "index.js" - ], - "main": "index.js", - "engines": { - "node": ">=0.10.0" - }, - "scripts": { - "test": "mocha" - }, - "dependencies": { - "resolve-modules": "^0.1.2" - }, - "devDependencies": { - "mocha": "*", - "should": "*" - }, - "keywords": [] -} diff --git a/test/fixtures/one/templates/a.txt b/test/fixtures/one/templates/a.txt deleted file mode 100644 index 2ff8250..0000000 --- a/test/fixtures/one/templates/a.txt +++ /dev/null @@ -1 +0,0 @@ -one: aaa \ No newline at end of file diff --git a/test/fixtures/one/templates/x.txt b/test/fixtures/one/templates/x.txt deleted file mode 100644 index 139e1d8..0000000 --- a/test/fixtures/one/templates/x.txt +++ /dev/null @@ -1 +0,0 @@ -one: xxx \ No newline at end of file diff --git a/test/fixtures/one/templates/y.txt b/test/fixtures/one/templates/y.txt deleted file mode 100644 index 7308ea9..0000000 --- a/test/fixtures/one/templates/y.txt +++ /dev/null @@ -1 +0,0 @@ -one: yyy \ No newline at end of file diff --git a/test/fixtures/one/templates/z.txt b/test/fixtures/one/templates/z.txt deleted file mode 100644 index 04c378a..0000000 --- a/test/fixtures/one/templates/z.txt +++ /dev/null @@ -1 +0,0 @@ -one: zzz \ No newline at end of file diff --git a/test/fixtures/pages/a.hbs b/test/fixtures/pages/a.hbs deleted file mode 100644 index 51320bd..0000000 --- a/test/fixtures/pages/a.hbs +++ /dev/null @@ -1,2 +0,0 @@ -

{{title}}

-

<%= title() %>

\ No newline at end of file diff --git a/test/fixtures/pages/b.hbs b/test/fixtures/pages/b.hbs deleted file mode 100644 index 51320bd..0000000 --- a/test/fixtures/pages/b.hbs +++ /dev/null @@ -1,2 +0,0 @@ -

{{title}}

-

<%= title() %>

\ No newline at end of file diff --git a/test/fixtures/pages/c.hbs b/test/fixtures/pages/c.hbs deleted file mode 100644 index 51320bd..0000000 --- a/test/fixtures/pages/c.hbs +++ /dev/null @@ -1,2 +0,0 @@ -

{{title}}

-

<%= title() %>

\ No newline at end of file diff --git a/test/fixtures/posts/a.txt b/test/fixtures/posts/a.txt deleted file mode 100644 index bca29ee..0000000 --- a/test/fixtures/posts/a.txt +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: AAA ---- -This is <%= title %> \ No newline at end of file diff --git a/test/fixtures/posts/b.txt b/test/fixtures/posts/b.txt deleted file mode 100644 index 1e128c7..0000000 --- a/test/fixtures/posts/b.txt +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: BBB ---- -This is <%= title %> \ No newline at end of file diff --git a/test/fixtures/posts/c.txt b/test/fixtures/posts/c.txt deleted file mode 100644 index 32f9187..0000000 --- a/test/fixtures/posts/c.txt +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: CCC ---- -This is <%= title %> \ No newline at end of file diff --git a/test/fixtures/templates/a.tmpl b/test/fixtures/templates/a.tmpl deleted file mode 100644 index 36f1f1b..0000000 --- a/test/fixtures/templates/a.tmpl +++ /dev/null @@ -1 +0,0 @@ -<%= name %> \ No newline at end of file diff --git a/test/fixtures/templates/b.tmpl b/test/fixtures/templates/b.tmpl deleted file mode 100644 index 36f1f1b..0000000 --- a/test/fixtures/templates/b.tmpl +++ /dev/null @@ -1 +0,0 @@ -<%= name %> \ No newline at end of file diff --git a/test/fixtures/templates/c.tmpl b/test/fixtures/templates/c.tmpl deleted file mode 100644 index 36f1f1b..0000000 --- a/test/fixtures/templates/c.tmpl +++ /dev/null @@ -1 +0,0 @@ -<%= name %> \ No newline at end of file diff --git a/test/fixtures/test-symlink b/test/fixtures/test-symlink deleted file mode 120000 index 3fcfe6c..0000000 --- a/test/fixtures/test-symlink +++ /dev/null @@ -1 +0,0 @@ -test.coffee \ No newline at end of file diff --git a/test/fixtures/test-symlink-dir/suchempty b/test/fixtures/test-symlink-dir/suchempty deleted file mode 100644 index 65bbcaa..0000000 --- a/test/fixtures/test-symlink-dir/suchempty +++ /dev/null @@ -1 +0,0 @@ -suchempty \ No newline at end of file diff --git a/test/fixtures/test.coffee b/test/fixtures/test.coffee deleted file mode 100644 index 6769dd6..0000000 --- a/test/fixtures/test.coffee +++ /dev/null @@ -1 +0,0 @@ -Hello world! \ No newline at end of file diff --git a/test/fixtures/three/four/five/generator.js b/test/fixtures/three/four/five/generator.js deleted file mode 100644 index 220c400..0000000 --- a/test/fixtures/three/four/five/generator.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -module.exports = function(update, base) { - update.task('default', function() {}); - update.task('a', function() {}); - update.task('b', function() {}); - update.task('c', function() {}); - - update.register('foo', function(app) { - app.task('x', function() {}); - app.task('y', function() {}); - app.task('z', function() {}); - }); -}; \ No newline at end of file diff --git a/test/fixtures/three/four/five/package.json b/test/fixtures/three/four/five/package.json deleted file mode 100644 index 984f0f6..0000000 --- a/test/fixtures/three/four/five/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "five", - "description": "The most interesting project in the world > Verb", - "version": "0.1.0", - "homepage": "https://github.com/jonschlinkert/five", - "author": "Jon Schlinkert (https://github.com/jonschlinkert)", - "repository": "jonschlinkert/five", - "bugs": { - "url": "https://github.com/jonschlinkert/five/issues" - }, - "license": "MIT", - "files": [ - "index.js" - ], - "main": "index.js", - "engines": { - "node": ">=0.10.0" - }, - "scripts": { - "test": "mocha" - }, - "dependencies": { - "resolve-modules": "^0.1.2" - }, - "devDependencies": { - "mocha": "*", - "should": "*" - }, - "keywords": [] -} diff --git a/test/fixtures/three/four/five/templates/a.txt b/test/fixtures/three/four/five/templates/a.txt deleted file mode 100644 index e69de29..0000000 diff --git a/test/fixtures/three/four/five/templates/b.txt b/test/fixtures/three/four/five/templates/b.txt deleted file mode 100644 index e69de29..0000000 diff --git a/test/fixtures/three/four/five/templates/c.txt b/test/fixtures/three/four/five/templates/c.txt deleted file mode 100644 index e69de29..0000000 diff --git a/test/fixtures/three/four/generator.js b/test/fixtures/three/four/generator.js deleted file mode 100644 index 220c400..0000000 --- a/test/fixtures/three/four/generator.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -module.exports = function(update, base) { - update.task('default', function() {}); - update.task('a', function() {}); - update.task('b', function() {}); - update.task('c', function() {}); - - update.register('foo', function(app) { - app.task('x', function() {}); - app.task('y', function() {}); - app.task('z', function() {}); - }); -}; \ No newline at end of file diff --git a/test/fixtures/three/four/package.json b/test/fixtures/three/four/package.json deleted file mode 100644 index 3f25912..0000000 --- a/test/fixtures/three/four/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "four", - "description": "The most interesting project in the world > Verb", - "version": "0.1.0", - "homepage": "https://github.com/jonschlinkert/four", - "author": "Jon Schlinkert (https://github.com/jonschlinkert)", - "repository": "jonschlinkert/four", - "bugs": { - "url": "https://github.com/jonschlinkert/four/issues" - }, - "license": "MIT", - "files": [ - "index.js" - ], - "main": "index.js", - "engines": { - "node": ">=0.10.0" - }, - "scripts": { - "test": "mocha" - }, - "dependencies": { - "resolve-modules": "^0.1.2" - }, - "devDependencies": { - "mocha": "*", - "should": "*" - }, - "keywords": [] -} diff --git a/test/fixtures/three/four/templates/a.txt b/test/fixtures/three/four/templates/a.txt deleted file mode 100644 index e69de29..0000000 diff --git a/test/fixtures/three/four/templates/b.txt b/test/fixtures/three/four/templates/b.txt deleted file mode 100644 index e69de29..0000000 diff --git a/test/fixtures/three/four/templates/c.txt b/test/fixtures/three/four/templates/c.txt deleted file mode 100644 index e69de29..0000000 diff --git a/test/fixtures/three/package.json b/test/fixtures/three/package.json deleted file mode 100644 index 12b5fac..0000000 --- a/test/fixtures/three/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "three", - "description": "The most interesting project in the world > Verb", - "version": "0.1.0", - "homepage": "https://github.com/jonschlinkert/three", - "author": "Jon Schlinkert (https://github.com/jonschlinkert)", - "repository": "jonschlinkert/three", - "bugs": { - "url": "https://github.com/jonschlinkert/three/issues" - }, - "license": "MIT", - "files": [ - "index.js" - ], - "main": "index.js", - "engines": { - "node": ">=0.10.0" - }, - "scripts": { - "test": "mocha" - }, - "dependencies": { - "resolve-modules": "^0.1.2" - }, - "devDependencies": { - "mocha": "*", - "should": "*" - }, - "keywords": [] -} diff --git a/test/fixtures/three/templates/a.txt b/test/fixtures/three/templates/a.txt deleted file mode 100644 index e69de29..0000000 diff --git a/test/fixtures/three/templates/b.txt b/test/fixtures/three/templates/b.txt deleted file mode 100644 index e69de29..0000000 diff --git a/test/fixtures/three/templates/c.txt b/test/fixtures/three/templates/c.txt deleted file mode 100644 index e69de29..0000000 diff --git a/test/fixtures/three/updatefile.js b/test/fixtures/three/updatefile.js deleted file mode 100644 index 220c400..0000000 --- a/test/fixtures/three/updatefile.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -module.exports = function(update, base) { - update.task('default', function() {}); - update.task('a', function() {}); - update.task('b', function() {}); - update.task('c', function() {}); - - update.register('foo', function(app) { - app.task('x', function() {}); - app.task('y', function() {}); - app.task('z', function() {}); - }); -}; \ No newline at end of file diff --git a/test/fixtures/two/package.json b/test/fixtures/two/package.json deleted file mode 100644 index 8a02df0..0000000 --- a/test/fixtures/two/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "two", - "description": "The most interesting project in the world > Verb", - "version": "0.1.0", - "homepage": "https://github.com/jonschlinkert/two", - "author": "Jon Schlinkert (https://github.com/jonschlinkert)", - "repository": "jonschlinkert/two", - "bugs": { - "url": "https://github.com/jonschlinkert/two/issues" - }, - "license": "MIT", - "files": [ - "index.js" - ], - "main": "index.js", - "engines": { - "node": ">=0.10.0" - }, - "scripts": { - "test": "mocha" - }, - "dependencies": { - "resolve-modules": "^0.1.2" - }, - "devDependencies": { - "mocha": "*", - "should": "*" - }, - "keywords": [] -} diff --git a/test/fixtures/two/templates/a.txt b/test/fixtures/two/templates/a.txt deleted file mode 100644 index e69de29..0000000 diff --git a/test/fixtures/two/templates/b.txt b/test/fixtures/two/templates/b.txt deleted file mode 100644 index e69de29..0000000 diff --git a/test/fixtures/two/templates/c.txt b/test/fixtures/two/templates/c.txt deleted file mode 100644 index e69de29..0000000 diff --git a/test/fixtures/two/updatefile.js b/test/fixtures/two/updatefile.js deleted file mode 100644 index ad4cb16..0000000 --- a/test/fixtures/two/updatefile.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -var Update = require('../../..'); -var update = new Update(); - -update.task('default', function() {}); -update.task('a', function() {}); -update.task('b', function() {}); -update.task('c', function() {}); - -update.register('foo', function(app) { - app.task('x', function() {}); - app.task('y', function() {}); - app.task('z', function() {}); -}); - -/** - * Expose this instance of `Update` - */ - -module.exports = update; diff --git a/test/fixtures/updaters-array.json b/test/fixtures/updaters-array.json new file mode 100644 index 0000000..003f59c --- /dev/null +++ b/test/fixtures/updaters-array.json @@ -0,0 +1,3 @@ +{ + "updaters": ["foo", "bar"] +} \ No newline at end of file diff --git a/test/fixtures/updaters-object.json b/test/fixtures/updaters-object.json new file mode 100644 index 0000000..03e075f --- /dev/null +++ b/test/fixtures/updaters-object.json @@ -0,0 +1,6 @@ +{ + "updaters": { + "foo": {}, + "bar": {} + } +} \ No newline at end of file diff --git a/test/fixtures/updaters/a.txt b/test/fixtures/updaters/a.txt deleted file mode 100644 index 7c4a013..0000000 --- a/test/fixtures/updaters/a.txt +++ /dev/null @@ -1 +0,0 @@ -aaa \ No newline at end of file diff --git a/test/fixtures/updaters/b.txt b/test/fixtures/updaters/b.txt deleted file mode 100644 index 01f02e3..0000000 --- a/test/fixtures/updaters/b.txt +++ /dev/null @@ -1 +0,0 @@ -bbb \ No newline at end of file diff --git a/test/fixtures/updaters/c.txt b/test/fixtures/updaters/c.txt deleted file mode 100644 index 2383bd5..0000000 --- a/test/fixtures/updaters/c.txt +++ /dev/null @@ -1 +0,0 @@ -ccc \ No newline at end of file diff --git a/test/fixtures/vinyl/bom-utf16be.txt b/test/fixtures/vinyl/bom-utf16be.txt deleted file mode 100644 index b9dce78a5d31af4803acd1a0f0dfc14f064a5de1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 244 zcmX|+I}XAy5Jacu26Qf=0Eq@sM*@i=qJaY#5&}waq&RRn3Xa4Tc-{cbe#Sc=zn?E@ zuZymVayru+l}y7P<@I1MK)hWXxZY@{g_hJzYt4Dvs;8dRDlmE2!LB37&GahJPDg5G zyEjIUb8?Hu>i*d9+Q4pAn^J>j{bf4+Qmn{O;+32Wrj#?&PC0#o+uan?UxFJmPf0ua E03tFf0ssI2 diff --git a/test/fixtures/vinyl/bom-utf16le.txt b/test/fixtures/vinyl/bom-utf16le.txt deleted file mode 100644 index 07cc600c98675d221bb56d10af38e650538734c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 244 zcmX|+%?`mp6otRFH?W%}3lbZ#mXJt@4G%E1O4?47PI);CkK`4cxy9!GoVn*`-p>~Y zuH1+?F6tGzrhboj9@;Y@-Y$;1UNd3FTy@Kesopkps%IL4CNFld>nNl)y+UZqNwu)u z8>5qRa*M`l|5*Q8iQQ0|QYFpu%XIuwER-RaS8~oYrJPIl?9@kcyPIPAOJL|a#!5Tj E15l', locals: {a: 'bbb'}}); - app.helper('upper', function(str) { - return str.toUpperCase(); - }); - - var page = app.pages.getView('a.tmpl'); - - app.render(page, function(err, view) { - if (err) return cb(err); - - assert.equal(typeof view.contents.toString(), 'string'); - assert.equal(view.contents.toString(), 'BBB'); - cb(); - }); - }); - - it('should use a namespaced helper:', function(cb) { - app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= foo.upper(a) %>', locals: {a: 'bbb'}}); - - app.helperGroup('foo', { - upper: function(str) { - return str.toUpperCase(); - } - }); - - // console.log(app._.helpers) - - var page = app.pages.getView('a.tmpl'); - app.render(page, function(err, view) { - if (err) return cb(err); - - assert.equal(typeof view.contents.toString(), 'string'); - assert.equal(view.contents.toString(), 'BBB'); - cb(); - }); - }); -}); - -describe('async helpers', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('page'); - }); - - it('should register an async helper:', function() { - app.asyncHelper('a', function() {}); - app.asyncHelper('b', function() {}); - app._.helpers.async.should.have.property('a'); - app._.helpers.async.should.have.property('b'); - }); - - it('should use an async helper:', function(cb) { - app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= lower(a) %>', locals: {a: 'BBB'}}); - app.asyncHelper('lower', function(str, next) { - if (typeof next !== 'function') return str; - next(null, str.toLowerCase()); - }); - - var page = app.pages.getView('a.tmpl'); - app.render(page, function(err, view) { - if (err) return cb(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'bbb'); - cb(); - }); - }); -}); - -describe('built-in helpers:', function() { - describe('automatically updated helpers for default view types:', function() { - beforeEach(function() { - app = new App({rethrow: false}); - app.engine('md', require('engine-base')); - app.engine('tmpl', require('engine-base')); - app.create('partials', { viewType: 'partial' }); - app.create('pages'); - - // parse front matter - app.onLoad(/./, function(view, next) { - matter.parse(view, next); - }); - }); - - it('should expose front matter to the `partial` helper.', function(cb) { - app.partial('a.md', {content: '---\nname: "AAA"\n---\n<%= name %>', locals: {name: 'BBB'}}); - app.page('b.md', {path: 'b.md', content: 'foo <%= partial("a.md") %> bar'}); - - app.render('b.md', function(err, res) { - if (err) return cb(err); - res.content.should.equal('foo AAA bar'); - cb(); - }); - }); - - it('should use helper locals.', function(cb) { - app.partial('abc.md', {content: '<%= name %>', locals: {name: 'BBB'}}); - app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md", { name: "CCC" }) %> bar'}); - - app.render('xyz.md', {name: 'DDD'}, function(err, res) { - if (err) return cb(err); - res.content.should.equal('foo CCC bar'); - cb(); - }); - }); - - it('should use front matter data.', function(cb) { - app.partial('abc.md', {content: '---\nname: "AAA"\n---\n<%= name %>'}); - app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}); - - app.render('xyz.md', {name: 'DDD'}, function(err, res) { - if (err) return cb(err); - res.content.should.equal('foo AAA bar'); - cb(); - }); - }); - - it('should prefer helper locals over front-matter', function(cb) { - app.partial('abc.md', {content: '---\nname: "AAA"\n---\n<%= name %>', locals: {name: 'BBB'}}); - app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md", { name: "CCC" }) %> bar'}); - - app.render('xyz.md', {name: 'DDD'}, function(err, res) { - if (err) return cb(err); - res.content.should.equal('foo CCC bar'); - cb(); - }); - }); - - it('should use partial locals:', function(cb) { - app.partial('abc.md', {content: '<%= name %>', locals: {name: 'EEE'}}); - - app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}) - .render({name: 'DDD'}, function(err, res) { - if (err) return cb(err); - res.content.should.equal('foo EEE bar'); - cb(); - }); - }); - - it('should use locals from the `view.render` method:', function(cb) { - app.partial('abc.md', {content: '<%= name %>', locals: {name: 'EEE'}}); - - app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}) - .render({name: 'DDD'}, function(err, res) { - if (err) return cb(err); - - res.content.should.equal('foo EEE bar'); - cb(); - }); - }); - - it('should use locals from the `app.render` method:', function(cb) { - app.partial('abc.md', {content: '<%= name %>'}); - app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}); - - app.render('xyz.md', {name: 'DDD'}, function(err, res) { - if (err) return cb(err); - res.content.should.equal('foo DDD bar'); - cb(); - }); - }); - - it('should use a `helperContext` function from app.options', function(cb) { - app.option('helperContext', function(view, locals) { - return { name: 'blah' }; - }); - - app.partial('abc.md', {content: '<%= name %>'}); - app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}); - - app.render('xyz.md', {name: 'DDD'}, function(err, res) { - if (err) return cb(err); - res.content.should.equal('foo blah bar'); - cb(); - }); - }); - - it('should return an empty string when the partial is missing.', function(cb) { - app.partial('abc.md', {content: '---\nname: "AAA"\n---\n<%= name %>', locals: {name: 'BBB'}}); - app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("def.md", { name: "CCC" }) %> bar'}); - app.render('xyz.md', {name: 'DDD'}, function(err, res) { - if (err) return cb(err); - res.content.should.equal('foo bar'); - cb(); - }); - }); - }); - - describe('helper context:', function() { - beforeEach(function() { - app = new App({rethrow: false}); - app.engine(['tmpl', 'md'], require('engine-base')); - app.create('partial', { viewType: 'partial' }); - app.create('page'); - - // parse front matter - app.onLoad(/./, function(view, next) { - matter.parse(view, next); - }); - }); - - it('should prefer helper locals over view locals.', function(cb) { - app.partial('abc.md', {content: '<%= name %>', name: 'BBB'}); - app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md", { name: "CCC" }) %> bar'}); - - app.render('xyz.md', {name: 'DDD'}, function(err, res) { - if (err) return cb(err); - res.content.should.equal('foo CCC bar'); - cb(); - }); - }); - - it('should give preference to view locals over render locals.', function(cb) { - app.partial('abc.md', {content: '<%= name %>', locals: {name: 'BBB'}}); - app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}); - - var page = app.pages.getView('xyz.md'); - - app.render(page, {name: 'DDD'}, function(err, res) { - if (err) return cb(err); - res.content.should.equal('foo BBB bar'); - cb(); - }); - }); - - it('should use render locals when other locals are not defined.', function(cb) { - app.partial('abc.md', {content: '<%= name %>'}); - app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}); - - app.render('xyz.md', {name: 'DDD'}, function(err, res) { - if (err) return cb(err); - res.content.should.equal('foo DDD bar'); - cb(); - }); - }); - }); - - describe('user-defined engines:', function() { - beforeEach(function() { - app = new App({rethrow: false}); - app.create('partial', { viewType: 'partial' }); - app.create('page'); - - // parse front matter - app.onLoad(/./, function(view, next) { - matter.parse(view, next); - }); - }); - - it('should use the `partial` helper with handlebars.', function(cb) { - app.engine(['tmpl', 'md'], require('engine-base')); - app.engine('hbs', handlebars); - - app.partial('title.hbs', {content: '{{name}}', locals: {name: 'BBB'}}); - app.page('a.hbs', {path: 'a.hbs', content: 'foo {{{partial "title.hbs" this}}} bar'}); - - app.render('a.hbs', {name: 'Halle Nicole'}, function(err, res) { - if (err) return cb(err); - res.content.should.equal('foo Halle Nicole bar'); - cb(); - }); - }); - - it('should use the `partial` helper with any engine.', function(cb) { - app.engine('hbs', handlebars); - app.engine('md', handlebars); - app.engine('swig', swig); - app.engine('tmpl', require('engine-base')); - - /** - * Partial - */ - - app.partial('a.hbs', { - content: '---\nname: "AAA"\n---\n{{name}}', - locals: { - name: 'BBB' - } - }); - - /** - * Pages - */ - - app.page('a.hbs', { - path: 'a.hbs', - content: '{{author}}', - locals: { - author: 'Halle Nicole' - } - }); - app.page('b.tmpl', { - path: 'b.tmpl', - content: '<%= author %>', - locals: { - author: 'Halle Nicole' - } - }); - app.page('d.swig', { - path: 'd.swig', - content: '{{author}}', - locals: { - author: 'Halle Nicole' - } - }); - app.page('e.swig', { - path: 'e.swig', - content: '{{author}}', - locals: { - author: 'Halle Nicole' - } - }); - app.page('f.hbs', { - content: '{{author}}', - locals: { - author: 'Halle Nicole' - } - }); - app.page('g.md', { - content: '---\nauthor: Brian Woodward\n---\n{{author}}', - locals: { - author: 'Halle Nicole' - } - }); - app.page('with-partial.hbs', { - path: 'with-partial.hbs', - content: '{{{partial "a.hbs" custom.locals}}}' - }); - - app.on('error', function(err) { - cb(err); - }); - - var locals = {custom: {locals: {name: 'Halle Nicole' }}}; - app.render('a.hbs', locals, function(err, res) { - if (err) { - app.emit('error', err); - return; - } - res.content.should.equal('Halle Nicole'); - }); - - app.render('with-partial.hbs', locals, function(err, res) { - if (err) { - app.emit('error', err); - return; - } - res.content.should.equal('Halle Nicole'); - }); - - var page = app.pages.getView('g.md'); - locals.author = page.data.author || locals.author; - page.render(locals, function(err, res) { - if (err) { - app.emit('error', err); - return; - } - res.content.should.equal('Brian Woodward'); - cb(null, res.content); - }); - }); - }); -}); - -describe('helpers integration', function() { - beforeEach(function() { - app = new App(); - app.create('pages'); - app.engine('md', require('engine-base')); - }); - - describe('.helpers()', function() { - it('should add helpers and use them in templates.', function(cb) { - app.helpers({ - upper: function(str) { - return str.toUpperCase(); - } - }); - - app.page('doc.md', {content: 'a <%= upper(name) %> b'}) - .render({name: 'Halle'}, function(err, res) { - if (err) return cb(err); - assert(res.content === 'a HALLE b'); - cb(); - }); - }); - }); - - describe('helper options:', function() { - it('should expose `this.options` to helpers:', function(cb) { - app.helper('cwd', function(fp) { - return path.join(this.options.cwd, fp); - }); - - app.option('one', 'two'); - app.option('cwd', 'foo/bar'); - app.page('doc.md', {content: 'a <%= cwd("baz") %> b'}) - .render(function(err, res) { - if (err) return cb(err); - assert(res.content === 'a foo/bar/baz b'); - cb(); - }); - }); - - it('should pass helper options to helpers:', function(cb) { - app.helper('cwd', function(fp) { - return path.join(this.options.cwd, fp); - }); - - app.option('helper.cwd', 'foo/bar'); - app.option('helper.whatever', '...'); - - app.page('doc.md', {content: 'a <%= cwd("baz") %> b'}) - .render(function(err, res) { - if (err) return cb(err); - assert(res.content === 'a foo/bar/baz b'); - cb(); - }); - }); - }); - - describe('options.helpers', function() { - it('should register helpers passed on the options:', function(cb) { - app.option({ - helpers: { - upper: function(str) { - return str.toUpperCase(); - }, - foo: function(str) { - return 'foo' + str; - } - } - }); - - app.page('doc.md', {content: 'a <%= upper(name) %> <%= foo("bar") %> b'}) - .render({name: 'Halle'}, function(err, res) { - if (err) return cb(err); - assert(res.content === 'a HALLE foobar b'); - cb(); - }); - }); - }); - - describe('options.helpers', function() { - it('should add helpers and use them in templates.', function(cb) { - app.options.helpers = { - upper: function(str) { - return str.toUpperCase(); - }, - foo: function(str) { - return 'foo' + str; - } - }; - - app.page('doc.md', {content: 'a <%= upper(name) %> b'}) - .render({name: 'Halle'}, function(err, res) { - if (err) return cb(err); - assert(res.content === 'a HALLE b'); - cb(); - }); - }); - }); -}); - -describe('collection helpers', function() { - beforeEach(function() { - app = new App(); - app.create('posts'); - app.create('pages', {engine: 'hbs'}); - app.create('partials', {viewType: 'partial', engine: 'hbs'}); - app.create('snippet', {viewType: 'partial'}); - app.engine('hbs', require('engine-handlebars')); - app.helper('log', function(ctx) { - console.log(ctx); - }); - }); - - describe('plural', function() { - it('should get the given collection', function(cb) { - app.post('a.hbs', {content: 'foo'}); - app.post('b.hbs', {content: 'bar'}); - app.post('c.hbs', {content: 'baz'}); - - app.partial('list.hbs', { - content: '{{#posts}}{{#each items}}{{content}}{{/each}}{{/posts}}' - }); - - app.page('index.hbs', { - content: '{{> list.hbs }}' - }) - .render(function(err, res) { - if (err) return cb(err); - assert(res.content === 'foobarbaz'); - cb(); - }); - }); - }); - - describe('single', function() { - it('should get a view from an unspecified collection', function(cb) { - app.post('a.hbs', {content: 'post-a'}); - app.post('b.hbs', {content: 'post-b'}); - - var one = app.page('one', {content: '{{view "a.hbs"}}'}) - .compile() - .fn(); - - var two = app.page('two', {content: '{{view "b.hbs"}}'}) - .compile() - .fn(); - - assert(one === 'post-a'); - assert(two === 'post-b'); - cb(); - }); - - it('should return an empty string if not found', function(cb) { - var one = app.page('one', {content: '{{view "foo.hbs"}}'}) - .compile() - .fn(); - assert(one === ''); - cb(); - }); - - it('should handle engine errors', function(cb) { - app.post('foo.hbs', {content: '{{one "two"}}'}); - app.page('one', {content: '{{posts "foo.hbs"}}'}) - .render(function(err) { - assert(err); - assert(typeof err === 'object'); - assert(typeof err.message === 'string'); - assert(/Missing helper: "one"/.test(err.message)); - cb(); - }); - }); - - it('should handle engine errors2', function(cb) { - app.engine('tmpl', require('engine-base')); - app.create('foo', {engine: 'tmpl'}); - app.create('bar', {engine: 'tmpl'}); - - app.create('foo', {viewType: 'partial'}); - app.foo('foo.tmpl', {path: 'foo.tmpl', content: '<%= blah.bar %>'}); - app.bar('one.tmpl', {content: '<%= foo("foo.tmpl") %>'}) - .render(function(err) { - assert(err); - assert(typeof err === 'object'); - assert(/blah is not defined/.test(err.message)); - cb(); - }); - }); - - it('should work with non-handlebars engine', function(cb) { - app.engine('tmpl', require('engine-base')); - app.create('foo', {engine: 'tmpl'}); - app.create('bar', {engine: 'tmpl'}); - - app.foo('a.tmpl', {content: 'foo-a'}); - app.foo('b.tmpl', {content: 'foo-b'}); - - var one = app.bar('one', {content: '<%= view("a.tmpl") %>'}) - .compile() - .fn(); - - var two = app.bar('two', {content: '<%= view("b.tmpl") %>'}) - .compile() - .fn(); - - assert(one === 'foo-a'); - assert(two === 'foo-b'); - cb(); - }); - - it('should get a specific view from the given collection', function(cb) { - app.post('a.hbs', {content: 'post-a'}); - app.post('b.hbs', {content: 'post-b'}); - app.post('c.hbs', {content: 'post-c'}); - app.page('a.hbs', {content: 'page-a'}); - app.page('b.hbs', {content: 'page-b'}); - app.page('c.hbs', {content: 'page-c'}); - - var one = app.page('one', {content: '{{view "a.hbs" "posts"}}'}) - .compile() - .fn(); - - var two = app.page('two', {content: '{{view "b.hbs" "pages"}}'}) - .compile() - .fn(); - - assert(one === 'post-a'); - assert(two === 'page-b'); - cb(); - }); - }); -}); diff --git a/test/item.js b/test/item.js deleted file mode 100644 index 1cc936d..0000000 --- a/test/item.js +++ /dev/null @@ -1,1062 +0,0 @@ -'use strict'; - -require('mocha'); -var should = require('should'); -var fs = require('fs'); -var path = require('path'); -var assert = require('assert'); -var es = require('event-stream'); -var Stream = require('stream'); -var support = require('./support'); -var App = support.resolve(); -var Item = App.Item; -var item; - -describe('Item', function() { - describe('instance', function() { - it('should create an instance of Item:', function() { - item = new Item(); - assert(item instanceof Item); - }); - - it('should instantiate without new:', function() { - item = Item(); - assert(item instanceof Item); - }); - - it('inspect should not double name `Stream` when ctor is `Stream`', function(done) { - var val = new Stream(); - item = new Item({contents: val}); - done(); - }); - }); - - describe('static methods', function() { - it('should expose `extend`:', function() { - assert(typeof Item.extend === 'function'); - }); - }); - - describe('prototype methods', function() { - beforeEach(function() { - item = new Item(); - }); - - it('should expose `set`:', function() { - assert(typeof item.set === 'function'); - }); - it('should expose `get`:', function() { - assert(typeof item.get === 'function'); - }); - it('should expose `del`:', function() { - assert(typeof item.del === 'function'); - }); - it('should expose `define`:', function() { - assert(typeof item.define === 'function'); - }); - it('should expose `visit`:', function() { - assert(typeof item.visit === 'function'); - }); - }); - - describe('properties', function() { - it('should expose an `options` property', function() { - item = new Item({}); - assert.deepEqual(item.options, {}); - assert(item.hasOwnProperty('options')); - }); - - it('should add `options` when passed on the constructor', function() { - item = new Item({options: {foo: 'bar'}}); - assert(item.options.foo === 'bar'); - }); - - it('should expose a `data` property', function() { - item = new Item({app: {}}); - assert.deepEqual(item.data, {}); - assert(item.hasOwnProperty('data')); - }); - - it('should add `data` when passed on the constructor', function() { - item = new Item({data: {foo: 'bar'}}); - assert(item.data.foo === 'bar'); - }); - - it('should add `locals` when passed on the constructor', function() { - item = new Item({locals: {foo: 'bar'}}); - assert(item.locals.foo === 'bar'); - }); - }); - - describe('set', function() { - it('should set properties on the object', function() { - item = new Item(); - item.set('foo', 'bar'); - assert.equal(item.foo, 'bar'); - }); - }); - - describe('get', function() { - it('should get properties from the object', function() { - item = new Item(); - item.set('foo', 'bar'); - assert.equal(item.get('foo'), 'bar'); - }); - }); - - describe('cwd', function() { - it('should get properties from the object', function() { - item = new Item({cwd: 'test/fixtures'}); - assert(item.cwd === 'test/fixtures'); - }); - }); - - describe('clone', function() { - it('should clone the item:', function() { - item = new Item({content: 'foo'}); - item.set({path: 'foo/bar'}); - item.set('options.one', 'two'); - var clone = item.clone(); - assert(clone.contents); - clone.set('baz', 'quux'); - clone.set('options.three', 'four'); - assert.equal(clone.get('foo'), item.get('foo')); - assert(clone.get('baz') === 'quux'); - assert(!item.get('baz')); - // not deep cloned - assert(clone.get('options.three') === 'four'); - assert(item.get('options.three') === 'four'); - }); - - it('should deep clone the entire object', function() { - item = new Item({content: 'foo'}); - item.set({path: 'foo/bar'}); - item.set('options.one', 'two'); - var clone = item.clone({deep: true}); - clone.set('options.three', 'four'); - assert(item.get('options.one') === 'two'); - assert(clone.get('options.one') === 'two'); - assert(clone.get('options.three') === 'four'); - assert(!item.get('options.three')); - }); - }); - - describe('visit', function() { - it('should visit all properties on an object and call the specified method', function() { - item = new Item(); - var obj = { - foo: 'bar', - bar: 'baz', - baz: 'bang' - }; - item.visit('set', obj); - assert.equal(item.get('foo'), 'bar'); - assert.equal(item.get('bar'), 'baz'); - assert.equal(item.get('baz'), 'bang'); - }); - - it('should visit all properties on all objects in an array and call the specified method', function() { - item = new Item(); - var arr = [{foo: 'bar', bar: 'baz', baz: 'bang'}]; - item.visit('set', arr); - assert.equal(item.get('foo'), 'bar'); - assert.equal(item.get('bar'), 'baz'); - assert.equal(item.get('baz'), 'bang'); - }); - }); -}); - -/** - * The following unit tests are from Vinyl - * Since we inherit vinyl in Item, we need - * to ensure that these still pass. - */ - -describe('Item', function() { - describe('isVinyl()', function() { - it('should return true on a vinyl object', function(done) { - item = new Item(); - assert(Item.isVinyl(item) === true); - done(); - }); - it('should return false on a normal object', function(done) { - assert(Item.isVinyl({}) === false); - done(); - }); - it('should return false on a null object', function(done) { - assert(Item.isVinyl({}) === false); - done(); - }); - }); - - describe('constructor()', function() { - it('should default cwd to process.cwd', function(done) { - item = new Item(); - item.cwd.should.equal(process.cwd()); - done(); - }); - - it('should default base to cwd', function(done) { - var cwd = '/'; - item = new Item({cwd: cwd}); - item.base.should.equal(cwd); - done(); - }); - - it('should default base to cwd even when none is given', function(done) { - item = new Item(); - item.base.should.equal(process.cwd()); - done(); - }); - - it('should default path to null', function(done) { - item = new Item(); - should.not.exist(item.path); - done(); - }); - - it('should default history to []', function(done) { - item = new Item(); - item.history.should.eql([]); - done(); - }); - - it('should default stat to null', function(done) { - item = new Item(); - should.not.exist(item.stat); - done(); - }); - - it('should default contents to null', function(done) { - item = new Item(); - should.not.exist(item.contents); - done(); - }); - - it('should set base to given value', function(done) { - var val = '/'; - item = new Item({base: val}); - item.base.should.equal(val); - done(); - }); - - it('should set cwd to given value', function(done) { - var val = '/'; - item = new Item({cwd: val}); - item.cwd.should.equal(val); - done(); - }); - - it('should set path to given value', function(done) { - var val = '/test.coffee'; - item = new Item({path: val}); - item.path.should.equal(val); - item.history.should.eql([val]); - done(); - }); - - it('should set history to given value', function(done) { - var val = '/test.coffee'; - item = new Item({history: [val]}); - item.path.should.equal(val); - item.history.should.eql([val]); - done(); - }); - - it('should set stat to given value', function(done) { - var val = {}; - item = new Item({stat: val}); - item.stat.should.equal(val); - done(); - }); - - it('should set contents to given value', function(done) { - var val = new Buffer('test'); - item = new Item({contents: val}); - item.contents.should.equal(val); - done(); - }); - }); - - describe('isBuffer()', function() { - it('should return true when the contents are a Buffer', function(done) { - var val = new Buffer('test'); - item = new Item({contents: val}); - item.isBuffer().should.equal(true); - done(); - }); - - it('should return false when the contents are a Stream', function(done) { - var val = new Stream(); - var item = new Item({contents: val}); - item.isBuffer().should.equal(false); - done(); - }); - - it('should return false when the contents are a null', function(done) { - var item = new Item({contents: null}); - item.isBuffer().should.equal(false); - done(); - }); - }); - - describe('isStream()', function() { - it('should return false when the contents are a Buffer', function(done) { - var val = new Buffer('test'); - var item = new Item({contents: val}); - item.isStream().should.equal(false); - done(); - }); - - it('should return true when the contents are a Stream', function(done) { - var val = new Stream(); - var item = new Item({contents: val}); - item.isStream().should.equal(true); - done(); - }); - - it('should return false when the contents are a null', function(done) { - var item = new Item({contents: null}); - item.isStream().should.equal(false); - done(); - }); - }); - - describe('isNull()', function() { - it('should return false when the contents are a Buffer', function(done) { - var val = new Buffer('test'); - var item = new Item({contents: val}); - item.isNull().should.equal(false); - done(); - }); - - it('should return false when the contents are a Stream', function(done) { - var val = new Stream(); - var item = new Item({contents: val}); - item.isNull().should.equal(false); - done(); - }); - - it('should return true when the contents are a null', function(done) { - var item = new Item({contents: null}); - item.isNull().should.equal(true); - done(); - }); - }); - - describe('isDirectory()', function() { - var fakeStat = { - isDirectory: function() { - return true; - } - }; - - it('should return false when the contents are a Buffer', function(done) { - var val = new Buffer('test'); - var item = new Item({contents: val, stat: fakeStat}); - item.isDirectory().should.equal(false); - done(); - }); - - it('should return false when the contents are a Stream', function(done) { - var val = new Stream(); - var item = new Item({contents: val, stat: fakeStat}); - item.isDirectory().should.equal(false); - done(); - }); - - it('should return true when the contents are a null', function(done) { - var item = new Item({contents: null, stat: fakeStat}); - item.isDirectory().should.equal(true); - done(); - }); - }); - - describe('clone()', function() { - it('should copy all attributes over with Buffer', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Buffer('test') - }; - var item = new Item(options); - var item2 = item.clone(); - - item2.should.not.equal(item, 'refs should be different'); - item2.cwd.should.equal(item.cwd); - item2.base.should.equal(item.base); - item2.path.should.equal(item.path); - item2.contents.should.not.equal(item.contents, 'buffer ref should be different'); - item2.contents.toString('utf8').should.equal(item.contents.toString('utf8')); - done(); - }); - - it('should copy buffer\'s reference with option contents: false', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.js', - contents: new Buffer('test') - }; - - var item = new Item(options); - - var copy1 = item.clone({ contents: false }); - copy1.contents.should.equal(item.contents); - - var copy2 = item.clone({}); - copy2.contents.should.not.equal(item.contents); - - var copy3 = item.clone({ contents: 'any string' }); - copy3.contents.should.not.equal(item.contents); - - done(); - }); - - it('should copy all attributes over with Stream', function(done) { - var contents = new Stream.PassThrough(); - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: contents - }; - var item = new Item(options); - var item2 = item.clone(); - - contents.write(new Buffer('wa')); - - process.nextTick(function() { - contents.write(new Buffer('dup')); - contents.end(); - }); - - item2.should.not.equal(item, 'refs should be different'); - item2.cwd.should.equal(item.cwd); - item2.base.should.equal(item.base); - item2.path.should.equal(item.path); - item2.contents.should.not.equal(item.contents, 'stream ref should not be the same'); - item.contents.pipe(es.wait(function(err, data) { - if (err) return done(err); - item2.contents.pipe(es.wait(function(err, data2) { - if (err) return done(err); - data2.should.not.equal(data, 'stream contents ref should not be the same'); - data2.should.eql(data, 'stream contents should be the same'); - })); - })); - done(); - }); - - it('should copy all attributes over with null', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - var item = new Item(options); - var item2 = item.clone(); - - item2.should.not.equal(item, 'refs should be different'); - item2.cwd.should.equal(item.cwd); - item2.base.should.equal(item.base); - item2.path.should.equal(item.path); - should.not.exist(item2.contents); - done(); - }); - - it('should properly clone the `stat` property', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.js', - contents: new Buffer('test'), - stat: fs.statSync(__filename) - }; - - var item = new Item(options); - var copy = item.clone(); - - assert(copy.stat.isFile()); - assert(!copy.stat.isDirectory()); - done(); - }); - - it('should properly clone the `history` property', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.js', - contents: new Buffer('test'), - stat: fs.statSync(__filename) - }; - - var item = new Item(options); - var copy = item.clone(); - - copy.history[0].should.equal(options.path); - copy.path = 'lol'; - item.path.should.not.equal(copy.path); - done(); - }); - - it('should copy custom properties', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - - var item = new Item(options); - item.custom = { a: 'custom property' }; - var item2 = item.clone(); - - item2.should.not.equal(item, 'refs should be different'); - item2.cwd.should.equal(item.cwd); - item2.base.should.equal(item.base); - item2.path.should.equal(item.path); - item2.custom.should.equal(item.custom); - item2.custom.a.should.equal(item.custom.a); - - done(); - }); - - it('should copy history', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - - var item = new Item(options); - item.path = '/test/test.js'; - item.path = '/test/test-938di2s.js'; - var item2 = item.clone(); - - item2.history.should.eql([ - '/test/test.coffee', - '/test/test.js', - '/test/test-938di2s.js' - ]); - item2.history.should.not.equal([ - '/test/test.coffee', - '/test/test.js', - '/test/test-938di2s.js' - ]); - item2.path.should.eql('/test/test-938di2s.js'); - - done(); - }); - - it('should copy all attributes deeply', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - - var item = new Item(options); - item.custom = { a: 'custom property' }; - - var item2 = item.clone(true); - item2.custom.should.eql(item.custom); - item2.custom.should.not.equal(item.custom); - item2.custom.a.should.equal(item.custom.a); - - var item3 = item.clone({ deep: true }); - item3.custom.should.eql(item.custom); - item3.custom.should.not.equal(item.custom); - item3.custom.a.should.equal(item.custom.a); - - var item4 = item.clone(false); - item4.custom.should.eql(item.custom); - item4.custom.should.equal(item.custom); - item4.custom.a.should.equal(item.custom.a); - - var item5 = item.clone({ deep: false }); - item5.custom.should.eql(item.custom); - item5.custom.should.equal(item.custom); - item5.custom.a.should.equal(item.custom.a); - - done(); - }); - }); - - describe('pipe()', function() { - it('should write to stream with Buffer', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Buffer('test') - }; - var item = new Item(options); - var stream = new Stream.PassThrough(); - stream.on('data', function(chunk) { - should.exist(chunk); - (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); - chunk.toString('utf8').should.equal(options.contents.toString('utf8')); - }); - stream.on('end', function() { - done(); - }); - var ret = item.pipe(stream); - ret.should.equal(stream, 'should return the stream'); - }); - - it('should pipe to stream with Stream', function(done) { - var testChunk = new Buffer('test'); - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Stream.PassThrough() - }; - var item = new Item(options); - var stream = new Stream.PassThrough(); - stream.on('data', function(chunk) { - should.exist(chunk); - (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); - chunk.toString('utf8').should.equal(testChunk.toString('utf8')); - done(); - }); - var ret = item.pipe(stream); - ret.should.equal(stream, 'should return the stream'); - - item.contents.write(testChunk); - }); - - it('should do nothing with null', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - var item = new Item(options); - var stream = new Stream.PassThrough(); - stream.on('data', function() { - throw new Error('should not write'); - }); - stream.on('end', function() { - done(); - }); - var ret = item.pipe(stream); - ret.should.equal(stream, 'should return the stream'); - }); - - it('should write to stream with Buffer', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Buffer('test') - }; - var item = new Item(options); - var stream = new Stream.PassThrough(); - stream.on('data', function(chunk) { - should.exist(chunk); - (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); - chunk.toString('utf8').should.equal(options.contents.toString('utf8')); - done(); - }); - stream.on('end', function() { - throw new Error('should not end'); - }); - var ret = item.pipe(stream, {end: false}); - ret.should.equal(stream, 'should return the stream'); - }); - - it('should pipe to stream with Stream', function(done) { - var testChunk = new Buffer('test'); - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Stream.PassThrough() - }; - var item = new Item(options); - var stream = new Stream.PassThrough(); - stream.on('data', function(chunk) { - should.exist(chunk); - (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); - chunk.toString('utf8').should.equal(testChunk.toString('utf8')); - done(); - }); - stream.on('end', function() { - throw new Error('should not end'); - }); - var ret = item.pipe(stream, {end: false}); - ret.should.equal(stream, 'should return the stream'); - - item.contents.write(testChunk); - }); - - it('should do nothing with null', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - var item = new Item(options); - var stream = new Stream.PassThrough(); - stream.on('data', function() { - throw new Error('should not write'); - }); - stream.on('end', function() { - throw new Error('should not end'); - }); - var ret = item.pipe(stream, {end: false}); - ret.should.equal(stream, 'should return the stream'); - process.nextTick(done); - }); - }); - - describe('inspect()', function() { - it('should return correct format when no contents and no path', function(done) { - var item = new Item(); - item.inspect().should.equal(''); - done(); - }); - - it('should return correct format when Buffer and no path', function(done) { - var val = new Buffer('test'); - var item = new Item({ - contents: val - }); - item.inspect().should.equal('>'); - done(); - }); - - it('should return correct format when Buffer and relative path', function(done) { - var val = new Buffer('test'); - var item = new Item({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: val - }); - item.inspect().should.equal('>'); - done(); - }); - - it('should return correct format when Buffer and only path and no base', function(done) { - var val = new Buffer('test'); - var item = new Item({ - cwd: '/', - path: '/test/test.coffee', - contents: val - }); - delete item.base; - item.inspect().should.equal('>'); - done(); - }); - - it('should return correct format when Stream and relative path', function(done) { - var item = new Item({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Stream.PassThrough() - }); - item.inspect().should.equal('>'); - done(); - }); - - it('should return correct format when null and relative path', function(done) { - var item = new Item({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }); - item.inspect().should.equal(''); - done(); - }); - }); - - describe('contents get/set', function() { - it('should work with Buffer', function(done) { - var val = new Buffer('test'); - var item = new Item(); - item.contents = val; - item.contents.should.equal(val); - done(); - }); - - it('should work with Stream', function(done) { - var val = new Stream.PassThrough(); - var item = new Item(); - item.contents = val; - item.contents.should.equal(val); - done(); - }); - - it('should work with null', function(done) { - var val = null; - var item = new Item(); - item.contents = val; - (item.contents === null).should.equal(true); - done(); - }); - - it('should work with string', function(done) { - var val = 'test'; - var item = new Item(); - item.contents = val; - item.contents.should.deepEqual(new Buffer(val)); - done(); - }); - }); - - describe('relative get/set', function() { - it('should error on set', function(done) { - var item = new Item(); - try { - item.relative = 'test'; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should error on get when no base', function(done) { - var item = new Item(); - delete item.base; - try { - item.relative; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should error on get when no path', function(done) { - var item = new Item(); - try { - item.relative; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should return a relative path from base', function(done) { - var item = new Item({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - item.relative.should.equal('test.coffee'); - done(); - }); - - it('should return a relative path from cwd', function(done) { - var item = new Item({ - cwd: '/', - path: '/test/test.coffee' - }); - item.relative.should.equal(path.join('test', 'test.coffee')); - done(); - }); - }); - - describe('dirname get/set', function() { - it('should error on get when no path', function(done) { - var item = new Item(); - try { - item.dirname; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should return the dirname of the path', function(done) { - var item = new Item({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - item.dirname.should.equal('/test'); - done(); - }); - - it('should error on set when no path', function(done) { - var item = new Item(); - try { - item.dirname = '/test'; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should set the dirname of the path', function(done) { - var item = new Item({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - item.dirname = '/test/foo'; - item.path.should.equal('/test/foo/test.coffee'); - done(); - }); - }); - - describe('basename get/set', function() { - it('should error on get when no path', function(done) { - item = new Item(); - try { - item.basename; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should return the basename of the path', function(done) { - item = new Item({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - item.basename.should.equal('test.coffee'); - done(); - }); - - it('should error on set when no path', function(done) { - item = new Item(); - try { - item.basename = 'test.coffee'; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should set the basename of the path', function(done) { - item = new Item({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - item.basename = 'foo.png'; - item.path.should.equal('/test/foo.png'); - done(); - }); - }); - - describe('extname get/set', function() { - it('should error on get when no path', function(done) { - item = new Item(); - try { - item.extname; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should return the extname of the path', function(done) { - item = new Item({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - item.extname.should.equal('.coffee'); - done(); - }); - - it('should error on set when no path', function(done) { - item = new Item(); - try { - item.extname = '.coffee'; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should set the extname of the path', function(done) { - item = new Item({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - item.extname = '.png'; - item.path.should.equal('/test/test.png'); - done(); - }); - }); - - describe('path get/set', function() { - - it('should record history when instantiation', function() { - var item = new Item({ - cwd: '/', - path: '/test/test.coffee' - }); - - item.path.should.eql('/test/test.coffee'); - item.history.should.eql(['/test/test.coffee']); - }); - - it('should record history when path change', function() { - var item = new Item({ - cwd: '/', - path: '/test/test.coffee' - }); - - item.path = '/test/test.js'; - item.path.should.eql('/test/test.js'); - item.history.should.eql(['/test/test.coffee', '/test/test.js']); - - item.path = '/test/test.coffee'; - item.path.should.eql('/test/test.coffee'); - item.history.should.eql(['/test/test.coffee', '/test/test.js', '/test/test.coffee']); - }); - - it('should not record history when set the same path', function() { - var item = new Item({ - cwd: '/', - path: '/test/test.coffee' - }); - - item.path = '/test/test.coffee'; - item.path = '/test/test.coffee'; - item.path.should.eql('/test/test.coffee'); - item.history.should.eql(['/test/test.coffee']); - - // ignore when set empty string - item.path = ''; - item.path.should.eql('/test/test.coffee'); - item.history.should.eql(['/test/test.coffee']); - }); - - it('should throw when set path null in constructor', function() { - (function() { - Item({ - cwd: '/', - path: null - }); - }).should.throw('path should be string'); - }); - - it('should throw when set path null', function() { - item = new Item({ - cwd: '/', - path: 'foo' - }); - - (function() { - item.path = null; - }).should.throw('path should be string'); - }); - }); -}); diff --git a/test/layouts.js b/test/layouts.js deleted file mode 100644 index fda29a8..0000000 --- a/test/layouts.js +++ /dev/null @@ -1,129 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('layouts', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('layout', { viewType: 'layout' }); - app.create('page'); - }); - - it('should apply a layout to a view:', function(done) { - app.layout('base', {path: 'base.tmpl', content: 'a {% body %} c'}); - app.pages('a.tmpl', {path: 'a.tmpl', content: 'b', layout: 'base'}); - var page = app.pages.getView('a.tmpl'); - - app.render(page, function(err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'a b c'); - done(); - }); - }); - - it('should not apply a layout when `layoutApplied` is set:', function(done) { - app.layout('base', {path: 'base.tmpl', content: 'a {% body %} c'}); - app.pages('a.tmpl', {path: 'a.tmpl', content: 'b', layout: 'base'}); - var page = app.pages.getView('a.tmpl'); - page.option('layoutApplied', true); - - app.render(page, function(err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'b'); - done(); - }); - }); - - it('should not apply a layout to itself:', function(done) { - app.layout('base', {path: 'base.tmpl', content: 'a {% body %} c', layout: 'base'}); - app.pages('a.tmpl', {path: 'a.tmpl', content: 'b', layout: 'base'}); - var page = app.pages.getView('a.tmpl'); - - app.render(page, function(err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'a b c'); - done(); - }); - }); - - it('should apply nested layouts to a view:', function(done) { - app.layout('a', {path: 'a.tmpl', content: 'a {% body %} a', layout: 'b'}); - app.layout('b', {path: 'b.tmpl', content: 'b {% body %} b', layout: 'c'}); - app.layout('c', {path: 'c.tmpl', content: 'c {% body %} c', layout: 'base'}); - app.layout('base', {path: 'base.tmpl', content: 'outter {% body %} outter'}); - - app.pages('z.tmpl', {path: 'a.tmpl', content: 'inner', layout: 'a'}); - var page = app.pages.getView('z.tmpl'); - - app.render(page, function(err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'outter c b a inner a b c outter'); - done(); - }); - }); - - it('should track layout stack history on `layoutStack`:', function(done) { - app.layout('a', {path: 'a.tmpl', content: 'a {% body %} a', layout: 'b'}); - app.layout('b', {path: 'b.tmpl', content: 'b {% body %} b', layout: 'c'}); - app.layout('c', {path: 'c.tmpl', content: 'c {% body %} c', layout: 'base'}); - app.layout('base', {path: 'base.tmpl', content: 'outter {% body %} outter'}); - - app.pages('z.tmpl', {path: 'a.tmpl', content: 'inner', layout: 'a'}); - var page = app.pages.getView('z.tmpl'); - - app.render(page, function(err, view) { - if (err) return done(err); - assert(view.layoutStack.length === 4); - assert(typeof view.layoutStack[0] === 'object'); - assert(typeof view.layoutStack[0].depth === 'number'); - done(); - }); - }); - - it('should track layout stack history on `layoutStack`:', function(done) { - app.layout('a', {path: 'a.tmpl', content: 'a {% body %} a', layout: 'b'}); - app.layout('b', {path: 'b.tmpl', content: 'b {% body %} b', layout: 'c'}); - app.layout('c', {path: 'c.tmpl', content: 'c {% body %} c', layout: 'base'}); - app.layout('base', {path: 'base.tmpl', content: 'outter {% body %} outter'}); - - app.pages('z.tmpl', {path: 'a.tmpl', content: 'inner', layout: 'a'}); - var page = app.pages.getView('z.tmpl'); - - app.render(page, function(err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'outter c b a inner a b c outter'); - done(); - }); - }); - - it('should get layouts from `layout` viewTypes:', function(done) { - app.create('section', { viewType: 'layout' }); - app.create('block', { viewType: 'layout' }); - - app.section('a', {path: 'a.tmpl', content: 'a {% body %} a', layout: 'b'}); - app.block('b', {path: 'b.tmpl', content: 'b {% body %} b', layout: 'c'}); - app.section('c', {path: 'c.tmpl', content: 'c {% body %} c', layout: 'base'}); - app.block('base', {path: 'base.tmpl', content: 'outter {% body %} outter'}); - - app.pages('z.tmpl', {path: 'a.tmpl', content: 'inner', layout: 'a'}); - var page = app.pages.getView('z.tmpl'); - - app.render(page, function(err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'outter c b a inner a b c outter'); - done(); - }); - }); -}); diff --git a/test/list.js b/test/list.js deleted file mode 100644 index a814777..0000000 --- a/test/list.js +++ /dev/null @@ -1,691 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var path = require('path'); -var get = require('get-value'); -var assert = require('assert'); -var typeOf = require('kind-of'); -var support = require('./support/'); -var isBuffer = require('is-buffer'); -assert.containEql = support.containEql; -var App = support.resolve(); -var List = App.List; -var Views = App.Views; -var list, views; - -describe('list', function() { - describe('constructor', function() { - it('should create an instance of List', function() { - var list = new List(); - assert(list instanceof List); - }); - - it('should instaniate without `new`', function() { - var list = List(); - assert(list instanceof List); - }); - }); - - describe('static methods', function() { - it('should expose `extend`', function() { - assert(typeof List.extend === 'function'); - }); - }); - - describe('prototype methods', function() { - beforeEach(function() { - list = new List(); - }); - - var methods = [ - 'use', - 'setItem', - 'addItem', - 'addItems', - 'addList', - 'getItem', - 'constructor', - 'set', - 'get', - 'del', - 'define', - 'visit', - 'on', - 'once', - 'off', - 'emit', - 'listeners', - 'hasListeners' - ]; - - methods.forEach(function(method) { - it('should expose the ' + method + ' method', function() { - assert(typeof list[method] === 'function'); - }); - }); - - it('should expose the isList property', function() { - assert(typeof list.isList === 'boolean'); - }); - - it('should expose the keys property', function() { - assert(Array.isArray(list.keys)); - }); - - it('should expose the queue property', function() { - assert(Array.isArray(list.queue)); - }); - - it('should expose the items property', function() { - assert(Array.isArray(list.items)); - }); - - it('should expose the options property', function() { - assert(typeOf(list.options) === 'object'); - }); - }); - - describe('instance', function() { - beforeEach(function() { - list = new List(); - }); - - it('should set a value on the instance', function() { - list.set('a', 'b'); - assert(list.a === 'b'); - }); - - it('should get a value from the instance', function() { - list.set('a', 'b'); - assert(list.get('a') === 'b'); - }); - }); - - describe('use', function() { - beforeEach(function() { - list = new List(); - }); - - it('should expose the instance to plugins', function() { - list - .use(function(inst) { - inst.foo = 'bar'; - }); - - assert(list.foo === 'bar'); - }); - - it('should expose `item` when the plugin returns a function', function() { - list - .use(function() { - return function(item) { - item.foo = 'bar'; - }; - }); - - list.addItem('aaa'); - list.addItem('bbb'); - list.addItem('ccc'); - - assert(list.items[0].foo === 'bar'); - assert(list.items[1].foo === 'bar'); - assert(list.items[2].foo === 'bar'); - }); - }); - - describe('addItem', function() { - beforeEach(function() { - list = new List(); - }); - - it('should add items to a list', function() { - list.addItem('a', {content: '...'}); - list.addItem('b', {content: '...'}); - list.addItem('c', {content: '...'}); - assert(list.items.length === 3); - }); - }); - - describe('removeItem', function() { - beforeEach(function() { - list = new List(); - }); - - it('should remove an item from `items`', function() { - list.addItem('a', {content: '...'}); - list.addItem('b', {content: '...'}); - list.addItem('c', {content: '...'}); - assert(list.items.length === 3); - var a = list.getItem('a'); - list.removeItem(a); - assert(list.items.length === 2); - var c = list.getItem(c); - list.removeItem(c); - assert(list.items[0].key === 'b'); - }); - - it('should remove an item from `items` by key', function() { - list.addItem('a', {content: '...'}); - list.addItem('b', {content: '...'}); - list.addItem('c', {content: '...'}); - assert(list.items.length === 3); - list.removeItem('c'); - assert(list.items.length === 2); - list.removeItem('b'); - assert(list.items[0].key === 'a'); - }); - }); - - describe('addItems', function() { - beforeEach(function() { - list = new List(); - }); - - it('should add an object with multiple items', function() { - list.addItems({ - one: {content: 'foo'}, - two: {content: 'bar'} - }); - assert(isBuffer(list.items[0].contents)); - assert(isBuffer(list.items[1].contents)); - }); - - it('should signal `loaded` when finished (addItems)', function() { - list.on('addItems', function(items) { - for (var key in items) { - if (key === 'c') { - list.loaded = true; - break; - } - list.addItem('foo/' + key, items[key]); - } - }); - - list.addItems({ - a: {path: 'a.txt'}, - b: {path: 'b.txt'}, - c: {path: 'c.txt'} - }); - - assert.equal(list.items.length, 2); - assert.equal(list.items[0].key, 'foo/a'); - assert.equal(list.items[0].path, 'a.txt'); - }); - }); - - describe('addList', function() { - beforeEach(function() { - list = new List(); - }); - - it('should add an array with multiple items', function() { - list.addList([ - {path: 'one', content: 'foo'}, - {path: 'two', content: 'bar'} - ]); - assert(isBuffer(list.items[0].contents)); - assert(isBuffer(list.items[1].contents)); - }); - - it('should take a callback on `addList`', function() { - function addContents(item) { - item.contents = new Buffer(item.path.charAt(0)); - } - - list.addList([ - { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, - { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, - { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } } - ], addContents); - - assert(isBuffer(list.items[0].contents)); - assert(isBuffer(list.items[1].contents)); - assert(isBuffer(list.items[2].contents)); - }); - - it('should throw an error when the list is not an array', function() { - function addContents(item) { - item.contents = new Buffer(item.path.charAt(0)); - } - - (function() { - list.addList({ - 'a.md': {locals: { date: '2014-01-01', foo: 'zzz', bar: 1 }}, - 'f.md': {locals: { date: '2014-01-01', foo: 'mmm', bar: 2 }}, - 'd.md': {locals: { date: '2014-01-01', foo: 'xxx', bar: 3 }} - }, addContents); - - assert(isBuffer(list.items[0].contents)); - assert(isBuffer(list.items[1].contents)); - assert(isBuffer(list.items[2].contents)); - }).should.throw('expected list to be an array.'); - }); - - it('should signal `loaded` when finished (addList)', function() { - list.on('addList', function(items) { - var len = items.length; - var i = -1; - - while (++i < len) { - if (items[i].path === 'd.md') { - list.loaded = true; - break; - } - list.addItem('foo/' + items[i].path, items[i]); - } - }); - - list.addList([ - { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, - { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, - { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } } - ]); - - assert.equal(list.items.length, 2); - assert.equal(list.keys.indexOf('d.md'), -1); - }); - }); - - describe('queue', function() { - beforeEach(function() { - list = new List(); - }); - - it('should emit arguments on addItem', function(done) { - list.on('addItem', function(args) { - assert(args[0] === 'a'); - assert(args[1] === 'b'); - assert(args[2] === 'c'); - assert(args[3] === 'd'); - assert(args[4] === 'e'); - done(); - }); - - list.addItem('a', 'b', 'c', 'd', 'e'); - }); - - it('should expose the `queue` property for loading items', function() { - list.queue.push(list.item('b', {path: 'b'})); - - list.addItem('a', {path: 'a'}); - assert(list.items[0].key === 'a'); - assert(list.items[1].key === 'b'); - }); - - it('should load all items on the queue when addItem is called', function() { - list.on('addItem', function(args) { - var len = args.length; - var last = args[len - 1]; - if (typeof last === 'string') { - args[len - 1] = { content: last }; - } - }); - - list.addItem('a.html', 'aaa'); - list.addItem('b.html', 'bbb'); - list.addItem('c.html', 'ccc'); - - assert(list.items[0].path === 'a.html'); - assert(list.getItem('a.html').content === 'aaa'); - assert(list.items[1].path === 'b.html'); - assert(list.getItem('b.html').content === 'bbb'); - assert(list.items[2].path === 'c.html'); - assert(list.getItem('c.html').content === 'ccc'); - }); - }); - - describe('sortBy', function() { - var items = [ - { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, - { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, - { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, - { path: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, - { path: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, - { path: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, - { path: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, - { path: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, - { path: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, - { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, - { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, - { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, - { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } } - ]; - - it('should sort a list', function() { - list = new List(); - list.addList(items); - - var compare = function(prop) { - return function(a, b, fn) { - var valA = get(a, prop); - var valB = get(b, prop); - return fn(valA, valB); - }; - }; - - var res = list.sortBy('locals.date', 'doesnt.exist', [ - compare('locals.foo'), - compare('locals.bar') - ]); - - assert.containEql(res.items, [ - { key: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, - { key: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, - { key: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, - { key: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, - { key: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, - { key: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, - { key: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, - { key: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, - { key: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, - { key: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, - { key: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, - { key: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, - { key: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } } - ]); - }); - - it('should not sort the (original) instance list `items`', function() { - list = new List(); - list.addList(items); - - var compare = function(prop) { - return function(a, b, fn) { - var valA = get(a, prop); - var valB = get(b, prop); - return fn(valA, valB); - }; - }; - - var res = list.sortBy('locals.date', 'doesnt.exist', [ - compare('locals.foo'), - compare('locals.bar') - ]); - - // should not be sorted - assert.containEql(list.items, items); - - // should be sorted - assert.containEql(res.items, [ - { key: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, - { key: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, - { key: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, - { key: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, - { key: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, - { key: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, - { key: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, - { key: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, - { key: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, - { key: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, - { key: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, - { key: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, - { key: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } } - ]); - }); - - it('should pass options to array-sort from the constructor', function() { - list = new List({sort: {reverse: true}}); - list.addList(items); - - var compare = function(prop) { - return function(a, b, fn) { - var valA = get(a, prop); - var valB = get(b, prop); - return fn(valA, valB); - }; - }; - - var res = list.sortBy('locals.date', 'doesnt.exist', [ - compare('locals.foo'), - compare('locals.bar') - ]); - - assert.containEql(res.items, [ - { key: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, - { key: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, - { key: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, - { key: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, - { key: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, - { key: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, - { key: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, - { key: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, - { key: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, - { key: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, - { key: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, - { key: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, - { key: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } } - ]); - }); - - it('should pass options to array-sort from the sortBy method', function() { - list = new List(); - list.addList(items); - - var compare = function(prop) { - return function(a, b, fn) { - var valA = get(a, prop); - var valB = get(b, prop); - return fn(valA, valB); - }; - }; - - var res = list.sortBy('locals.date', 'doesnt.exist', [ - compare('locals.foo'), - compare('locals.bar') - ], {reverse: true}); - - assert.containEql(res.items, [ - { key: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, - { key: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, - { key: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, - { key: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, - { key: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, - { key: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, - { key: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, - { key: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, - { key: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, - { key: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, - { key: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, - { key: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, - { key: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } } - ]); - }); - }); - - describe('groupBy', function() { - var items = [ - { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, - { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, - { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, - { path: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, - { path: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, - { path: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, - { path: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, - { path: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, - { path: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, - { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, - { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, - { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, - { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } } - ]; - - it('should group a list by a property', function() { - list = new List(); - list.addList(items); - - var res = list.groupBy('locals.foo'); - var keys = ['zzz', 'mmm', 'xxx', 'aaa', 'ccc', 'rrr', 'ttt', 'yyy']; - assert.deepEqual(Object.keys(res), keys); - }); - }); - - describe('sort and group', function() { - var items = [ - { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, - { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, - { path: 'd.md', locals: { date: '2013-01-01', foo: 'xxx', bar: 3 } }, - { path: 'i.md', locals: { date: '2013-02-01', foo: 'xxx', bar: 5 } }, - { path: 'i.md', locals: { date: '2013-02-01', foo: 'lll', bar: 5 } }, - { path: 'k.md', locals: { date: '2013-03-01', foo: 'xxx', bar: 1 } }, - { path: 'j.md', locals: { date: '2013-02-01', foo: 'xxx', bar: 4 } }, - { path: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, - { path: 'm.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, - { path: 'n.md', locals: { date: '2013-01-01', foo: 'xxx', bar: 7 } }, - { path: 'o.md', locals: { date: '2013-01-01', foo: 'xxx', bar: 7 } }, - { path: 'p.md', locals: { date: '2013-01-01', foo: 'xxx', bar: 7 } }, - { path: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, - { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, - { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, - { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, - { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } } - ]; - - it('should group a list by a property', function() { - list = new List(items); - - var context = list - .sortBy('locals.date') - .groupBy(function(view) { - var date = view.locals.date; - view.locals.year = date.slice(0, 4); - view.locals.month = date.slice(5, 7); - view.locals.day = date.slice(8, 10); - return view.locals.year; - }, 'locals.month'); - - var keys = Object.keys(context); - assert(keys[0] === '2012'); - assert(keys[1] === '2013'); - assert(keys[2] === '2014'); - assert(keys[3] === '2015'); - }); - }); - - describe('paginate', function() { - var items = [ - { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, - { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, - { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, - { path: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, - { path: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, - { path: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, - { path: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, - { path: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, - { path: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, - { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, - { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, - { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, - { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } } - ]; - - it('should paginate a list', function() { - list = new List(items); - - var res = list.paginate(); - assert.equal(res.length, 2); - assert.containEql(res[0].items, items.slice(0, 10)); - assert.containEql(res[1].items, items.slice(10)); - }); - - it('should add pager properties', function() { - list = new List({pager: true}); - list.addList(items); - list.items.forEach(function(item, i) { - assert.equal(item.data.pager.index, i); - }); - }); - - it('should paginate a list with given options', function() { - list = new List(items); - var res = list.paginate({limit: 5}); - - assert.equal(res.length, 3); - assert.containEql(res[0].items, items.slice(0, 5)); - assert.containEql(res[1].items, items.slice(5, 10)); - assert.containEql(res[2].items, items.slice(10)); - }); - }); - - describe('Views instance', function() { - beforeEach(function() { - views = new Views(); - }); - - it('should add views from an instance of Views', function() { - views.addViews({ - one: {content: 'foo'}, - two: {content: 'bar'} - }); - - list = new List(views); - assert(isBuffer(list.items[0].contents)); - assert(isBuffer(list.items[1].contents)); - }); - }); - - describe('getIndex', function() { - beforeEach(function() { - list = new List(); - }); - it('should get the index of a key when key is not renamed', function() { - list.addItem('a/b/c/ddd.hbs', {content: 'ddd'}); - list.addItem('a/b/c/eee.hbs', {content: 'eee'}); - assert(list.getIndex('a/b/c/ddd.hbs') === 0); - assert(list.getIndex('a/b/c/eee.hbs') === 1); - }); - - it('should get the index of a key when key is renamed', function() { - list = new List({ - renameKey: function(key) { - return path.basename(key); - } - }); - list.addItem('a/b/c/ddd.hbs', {content: 'ddd'}); - list.addItem('a/b/c/eee.hbs', {content: 'eee'}); - assert(list.getIndex('a/b/c/ddd.hbs') === 0); - assert(list.getIndex('ddd.hbs') === 0); - assert(list.getIndex('a/b/c/eee.hbs') === 1); - assert(list.getIndex('eee.hbs') === 1); - }); - }); - - describe('getItem', function() { - beforeEach(function() { - list = new List(); - }); - - it('should get an view from `views`', function() { - list.addItem('one', {content: 'aaa'}); - list.addItem('two', {content: 'zzz'}); - assert(list.items.length === 2); - assert(isBuffer(list.items[0].contents)); - assert(isBuffer(list.getItem('one').contents)); - assert(list.getItem('one').contents.toString() === 'aaa'); - assert(list.getItem('two').contents.toString() === 'zzz'); - }); - }); - - describe('use', function() { - beforeEach(function() { - list = new List(); - }); - - it('should use middleware on a list', function() { - list.addItem('one', {content: 'aaa'}); - list.addItem('two', {content: 'zzz'}); - - list - .use(function() { - this.set('foo', 'bar'); - }) - .use(function() { - this.set('one', 'two'); - }); - - assert(list.one === 'two'); - assert(list.foo === 'bar'); - }); - }); -}); - diff --git a/test/list.render.js b/test/list.render.js deleted file mode 100644 index c7d41f8..0000000 --- a/test/list.render.js +++ /dev/null @@ -1,139 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var async = require('async'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var List = App.List; -var pages; - -describe('render', function() { - describe('rendering', function() { - beforeEach(function() { - pages = new List(); - pages.engine('tmpl', require('engine-base')); - }); - - it('should throw an error when no callback is given:', function() { - (function() { - pages.render({}); - }).should.throw('List#render is async and expects a callback function'); - }); - - it('should throw an error when an engine is not defined:', function(done) { - pages.addItem('foo.bar', {content: '<%= name %>'}); - var page = pages.getItem('foo.bar'); - - pages.render(page, function(err) { - assert(err.message === 'List#render cannot find an engine for: .bar'); - done(); - }); - }); - - it('should use helpers to render a item:', function(done) { - var locals = {name: 'Halle'}; - - pages.helper('upper', function(str) { - return str.toUpperCase(str); - }); - - pages.addItem('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); - var page = pages.getItem('a.tmpl'); - - pages.render(page, function(err, res) { - if (err) return done(err); - - assert(res.content === 'a HALLE b'); - done(); - }); - }); - - it('should use helpers when rendering a item:', function(done) { - var locals = {name: 'Halle'}; - pages.helper('upper', function(str) { - return str.toUpperCase(str); - }); - - pages.addItem('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); - var page = pages.getItem('a.tmpl'); - - pages.render(page, function(err, res) { - if (err) return done(err); - assert(res.content === 'a HALLE b'); - done(); - }); - }); - - it('should render a template when contents is a buffer:', function(done) { - pages.addItem('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - var item = pages.getItem('a.tmpl'); - - pages.render(item, function(err, item) { - if (err) return done(err); - assert(item.contents.toString() === 'b'); - done(); - }); - }); - - it('should render a template when content is a string:', function(done) { - pages.addItem('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - var item = pages.getItem('a.tmpl'); - - pages.render(item, function(err, item) { - if (err) return done(err); - assert(item.contents.toString() === 'b'); - done(); - }); - }); - - it('should render a item from its path:', function(done) { - pages.addItem('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - - pages.render('a.tmpl', function(err, item) { - if (err) return done(err); - assert(item.content === 'b'); - done(); - }); - }); - - it('should use a plugin for rendering:', function(done) { - pages.engine('tmpl', require('engine-base')); - pages.option('engine', 'tmpl'); - - pages.addItems({ - 'a': {content: '<%= title %>', locals: {title: 'aaa'}}, - 'b': {content: '<%= title %>', locals: {title: 'bbb'}}, - 'c': {content: '<%= title %>', locals: {title: 'ccc'}}, - 'd': {content: '<%= title %>', locals: {title: 'ddd'}}, - 'e': {content: '<%= title %>', locals: {title: 'eee'}}, - 'f': {content: '<%= title %>', locals: {title: 'fff'}}, - 'g': {content: '<%= title %>', locals: {title: 'ggg'}}, - 'h': {content: '<%= title %>', locals: {title: 'hhh'}}, - 'i': {content: '<%= title %>', locals: {title: 'iii'}}, - 'j': {content: '<%= title %>', locals: {title: 'jjj'}} - }); - - pages.use(function(collection) { - collection.option('pager', false); - - collection.renderEach = function(cb) { - var list = new List(collection); - - async.map(list.items, function(item, next) { - collection.render(item, next); - }, cb); - }; - }); - - pages.renderEach(function(err, items) { - if (err) return done(err); - assert(items[0].content === 'aaa'); - assert(items[9].content === 'jjj'); - assert(items.length === 10); - done(); - }); - }); - }); -}); diff --git a/test/list.use.js b/test/list.use.js deleted file mode 100644 index 2802a75..0000000 --- a/test/list.use.js +++ /dev/null @@ -1,158 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var List = App.List; -var Item = App.Item; -var list; - -describe('list.use', function() { - beforeEach(function() { - list = new List(); - }); - - it('should expose the instance to `use`:', function(done) { - list.use(function(inst) { - assert(inst instanceof List); - done(); - }); - }); - - it('should be chainable:', function(done) { - list.use(function(inst) { - assert(inst instanceof List); - }) - .use(function(inst) { - assert(inst instanceof List); - }) - .use(function(inst) { - assert(inst instanceof List); - done(); - }); - }); - - it('should expose the list to a plugin:', function() { - list.use(function(items) { - assert(items instanceof List); - items.foo = items.addItem.bind(items); - }); - - list.foo('a', {content: '...'}); - assert(list.hasItem('a')); - }); - - it('should expose list when chained:', function() { - list - .use(function(items) { - assert(items instanceof List); - items.foo = items.addItem.bind(items); - }) - .use(function(items) { - assert(items instanceof List); - items.bar = items.addItem.bind(items); - }) - .use(function(items) { - assert(items instanceof List); - items.baz = items.addItem.bind(items); - }); - - var pages = list; - - pages.foo({path: 'a', content: '...'}); - pages.bar({path: 'b', content: '...'}); - pages.baz({path: 'c', content: '...'}); - - assert(list.hasItem('a')); - assert(list.hasItem('b')); - assert(list.hasItem('c')); - }); - - it('should work when a custom `Item` constructor is passed:', function() { - list = new List({Item: require('vinyl')}); - list - .use(function(items) { - assert(items instanceof List); - items.foo = items.addItem.bind(items); - }) - .use(function(items) { - assert(items instanceof List); - items.bar = items.addItem.bind(items); - }) - .use(function(items) { - assert(items instanceof List); - items.baz = items.addItem.bind(items); - }); - - var pages = list; - - pages.foo({path: 'a', content: '...'}); - pages.bar({path: 'b', content: '...'}); - pages.baz({path: 'c', content: '...'}); - - assert(list.hasItem('a')); - assert(list.hasItem('b')); - assert(list.hasItem('c')); - }); - - it('should pass to item `use` if a function is returned:', function() { - list.use(function(items) { - assert(items instanceof List); - - return function(item) { - item.foo = items.addItem.bind(items); - assert(item.isItem || item.isView); - }; - }); - - list.addItem('a', {content: '...'}) - .foo({path: 'b', content: '...'}) - .foo({path: 'c', content: '...'}) - .foo({path: 'd', content: '...'}); - - assert(list.hasItem('a')); - assert(list.hasItem('b')); - assert(list.hasItem('c')); - assert(list.hasItem('d')); - }); - - it('should be chainable when a item function is returned:', function() { - list - .use(function(items) { - assert(items instanceof List); - - return function(item) { - item.foo = items.addItem.bind(items); - assert(item instanceof Item); - }; - }) - .use(function(items) { - assert(items instanceof List); - - return function(item) { - item.bar = items.addItem.bind(items); - assert(item instanceof Item); - }; - }) - .use(function(items) { - assert(items instanceof List); - - return function(item) { - item.baz = items.addItem.bind(items); - assert(item instanceof Item); - }; - }); - - list.addItem('a', {content: '...'}) - .foo({path: 'b', content: '...'}) - .bar({path: 'c', content: '...'}) - .baz({path: 'd', content: '...'}); - - assert(list.hasItem('a')); - assert(list.hasItem('b')); - assert(list.hasItem('c')); - assert(list.hasItem('d')); - }); -}); diff --git a/test/mergePartials.js b/test/mergePartials.js deleted file mode 100644 index b00a14a..0000000 --- a/test/mergePartials.js +++ /dev/null @@ -1,106 +0,0 @@ -'use strict'; - -require('should'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('mergePartials', function() { - beforeEach(function() { - app = new App(); - }); - - it('should merge multiple partials collections onto one collection:', function() { - var opts = { viewType: 'partial' }; - app.create('foo', opts); - app.create('bar', opts); - app.create('baz', opts); - - app.foo('a', {path: 'a', content: 'aaa'}); - app.bar('b', {path: 'b', content: 'bbb'}); - app.baz('c', {path: 'c', content: 'ccc'}); - - var actual = app.mergePartials(); - actual.should.have.property('partials'); - actual.partials.should.have.properties(['a', 'b', 'c']); - }); - - it('should keep partials collections on separaet collections:', function() { - var opts = { viewType: 'partial' }; - app.create('foo', opts); - app.create('bar', opts); - app.create('baz', opts); - - app.foo('a', {path: 'a', content: 'aaa'}); - app.bar('b', {path: 'b', content: 'bbb'}); - app.baz('c', {path: 'c', content: 'ccc'}); - - var actual = app.mergePartials({mergePartials: false}); - actual.should.not.have.property('partials'); - actual.should.eql({ foos: { a: 'aaa' }, bars: { b: 'bbb' }, bazs: { c: 'ccc' } }); - }); - - it('should emit `mergePartials`:', function() { - var opts = { viewType: 'partial' }; - app.create('foo', opts); - app.create('bar', opts); - app.create('baz', opts); - var arr = []; - - app.on('onMerge', function(view) { - arr.push(view.content); - }); - - app.foo('a', {path: 'a', content: 'aaa'}); - app.bar('b', {path: 'b', content: 'bbb'}); - app.baz('c', {path: 'c', content: 'ccc'}); - - var actual = app.mergePartials({mergePartials: false}); - actual.should.not.have.property('partials'); - actual.should.eql({ foos: { a: 'aaa' }, bars: { b: 'bbb' }, bazs: { c: 'ccc' } }); - arr.should.eql(['aaa', 'bbb', 'ccc']); - }); - - it('should handle `onMerge` middleware:', function() { - var opts = { viewType: 'partial' }; - app.create('foo', opts); - app.create('bar', opts); - app.create('baz', opts); - - app.onMerge(/./, function(view, next) { - view.content += ' onMerge'; - next(); - }); - - app.foo('a', {path: 'a', content: 'aaa'}); - app.bar('b', {path: 'b', content: 'bbb'}); - app.baz('c', {path: 'c', content: 'ccc'}); - - var actual = app.mergePartials({mergePartials: false}); - actual.should.eql({ - foos: {a: 'aaa onMerge'}, - bars: {b: 'bbb onMerge'}, - bazs: {c: 'ccc onMerge'} - }); - }); - - it('should skip views with `nomerge=true`:', function() { - var opts = { viewType: 'partial' }; - - app.create('foo', opts); - app.create('bar', opts); - app.create('baz', opts); - - app.onMerge(/[ab]/, function(view, next) { - view.options.nomerge = true; - next(); - }); - - app.foo('a', {path: 'a', content: 'aaa'}); - app.bar('b', {path: 'b', content: 'bbb'}); - app.baz('c', {path: 'c', content: 'ccc'}); - - var actual = app.mergePartials({mergePartials: false}); - actual.should.eql({ bazs: { c: 'ccc' } }); - }); -}); diff --git a/test/partials.js b/test/partials.js deleted file mode 100644 index 9dea3b1..0000000 --- a/test/partials.js +++ /dev/null @@ -1,204 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app, pages; - -describe('partials', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.engine('hbs', require('engine-handlebars')); - - app.create('partials', { viewType: 'partial' }); - app.create('include', { viewType: 'partial' }); - app.create('layouts', { viewType: 'layout' }); - pages = app.create('page'); - }); - - it('should inject a partial with a helper:', function(done) { - app.include('base', {path: 'base.tmpl', content: 'xyz'}); - app.pages('a.tmpl', {path: 'a.tmpl', content: 'a <%= include("base") %> c'}); - var page = app.pages.getView('a.tmpl'); - - app.render(page, function(err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'a xyz c'); - done(); - }); - }); - - it('should inject a partial with a helper on a collection:', function(done) { - app.include('base', {path: 'base.tmpl', content: 'xyz'}); - pages.engine('.tmpl', require('engine-handlebars')); - pages.helpers(app._.helpers.sync); - pages.asyncHelpers(app._.helpers.async); - pages.addView('a.tmpl', {path: 'a.tmpl', content: 'a {{include "base" }} c'}); - var page = pages.getView('a.tmpl'); - - pages.render(page, function(err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'a xyz c'); - done(); - }); - }); - - it('should use handlebars partial with a helper on a collection:', function(done) { - app.include('base', {path: 'base.tmpl', content: 'xyz'}); - pages.engine('.tmpl', require('engine-handlebars')); - pages.helpers(app._.helpers.sync); - pages.asyncHelpers(app._.helpers.async); - pages.addView('a.tmpl', {path: 'a.tmpl', content: 'a {{> base }} c'}); - - var page = pages.getView('a.tmpl'); - var locals = app.mergePartials(this.options); - - pages.render(page, locals, function(err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'a xyz c'); - done(); - }); - }); - - it('should use layouts with partials:', function(done) { - app.layout('default', {path: 'a.tmpl', content: 'a {% body %} c'}); - app.include('b', {path: 'b.tmpl', content: 'b', layout: 'default'}); - app.pages('a.tmpl', {path: 'c.tmpl', content: '<%= include("b") %>'}); - var page = app.pages.getView('a.tmpl'); - - app.render(page, function(err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'a b c'); - done(); - }); - }); - - it('should add `layoutApplied` after layout is applied:', function(done) { - app.layout('default', {path: 'a.tmpl', content: 'a {% body %} c'}); - app.include('b', {path: 'b.tmpl', content: 'b', layout: 'default'}); - app.pages('a.tmpl', {path: 'c.tmpl', content: '<%= include("b") %>'}); - var page = app.pages.getView('a.tmpl'); - - app.render(page, function(err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(app.layouts.getView('default').options.layoutApplied); - assert.equal(view.content, 'a b c'); - done(); - }); - }); - - it('should pass partials to handlebars:', function(done) { - app.onMerge(/\.hbs$/, function(view, next) { - app.applyLayout(view); - next(); - }); - - app.layout('default', {path: 'a.hbs', content: 'a {% body %} c'}); - app.include('foo', {path: 'foo.hbs', content: 'foo', layout: 'default'}); - app.pages('a.hbs', {path: 'c.hbs', content: '{{> foo }}'}); - var page = app.pages.getView('a.hbs'); - - app.render(page, function(err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'a foo c'); - done(); - }); - }); - - it('should only merge in the specified viewTypes:', function(done) { - app.onMerge(/\.hbs$/, function(view, next) { - app.applyLayout(view); - next(); - }); - - app.layout('default', {path: 'a.hbs', content: 'a {% body %} c'}); - app.option('mergeTypes', ['includes']); - - app.partial('foo', {path: 'bar.hbs', content: 'bar', layout: 'default'}); - app.include('foo', {path: 'foo.hbs', content: 'foo', layout: 'default'}); - - app.pages('a.hbs', {path: 'c.hbs', content: '{{> foo }}'}); - app.pages.getView('a.hbs') - .render(function(err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'a foo c'); - done(); - }); - - }); - - it('should merge the specified viewTypes in the order defined:', function(done) { - app.onMerge(/\.hbs$/, function(view, next) { - app.applyLayout(view); - next(); - }); - - app.layout('default', {path: 'a.hbs', content: 'a {% body %} c'}); - app.option('mergeTypes', ['includes', 'partials']); - - app.partial('foo', {path: 'bar.hbs', content: 'bar', layout: 'default'}); - app.include('foo', {path: 'foo.hbs', content: 'foo', layout: 'default'}); - - app.pages('a.hbs', {path: 'c.hbs', content: '{{> foo }}'}); - app.pages.getView('a.hbs') - .render(function(err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'a bar c'); - done(); - }); - }); - - it('should not merge in partials with `options.nomerge` defined:', function(done) { - app.onMerge(/\.hbs$/, function(view, next) { - app.applyLayout(view); - next(); - }); - - app.layout('default', {path: 'a.hbs', content: 'a {% body %} c'}); - app.option('mergeTypes', ['includes', 'partials']); - - app.partial('foo', {path: 'bar.hbs', content: 'bar', layout: 'default', options: {nomerge: true}}); - app.include('foo', {path: 'foo.hbs', content: 'foo', layout: 'default'}); - - app.pages('a.hbs', {path: 'c.hbs', content: '{{> foo }}'}); - app.pages.getView('a.hbs') - .render(function(err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'a foo c'); - done(); - }); - }); - - it('should emit an `onMerge` event:', function(done) { - app.on('onMerge', function(view) { - app.applyLayout(view); - }); - - app.layout('default', {path: 'a.hbs', content: 'a {% body %} c'}); - app.option('mergeTypes', ['includes', 'partials']); - - app.partial('foo', {path: 'bar.hbs', content: 'bar', layout: 'default'}); - app.include('foo', {path: 'foo.hbs', content: 'foo', layout: 'default'}); - - app.pages('a.hbs', {path: 'c.hbs', content: '{{> foo }}'}); - app.pages.getView('a.hbs') - .render(function(err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'a bar c'); - done(); - }); - }); -}); diff --git a/test/questions.js b/test/questions.js deleted file mode 100644 index e58be5e..0000000 --- a/test/questions.js +++ /dev/null @@ -1,60 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var fs = require('fs'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe.skip('content', function() { - beforeEach(function() { - app = new App(); - }); - - it('should store a question:', function() { - app.question('a', 'b'); - assert(app.questions); - assert(app.questions.cache); - assert(app.questions.cache.a); - assert(app.questions.cache.a.name === 'a'); - assert(app.questions.cache.a.message === 'b'); - }); - - it('should ask a question and use data value to answer:', function(done) { - app.question('a', 'b'); - app.data('a', 'b'); - - app.ask('a', function(err, answer) { - assert(!err); - assert(answer); - assert(answer === 'b'); - done(); - }) - }); - - it('should ask a question and use store value to answer:', function(done) { - app.question('a', 'b'); - app.store.set('a', 'c'); - - app.ask('a', function(err, answer) { - assert(!err); - assert(answer); - assert(answer === 'c'); - done(); - }) - }); - - it('should ask a question and use config value to answer:', function(done) { - app.question('a', 'b'); - app.store.set('a', 'c'); - - app.ask('a', function(err, answer) { - assert(!err); - assert(answer); - assert(answer === 'c'); - done(); - }) - }); -}); diff --git a/test/renameKey.js b/test/renameKey.js deleted file mode 100644 index 801c1c2..0000000 --- a/test/renameKey.js +++ /dev/null @@ -1,352 +0,0 @@ -'use strict'; - -var path = require('path'); -var support = require('./support'); -var App = support.resolve(); -var app; - -function renameKey(key) { - return path.basename(key, path.extname(key)); -} - -describe('renameKey', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('pages'); - app.create('posts'); - }); - - describe('global options:', function() { - it('should use `renameKey` function defined on global opts:', function() { - app.option('renameKey', renameKey); - - app.posts('a/b/c/a.txt', {content: '...'}); - app.posts('a/b/c/b.txt', {content: '...'}); - app.posts('a/b/c/c.txt', {content: '...'}); - app.post('a/b/c/d.txt', {content: '...'}); - app.post('a/b/c/e.txt', {content: '...'}); - - app.views.posts.should.have.property('a'); - app.views.posts.should.have.property('b'); - app.views.posts.should.have.property('c'); - app.views.posts.should.have.property('d'); - app.views.posts.should.have.property('e'); - }); - - it('should not have conflicts when view name is the collection name:', function() { - app.option('renameKey', renameKey); - - app.post('a/b/c/post.txt', {content: 'this is contents'}); - app.page('a/b/c/page.txt', {content: 'this is contents'}); - - app.views.posts.should.have.property('post'); - app.views.pages.should.have.property('page'); - }); - }); - - describe('create method:', function() { - it('should use `renameKey` option chained from the `create` method:', function() { - app.create('post') - .option('renameKey', function(key) { - return 'posts/' + path.basename(key); - }); - - app.posts('a/b/c/a.txt', {content: '...'}); - app.posts('a/b/c/b.txt', {content: '...'}); - app.posts('a/b/c/c.txt', {content: '...'}); - app.post('a/b/c/d.txt', {content: '...'}); - app.post('a/b/c/e.txt', {content: '...'}); - - app.views.posts.should.have.property('posts/a.txt'); - app.views.posts.should.have.property('posts/b.txt'); - app.views.posts.should.have.property('posts/c.txt'); - app.views.posts.should.have.property('posts/d.txt'); - app.views.posts.should.have.property('posts/e.txt'); - }); - }); - - describe('create method:', function() { - it('should use `renameKey` defined on the `create` method:', function() { - app.create('post', { - renameKey: function(key) { - return 'posts/' + path.basename(key); - } - }); - - app.posts('a/b/c/a.txt', {content: '...'}); - app.posts('a/b/c/b.txt', {content: '...'}); - app.posts('a/b/c/c.txt', {content: '...'}); - app.post('a/b/c/d.txt', {content: '...'}); - app.post('a/b/c/e.txt', {content: '...'}); - - app.views.posts.should.have.property('posts/a.txt'); - app.views.posts.should.have.property('posts/b.txt'); - app.views.posts.should.have.property('posts/c.txt'); - app.views.posts.should.have.property('posts/d.txt'); - app.views.posts.should.have.property('posts/e.txt'); - }); - }); - - describe('collections:', function() { - describe('setting:', function() { - it('should get a view with the `renameKey` defined on app.options:', function() { - app.option('renameKey', function(key) { - return 'foo/' + path.basename(key); - }); - - app.posts('a/b/c/a.txt', {content: '...'}); - app.posts('a/b/c/b.txt', {content: '...'}); - app.post('a/b/c/c.txt', {content: '...'}); - - app.views.posts.should.have.property('foo/a.txt'); - app.views.posts.should.have.property('foo/b.txt'); - app.views.posts.should.have.property('foo/c.txt'); - }); - - it('should use `renameKey` defined on collection.options:', function() { - app.pages.option('renameKey', function(key) { - return 'page/' + path.basename(key); - }); - - app.posts.option('renameKey', function(key) { - return 'post/' + path.basename(key); - }); - - app.pages('a/b/c/a.txt', {content: '...'}); - app.pages('a/b/c/b.txt', {content: '...'}); - app.pages('a/b/c/c.txt', {content: '...'}); - app.page('a/b/c/d.txt', {content: '...'}); - app.page('a/b/c/e.txt', {content: '...'}); - - app.posts('a/b/c/a.txt', {content: '...'}); - app.posts('a/b/c/b.txt', {content: '...'}); - app.posts('a/b/c/c.txt', {content: '...'}); - app.post('a/b/c/d.txt', {content: '...'}); - app.post('a/b/c/e.txt', {content: '...'}); - - app.views.pages.should.have.property('page/a.txt'); - app.views.pages.should.have.property('page/b.txt'); - app.views.pages.should.have.property('page/c.txt'); - app.views.pages.should.have.property('page/d.txt'); - app.views.pages.should.have.property('page/e.txt'); - - app.views.posts.should.have.property('post/a.txt'); - app.views.posts.should.have.property('post/b.txt'); - app.views.posts.should.have.property('post/c.txt'); - app.views.posts.should.have.property('post/d.txt'); - app.views.posts.should.have.property('post/e.txt'); - }); - - it('should use the `collection.renameKey()` method:', function() { - app.pages.renameKey(function(key) { - return 'baz/' + path.basename(key); - }); - - app.pages('a/b/c/a.txt', {content: '...'}); - app.pages('a/b/c/b.txt', {content: '...'}); - app.pages('a/b/c/c.txt', {content: '...'}); - app.page('a/b/c/d.txt', {content: '...'}); - app.page('a/b/c/e.txt', {content: '...'}); - - app.views.pages.should.have.property('baz/a.txt'); - app.views.pages.should.have.property('baz/b.txt'); - app.views.pages.should.have.property('baz/c.txt'); - app.views.pages.should.have.property('baz/d.txt'); - app.views.pages.should.have.property('baz/e.txt'); - }); - - it('should use the `app.renameKey()` method:', function() { - app.renameKey(function(key) { - return 'app/' + path.basename(key); - }); - - app.pages('a/b/c/a.txt', {content: '...'}); - app.pages('a/b/c/b.txt', {content: '...'}); - app.pages('a/b/c/c.txt', {content: '...'}); - app.page('a/b/c/d.txt', {content: '...'}); - app.page('a/b/c/e.txt', {content: '...'}); - - app.views.pages.should.have.property('app/a.txt'); - app.views.pages.should.have.property('app/b.txt'); - app.views.pages.should.have.property('app/c.txt'); - app.views.pages.should.have.property('app/d.txt'); - app.views.pages.should.have.property('app/e.txt'); - }); - - it('should prefer collection method over app.options:', function() { - // this works when you switch the order around... - app.pages.renameKey(function pagesRenameKey(key) { - return 'aaa/' + path.basename(key); - }); - app.option('renameKey', function optsRenameKey(key) { - return 'foo/' + path.basename(key); - }); - - app.pages('a/b/c/a.txt', {content: '...'}); - app.pages('a/b/c/b.txt', {content: '...'}); - app.pages('a/b/c/c.txt', {content: '...'}); - app.page('a/b/c/d.txt', {content: '...'}); - app.page('a/b/c/e.txt', {content: '...'}); - - app.views.pages.should.have.property('aaa/a.txt'); - app.views.pages.should.have.property('aaa/b.txt'); - app.views.pages.should.have.property('aaa/c.txt'); - app.views.pages.should.have.property('aaa/d.txt'); - app.views.pages.should.have.property('aaa/e.txt'); - }); - - it('should prefer collection method over app method:', function() { - app.pages.renameKey(function(key) { - return 'aaa/' + path.basename(key); - }); - app.renameKey(function(key) { - return 'zzz/' + path.basename(key); - }); - - app.pages('a/b/c/a.txt', {content: '...'}); - app.pages('a/b/c/b.txt', {content: '...'}); - app.pages('a/b/c/c.txt', {content: '...'}); - app.page('a/b/c/d.txt', {content: '...'}); - app.page('a/b/c/e.txt', {content: '...'}); - - app.views.pages.should.have.property('aaa/a.txt'); - app.views.pages.should.have.property('aaa/b.txt'); - app.views.pages.should.have.property('aaa/c.txt'); - app.views.pages.should.have.property('aaa/d.txt'); - app.views.pages.should.have.property('aaa/e.txt'); - }); - - it('should prefer collection options over app.options:', function() { - app.pages.option('renameKey', function(key) { - return 'collection/' + path.basename(key); - }); - app.option('renameKey', function(key) { - return 'app/' + path.basename(key); - }); - - app.pages('a/b/c/a.txt', {content: '...'}); - app.pages('a/b/c/b.txt', {content: '...'}); - app.pages('a/b/c/c.txt', {content: '...'}); - app.page('a/b/c/d.txt', {content: '...'}); - app.page('a/b/c/e.txt', {content: '...'}); - - app.views.pages.should.have.property('collection/a.txt'); - app.views.pages.should.have.property('collection/b.txt'); - app.views.pages.should.have.property('collection/c.txt'); - app.views.pages.should.have.property('collection/d.txt'); - app.views.pages.should.have.property('collection/e.txt'); - }); - - it('should prefer collection options over app method:', function() { - app.pages.option('renameKey', function(key) { - return 'collection/' + path.basename(key); - }); - app.renameKey(function(key) { - return 'app/' + path.basename(key); - }); - - app.pages('a/b/c/a.txt', {content: '...'}); - app.pages('a/b/c/b.txt', {content: '...'}); - app.pages('a/b/c/c.txt', {content: '...'}); - app.page('a/b/c/d.txt', {content: '...'}); - app.page('a/b/c/e.txt', {content: '...'}); - - app.views.pages.should.have.property('collection/a.txt'); - app.views.pages.should.have.property('collection/b.txt'); - app.views.pages.should.have.property('collection/c.txt'); - app.views.pages.should.have.property('collection/d.txt'); - app.views.pages.should.have.property('collection/e.txt'); - }); - - it('should use renameKey on chained methods:', function() { - app.page('test/fixtures/pages/a.txt', { - options: { - renameKey: function foo(key) { - return 'foo/' + path.basename(key); - } - } - }); - - app.page('test/fixtures/pages/a.hbs', { - options: { - renameKey: function bar(key) { - return 'bar/' + path.basename(key); - } - } - }); - - app.views.pages.should.have.properties([ - 'foo/a.txt', - 'bar/a.hbs' - ]); - }); - }); - - describe('getting', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('post'); - app.create('page'); - }); - - it('should get a view with the `renameKey` defined on the `create` method:', function() { - app.create('post', { - renameKey: function createRenameKey(key) { - return 'posts/' + path.basename(key); - } - }); - - app.posts('a/b/c/a.txt', {content: '...'}); - app.posts('a/b/c/b.txt', {content: '...'}); - app.post('a/b/c/c.txt', {content: '...'}); - - app.posts.getView('a.txt').should.have.property('path', 'a/b/c/a.txt'); - app.posts.getView('posts/a.txt').should.have.property('path', 'a/b/c/a.txt'); - }); - - it('should get a view with `renameKey` on collection.options:', function() { - app.pages.option('renameKey', function(key) { - return 'bar/' + path.basename(key); - }); - - app.pages('a/b/c/a.txt', {content: '...'}); - app.pages('a/b/c/b.txt', {content: '...'}); - app.page('a/b/c/c.txt', {content: '...'}); - - app.views.pages.should.have.property('bar/a.txt'); - app.views.pages.should.have.property('bar/b.txt'); - app.views.pages.should.have.property('bar/c.txt'); - }); - - it('should get a view with the the `app.renameKey()` method:', function() { - app.renameKey(function(key) { - return 'baz/' + path.basename(key); - }); - - app.pages('a/b/c/a.txt', {content: '...'}); - app.pages('a/b/c/b.txt', {content: '...'}); - app.page('a/b/c/c.txt', {content: '...'}); - - app.views.pages.should.have.property('baz/a.txt'); - app.views.pages.should.have.property('baz/b.txt'); - app.views.pages.should.have.property('baz/c.txt'); - }); - - it('should get a view with the the `collection.renameKey()` method:', function() { - app.pages.renameKey(function(key) { - return 'baz/' + path.basename(key); - }); - - app.pages('a/b/c/a.txt', {content: '...'}); - app.pages('a/b/c/b.txt', {content: '...'}); - app.page('a/b/c/c.txt', {content: '...'}); - - app.views.pages.should.have.property('baz/a.txt'); - app.views.pages.should.have.property('baz/b.txt'); - app.views.pages.should.have.property('baz/c.txt'); - }); - }); - }); -}); diff --git a/test/render.js b/test/render.js deleted file mode 100644 index aae8d0e..0000000 --- a/test/render.js +++ /dev/null @@ -1,72 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('render', function() { - describe('engine', function() { - var view; - - beforeEach(function() { - app = new App({silent: true}); - app.engine('tmpl', require('engine-base')); - app.create('page'); - view = {contents: new Buffer('a <%= name %> b'), locals: {name: 'Halle'}}; - }); - - it('should render a view from an object:', function(done) { - app.page('a.tmpl', view) - .render(function(err, res) { - if (err) return done(err); - assert(res.contents.toString() === 'a Halle b'); - done(); - }); - }); - - it('should throw an error when a variable is undefined:', function(done) { - delete view.locals.name; - - app.page('a.tmpl', view) - .render(function(err) { - assert(err.message === 'name is not defined'); - done(); - }); - }); - - it('should re-throw an error when rethrow is true:', function(done) { - delete view.locals.name; - - app = new App({rethrow: true, silent: true}); - app.engine('tmpl', require('engine-base')); - app.create('page'); - - app.page('a.tmpl', view) - .render(function(err) { - assert(err.message === 'name is not defined'); - done(); - }); - }); - - it('should emit a re-thrown error when rethrow is true:', function(done) { - delete view.locals.name; - - app = new App({rethrow: true, silent: false}); - app.engine('tmpl', require('engine-base')); - app.create('page'); - - app.on('error', function(err) { - assert(err.message === 'name is not defined'); - done(); - }); - - app.page('a.tmpl', view) - .render(function(err) { - assert(err.message === 'name is not defined'); - }); - }); - }); -}); diff --git a/test/routes.js b/test/routes.js deleted file mode 100644 index e5ff13a..0000000 --- a/test/routes.js +++ /dev/null @@ -1,100 +0,0 @@ -'use strict'; - -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -function append(str) { - return function(view, next) { - var content = view.contents.toString(); - view.contents = new Buffer(content + ' ' + str); - next(); - }; -} -function prepend(str) { - return function(view, next) { - var content = view.contents.toString(); - view.contents = new Buffer(str + ' ' + content); - next(); - }; -} - -describe('routes', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('page'); - }); - - describe('params', function() { - it('should call param function when routing', function(done) { - app.param('id', function(view, next, id) { - assert.equal(id, '123'); - next(); - }); - - app.all('/foo/:id/bar', function(view, next) { - assert.equal(view.options.params.id, '123'); - next(); - }); - - app.router.handle({ path: '/foo/123/bar' }, done); - }); - }); - - describe('onLoad middleware', function() { - it('should run when templates are loaded:', function() { - app.onLoad(/\.tmpl/, prepend('onLoad')); - app.pages('a.tmpl', { path: 'a.tmpl', content: '<%= name %>'}); - - var page = app.pages.getView('a.tmpl'); - page.contents.toString().should.equal('onLoad <%= name %>'); - }); - }); - - describe('preCompile middleware', function() { - it('should run before templates are compiled:', function() { - - }); - }); - - describe('postCompile middleware', function() { - it('should run after templates are compiled:', function() { - - }); - }); - - describe('preRender middleware', function() { - it('should run before templates are rendered:', function(done) { - app.preRender(/\.tmpl/, prepend('preRender')); - app.pages('a.tmpl', { path: 'a.tmpl', content: '<%= name %>', locals: {name: 'aaa'} }); - - var page = app.pages.getView('a.tmpl'); - page.contents.toString().should.equal('<%= name %>'); - - page.render({}, function(err, res) { - if (err) return done(err); - res.contents.toString().should.equal('preRender aaa'); - done(); - }); - }); - }); - - describe('postRender middleware', function() { - it('should run after templates are rendered:', function(done) { - app.postRender(/\.tmpl/, append('postRender')); - app.pages('a.tmpl', { path: 'a.tmpl', content: '<%= name %>', locals: {name: 'aaa' }}); - - var page = app.pages.getView('a.tmpl'); - page.contents.toString().should.equal('<%= name %>'); - - page.render({}, function(err, res) { - if (err) return done(err); - res.contents.toString().should.equal('aaa postRender'); - done(); - }); - }); - }); -}); diff --git a/test/store.js b/test/store.js deleted file mode 100644 index b955589..0000000 --- a/test/store.js +++ /dev/null @@ -1,242 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var fs = require('fs'); -var path = require('path'); -var Store = require('data-store'); -var assert = require('assert'); -var App = require('../'); -var app; - -describe('store', function() { - beforeEach(function() { - app = new App(); - }); - - afterEach(function(cb) { - app.store.del({force: true}); - app.store.data = {}; - cb(); - }); - - it('should create a store at the given `cwd`', function() { - app = new App({store: {cwd: __dirname + '/actual'}}); - app.store.set('foo', 'bar'); - assert(path.basename(app.store.path) === 'update.json'); - assert(app.store.data.hasOwnProperty('foo')); - assert(app.store.data.foo === 'bar'); - assert(fs.existsSync(path.join(__dirname, 'actual', 'update.json'))); - }); - - it('should create a store using the given `indent` value', function() { - app = new App({store: {cwd: __dirname + '/actual', indent: 0}}); - app.store.set('foo', 'bar'); - var contents = fs.readFileSync(path.join(__dirname, 'actual', 'update.json'), 'utf8'); - assert(contents === '{"foo":"bar"}'); - }); - - it('should set a value on the store', function() { - app.store.set('one', 'two'); - app.store.data.one.should.equal('two'); - }); - - it('should set an object', function() { - app.store.set({four: 'five', six: 'seven'}); - app.store.data.four.should.equal('five'); - app.store.data.six.should.equal('seven'); - }); - - it('should set a nested value', function() { - app.store.set('a.b.c.d', {e: 'f'}); - app.store.data.a.b.c.d.e.should.equal('f'); - }); - - it('should union a value onto an array on the store', function() { - app.store.union('one', 'two'); - app.store.data.one.should.eql(['two']); - }); - - it('should not union duplicate values', function() { - app.store.union('one', 'two'); - app.store.data.one.should.eql(['two']); - - app.store.union('one', ['two']); - app.store.data.one.should.eql(['two']); - }); - - it('should concat an existing array:', function() { - app.store.union('one', 'a'); - app.store.data.one.should.eql(['a']); - - app.store.union('one', ['b']); - app.store.data.one.should.eql(['a', 'b']); - - app.store.union('one', ['c', 'd']); - app.store.data.one.should.eql(['a', 'b', 'c', 'd']); - }); - - it('should return true if a key exists on the store', function() { - app.store.set('foo', 'bar'); - assert(app.store.has('foo')); - }); - - it('should return true when the value is null', function() { - app.store.set('baz', null); - assert(app.store.has('baz')); - }); - - it('should return false when the value is undefined', function() { - app.store.set('qux', undefined); - assert(!app.store.has('qux')); - }); - - it('should return true if a nested key exists on the store', function() { - app.store.set('a.b.c.d', {x: 'zzz'}); - app.store.set('a.b.c.e', {f: null}); - app.store.set('a.b.g.j', {k: undefined}); - - assert(!app.store.has('a.b.bar')); - assert(app.store.has('a.b.c.d')); - assert(app.store.has('a.b.c.d.x')); - assert(!app.store.has('a.b.c.d.z')); - assert(app.store.has('a.b.c.e')); - assert(app.store.has('a.b.c.e.f')); - assert(!app.store.has('a.b.c.e.z')); - assert(app.store.has('a.b.g.j')); - assert(!app.store.has('a.b.g.j.k')); - assert(!app.store.has('a.b.g.j.z')); - }); - - it('should return true if a key exists `.hasOwn()` on the store', function() { - app.store.set('foo', 'bar'); - app.store.set('baz', null); - app.store.set('qux', undefined); - - assert(app.store.hasOwn('foo')); - assert(!app.store.hasOwn('bar')); - assert(app.store.hasOwn('baz')); - assert(app.store.hasOwn('qux')); - }); - - it('should return true if a nested key exists `.hasOwn()` on the store', function() { - app.store.set('a.b.c.d', {x: 'zzz'}); - app.store.set('a.b.c.e', {f: null}); - app.store.set('a.b.g.j', {k: undefined}); - - assert(app.store.hasOwn('a.b.c.d')); - assert(app.store.hasOwn('a.b.c.d.x')); - assert(app.store.has('a.b.c.e.f')); - assert(app.store.hasOwn('a.b.c.e.f')); - assert(app.store.hasOwn('a.b.g.j.k')); - - assert(!app.store.hasOwn('a.b.bar')); - assert(!app.store.hasOwn('a.b.c.d.z')); - assert(!app.store.hasOwn('a.b.c.e.bar')); - assert(!app.store.has('a.b.g.j.k')); - assert(!app.store.hasOwn('a.b.g.j.foo')); - }); - - it('should `.get()` a stored value', function() { - app.store.set('three', 'four'); - app.store.get('three').should.equal('four'); - }); - - it('should `.get()` a nested value', function() { - app.store.set({a: {b: {c: 'd'}}}); - app.store.get('a.b.c').should.equal('d'); - }); - - it('should `.del()` a stored value', function() { - app.store.set('a', 'b'); - app.store.set('c', 'd'); - app.store.del('a'); - assert(!app.store.hasOwnProperty('a')); - }); - - it('should `.del()` multiple stored values', function() { - app.store.set('a', 'b'); - app.store.set('c', 'd'); - app.store.set('e', 'f'); - app.store.del(['a', 'c', 'e']); - app.store.data.should.eql({}); - }); -}); - -describe('events', function() { - beforeEach(function() { - app = new App(); - app.store = new Store('update-tests'); - }); - - afterEach(function(cb) { - app.store.del({force: true}); - cb(); - }); - - it('should emit `set` when an object is set:', function() { - var keys = []; - app.store.on('set', function(key) { - keys.push(key); - }); - - app.store.set({a: {b: {c: 'd'}}}); - assert(keys[0] === 'a'); - }); - - it('should emit `set` when a key/value pair is set:', function() { - var keys = []; - app.store.on('set', function(key) { - keys.push(key); - }); - - app.store.set('a', 'b'); - assert(keys[0] === 'a'); - }); - - it('should emit `set` when an object value is set:', function() { - var keys = []; - app.store.on('set', function(key) { - keys.push(key); - }); - - app.store.set('a', {b: 'c'}); - assert(keys[0] === 'a'); - }); - - it('should emit `set` when an array of objects is passed:', function() { - var keys = []; - app.store.on('set', function(key) { - keys.push(key); - }); - - app.store.set([{a: 'b'}, {c: 'd'}]); - assert(keys[0] === 'a'); - assert(keys[1] === 'c'); - }); - - it('should emit `del` when a value is deleted:', function(cb) { - app.store.on('del', function(key) { - assert(key === 'a'); - cb(); - }); - - app.store.set('a', {b: 'c'}); - app.store.get('a').should.eql({b: 'c'}); - app.store.del('a'); - }); - - it('should emit deleted keys on `del`:', function(cb) { - app.store.once('del', function(key) { - assert(key === 'a'); - cb(); - }); - - app.store.set('a', 'b'); - app.store.set('c', 'd'); - app.store.set('e', 'f'); - assert.deepEqual(Object.keys(app.store.data), ['a', 'c', 'e']); - app.store.del({force: true}); - assert.deepEqual(Object.keys(app.store.data), []); - }); -}); diff --git a/test/support/ignore.js b/test/support/ignore.js deleted file mode 100644 index ef59903..0000000 --- a/test/support/ignore.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = [ - 'addEventListener', - 'removeEventListener', - 'removeAllListeners', - 'removeListener' -]; \ No newline at end of file diff --git a/test/support/index.js b/test/support/index.js deleted file mode 100644 index fc91929..0000000 --- a/test/support/index.js +++ /dev/null @@ -1,75 +0,0 @@ -'use strict'; - -var path = require('path'); -var loadPkg = require('load-pkg'); -var assert = require('assert'); -var ignore = require('./ignore'); -var cache = {}; - -exports.containEql = function containEql(actual, expected) { - if (Array.isArray(expected)) { - var len = expected.length; - while (len--) { - exports.containEql(actual[len], expected[len]); - } - } else { - for (var key in expected) { - assert.deepEqual(actual[key], expected[key]); - } - } -}; - -exports.keys = function keys(obj) { - var arr = []; - for (var key in obj) { - if (ignore.indexOf(key) === -1) { - arr.push(key); - } - } - return arr; -}; - -exports.resolve = function(filepath) { - filepath = filepath || ''; - var key = 'app:' + filepath; - if (cache.hasOwnProperty(key)) { - return cache[key]; - } - - var pkg = loadPkg.sync(process.cwd()); - var prefix = pkg.name !== 'templates' - ? 'templates' - : ''; - - var base = filepath - ? path.join(prefix, filepath) - : process.cwd(); - - var fp = tryResolve(base); - - if (typeof fp === 'undefined') { - throw new Error('cannot resolve: ' + fp); - } - return (cache[key] = require(fp)); -}; - -function tryResolve(name) { - try { - return require.resolve(name); - } catch(err) {} - - try { - return require.resolve(path.resolve(name)); - } catch(err) {} -} - -function tryRequire(name) { - try { - return require(name); - } catch(err) {} - - try { - return require(path.resolve(name)); - } catch(err) {} -} - diff --git a/test/support/spy.js b/test/support/spy.js deleted file mode 100644 index e14512b..0000000 --- a/test/support/spy.js +++ /dev/null @@ -1,27 +0,0 @@ -var fs = require('fs'); -var sinon = require('sinon'); - -var errorfn = false; - -function maybeCallAsync(module, func) { - var original = module[func]; - return sinon.stub(module, func, function() { - var args = Array.prototype.slice.call(arguments); - args.unshift(module, func); - var err = typeof errorfn === 'function' && - errorfn.apply(this, args); - if (!err) { - original.apply(this, arguments); - } else { - arguments[arguments.length - 1](err); - } - }); -} - -module.exports = { - setError: function(fn) { - errorfn = fn; - }, - chmodSpy: maybeCallAsync(fs, 'chmod'), - statSpy: maybeCallAsync(fs, 'stat') -}; diff --git a/test/update.compose.js b/test/update.compose.js deleted file mode 100644 index ec39d79..0000000 --- a/test/update.compose.js +++ /dev/null @@ -1,79 +0,0 @@ -'use strict'; - -/* deps: coveralls istanbul */ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var Update = support.resolve(); -var Base = Update.Base; -var update; - -describe('update.compose', function() { - beforeEach(function() { - update = new Update(); - }); - - it('should throw an error when trying to compose an instance', function(cb) { - var foo = new Update({name: 'foo'}); - delete foo.fn; - - try { - update.compose(foo, 'foo'); - cb(new Error('Expected an error.')); - } catch (err) { - assert.equal(err.message, 'generators must export a function to extend other generators'); - cb(); - } - }); - - it('should compose a generator', function() { - var foo = update.generator('foo', function(app) { - app.task('foo', function(cb) { - cb(); - }); - }); - - var bar = update.generator('bar', function(app) { - app.task('bar', function(cb) { - cb(); - }); - }); - - foo.tasks.should.have.property('foo'); - bar.tasks.should.have.property('bar'); - - bar.tasks.should.not.have.property('foo'); - foo.tasks.should.not.have.property('bar'); - - foo.compose(bar, 'foo'); - bar.tasks.should.have.property('foo'); - bar.compose(foo, 'bar'); - foo.tasks.should.have.property('bar'); - }); - - it('should compose a generator by name', function() { - var foo = update.generator('foo', function(app) { - app.task('foo', function(cb) { - cb(); - }); - }); - - var bar = update.generator('bar', function(app) { - app.task('bar', function(cb) { - cb(); - }); - }); - - foo.tasks.should.have.property('foo'); - bar.tasks.should.have.property('bar'); - - bar.tasks.should.not.have.property('foo'); - foo.tasks.should.not.have.property('bar'); - - update.compose(bar, 'foo'); - bar.tasks.should.have.property('foo'); - update.compose(foo, 'bar'); - foo.tasks.should.have.property('bar'); - }); -}); diff --git a/test/update.config.js b/test/update.config.js deleted file mode 100644 index 695c730..0000000 --- a/test/update.config.js +++ /dev/null @@ -1,99 +0,0 @@ -'use strict'; - -require('mocha'); -var assert = require('assert'); -var support = require('./support'); -var Update = support.resolve(); -var update; - -describe('.config', function() { - beforeEach(function() { - update = new Update(); - }); - - // describe('loading config', function() { - // it('should map "plugins" when app.config exists', function() { - // app.use(options()); - // app.use(config()); - // app.use(pipeline()); - // assert(app.config.config.hasOwnProperty('plugins')); - // }); - - // it('should register plugin functions from config', function() { - // app.use(options()); - // app.use(config()); - // app.use(pipeline()); - // var args = { - // plugins: { - // a: function() {}, - // b: function() {}, - // c: function() {}, - // } - // }; - // app.config.process(args); - // assert(app.plugins.hasOwnProperty('a')); - // assert(app.plugins.hasOwnProperty('b')); - // assert(app.plugins.hasOwnProperty('c')); - // }); - - // it('should register plugins from config paths', function() { - // app.use(options()); - // app.use(config()); - // app.use(pipeline()); - // var args = { - // plugins: { - // a: 'test/fixtures/plugins/a.js', - // b: 'test/fixtures/plugins/b.js', - // c: 'test/fixtures/plugins/c.js', - // } - // }; - - // app.config.process(args); - // assert(app.plugins.hasOwnProperty('a')); - // assert(app.plugins.hasOwnProperty('b')); - // assert(app.plugins.hasOwnProperty('c')); - - // assert(typeof app.plugins.a === 'function'); - // assert(typeof app.plugins.b === 'function'); - // assert(typeof app.plugins.c === 'function'); - // }); - - // it('should register plugins with keys as paths', function() { - // app.use(options()); - // app.use(config()); - // app.use(pipeline()); - // var args = { - // plugins: { - // 'test/fixtures/plugins/a.js': {aaa: 'bbb'}, - // 'test/fixtures/plugins/b.js': {bbb: 'ccc'}, - // 'test/fixtures/plugins/c.js': {ddd: 'eee'} - // } - // }; - - // app.config.process(args); - // assert(app.plugins.hasOwnProperty('a')); - // assert(app.plugins.hasOwnProperty('b')); - // assert(app.plugins.hasOwnProperty('c')); - - // assert(typeof app.plugins.a === 'function'); - // assert(typeof app.plugins.b === 'function'); - // assert(typeof app.plugins.c === 'function'); - // }); - - // it('should throw an error when invalid config is used', function(cb) { - // app.use(options()); - // app.use(config()); - // app.use(pipeline()); - // var args = {plugins: {'test/fixtures/plugins/a.js': null}}; - // try { - // app.config.process(args); - // cb(new Error('expected an error')); - // } catch (err) { - // assert(err); - // assert(err.message); - // assert(/configuration/.test(err.message)); - // cb(); - // } - // }); - // }); -}); diff --git a/test/update.extendGenerator.js b/test/update.extendGenerator.js deleted file mode 100644 index 97586e5..0000000 --- a/test/update.extendGenerator.js +++ /dev/null @@ -1,50 +0,0 @@ -'use strict'; - -/* deps: coveralls istanbul */ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var Update = support.resolve(); -var Base = Update.Base; -var update; - -describe('update.extendGenerator', function() { - beforeEach(function() { - update = new Update(); - }); - - it('should throw an error when trying to extend an instance', function(cb) { - var foo = new Update({name: 'foo'}); - delete foo.fn; - - try { - update.extendGenerator(foo); - cb(new Error('Expected an error.')); - } catch (err) { - err.message.should.equal('generators must export a function to extend other generators'); - cb(); - } - }); - - it('should extend a generator', function() { - var foo = update.generator('foo', function(app) { - app.task('foo', function(cb) { cb(); }); - }); - - var bar = update.generator('bar', function(app) { - app.task('bar', function(cb) { cb(); }); - }); - - foo.tasks.should.have.property('foo'); - bar.tasks.should.have.property('bar'); - - bar.tasks.should.not.have.property('foo'); - foo.tasks.should.not.have.property('bar'); - - foo.extendGenerator(bar); - bar.tasks.should.have.property('foo'); - bar.extendGenerator(foo); - foo.tasks.should.have.property('bar'); - }); -}); diff --git a/test/update.generator.js b/test/update.generator.js deleted file mode 100644 index a1d5848..0000000 --- a/test/update.generator.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict'; - -/* deps: coveralls istanbul */ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var Update = support.resolve(); -var Base = Update.Base; -var update; -var one; -var two; - -describe('update.generator', function() { - before(function() { - update = new Update(); - }); - - it('should register a generator function from a file path', function() { - one = update.generator('one', './test/fixtures/one/generator.js'); - update.generators.should.have.property('one'); - assert(typeof update.generators.one === 'object'); - update.generators.one.should.deepEqual(one); - }); - - it('should register a Update instance from a file path', function() { - two = update.generator('two', './test/fixtures/two/updatefile.js'); - update.generators.should.have.property('two'); - assert(typeof update.generators.two === 'object'); - update.generators.two.should.deepEqual(two); - }); - - it('should get a registered generator by name', function() { - one = update.generator('one', './test/fixtures/one/generator.js'); - two = update.generator('two', './test/fixtures/two/updatefile.js'); - update.generator('one').should.deepEqual(one); - update.generator('two').should.deepEqual(two); - }); -}); diff --git a/test/update.getGenerator.js b/test/update.getGenerator.js deleted file mode 100644 index 28fa40e..0000000 --- a/test/update.getGenerator.js +++ /dev/null @@ -1,47 +0,0 @@ -'use strict'; - -var assert = require('assert'); -var Update = require('..'); -var update; - -describe('.getGenerator', function() { - beforeEach(function() { - update = new Update(); - }); - - it('should get a generator from the base instance', function() { - update.register('abc', function() {}); - var generator = update.getGenerator('abc'); - assert(generator); - assert(typeof generator === 'object'); - assert(generator.name === 'abc'); - }); - - it('should get nested generator', function() { - update.register('abc', function(abc) { - abc.register('def', function() {}); - }); - - var generator = update.getGenerator('abc.def'); - assert(generator); - assert(typeof generator === 'object'); - assert(generator.name === 'def'); - }); - - it('should get a deeply nested generator', function() { - update.register('abc', function(abc) { - abc.register('def', function(def) { - def.register('ghi', function(ghi) { - ghi.register('jkl', function(jkl) { - jkl.register('mno', function() {}); - }); - }); - }); - }); - - var generator = update.getGenerator('abc.def.ghi.jkl.mno'); - assert(generator); - assert(typeof generator === 'object'); - assert(generator.name === 'mno'); - }); -}); diff --git a/test/update.js b/test/update.js deleted file mode 100644 index c390a80..0000000 --- a/test/update.js +++ /dev/null @@ -1,135 +0,0 @@ -'use strict'; - -/* deps: coveralls istanbul */ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var Update = support.resolve(); -var update; - -describe('update', function() { - describe('constructor', function() { - it('should create an instance of Update:', function() { - update = new Update(); - assert(update instanceof Update); - }); - - it('should new up without new:', function() { - update = Update(); - assert(update instanceof Update); - }); - }); - - describe('prototype methods', function() { - beforeEach(function() { - update = new Update(); - }); - - it('should expose `addLeaf`', function() { - assert(typeof update.addLeaf === 'function'); - }); - - it('should expose `compose`', function() { - assert(typeof update.compose === 'function'); - }); - - it('should expose `generator`', function() { - assert(typeof update.generator === 'function'); - }); - - it('should expose `getGenerator`', function() { - assert(typeof update.getGenerator === 'function'); - }); - - it('should expose `registerPath`', function() { - assert(typeof update.registerPath === 'function'); - }); - - it('should expose `register`', function() { - assert(typeof update.register === 'function'); - }); - - it('should expose `extendGenerator`', function() { - assert(typeof update.extendGenerator === 'function'); - }); - - it('should expose `process`', function() { - assert(typeof update.process === 'function'); - }); - - it('should expose `each`', function() { - assert(typeof update.each === 'function'); - }); - - it('should expose `eachSeries`', function() { - assert(typeof update.eachSeries === 'function'); - }); - - it('should expose `scaffold`', function() { - assert(typeof update.scaffold === 'function'); - }); - }); - - describe('prototype properties', function() { - beforeEach(function() { - update = new Update(); - }); - - it('should expose `name`', function() { - assert(typeof update.name === 'string'); - }); - - it('should expose `base`', function() { - assert(typeof update.base === 'object'); - }); - }); - - describe('instance', function() { - beforeEach(function() { - update = new Update(); - }); - - it('should set `name` to `base` when `_name` is defined', function() { - assert.equal(update.name, 'base'); - }); - - it('should set `name` to `base` when `_name` is not defined', function() { - delete update._name; - assert.equal(update.name, 'base'); - }); - - it('should set `name` to `base` when `_appname` is not defined', function() { - delete update._name; - delete update._appname; - assert.equal(update.name, 'base'); - }); - - it('should allow name setter to be set (configurable)', function() { - update.name = 'base'; - assert.equal(update.name, 'base'); - }); - - it('should use `options.name` for `name`', function() { - update = new Update({name: 'update'}); - delete update._name; - assert.equal(update.name, 'update'); - }); - - it('should return `this` as `base`', function() { - update.base.should.deepEqual(update); - }); - - it('should return generator "base" as `base`', function() { - var base = new Update(); - update.register('base', base); - update.base.should.deepEqual(base); - }); - - it('should return update as `base`', function() { - var child = new Update(); - update.register('child', child); - child.base.should.deepEqual(update); - }); - }); -}); diff --git a/test/update.pkg.js b/test/update.pkg.js deleted file mode 100644 index 637cba6..0000000 --- a/test/update.pkg.js +++ /dev/null @@ -1,42 +0,0 @@ -'use strict'; - -require('mocha'); -var assert = require('assert'); -var support = require('./support'); -var Update = support.resolve(); -var update; - -describe('.pkg', function() { - beforeEach(function() { - update = new Update(); - }); - - describe('methods', function() { - it('should expose a pkg object', function() { - assert(update.pkg); - assert.equal(typeof update.pkg, 'object'); - }); - - it('should expose a pkg.set method', function() { - assert.equal(typeof update.pkg.set, 'function'); - }); - - it('should expose a pkg.get method', function() { - assert.equal(typeof update.pkg.get, 'function'); - }); - - it('should expose a pkg.del method', function() { - assert.equal(typeof update.pkg.del, 'function'); - }); - - it('should expose a pkg.union method', function() { - assert.equal(typeof update.pkg.union, 'function'); - }); - }); - - describe('cwd', function() { - it('should get the package.json from the working directory', function() { - assert.equal(update.pkg.get('name'), 'update'); - }); - }); -}); diff --git a/test/update.process.js b/test/update.process.js deleted file mode 100644 index 6d9c08f..0000000 --- a/test/update.process.js +++ /dev/null @@ -1,245 +0,0 @@ -'use strict'; - -require('mocha'); -var fs = require('fs'); -var path = require('path'); -var assert = require('assert'); -var rimraf = require('rimraf'); -var through = require('through2'); -var files = require('expand-files'); -var Update = require('..'); -var app, files, config; - -var fixtures = path.join(__dirname, 'fixtures'); -var actual = path.join(__dirname, 'actual'); - -function expand(options) { - var config = files(); - config.expand(options); - return config.files[0]; -} - -function output(name) { - return path.join(actual, name); -} - -function fixture(name) { - return path.join(fixtures, name); -} - -function exists(name) { - try { - fs.statSync(output(name)); - return true; - } catch (err) {} - return false; -} - -function base(cb) { - return through.obj(function(file, enc, next) { - var str = file.contents.toString(); - cb(file, str, next); - }); -} - -describe('process plugins', function() { - beforeEach(function(cb) { - app = new Update(); - rimraf(actual, cb); - }); - - afterEach(function(cb) { - rimraf(actual, cb); - }); - - describe('plugin', function() { - it('should use a plugin to modify file contents', function(cb) { - app.plugin('append', function(opts) { - opts = opts || {}; - return base(function(file, str, next) { - file.contents = new Buffer(str + opts.suffix); - next(null, file); - }); - }); - - config = expand({ - cwd: fixtures, - src: '*.txt', - dest: actual - }); - - app.process(config, {suffix: 'zzz'}) - .on('error', cb) - .on('data', function(data) { - var str = data.contents.toString(); - var end = str.slice(-3); - assert(end === 'zzz'); - }) - .on('end', function() { - assert(exists('example.txt')); - cb(); - }); - }); - - it('should run plugins defined on config.options', function(cb) { - function appendString(suffix) { - return base(function(file, str, next) { - file.contents = new Buffer(str + suffix); - next(null, file); - }); - } - - app.plugin('a', appendString('aaa')); - app.plugin('b', appendString('bbb')); - app.plugin('c', appendString('ccc')); - - config = expand({ - options: {pipeline: ['a', 'c']}, - cwd: fixtures, - src: 'a.txt', - dest: actual - }); - - app.process(config, {suffix: 'zzz'}) - .on('error', cb) - .on('data', function(data) { - var str = data.contents.toString(); - assert(str.indexOf('bbb') === -1); - var end = str.slice(-6); - assert(end === 'aaaccc'); - }) - .on('end', function() { - assert(exists('a.txt')); - cb(); - }); - }); - - it('should run plugins defined on process.options', function(cb) { - function appendString(suffix) { - return base(function(file, str, next) { - file.contents = new Buffer(str + suffix); - next(null, file); - }); - } - - app.plugin('a', appendString('aaa')); - app.plugin('b', appendString('bbb')); - app.plugin('c', appendString('ccc')); - - config = expand({ - cwd: fixtures, - src: 'a.txt', - dest: actual - }); - - app.process(config, {pipeline: ['a', 'c'], suffix: 'zzz'}) - .on('error', cb) - .on('data', function(data) { - var str = data.contents.toString(); - assert(str.indexOf('bbb') === -1); - var end = str.slice(-6); - assert(end === 'aaaccc'); - }) - .on('end', function() { - assert(exists('a.txt')); - cb(); - }); - }); - }); -}); - -describe('process()', function() { - beforeEach(function(cb) { - app = new Update(); - rimraf(actual, cb); - }); - - afterEach(function(cb) { - rimraf(actual, cb); - }); - - describe('setup', function() { - it('should clean out all test fixtures', function(cb) { - assert(!exists(actual)); - cb(); - }); - }); - - describe('streams', function() { - it('should process files from the process options.cwd', function(cb) { - config = expand({src: 'b.txt', dest: actual, cwd: fixtures}); - - app.process(config, {cwd: fixtures}) - .on('error', cb) - .on('end', function() { - assert(exists('b.txt')); - cb(); - }); - }); - - it('should use the cwd passed on the config.options.cwd', function(cb) { - assert(!exists('b.txt')); - - config = expand({ - cwd: fixtures, - src: 'b.txt', - dest: actual - }); - - app.process(config) - .on('error', cb) - .on('end', function() { - assert(exists('b.txt')); - cb(); - }); - }); - - it('should work with no options:', function(cb) { - config = expand({src: 'b.txt', dest: actual, cwd: fixtures}); - app.process(config) - .on('error', cb) - .on('end', function() { - assert(exists('b.txt')); - cb(); - }); - }); - - it('should process a single file', function(cb) { - assert(!exists('a.txt')); - - config = expand({ - cwd: fixtures, - src: 'a.txt', - dest: actual - }); - - app.process(config) - .on('error', cb) - .on('end', function() { - assert(exists('a.txt')); - cb(); - }); - }); - - it('should process a glob of files', function(cb) { - assert(!exists('a.txt')); - assert(!exists('b.txt')); - assert(!exists('c.txt')); - - config = expand({ - cwd: fixtures, - src: '*.txt', - dest: actual - }); - - app.process(config) - .on('error', cb) - .on('end', function() { - assert(exists('a.txt')); - assert(exists('b.txt')); - assert(exists('c.txt')); - cb(); - }); - }); - }); -}); diff --git a/test/update.register.js b/test/update.register.js deleted file mode 100644 index 069bcb8..0000000 --- a/test/update.register.js +++ /dev/null @@ -1,52 +0,0 @@ -'use strict'; - -/* deps: coveralls istanbul */ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var Update = support.resolve(); -var Base = Update.Base; -var update; - -describe('update.register', function() { - beforeEach(function() { - update = new Update(); - }); - - it('should register a Update instance', function() { - var child = new Update(); - update.register('child', child); - update.generators.should.have.property('child'); - assert(typeof update.generators.child === 'object'); - update.generators.child.should.deepEqual(child); - }); - - it('should register a generator function', function() { - var registered = false; - var child = update.register('child', function(app, base, env) { - registered = true; - assert(typeof app === 'object'); - assert(app.isUpdate === true); - }); - assert(registered); - update.generators.should.have.property('child'); - assert(typeof update.generators.child === 'object'); - update.generators.child.should.deepEqual(child); - }); - - it('should register a non-update instance', function() { - var child = new Base(); - update.register('child', child); - update.generators.should.have.property('child'); - assert(typeof update.generators.child === 'object'); - update.generators.child.should.deepEqual(child); - }); - - it('should register a generator from a string', function() { - var one = update.register('one', './test/fixtures/one/generator.js'); - update.generators.should.have.property('one'); - assert(typeof update.generators.one === 'object'); - update.generators.one.should.deepEqual(one); - }); -}); diff --git a/test/update.registerPath.js b/test/update.registerPath.js deleted file mode 100644 index 835b2ba..0000000 --- a/test/update.registerPath.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict'; - -/* deps: coveralls istanbul */ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var Update = support.resolve(); -var Base = Update.Base; -var update; - -describe('update.registerPath', function() { - beforeEach(function() { - update = new Update(); - }); - - it('should register a generator function from a filepath', function() { - var one = update.registerPath('one', './test/fixtures/one/generator.js'); - update.generators.should.have.property('one'); - assert(typeof update.generators.one === 'object'); - update.generators.one.should.deepEqual(one); - }); - - it('should register a Update instance from a filepath', function() { - var two = update.registerPath('two', './test/fixtures/two/updatefile.js'); - update.generators.should.have.property('two'); - assert(typeof update.generators.two === 'object'); - update.generators.two.should.deepEqual(two); - }); -}); diff --git a/test/updaters.js b/test/updaters.js new file mode 100644 index 0000000..5d1367c --- /dev/null +++ b/test/updaters.js @@ -0,0 +1,10 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var Update = require('..'); +var app; + +describe('updaters', function() { + +}); diff --git a/test/view.content.js b/test/view.content.js deleted file mode 100644 index a966e69..0000000 --- a/test/view.content.js +++ /dev/null @@ -1,31 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var fs = require('fs'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('content', function() { - beforeEach(function() { - app = new App(); - app.create('page'); - app.engine('tmpl', require('engine-base')); - }); - - it('should normalize the `content` property on a view to a string:', function(done) { - app.page('abc', {path: 'test/fixtures/templates/a.tmpl'}) - .set('read', function() { - this.contents = fs.readFileSync(this.path); - return this; - }); - - app.views.pages.abc.read(); - - assert('content' in app.views.pages.abc); - assert(typeof app.views.pages.abc.content === 'string'); - done(); - }); -}); diff --git a/test/view.events.js b/test/view.events.js deleted file mode 100644 index 837b955..0000000 --- a/test/view.events.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict'; - -require('should'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('view.option()', function() { - beforeEach(function() { - app = new App(); - app.create('page'); - }); - - it('should emit events:', function() { - app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}); - var page = app.pages.getView('a.tmpl'); - var events = []; - - page.on('option', function(key) { - events.push(key); - }); - - page.option('a', 'b'); - page.option('c', 'd'); - page.option('e', 'f'); - page.option({g: 'h'}); - - events.should.eql(['a', 'c', 'e', 'g']); - }); -}); diff --git a/test/view.js b/test/view.js deleted file mode 100644 index 753e4b8..0000000 --- a/test/view.js +++ /dev/null @@ -1,1150 +0,0 @@ -'use strict'; - -require('mocha'); -var should = require('should'); -var fs = require('fs'); -var path = require('path'); -var assert = require('assert'); -var Stream = require('stream'); -var es = require('event-stream'); -var support = require('./support'); -var App = support.resolve(); -var View = App.View; -var view; - -describe('View', function() { - describe('instance', function() { - it('should create an instance of View:', function() { - view = new View(); - assert(view instanceof View); - }); - }); - - describe('static methods', function() { - it('should expose `extend`:', function() { - assert(typeof View.extend === 'function'); - }); - }); - - describe('prototype methods', function() { - beforeEach(function() { - view = new View(); - }); - - it('should expose `set`:', function() { - assert(typeof view.set === 'function'); - }); - it('should expose `get`:', function() { - assert(typeof view.get === 'function'); - }); - it('should expose `del`:', function() { - assert(typeof view.del === 'function'); - }); - it('should expose `define`:', function() { - assert(typeof view.define === 'function'); - }); - it('should expose `visit`:', function() { - assert(typeof view.visit === 'function'); - }); - it('should expose `compile`:', function() { - assert(typeof view.compile === 'function'); - }); - it('should expose `render`:', function() { - assert(typeof view.render === 'function'); - }); - it('should expose `isType`:', function() { - assert(typeof view.isType === 'function'); - }); - }); - - describe('properties', function() { - it('should expose an `options` property', function() { - view = new View({}); - assert.deepEqual(view.options, {}); - assert(view.hasOwnProperty('options')); - }); - - it('should add `options` when passed on the constructor', function() { - view = new View({options: {foo: 'bar'}}); - assert(view.options.foo === 'bar'); - }); - - it('should expose a `data` property', function() { - view = new View({app: {}}); - assert.deepEqual(view.data, {}); - assert(view.hasOwnProperty('data')); - }); - - it('should add `data` when passed on the constructor', function() { - view = new View({data: {foo: 'bar'}}); - assert(view.data.foo === 'bar'); - }); - - it('should add `locals` when passed on the constructor', function() { - view = new View({locals: {foo: 'bar'}}); - assert(view.locals.foo === 'bar'); - }); - }); - - describe('set', function() { - it('should set properties on the object', function() { - view = new View(); - view.set('foo', 'bar'); - assert.equal(view.foo, 'bar'); - }); - }); - - describe('get', function() { - it('should get properties from the object', function() { - view = new View(); - view.set('foo', 'bar'); - assert.equal(view.get('foo'), 'bar'); - }); - }); - - describe('cwd', function() { - it('should get properties from the object', function() { - view = new View({cwd: 'test/fixtures'}); - assert(view.cwd === 'test/fixtures'); - }); - }); - - describe('clone', function() { - it('should clone the view:', function() { - view = new View({content: 'foo'}); - view.set({path: 'foo/bar'}); - view.set('options.one', 'two'); - var clone = view.clone(); - assert(clone.contents); - clone.set('baz', 'quux'); - clone.set('options.three', 'four'); - assert.equal(clone.get('foo'), view.get('foo')); - assert(clone.get('baz') === 'quux'); - assert(!view.get('baz')); - // not deep cloned - assert(clone.get('options.three') === 'four'); - assert(view.get('options.three') === 'four'); - }); - - it('should deep clone the entire object', function() { - view = new View({content: 'foo'}); - view.set({path: 'foo/bar'}); - view.set('options.one', 'two'); - var clone = view.clone({deep: true}); - clone.set('options.three', 'four'); - assert(view.get('options.one') === 'two'); - assert(clone.get('options.one') === 'two'); - assert(clone.get('options.three') === 'four'); - assert(!view.get('options.three')); - }); - }); - - describe('visit', function() { - it('should visit all properties on an object and call the specified method', function() { - view = new View(); - var obj = { - foo: 'bar', - bar: 'baz', - baz: 'bang' - }; - view.visit('set', obj); - assert.equal(view.get('foo'), 'bar'); - assert.equal(view.get('bar'), 'baz'); - assert.equal(view.get('baz'), 'bang'); - }); - - it('should visit all properties on all objects in an array and call the specified method', function() { - view = new View(); - var arr = [{foo: 'bar', bar: 'baz', baz: 'bang'}]; - view.visit('set', arr); - assert.equal(view.get('foo'), 'bar'); - assert.equal(view.get('bar'), 'baz'); - assert.equal(view.get('baz'), 'bang'); - }); - }); - - describe('compile', function() { - it('should get view.layout from view.data.layout', function() { - view = new View({path: 'foo', contents: 'a b c', data: {layout: 'default'}}); - assert(view.layout === 'default'); - }); - it('should get view.layout from view.options.layout', function() { - view = new View({path: 'foo', contents: 'a b c', options: {layout: 'default'}}); - assert(view.layout === 'default'); - }); - it('should get view.layout from view.locals.layout', function() { - view = new View({path: 'foo', contents: 'a b c', locals: {layout: 'default'}}); - assert(view.layout === 'default'); - }); - it('should get view.layout from the view', function() { - view = new View({path: 'foo', contents: 'a b c', layout: 'default'}); - assert(view.layout === 'default'); - }); - - it('should add a compiled function to `view.fn`', function() { - view = new View({path: 'foo', contents: 'a <%= name %> z'}); - view.compile(); - assert(typeof view.fn === 'function'); - }); - - it('should render a compiled template', function(done) { - view = new View({path: 'foo', contents: 'a <%= name %> z'}); - view.compile(); - view.render({name: 'Halle'}, function(err, res) { - if (err) return done(err); - assert(res.contents.toString() === 'a Halle z'); - done(); - }); - }); - - it('should render `fn` using data passed on the constructor', function(done) { - view = new View({ - path: 'foo', - contents: 'a <%= name %> z', - data: { - name: 'Brooke' - } - }); - - view.compile(); - view.render(function(err, res) { - if (err) return done(err); - assert(res.contents.toString() === 'a Brooke z'); - done(); - }); - }); - }); - - describe('render', function() { - it('should render a template', function(done) { - view = new View({path: 'foo', contents: 'a <%= name %> z'}); - view.render({name: 'Halle'}, function(err, res) { - if (err) return done(err); - assert(res.contents.toString() === 'a Halle z'); - done(); - }); - }); - - it('should render fn using data passed on the constructor', function(done) { - view = new View({ - path: 'foo', - contents: 'a <%= name %> z', - data: { - name: 'Brooke' - } - }); - - view.render(function(err, res) { - if (err) return done(err); - assert(res.contents.toString() === 'a Brooke z'); - done(); - }); - }); - - it('should pass errors in the callback.', function(done) { - view = new View({ - path: 'foo', - contents: 'a <%= name %> z' - }); - - view.render(function(err) { - assert(err.message === 'name is not defined'); - done(); - }); - }); - }); -}); - -/** - * The following unit tests are from Vinyl - * Since we inherit vinyl in View, we need - * to ensure that these still pass. - */ - -describe('View', function() { - describe('isVinyl()', function() { - it('should return true on a vinyl object', function(done) { - var view = new View(); - assert(View.isVinyl(view) === true); - done(); - }); - it('should return false on a normal object', function(done) { - assert(View.isVinyl({}) === false); - done(); - }); - it('should return false on a null object', function(done) { - assert(View.isVinyl({}) === false); - done(); - }); - }); - - describe('constructor()', function() { - it('should default cwd to process.cwd', function(done) { - var view = new View(); - view.cwd.should.equal(process.cwd()); - done(); - }); - - it('should default base to cwd', function(done) { - var cwd = '/'; - var view = new View({cwd: cwd}); - view.base.should.equal(cwd); - done(); - }); - - it('should default base to cwd even when none is given', function(done) { - var view = new View(); - view.base.should.equal(process.cwd()); - done(); - }); - - it('should default path to null', function(done) { - var view = new View(); - should.not.exist(view.path); - done(); - }); - - it('should default history to []', function(done) { - var view = new View(); - view.history.should.eql([]); - done(); - }); - - it('should default stat to null', function(done) { - var view = new View(); - should.not.exist(view.stat); - done(); - }); - - it('should default contents to null', function(done) { - var view = new View(); - should.not.exist(view.contents); - done(); - }); - - it('should set base to given value', function(done) { - var val = '/'; - var view = new View({base: val}); - view.base.should.equal(val); - done(); - }); - - it('should set cwd to given value', function(done) { - var val = '/'; - var view = new View({cwd: val}); - view.cwd.should.equal(val); - done(); - }); - - it('should set path to given value', function(done) { - var val = '/test.coffee'; - var view = new View({path: val}); - view.path.should.equal(val); - view.history.should.eql([val]); - done(); - }); - - it('should set history to given value', function(done) { - var val = '/test.coffee'; - var view = new View({history: [val]}); - view.path.should.equal(val); - view.history.should.eql([val]); - done(); - }); - - it('should set stat to given value', function(done) { - var val = {}; - var view = new View({stat: val}); - view.stat.should.equal(val); - done(); - }); - - it('should set contents to given value', function(done) { - var val = new Buffer('test'); - var view = new View({contents: val}); - view.contents.should.equal(val); - done(); - }); - }); - - describe('isBuffer()', function() { - it('should return true when the contents are a Buffer', function(done) { - var val = new Buffer('test'); - var view = new View({contents: val}); - view.isBuffer().should.equal(true); - done(); - }); - - it('should return false when the contents are a Stream', function(done) { - var val = new Stream(); - var view = new View({contents: val}); - assert(!view.isBuffer()); - done(); - }); - - it('should return false when the contents are a null', function(done) { - var view = new View({contents: null}); - assert(!view.isBuffer()); - done(); - }); - }); - - describe('isStream()', function() { - it('should return false when the contents are a Buffer', function(done) { - var val = new Buffer('test'); - var view = new View({contents: val}); - assert(!view.isStream()); - done(); - }); - - it('should return true when the contents are a Stream', function(done) { - var val = new Stream(); - var view = new View({contents: val}); - view.isStream().should.equal(true); - done(); - }); - - it('should return false when the contents are a null', function(done) { - var view = new View({contents: null}); - assert(!view.isStream()); - done(); - }); - }); - - describe('isNull()', function() { - it('should return false when the contents are a Buffer', function(done) { - var val = new Buffer('test'); - var view = new View({contents: val}); - assert(!view.isNull()); - done(); - }); - - it('should return false when the contents are a Stream', function(done) { - var val = new Stream(); - var view = new View({contents: val}); - assert(!view.isNull()); - done(); - }); - - it('should return true when the contents are a null', function(done) { - var view = new View({contents: null}); - view.isNull().should.equal(true); - done(); - }); - }); - - describe('isDirectory()', function() { - var fakeStat = { - isDirectory: function() { - return true; - } - }; - - it('should return false when the contents are a Buffer', function(done) { - var val = new Buffer('test'); - var view = new View({contents: val, stat: fakeStat}); - assert(!view.isDirectory()); - done(); - }); - - it('should return false when the contents are a Stream', function(done) { - var val = new Stream(); - var view = new View({contents: val, stat: fakeStat}); - assert(!view.isDirectory()); - done(); - }); - - it('should return true when the contents are a null', function(done) { - var view = new View({contents: null, stat: fakeStat}); - view.isDirectory().should.equal(true); - done(); - }); - }); - - describe('clone()', function() { - it('should copy all attributes over with Buffer', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Buffer('test') - }; - var view = new View(options); - var view2 = view.clone(); - - view2.should.not.equal(view, 'refs should be different'); - view2.cwd.should.equal(view.cwd); - view2.base.should.equal(view.base); - view2.path.should.equal(view.path); - view2.contents.should.not.equal(view.contents, 'buffer ref should be different'); - view2.contents.toString('utf8').should.equal(view.contents.toString('utf8')); - done(); - }); - - it('should copy buffer\'s reference with option contents: false', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.js', - contents: new Buffer('test') - }; - - var view = new View(options); - - var copy1 = view.clone({ contents: false }); - copy1.contents.should.equal(view.contents); - - var copy2 = view.clone({}); - copy2.contents.should.not.equal(view.contents); - - var copy3 = view.clone({ contents: 'any string' }); - copy3.contents.should.not.equal(view.contents); - - done(); - }); - - it('should copy all attributes over with Stream', function(done) { - var contents = new Stream.PassThrough(); - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: contents - }; - var view = new View(options); - var view2 = view.clone(); - - contents.write(new Buffer('wa')); - - process.nextTick(function() { - contents.write(new Buffer('dup')); - contents.end(); - }); - - view2.should.not.equal(view, 'refs should be different'); - view2.cwd.should.equal(view.cwd); - view2.base.should.equal(view.base); - view2.path.should.equal(view.path); - view2.contents.should.not.equal(view.contents, 'stream ref should not be the same'); - view.contents.pipe(es.wait(function(err, data) { - if (err) return done(err); - view2.contents.pipe(es.wait(function(err, data2) { - if (err) return done(err); - data2.should.not.equal(data, 'stream contents ref should not be the same'); - data2.should.eql(data, 'stream contents should be the same'); - })); - })); - done(); - }); - - it('should copy all attributes over with null', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - var view = new View(options); - var view2 = view.clone(); - - view2.should.not.equal(view, 'refs should be different'); - view2.cwd.should.equal(view.cwd); - view2.base.should.equal(view.base); - view2.path.should.equal(view.path); - should.not.exist(view2.contents); - done(); - }); - - it('should properly clone the `stat` property', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.js', - contents: new Buffer('test'), - stat: fs.statSync(__filename) - }; - - var view = new View(options); - var copy = view.clone(); - - assert(copy.stat.isFile()); - assert(!copy.stat.isDirectory()); - done(); - }); - - it('should properly clone the `history` property', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.js', - contents: new Buffer('test'), - stat: fs.statSync(__filename) - }; - - var view = new View(options); - var copy = view.clone(); - - copy.history[0].should.equal(options.path); - copy.path = 'lol'; - view.path.should.not.equal(copy.path); - done(); - }); - - it('should copy custom properties', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - - var view = new View(options); - view.custom = { a: 'custom property' }; - var view2 = view.clone(); - - view2.should.not.equal(view, 'refs should be different'); - view2.cwd.should.equal(view.cwd); - view2.base.should.equal(view.base); - view2.path.should.equal(view.path); - view2.custom.should.equal(view.custom); - view2.custom.a.should.equal(view.custom.a); - - done(); - }); - - it('should copy history', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - - var view = new View(options); - view.path = '/test/test.js'; - view.path = '/test/test-938di2s.js'; - var view2 = view.clone(); - - view2.history.should.eql([ - '/test/test.coffee', - '/test/test.js', - '/test/test-938di2s.js' - ]); - view2.history.should.not.equal([ - '/test/test.coffee', - '/test/test.js', - '/test/test-938di2s.js' - ]); - view2.path.should.eql('/test/test-938di2s.js'); - - done(); - }); - - it('should copy all attributes deeply', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - - var view = new View(options); - view.custom = { a: 'custom property' }; - - var view2 = view.clone(true); - view2.custom.should.eql(view.custom); - view2.custom.should.not.equal(view.custom); - view2.custom.a.should.equal(view.custom.a); - - var view3 = view.clone({ deep: true }); - view3.custom.should.eql(view.custom); - view3.custom.should.not.equal(view.custom); - view3.custom.a.should.equal(view.custom.a); - - var view4 = view.clone(false); - view4.custom.should.eql(view.custom); - view4.custom.should.equal(view.custom); - view4.custom.a.should.equal(view.custom.a); - - var view5 = view.clone({ deep: false }); - view5.custom.should.eql(view.custom); - view5.custom.should.equal(view.custom); - view5.custom.a.should.equal(view.custom.a); - - done(); - }); - }); - - describe('pipe()', function() { - it('should write to stream with Buffer', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Buffer('test') - }; - var view = new View(options); - var stream = new Stream.PassThrough(); - stream.on('data', function(chunk) { - should.exist(chunk); - (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); - chunk.toString('utf8').should.equal(options.contents.toString('utf8')); - }); - stream.on('end', function() { - done(); - }); - var ret = view.pipe(stream); - ret.should.equal(stream, 'should return the stream'); - }); - - it('should pipe to stream with Stream', function(done) { - var testChunk = new Buffer('test'); - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Stream.PassThrough() - }; - var view = new View(options); - var stream = new Stream.PassThrough(); - stream.on('data', function(chunk) { - should.exist(chunk); - (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); - chunk.toString('utf8').should.equal(testChunk.toString('utf8')); - done(); - }); - var ret = view.pipe(stream); - ret.should.equal(stream, 'should return the stream'); - - view.contents.write(testChunk); - }); - - it('should do nothing with null', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - var view = new View(options); - var stream = new Stream.PassThrough(); - stream.on('data', function() { - throw new Error('should not write'); - }); - stream.on('end', function() { - done(); - }); - var ret = view.pipe(stream); - ret.should.equal(stream, 'should return the stream'); - }); - - it('should write to stream with Buffer', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Buffer('test') - }; - var view = new View(options); - var stream = new Stream.PassThrough(); - stream.on('data', function(chunk) { - should.exist(chunk); - (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); - chunk.toString('utf8').should.equal(options.contents.toString('utf8')); - done(); - }); - stream.on('end', function() { - throw new Error('should not end'); - }); - var ret = view.pipe(stream, {end: false}); - ret.should.equal(stream, 'should return the stream'); - }); - - it('should pipe to stream with Stream', function(done) { - var testChunk = new Buffer('test'); - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Stream.PassThrough() - }; - var view = new View(options); - var stream = new Stream.PassThrough(); - stream.on('data', function(chunk) { - should.exist(chunk); - (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); - chunk.toString('utf8').should.equal(testChunk.toString('utf8')); - done(); - }); - stream.on('end', function() { - throw new Error('should not end'); - }); - var ret = view.pipe(stream, {end: false}); - ret.should.equal(stream, 'should return the stream'); - - view.contents.write(testChunk); - }); - - it('should do nothing with null', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - var view = new View(options); - var stream = new Stream.PassThrough(); - stream.on('data', function() { - throw new Error('should not write'); - }); - stream.on('end', function() { - throw new Error('should not end'); - }); - var ret = view.pipe(stream, {end: false}); - ret.should.equal(stream, 'should return the stream'); - process.nextTick(done); - }); - }); - - describe('inspect()', function() { - it('should return correct format when no contents and no path', function(done) { - var view = new View(); - view.inspect().should.equal(''); - done(); - }); - - it('should return correct format when Buffer and no path', function(done) { - var val = new Buffer('test'); - var view = new View({ - contents: val - }); - view.inspect().should.equal('>'); - done(); - }); - - it('should return correct format when Buffer and relative path', function(done) { - var val = new Buffer('test'); - var view = new View({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: val - }); - view.inspect().should.equal('>'); - done(); - }); - - it('should return correct format when Buffer and only path and no base', function(done) { - var val = new Buffer('test'); - var view = new View({ - cwd: '/', - path: '/test/test.coffee', - contents: val - }); - delete view.base; - view.inspect().should.equal('>'); - done(); - }); - - it('should return correct format when Stream and relative path', function(done) { - var view = new View({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Stream.PassThrough() - }); - view.inspect().should.equal('>'); - done(); - }); - - it('should return correct format when null and relative path', function(done) { - var view = new View({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }); - view.inspect().should.equal(''); - done(); - }); - }); - - describe('contents get/set', function() { - it('should work with Buffer', function(done) { - var val = new Buffer('test'); - var view = new View(); - view.contents = val; - view.contents.should.equal(val); - done(); - }); - - it('should work with Stream', function(done) { - var val = new Stream.PassThrough(); - var view = new View(); - view.contents = val; - view.contents.should.equal(val); - done(); - }); - - it('should work with null', function(done) { - var val = null; - var view = new View(); - view.contents = val; - (view.contents === null).should.equal(true); - done(); - }); - - it('should work with string', function(done) { - var val = 'test'; - var view = new View(); - view.contents = val; - view.contents.should.deepEqual(new Buffer(val)); - done(); - }); - }); - - describe('relative get/set', function() { - it('should error on set', function(done) { - var view = new View(); - try { - view.relative = 'test'; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should error on get when no base', function(done) { - var view = new View(); - delete view.base; - try { - view.relative; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should error on get when no path', function(done) { - var view = new View(); - try { - view.relative; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should return a relative path from base', function(done) { - var view = new View({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - view.relative.should.equal('test.coffee'); - done(); - }); - - it('should return a relative path from cwd', function(done) { - var view = new View({ - cwd: '/', - path: '/test/test.coffee' - }); - view.relative.should.equal(path.join('test', 'test.coffee')); - done(); - }); - }); - - describe('dirname get/set', function() { - it('should error on get when no path', function(done) { - var view = new View(); - try { - view.dirname; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should return the dirname of the path', function(done) { - var view = new View({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - view.dirname.should.equal('/test'); - done(); - }); - - it('should error on set when no path', function(done) { - var view = new View(); - try { - view.dirname = '/test'; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should set the dirname of the path', function(done) { - var view = new View({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - view.dirname = '/test/foo'; - view.path.should.equal('/test/foo/test.coffee'); - done(); - }); - }); - - describe('basename get/set', function() { - it('should error on get when no path', function(done) { - var view = new View(); - try { - view.basename; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should return the basename of the path', function(done) { - var view = new View({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - view.basename.should.equal('test.coffee'); - done(); - }); - - it('should error on set when no path', function(done) { - var view = new View(); - try { - view.basename = 'test.coffee'; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should set the basename of the path', function(done) { - var view = new View({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - view.basename = 'foo.png'; - view.path.should.equal('/test/foo.png'); - done(); - }); - }); - - describe('extname get/set', function() { - it('should error on get when no path', function(done) { - var view = new View(); - try { - view.extname; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should return the extname of the path', function(done) { - var view = new View({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - view.extname.should.equal('.coffee'); - done(); - }); - - it('should error on set when no path', function(done) { - var view = new View(); - try { - view.extname = '.coffee'; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should set the extname of the path', function(done) { - var view = new View({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - view.extname = '.png'; - view.path.should.equal('/test/test.png'); - done(); - }); - }); - - describe('path get/set', function() { - it('should record history when instantiation', function() { - var view = new View({ - cwd: '/', - path: '/test/test.coffee' - }); - - view.path.should.eql('/test/test.coffee'); - view.history.should.eql(['/test/test.coffee']); - }); - - it('should record history when path change', function() { - var view = new View({ - cwd: '/', - path: '/test/test.coffee' - }); - - view.path = '/test/test.js'; - view.path.should.eql('/test/test.js'); - view.history.should.eql(['/test/test.coffee', '/test/test.js']); - - view.path = '/test/test.coffee'; - view.path.should.eql('/test/test.coffee'); - view.history.should.eql(['/test/test.coffee', '/test/test.js', '/test/test.coffee']); - }); - - it('should not record history when set the same path', function() { - var view = new View({ - cwd: '/', - path: '/test/test.coffee' - }); - - view.path = '/test/test.coffee'; - view.path = '/test/test.coffee'; - view.path.should.eql('/test/test.coffee'); - view.history.should.eql(['/test/test.coffee']); - - // ignore when set empty string - view.path = ''; - view.path.should.eql('/test/test.coffee'); - view.history.should.eql(['/test/test.coffee']); - }); - - it('should throw when set path null in constructor', function() { - (function() { - View({ - cwd: '/', - path: null - }); - }).should.throw('path should be string'); - }); - - it('should throw when set path null', function() { - var view = new View({ - cwd: '/', - path: 'foo' - }); - - (function() { - view.path = null; - }).should.throw('path should be string'); - }); - }); -}); diff --git a/test/view.methods.js b/test/view.methods.js deleted file mode 100644 index fe49072..0000000 --- a/test/view.methods.js +++ /dev/null @@ -1,41 +0,0 @@ -'use strict'; - -require('should'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('view.option()', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('page'); - }); - - describe('.use', function() { - it('should expose `.use` for running plugins on a view:', function() { - app.page('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}) - .use(function() { - this.options.foo = 'bar'; - }) - .use(function() { - this.options.bar = 'baz'; - }); - - var page = app.pages.getView('a.tmpl'); - page.options.should.have.property('foo'); - page.options.should.have.property('bar'); - }); - }); - - describe('.render:', function() { - it('should expose `.render` for rendering a view:', function(done) { - app.page('a.tmpl', {path: 'a.tmpl', content: '<%= a %>', locals: {a: 'bbb'}}) - .render({}, function(err, res) { - if (err) return done(err); - res.contents.toString().should.equal('bbb'); - done(); - }); - }); - }); -}); diff --git a/test/view.option.js b/test/view.option.js deleted file mode 100644 index ebde730..0000000 --- a/test/view.option.js +++ /dev/null @@ -1,31 +0,0 @@ -'use strict'; - -require('should'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('view.option()', function() { - beforeEach(function() { - app = new App(); - app.create('page'); - }); - - it('should set an option:', function() { - app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}); - var page = app.pages.getView('a.tmpl'); - - page.options.should.not.have.property('foo'); - page.option('foo', 'bar'); - page.options.should.have.property('foo'); - }); - - it('should extend options:', function() { - app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}); - var page = app.pages.getView('a.tmpl'); - page.option('a', 'b'); - page.option('c', 'd'); - page.option('e', 'f'); - page.options.should.have.properties(['a', 'c', 'e']); - }); -}); diff --git a/test/view.render.js b/test/view.render.js deleted file mode 100644 index cd367c5..0000000 --- a/test/view.render.js +++ /dev/null @@ -1,54 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('helpers', function() { - describe('rendering', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('layouts', {viewType: 'layout'}); - app.create('pages'); - }); - - it('should expose `.render` for rendering a view:', function(done) { - app.page('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}) - .render({a: 'bbb'}, function(err, res) { - if (err) return done(err); - res.content.should.equal('bbb'); - done(); - }); - }); - - it('should render a view with a layout', function(done) { - app.layout('default.tmpl', {content: 'a {% body %} b'}); - app.page('a.tmpl', {content: '<%= title %>', layout: 'default.tmpl'}) - .render({title: 'zzz'}, function(err, res) { - if (err) return done(err); - res.content.should.equal('a zzz b'); - done(); - }); - }); - - it('should render a view with a layout', function(done) { - app.layout('foo.tmpl', {content: 'a {% body %} a'}); - app.layout('bar.tmpl', {content: 'b {% body %} b'}); - app.pages('a.tmpl', {content: '<%= title %>'}); - - app.pages.getView('a.tmpl') - .option('resolveLayout', function() { - return 'bar.tmpl'; - }) - .render({title: 'zzz'}, function(err, res) { - if (err) return done(err); - res.content.should.equal('b zzz b'); - done(); - }); - }); - }); -}); - diff --git a/test/view.set.js b/test/view.set.js deleted file mode 100644 index dd5e658..0000000 --- a/test/view.set.js +++ /dev/null @@ -1,36 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var fs = require('fs'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('set', function() { - beforeEach(function() { - app = new App(); - app.create('page'); - app.engine('tmpl', require('engine-base')); - }); - - it('should set a property on a view:', function(done) { - app.page('abc', {path: 'test/fixtures/templates/a.tmpl'}) - .set('read', function() { - this.contents = fs.readFileSync(this.path); - return this; - }); - - assert('read' in app.views.pages.abc); - app.views.pages.abc - .read() - .set('data.name', 'Brooke') - .render(function(err, res) { - if (err) return done(err); - - assert(res.content === 'Brooke'); - done(); - }); - }); -}); diff --git a/test/view.use.js b/test/view.use.js deleted file mode 100644 index d6fa379..0000000 --- a/test/view.use.js +++ /dev/null @@ -1,62 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var View = App.View; -var view; - -describe('view.use', function() { - beforeEach(function() { - view = new View(); - }); - - it('should expose the instance to `use`:', function(done) { - view.use(function(inst) { - assert(inst instanceof View); - done(); - }); - }); - - it('should be chainable:', function(done) { - view.use(function(inst) { - assert(inst instanceof View); - }) - .use(function(inst) { - assert(inst instanceof View); - }) - .use(function(inst) { - assert(inst instanceof View); - done(); - }); - }); - - it('should expose the view to a plugin:', function() { - view.use(function(view) { - assert(view instanceof View); - view.foo = function(str) { - return str + ' ' + 'bar'; - }; - }); - assert(view.foo('foo') === 'foo bar'); - }); - - it('should be chainable:', function() { - view - .use(function(view) { - view.a = 'aaa'; - }) - .use(function(view) { - view.b = 'bbb'; - }) - .use(function(view) { - view.c = 'ccc'; - }); - - assert(view.a === 'aaa'); - assert(view.b === 'bbb'); - assert(view.c === 'ccc'); - }); -}); diff --git a/test/viewTypes.js b/test/viewTypes.js deleted file mode 100644 index 866159b..0000000 --- a/test/viewTypes.js +++ /dev/null @@ -1,54 +0,0 @@ -'use strict'; - -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('viewType', function() { - describe('view types', function() { - beforeEach(function() { - app = new App(); - app.engine('tmpl', require('engine-base')); - }); - - it('should add collection (plural) to the `viewTypes` object', function() { - app.viewTypes = []; // reset - app.create('foo', {viewType: 'layout'}); - app.create('bar', {viewType: 'layout'}); - assert.deepEqual(app.viewTypes.layout, [ 'foos', 'bars' ]); - - app.create('baz', {viewType: 'renderable'}); - assert.deepEqual(app.viewTypes.renderable, [ 'bazs' ]); - }); - - it('should add collection to the given viewType', function() { - app.create('layout', {viewType: 'layout'}); - assert(app.layouts.options.viewType[0] === 'layout'); - }); - - it('should return true if a collection has the given viewType', function() { - app.create('layout', {viewType: 'layout'}); - assert(app.layouts.isType('layout')); - assert(!app.layouts.isType('partial')); - }); - - it('should add types to the collection', function() { - app.create('layout', {viewType: 'layout'}); - app.layouts.viewType('partial'); - assert(app.layouts.options.viewType[0] === 'layout'); - assert(app.layouts.options.viewType[1] === 'partial'); - }); - - it('should add a collection to multiple viewTypes', function() { - app.create('foo', {viewType: ['layout', 'renderable']}); - assert.deepEqual(app.foos.options.viewType, ['layout', 'renderable']); - }); - - it('should expose viewType on `view`', function() { - app.create('foo', {viewType: ['layout', 'renderable']}); - var a = app.foo('a', {content: ''}); - assert.deepEqual(app.foos.options.viewType, a.options.viewType); - }); - }); -}); diff --git a/test/views.js b/test/views.js deleted file mode 100644 index 04cfd65..0000000 --- a/test/views.js +++ /dev/null @@ -1,527 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var path = require('path'); -var assert = require('assert'); -var typeOf = require('kind-of'); -var support = require('./support'); -var isBuffer = require('is-buffer'); -var App = support.resolve(); -var List = App.List; -var View = App.View; -var Views = App.Views; -var collection; - -describe('views', function() { - describe('constructor', function() { - it('should create an instance of Views:', function() { - var collection = new Views(); - assert(collection instanceof Views); - }); - - it('should instantiate without `new`:', function() { - var collection = Views(); - assert(collection instanceof Views); - }); - }); - - describe('static methods', function() { - it('should expose `extend`:', function() { - assert(typeof Views.extend === 'function'); - }); - }); - - describe('prototype methods', function() { - beforeEach(function() { - collection = new Views(); - }); - - var methods = [ - 'use', - 'setView', - 'addView', - 'addViews', - 'addList', - 'getView', - 'constructor', - 'set', - 'get', - 'del', - 'define', - 'visit', - 'on', - 'once', - 'off', - 'emit', - 'listeners', - 'hasListeners' - ]; - - methods.forEach(function(method) { - it('should expose ' + method + ' method', function() { - assert(typeof collection[method] === 'function'); - }); - }); - - it('should expose isCollection property', function() { - assert(typeof collection.isCollection === 'boolean'); - }); - - it('should expose queue property', function() { - assert(Array.isArray(collection.queue)); - }); - - it('should expose views property', function() { - assert(typeOf(collection.views) === 'object'); - }); - - it('should expose options property', function() { - assert(typeOf(collection.options) === 'object'); - }); - }); - - describe('instance', function() { - beforeEach(function() { - collection = new Views(); - }); - - it('should set a value on the instance:', function() { - collection.set('a', 'b'); - assert(collection.a === 'b'); - }); - - it('should get a value from the instance:', function() { - collection.set('a', 'b'); - assert(collection.get('a') === 'b'); - }); - }); - - describe('option', function() { - beforeEach(function() { - collection = new Views(); - }); - - it('should set a key/value pair on options:', function() { - collection.option('a', 'b'); - assert(collection.options.a === 'b'); - }); - - it('should set an object on options:', function() { - collection.option({c: 'd'}); - assert(collection.options.c === 'd'); - }); - - it('should get an option:', function() { - collection.option({c: 'd'}); - var c = collection.option('c'); - assert(c === 'd'); - }); - }); - - describe('addView', function() { - beforeEach(function() { - collection = new Views(); - }); - - it('should throw an error when args are invalid:', function() { - (function() { - collection.addView(function() {}); - }).should.throw('expected value to be an object.'); - }); - - it('should add a view to `views`:', function() { - collection.addView('foo'); - collection.views.should.have.property('foo'); - - collection.addView('one', {content: '...'}); - assert(typeof collection.views.one === 'object'); - assert(isBuffer(collection.views.one.contents)); - }); - - it('should create an instance of `View`:', function() { - collection.addView('one', {content: '...'}); - assert(collection.views.one instanceof collection.View); - }); - - it('should allow an `View` constructor to be passed:', function() { - View.prototype.foo = function(key, value) { - this[key] = value; - }; - collection = new Views({View: View}); - collection.addView('one', {content: '...'}); - collection.views.one.foo('bar', 'baz'); - assert(collection.views.one.bar === 'baz'); - }); - - it('should allow an instance of `View` to be passed:', function() { - var collection = new Views({View: View}); - var view = new View({content: '...'}); - collection.addView('one', view); - view.set('abc', 'xyz'); - assert(collection.views.one instanceof collection.View); - assert(isBuffer(collection.views.one.contents)); - assert(collection.views.one.abc === 'xyz'); - }); - - it('should expose the `isType` method on items', function() { - var collection = new Views({View: View}); - var view = new View({content: '...'}); - collection.setView('one', view); - - var one = collection.getView('one'); - assert(one.isType('renderable')); - }); - - it('should set viewTypes on a collection', function() { - var collection = new Views({View: View}); - collection.viewType(['partial']); - - var view = new View({content: '...'}); - collection.setView('one', view); - - var one = collection.getView('one'); - assert(!one.isType('renderable')); - assert(one.isType('partial')); - }); - }); - - describe('addViews', function() { - beforeEach(function() { - collection = new Views(); - }); - - it('should emit an error if a string glob pattern is passed', function(done) { - try { - collection.addViews('*.js'); - done(new Error('expected an error')); - } catch (err) { - assert(err); - assert(err.message); - assert(/glob/.test(err.message)); - done(); - } - }); - - it('should emit an error if an array glob pattern is passed', function(done) { - try { - collection.addViews(['*.js']); - done(new Error('expected an error')); - } catch (err) { - assert(err); - assert(err.message); - assert(/glob/.test(err.message)); - done(); - } - }); - - it('should add multiple views:', function() { - collection.addViews({ - one: {content: 'foo'}, - two: {content: 'bar'} - }); - assert(isBuffer(collection.views.one.contents)); - assert(isBuffer(collection.views.two.contents)); - }); - - it('should return the collection instance for chaining:', function() { - var views = collection.addViews({ - one: {content: 'foo'}, - two: {content: 'bar'} - }); - - var view = views.getView('one'); - assert(view); - assert(view.content); - assert(view.content === 'foo'); - }); - - it('should create views from an instance of Views', function() { - collection.addViews({ - one: {content: 'foo'}, - two: {content: 'bar'} - }); - var pages = new Views(collection); - assert(isBuffer(pages.views.one.contents)); - assert(isBuffer(pages.views.two.contents)); - }); - - it('should add an array of views:', function() { - collection.addViews([ - {path: 'one', content: 'foo'}, - {path: 'two', content: 'bar'} - ]); - assert(isBuffer(collection.views.one.contents)); - assert(isBuffer(collection.views.two.contents)); - }); - }); - - describe('view', function() { - beforeEach(function() { - collection = new Views(); - }); - - it('should return a single collection view from a key-value pair', function() { - var one = collection.view('one', {content: 'foo'}); - var two = collection.view('two', {content: 'bar'}); - - assert(one.isView); - assert(one.path === 'one'); - assert(two.isView); - assert(two.path === 'two'); - }); - - it('should return a single collection view from an object', function() { - var one = collection.view({path: 'one', content: 'foo'}); - var two = collection.view({path: 'two', content: 'bar'}); - - assert(one.isView); - assert(one.path === 'one'); - assert(two.isView); - assert(two.path === 'two'); - }); - }); - - describe('addList', function() { - beforeEach(function() { - collection = new Views(); - }); - - it('should emit an error if a string glob pattern is passed', function(done) { - try { - collection.addList('*.js'); - done(new Error('expected an error')); - } catch (err) { - assert(err); - assert(err.message); - assert(/glob/.test(err.message)); - done(); - } - }); - - it('should emit an error if an array glob pattern is passed', function(done) { - try { - collection.addList(['*.js']); - done(new Error('expected an error')); - } catch (err) { - assert(err); - assert(err.message); - assert(/glob/.test(err.message)); - done(); - } - }); - - it('should add a list of views:', function() { - collection.addList([ - {path: 'one', content: 'foo'}, - {path: 'two', content: 'bar'} - ]); - assert(isBuffer(collection.views.one.contents)); - assert(isBuffer(collection.views.two.contents)); - }); - - it('should add a list from the constructor:', function() { - var list = new List([ - {path: 'one', content: 'foo'}, - {path: 'two', content: 'bar'} - ]); - - collection = new Views(list); - assert(isBuffer(collection.views.one.contents)); - assert(isBuffer(collection.views.two.contents)); - }); - - it('should add list items from the constructor:', function() { - var list = new List([ - {path: 'one', content: 'foo'}, - {path: 'two', content: 'bar'} - ]); - - collection = new Views(list.items); - assert(isBuffer(collection.views.one.contents)); - assert(isBuffer(collection.views.two.contents)); - }); - - it('should throw an error when list is not an array:', function() { - var views = new Views(); - (function() { - views.addList(); - }).should.throw('expected list to be an array.'); - - (function() { - views.addList({}); - }).should.throw('expected list to be an array.'); - - (function() { - views.addList('foo'); - }).should.throw('expected list to be an array.'); - }); - - it('should load an array of items from an event:', function() { - var pages = new Views(); - - pages.on('addList', function(list) { - while (list.length) { - pages.addView({path: list.pop()}); - } - this.loaded = true; - }); - - pages.addList(['a.txt', 'b.txt', 'c.txt']); - assert(pages.views.hasOwnProperty('a.txt')); - assert(pages.views['a.txt'].path === 'a.txt'); - }); - - it('should load an array of items from the addList callback:', function() { - var collection = new Views(); - - collection.addList(['a.txt', 'b.txt', 'c.txt'], function(fp) { - return {path: fp}; - }); - assert(collection.views.hasOwnProperty('a.txt')); - assert(collection.views['a.txt'].path === 'a.txt'); - }); - - it('should load an object of views from an event:', function() { - var collection = new Views(); - - collection.on('addViews', function(views) { - for (var key in views) { - collection.addView('foo/' + key, views[key]); - delete views[key]; - } - }); - - collection.addViews({ - a: {path: 'a.txt'}, - b: {path: 'b.txt'}, - c: {path: 'c.txt'} - }); - - assert(collection.views.hasOwnProperty('foo/a')); - assert(collection.views['foo/a'].path === 'a.txt'); - }); - - it('should signal `loaded` when finished:', function() { - var collection = new Views(); - - collection.on('addViews', function(views) { - for (var key in views) { - if (key === 'c') break; - collection.addView('foo/' + key, views[key]); - } - }); - - collection.addViews({ - a: {path: 'a.txt'}, - b: {path: 'b.txt'}, - c: {path: 'c.txt'} - }); - - assert(collection.views.hasOwnProperty('foo/a')); - assert(!collection.views.hasOwnProperty('foo/c')); - assert(collection.views['foo/a'].path === 'a.txt'); - }); - }); - - describe('getView', function() { - beforeEach(function() { - collection = new Views(); - }); - it('should get a view from `views`:', function() { - collection.addView('one', {content: 'aaa'}); - collection.addView('two', {content: 'zzz'}); - assert(isBuffer(collection.views.one.contents)); - assert(isBuffer(collection.getView('one').contents)); - assert(collection.getView('one').contents.toString() === 'aaa'); - assert(collection.getView('two').contents.toString() === 'zzz'); - }); - }); - - describe('count', function() { - beforeEach(function() { - collection = new Views(); - }); - - it('should get the number of views:', function() { - collection.addView('one', {content: 'aaa'}); - collection.addView('two', {content: 'zzz'}); - assert(Object.keys(collection.views).length === 2); - }); - }); -}); - -describe('options', function() { - describe('options.renameKey', function() { - beforeEach(function() { - collection = new Views({ - renameKey: function(key) { - return path.basename(key); - } - }); - }); - - it('should use a custom rename key function on view keys', function() { - collection.addView('a/b/c/d.hbs', {content: 'foo bar baz'}); - assert(collection.views['d.hbs'].contents.toString() === 'foo bar baz'); - }); - - it('should get a view with the renamed key:', function() { - collection.addView('a/b/c/d.hbs', {content: 'foo bar baz'}); - assert(collection.getView('d.hbs').contents.toString() === 'foo bar baz'); - }); - - it('should get a view with the original key:', function() { - collection.addView('a/b/c/d.hbs', {content: 'foo bar baz'}); - assert(collection.getView('a/b/c/d.hbs').contents.toString() === 'foo bar baz'); - }); - }); -}); - -describe('queue', function() { - beforeEach(function() { - collection = new Views(); - }); - - it('should emit arguments on addView', function(done) { - collection.on('addView', function(args) { - assert(args[0] === 'a'); - assert(args[1] === 'b'); - assert(args[2] === 'c'); - assert(args[3] === 'd'); - assert(args[4] === 'e'); - done(); - }); - - collection.addView('a', 'b', 'c', 'd', 'e'); - }); - - it('should expose the `queue` property for loading views', function() { - collection.queue.push(collection.view('b', {path: 'b'})); - - collection.addView('a', {path: 'a'}); - assert(collection.views.hasOwnProperty('a')); - assert(collection.views.hasOwnProperty('b')); - }); - - it('should load all views on the queue when addView is called', function() { - collection.on('addView', function(args) { - var len = args.length; - var last = args[len - 1]; - if (typeof last === 'string') { - args[len - 1] = { content: last }; - } - }); - - collection.addView('a.html', 'aaa'); - collection.addView('b.html', 'bbb'); - collection.addView('c.html', 'ccc'); - - assert(collection.views.hasOwnProperty('a.html')); - assert(collection.getView('a.html').content === 'aaa'); - assert(collection.views.hasOwnProperty('b.html')); - assert(collection.getView('b.html').content === 'bbb'); - assert(collection.views.hasOwnProperty('c.html')); - assert(collection.getView('c.html').content === 'ccc'); - }); -}); diff --git a/test/views.use.js b/test/views.use.js deleted file mode 100644 index cc9ef23..0000000 --- a/test/views.use.js +++ /dev/null @@ -1,158 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var Views = App.Views; -var View = App.View; -var collection; - -describe('views.use', function() { - beforeEach(function() { - collection = new Views(); - }); - - it('should expose the instance to `use`:', function(done) { - collection.use(function(inst) { - assert(inst instanceof Views); - done(); - }); - }); - - it('should be chainable:', function(done) { - collection.use(function(inst) { - assert(inst instanceof Views); - }) - .use(function(inst) { - assert(inst instanceof Views); - }) - .use(function(inst) { - assert(inst instanceof Views); - done(); - }); - }); - - it('should expose the collection to a plugin:', function() { - collection.use(function(views) { - assert(views instanceof Views); - views.foo = views.addView.bind(views); - }); - - collection.foo('a', {content: '...'}); - assert(collection.views.hasOwnProperty('a')); - }); - - it('should expose collection when chained:', function() { - collection - .use(function(views) { - assert(views instanceof Views); - views.foo = views.addView.bind(views); - }) - .use(function(views) { - assert(views instanceof Views); - views.bar = views.addView.bind(views); - }) - .use(function(views) { - assert(views instanceof Views); - views.baz = views.addView.bind(views); - }); - - var pages = collection; - - pages.foo({path: 'a', content: '...'}); - pages.bar({path: 'b', content: '...'}); - pages.baz({path: 'c', content: '...'}); - - assert(collection.views.hasOwnProperty('a')); - assert(collection.views.hasOwnProperty('b')); - assert(collection.views.hasOwnProperty('c')); - }); - - it('should work when a custom `View` constructor is passed:', function() { - collection = new Views({View: require('vinyl')}); - collection - .use(function(views) { - assert(views instanceof Views); - views.foo = views.addView.bind(views); - }) - .use(function(views) { - assert(views instanceof Views); - views.bar = views.addView.bind(views); - }) - .use(function(views) { - assert(views instanceof Views); - views.baz = views.addView.bind(views); - }); - - var pages = collection; - - pages.foo({path: 'a', content: '...'}); - pages.bar({path: 'b', content: '...'}); - pages.baz({path: 'c', content: '...'}); - - assert(collection.views.hasOwnProperty('a')); - assert(collection.views.hasOwnProperty('b')); - assert(collection.views.hasOwnProperty('c')); - }); - - it('should pass to view `use` if a function is returned:', function() { - collection.use(function(views) { - assert(views instanceof Views); - - return function(view) { - view.foo = views.addView.bind(views); - assert(view instanceof View); - }; - }); - - collection.addView('a', {content: '...'}) - .foo({path: 'b', content: '...'}) - .foo({path: 'c', content: '...'}) - .foo({path: 'd', content: '...'}); - - assert(collection.views.hasOwnProperty('a')); - assert(collection.views.hasOwnProperty('b')); - assert(collection.views.hasOwnProperty('c')); - assert(collection.views.hasOwnProperty('d')); - }); - - it('should be chainable when a view function is returned:', function() { - collection - .use(function(views) { - assert(views instanceof Views); - - return function(view) { - view.foo = views.addView.bind(views); - assert(view instanceof View); - }; - }) - .use(function(views) { - assert(views instanceof Views); - - return function(view) { - view.bar = views.addView.bind(views); - assert(view instanceof View); - }; - }) - .use(function(views) { - assert(views instanceof Views); - - return function(view) { - view.baz = views.addView.bind(views); - assert(view instanceof View); - }; - }); - - collection.addView('a', {content: '...'}) - .foo({path: 'b', content: '...'}) - .bar({path: 'c', content: '...'}) - .baz({path: 'd', content: '...'}); - - assert(collection.views.hasOwnProperty('a')); - assert(collection.views.hasOwnProperty('b')); - assert(collection.views.hasOwnProperty('c')); - assert(collection.views.hasOwnProperty('d')); - }); -}); diff --git a/updatefile.js b/updatefile.js deleted file mode 100644 index b9f4e61..0000000 --- a/updatefile.js +++ /dev/null @@ -1,150 +0,0 @@ -'use strict'; - -var fs = require('fs'); -var path = require('path'); -var utils = require('./lib/utils'); - -/** - * This is the defalt updater, it can also be used - * to extend other updaters. - */ - -module.exports = function(update, base, env) { - var async = utils.async; - var glob = utils.glob; - - /** - * TODO: User help and defaults - */ - - update.register('defaults', function(app) { - app.task('init', function(cb) { - app.questions.set('init', 'Want to generate a new project?'); - app.ask('init', { force: true }, function(err, answers) { - if (err) return cb(err); - - if (answers.init === 'y') { - var generate = require('generate')(); - generate.fn(update, update, update.env); - update.build(['prompt', 'files', 'write'], cb); - } else { - cb(); - } - }); - }); - - app.task('help', function(cb) { - console.log('Would you like to choose a updater to run?'); - console.log('(implement me!)') - cb(); - }); - - app.task('error', function(cb) { - console.log('update > error (implement me!)'); - cb(); - }); - }); - - /** - * Data store tasks - */ - - update.register('store', function(app) { - app.task('del', function(cb) { - update.store.del({ force: true }); - console.log('deleted data store'); - cb(); - }); - }); - - /** - * User prompts - */ - - update.task('prompt', function(cb) { - var pkg = env.config.pkg; - - if (!pkg || env.user.isEmpty || env.argv.raw.init) { - forceQuestions(update); - } - - update.questions.setData(pkg || {}); - update.ask({ save: false }, function(err, answers) { - if (err) return cb(err); - if (!pkg) answers = {}; - - answers.name = answers.name || utils.project(); - answers.varname = utils.namify(answers.name); - update.set('answers', answers); - cb(); - }); - }); - - update.plugin('render', update.renderFile.bind(update, 'text')); - - /** - * Default configuration settings - */ - - update.task('defaultConfig', function(cb) { - update.lazyIgnores(); - update.engine(['md', 'text'], require('engine-base')); - update.data({year: new Date().getFullYear()}); - cb(); - }); - - /** - * Load files to be rendered - */ - - update.task('files', ['defaultConfig'], function(cb) { - var ignore = update.get('cache.ignores'); - update.files(['*'], {cwd: update.cwd, dot: true, ignore: ignore}); - cb(); - }); - - /** - * Write files to disk - */ - - update.task('write', function() { - var plugins = update.get('argv.plugins'); - var dest = update.get('argv.dest'); - var data = update.get('answers'); - - return update.toStream('files') - .pipe(base.pipeline(plugins)) - .pipe(update.dest(rename(dest))); - }); - - /** - * Default task to be run - */ - - update.task('default', ['files', 'write']); -}; - -/** - * First init questions to be asked - */ - -function forceQuestions(update) { - update.questions.options.forceAll = true; -} - -/** - * Rename template files - */ - -function rename(dest) { - return function(file) { - if (/\/templates\//.test(file.path)) { - file.basename = file.basename.replace(/^_/, '.'); - file.basename = file.basename.replace(/^\$/, ''); - } - - file.base = file.dest || dest || path.dirname(file.path); - file.path = path.join(file.base, file.basename); - return file.base; - }; -} From 0499fde74273cf4e2f38d183dd47a18e78699418 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 28 Jun 2016 09:20:33 -0400 Subject: [PATCH 176/274] docs --- support/assemblefile.js | 46 +++++++ support/docs/_drafts/notes.md | 58 ++++++++ support/docs/api.plugins.md | 64 +++++++++ support/docs/api.register.md | 33 +++++ support/docs/api.updater.md | 33 +++++ support/docs/cli.built-in-updaters.md | 51 +++++++ support/docs/faq.md | 11 ++ support/docs/features.md | 14 ++ support/docs/installing-the-cli.md | 48 +++++++ support/docs/installing-updaters.md | 25 ++++ support/docs/introduction.md | 101 ++++++++++++++ support/docs/layouts/default.md | 7 + support/docs/tasks.md | 165 +++++++++++++++++++++++ support/docs/tutorial.md | 111 ++++++++++++++++ support/docs/updatefile.md | 46 +++++++ support/docs/updaters.md | 185 ++++++++++++++++++++++++++ support/lib/common.js | 41 ++++++ support/lib/middleware.js | 35 +++++ support/lib/paths.js | 20 +++ support/package.json | 43 ++++++ support/verbfile.js | 35 +++++ 21 files changed, 1172 insertions(+) create mode 100644 support/assemblefile.js create mode 100644 support/docs/_drafts/notes.md create mode 100644 support/docs/api.plugins.md create mode 100644 support/docs/api.register.md create mode 100644 support/docs/api.updater.md create mode 100644 support/docs/cli.built-in-updaters.md create mode 100644 support/docs/faq.md create mode 100644 support/docs/features.md create mode 100644 support/docs/installing-the-cli.md create mode 100644 support/docs/installing-updaters.md create mode 100644 support/docs/introduction.md create mode 100644 support/docs/layouts/default.md create mode 100644 support/docs/tasks.md create mode 100644 support/docs/tutorial.md create mode 100644 support/docs/updatefile.md create mode 100644 support/docs/updaters.md create mode 100644 support/lib/common.js create mode 100644 support/lib/middleware.js create mode 100644 support/lib/paths.js create mode 100644 support/package.json create mode 100644 support/verbfile.js diff --git a/support/assemblefile.js b/support/assemblefile.js new file mode 100644 index 0000000..868cd64 --- /dev/null +++ b/support/assemblefile.js @@ -0,0 +1,46 @@ +'use strict'; + +var path = require('path'); +var helpers = require('template-helpers'); +var generators = require('base-generators'); +var common = require('./lib/common'); +var paths = require('./lib/paths'); + +module.exports = function(app) { + app.helpers(helpers()); + app.use(generators()); + app.use(common()); + // app.register('verb', require('./verbfile')); + + // app.task('verb', function(cb) { + // app.generate('verb', cb); + // }); + + // app.task('default', ['verb'], function() { + // app.layouts(paths.tmpl('layouts/*.hbs')); + // app.includes(paths.tmpl('includes/*.hbs')); + // app.pages(paths.docs('**/*.md')); + // return app.toStream('pages') + // .pipe(app.renderFile()) + // // .pipe(app.dest(paths.site())); + // }); + + app.onLoad(/\.md$/, function(view, next) { + var data = '---\ntitle: '; + data += view.stem.charAt(0).toUpperCase() + view.stem.slice(1); + data += '\n'; + data += 'layout: default'; + data += 'related:'; + data += ' docs: []'; + data += '---\n\n'; + view.content = data + view.content; + next(); + }); + + app.task('default', function() { + app.pages('docs/**/*.md'); + return app.toStream('pages') + // .pipe(app.renderFile()) + .pipe(app.dest('docs')); + }); +}; diff --git a/support/docs/_drafts/notes.md b/support/docs/_drafts/notes.md new file mode 100644 index 0000000..5e549a6 --- /dev/null +++ b/support/docs/_drafts/notes.md @@ -0,0 +1,58 @@ +Visit the [getting started guide][getting-started] to learn more. + + +Not all generators are created for rendering templates and writing files to the file system. Some generators are created to handle a specific part of the build workflow. For example, [generate-dest][] does one specific thing: when `gen dest` is run in the command line, it will prompt you for the destination directory to use for any generated files. So you can run `gen dest foo` to set the destination directory for files written by `generator-foo`. You can run a generator more than once in the same command, so it's also possible to do: `gen dest foo dest bar`, if both `foo` and `bar` require different destinations. + +Generators can be stacked, chained, and nested, making it possible and easy to + +build-system features + +Powered by some of the same libraries used in [gulp][] and [assemble][], Generate was built from the ground up to kick ass at generating projects. + +maintain a separation of concerns between configuration and application logic. + +use of [generators](#generators) and [tasks](#tasks) + + +## tldr + +Install `update` globally with the following command: + +```js +$ npm install --global update +``` + + + +Update is installed globally and run with the `update` command. + + + +- download +- git commit! +- run `update` + + + +```js +var updater = require('{%= name %}'); +``` + + + +**Example** + +This example shows two updaters working together seamlessly. + +![Example of how to update a gulpfile.js](demo.gif) + +**Want to know more?** + +You can use update from the command line, or as a node.js library as a part of your own application. + +- Jump to [feature highlights](#feature-highlights) +- Visit the [getting started guide][getting-started] + +Continue on, and start updating! + +Using a combination of through the use of [updaters]() and [tasks](). intuitive CLI, and a powerful and expressive API, Update is easy to learn, and enjoyable to use. \ No newline at end of file diff --git a/support/docs/api.plugins.md b/support/docs/api.plugins.md new file mode 100644 index 0000000..aa36d89 --- /dev/null +++ b/support/docs/api.plugins.md @@ -0,0 +1,64 @@ +# Plugins API + +A plugin is function that takes an instance of `Update` and is registered with the `.use` method. See the [base-plugins][] documentation for additional details. + +### .use + +The `.use` method is used for registering plugins that should be immediately invoked. + +**Example** + +```js +var Update = require('update'); +var app = new Update(); + +function plugin(app) { + // "app" and "this" both expose the instance of update we created above +} + +app.use(plugin); +``` + +Once a plugin is invoked, it will not be called again. + +### .run + +If a plugin returns a function after it's invoked by `.use`, the function will be pushed onto an array allowing it to be called again by the `.run` method. + +**Example** + +```js +var Update = require('update'); +var app = new Update(); + +function plugin(app) { + // "app" and "this" both expose the instance of update we created above + return plugin; +} + +app.use(plugin); +``` + +We can now run all plugins that were pushed onto the `.fns` array on any arbitrary object: + +```js +var obj = {}; +app.run(obj); +``` + +Additionally: + +* If `obj` has a `.use` method, it will be used on each plugin (e.g. `obj.use(fn)`). Otherwise `fn(obj)`. +* If the plugin returns a function again and `obj` has a `.run` method, the plugin will be pushed onto the `obj.fns` array + +This can continue indefinitely as long as the plugin returns a function and the receiving object has `.use`/`.run` functions. + +## Updaters + +When plugins are [registered by name](../docs/updaters.md), they are referred to as "updaters". See the [updater documentation](../docs/updaters.md) for more details. + +## Related + +* [default-updater](default-updater.md) +* [resolving-updaters](resolving-updaters.md) +* [registering-updaters](registering-updaters.md) \ No newline at end of file diff --git a/support/docs/api.register.md b/support/docs/api.register.md new file mode 100644 index 0000000..900eb8b --- /dev/null +++ b/support/docs/api.register.md @@ -0,0 +1,33 @@ +# .register + +Register an updater function by name. Similar to [.updater](updater.md) but does not invoke the updater function. + +```js +app.register(name, fn); +``` + +**Params** + +* `name` **{String}**: name of the updater to register +* `fn` **{Function}**: updater function + +**Example** + +```js +var Update = require('update'); +var app = new Update(); + +// not invoked until called by `.update` +app.register('foo', function(app) { + // do updater stuff +}); + +app.update('foo', function(err) { + if (err) return console.log(err); +}); +``` + +## Related + +* [plugin](plugin.md) +* [updaters](updaters.md) \ No newline at end of file diff --git a/support/docs/api.updater.md b/support/docs/api.updater.md new file mode 100644 index 0000000..f146caf --- /dev/null +++ b/support/docs/api.updater.md @@ -0,0 +1,33 @@ +# .updater + +Register an updater function by name. Similar to [.register](register.md) but immediately invokes the updater function upon registering it. + +```js +app.updater(name, fn); +``` + +**Params** + +* `name` **{String}**: name of the updater to register +* `updater` **{Function}**: updater function + +**Example** + +```js +var Update = require('update'); +var app = new Update(); + +// immediately invoked +app.updater('bar', function(app) { + // do updater stuff +}); + +app.update('bar', function(err) { + if (err) return console.log(err); +}); +``` + +## Related + +* [plugin](plugin.md) +* [updaters](updaters.md) \ No newline at end of file diff --git a/support/docs/cli.built-in-updaters.md b/support/docs/cli.built-in-updaters.md new file mode 100644 index 0000000..c6970df --- /dev/null +++ b/support/docs/cli.built-in-updaters.md @@ -0,0 +1,51 @@ +# Built-in updaters + +Update only has a few built-in [updaters](docs/updaters.md) (these might be externalized at some point): + +* [init](#init): Choose the updaters to run by default each time `update` is run from the command line +* [list](#list): List all globally and locally installed updaters +* [show](#show): show the list of updaters that will run on the current project when the `update` command is given +* [new](#new): create a new `updatefile.js` in the current working directory +* [help](#help): show a help menu with all available commands + +## Usage + +### init + +Choose the updaters to run by default each time `update` is run from the command line: + +```sh +$ update init +``` + +### list + +List all globally and locally installed updaters: + +```sh +$ update list +``` + +### show + +Show the list of updaters that will run on the current project when the `update` command is given: + +```sh +$ update show +``` + +### new + +Create a new `updatefile.js` in the current working directory: + +```sh +$ update new +``` + +### help + +Display a help menu with all available commands: + +```sh +$ update help +``` \ No newline at end of file diff --git a/support/docs/faq.md b/support/docs/faq.md new file mode 100644 index 0000000..f29b00d --- /dev/null +++ b/support/docs/faq.md @@ -0,0 +1,11 @@ +# FAQ + +
+ +**What's an alias, and what do they do?** + +Update tries to find globally installed updaters using an "alias" first, falling back on the updater's full name if not found by its alias. + +A updater's alias is created by stripping the substring `update-` from the _full name_ of updater. Thus, when publishing a updater the naming convention `update-foo` should be used (where `foo` is the alias, and `update-foo` is the full name). + +Note that **no dots may be used in published updater names**. Aside from that, any characters considered valid by npm are fine. \ No newline at end of file diff --git a/support/docs/features.md b/support/docs/features.md new file mode 100644 index 0000000..040d776 --- /dev/null +++ b/support/docs/features.md @@ -0,0 +1,14 @@ +# Features + +Update offers an elegant and robust suite of methods, carefully organized to help you accomplish common activities in less time, including: + +* **unparalleled flow control**: through the use of [updaters][getting-started], [sub-updaters][getting-started] and [tasks][getting-started] +* **templates, scaffolds and boilerplates**: update a single file, initialize an entire project, or provide ad-hoc "components" throughout the duration of a project using any combination of [templates, scaffolds and boilerplates](#templates-scaffolds-and-boilerplates). +* **any engine**: use any template engine to render templates, including [handlebars][], [lodash][], [swig][] and [pug][] +* **prompts**: asks you for data when it can't find what it needs, and it's easy to customize prompts for any data you want. +* **data**: gathers data from the user's environment to populate "hints" in user prompts and render templates +* **streams**: interact with the file system, with full support for [gulp][] and [assemble][] plugins +* **smart plugins**: Update is built on [base][], so any "smart" plugin can be used +* **stores**: persist configuration settings, global defaults, project-specific defaults, answers to prompts, and so on. + +Visit the [getting started guide][getting-started] to learn more. \ No newline at end of file diff --git a/support/docs/installing-the-cli.md b/support/docs/installing-the-cli.md new file mode 100644 index 0000000..912b886 --- /dev/null +++ b/support/docs/installing-the-cli.md @@ -0,0 +1,48 @@ +# Installing the CLI + +To run update from the command line, you'll need to install Update's CLI globally first. You can do that now with the following command: + +```sh +$ npm install --global update +``` + +This adds the `update` command to your system path, allowing it to be run from any directory. + +You should now be able to use the `update` command to execute code in a local `updatefile.js` file, or to execute globally installed updaters by their [aliases](#aliases). + +**Init** + +If it's your first time using update, run `update init` to set your global defaults. + +**CLI help** + +``` +Usage: update [options] + +Command: Generator or tasks to run + +Examples: + + # run the "foo" updater + $ update foo + + # run the "bar" task on updater "foo" + $ update foo:bar + + # run multiple tasks on updater "foo" + $ update foo:bar,baz,qux + + # run a sub-updater on updater "foo" + $ update foo.abc + + # run task "xyz" on sub-updater "foo.abc" + $ update foo.abc:xyz + + Update attempts to automatically determine if "foo" is a task or updater. + If there is a conflict, you can force update to run updater "foo" + by specifying a task on the updater. Example: `update foo:default` +``` + +## Related + +* [installing-updaters](installing-updaters.md) \ No newline at end of file diff --git a/support/docs/installing-updaters.md b/support/docs/installing-updaters.md new file mode 100644 index 0000000..73e9f8d --- /dev/null +++ b/support/docs/installing-updaters.md @@ -0,0 +1,25 @@ +# Installing updaters + +Updaters are responsible for all of the "updating" that happens in update. You can find [updaters to install](#updaters to install) on npm, or create your own. + +If you author and publish a updater, please name your updater using the pattern `updater-foo`, where `foo` is the `alias` to be passed to Update's CLI for executing your updater (example: `update foo` would run `updater-foo`). + +_(Before proceding, please keep in mind that update's job is to make changes to files that are often neglected. You will get used to this quickly, but to prevent surprises always make sure your work is committed before running `update`)_ + +Let's install an updater so you can see how this works. + +```sh +$ npm install --global updater-license +``` + +_(The `license` updater will update the copyright statement your `LICENSE` or `LICENSE-MIT` file. If you don't use MIT, that's okay. This is just an example intended to give you the gist of running update. Just be sure to `git commit` first so you can revert any changes afterwards.)_ + +If `updater-license` installed successfully, you should now be able to run it with the following command: + +```sh +$ update license +``` + +## Related + +* [installing-the-cli](installing-the-cli.md) \ No newline at end of file diff --git a/support/docs/introduction.md b/support/docs/introduction.md new file mode 100644 index 0000000..9edf11b --- /dev/null +++ b/support/docs/introduction.md @@ -0,0 +1,101 @@ +# Introduction + +## What is update? + +Update is a new, open-source developer framework for automating updates of any kind to code projects. + +* update files that are typically excluded from the automated parts of the software lifecycle, and thus are mostly forgotten about after they're created. +* fix dates in copyrights, licenses and banners, removing deprecated fields from project manifests, updating settings in runtime config files, preferences in dotfiles, and so on. +* normalize configuration settings, verbiage, or preferences across all of your projects + +## How does it work? + +**Updaters** + +All "updates" are accomplished using plugins called [updaters](#updaters). Updaters are functions that are registered by name, and can be run by [command line](#command-line) or [API](#api). + +**Update core** + +Update itself is system for [creating](#creating-updaters), [registering](#registering-updaters), [resolving](#resolving-updaters) and [running](#running-updaters) updaters. + +## Updaters + +**What are updaters?** + +Updaters are plugins that provide all of the "updating" capabilities to update. Technically speaking, updaters are functions that are either registered by name using the [.register](#register) method, or directly using the [.use](#use) method. + +Updaters may be published to [npm](https://www.npmjs.com) using the `updater-foo` naming convention, where `foo` is the [alias](#aliases) of your updater. Published updaters can be installed locally or globally. + +1. **Plugins**: + +Since updaters tend to be used on unstructured data, or things that are often updated "by hand", the first few times you run update (depending on the updaters you run) you might be suprised at the number of inconsistencies and errors that are uncovered. + +## Command line + +### updatefile.js + +Each time `update` is run, Update's CLI looks for an [updatefile.js](docs/updatefile.md) in the current working directory: + +**If `updatefile.js` exists** + +If found, Update's CLI will attempt to load a local installation of the Update library using node's `require()` system, falling back to a global installation if necessary. Next, Update's CLI loads the configuration from your `updatefile.js`, and executes any tasks or updaters you've specified for it to run. + +**If `updatefile.js` does not exist** + +If not found, Update's CLI attempts to find any updaters you've specified for it to run by using node's `require()` system to search for locally installed modules with the name `updater-*`, + +### Running updaters + +To run updaters by command line, pass the [aliases](#aliases) or full [npm](https://www.npmjs.com) package names (if published) of the updaters to run after the `update` command. + +**Example** + +Run updaters `foo`, `bar` and `baz` in series: + +```sh +$ update foo bar baz +# or +$ update updater-foo updater-bar updater-baz +``` + +Note that _updaters are run in series_, so given the previous example, updater `bar` will not run until updater `foo` is completely finished executing. + +### Aliases + +Get the alias of an updater by removing the `updater-` substring from the begining of the full name. + +## Resolving updaters + +When run by command line, Update's CLI will attempt to find and run updaters matching the names you've given, by first searching in the local `updatefile.js`, then using node's `require()` system to find locally installed updaters, and last by searching for globally installed updaters. + +If any of the updaters specified is not found, _an error is thrown and the process will exit_. + +## API + +by passing the names of updaters to run to the `.update` + +## Update + +Updaters via CLI or API. (tasks are powered by [bach][], the same library used in [gulp][] v4.0). + +The main export of the library is a constructor function, `Update`. + +Updaters themselves are just functions that take an instance of `Update`. wrap code to be executed when the + +Update gives you a way to automate the maintenance of files that are typically excluded from the automated parts of the software lifecycle, and thus are mostly forgotten about after they're created. + +For example, if we were to sift the files in the average code project into major generic buckets we would end up with something like this: + +* **code**: the actual source code of the project (compiled, lib, src, and so on) +* **dist**: the "deliverable" of the project (this could be HTML, CSS, minified JavaScript, or something similar for non-web projects) +* **docs**: documentation for the project +* **everything else**: LICENSE and copyright files, dotfiles, manifests, config files, and so on. + +Update maintains **everything else**. + +## Related + +* [updaters](updaters.md) +* [tasks](tasks.md) +* [features](features.md) +* [faq](faq.md) \ No newline at end of file diff --git a/support/docs/layouts/default.md b/support/docs/layouts/default.md new file mode 100644 index 0000000..c10d48e --- /dev/null +++ b/support/docs/layouts/default.md @@ -0,0 +1,7 @@ +# <%= title %> + +{% body %} + +# Related + +<%= links(related.docs) %> \ No newline at end of file diff --git a/support/docs/tasks.md b/support/docs/tasks.md new file mode 100644 index 0000000..3d91241 --- /dev/null +++ b/support/docs/tasks.md @@ -0,0 +1,165 @@ +# Tasks + +Tasks are used for wrapping code that should be executed at a later point, either when specified by command line or explicitly run when using the API. + +- [Creating tasks](#creating-tasks) +- [Running tasks](#running-tasks) + * [Command line](#command-line) + * [Task API](#task-api) + + [.task](#task) + + [.build](#build) + + [.update](#update) + * [Task composition](#task-composition) + + [Task dependencies](#task-dependencies) + + [Alias tasks](#alias-tasks) + * [default task](#default-task) +- [Related](#related) + +## Creating tasks + +Tasks are asynchronous functions that are registered by name using the `.task` method, and can be run using the `.build` method. + +## Running tasks + +Tasks can be run by command line or API. + +### Command line + +Pass the names of the tasks to run after the `update` command. + +**Example** + +Run tasks `foo`, `bar` and `baz`: + +```sh +update foo bar baz +``` + +**Conflict resolution** + +You might notice that [updaters](updaters.md) can also be run from the command line using the same syntax. Update can usually determine whether you meant to call tasks or updaters. Visit the [running updaters](updaters.md#running-updaters) documentation for more information. + +### Task API + +#### .task + +Create a task: + +```js +app.task(name, fn); +``` + +**Params** + +* `name` **{String}**: name of the task to register +* `fn` **{Function}**: asynchronous callback function, or es6 generator function + +**Example** + +```js +app.task('default', function(cb) { + // do task stuff (be sure to call the callback) + cb(); +}); +``` + +**Stream or callback** + +When using update's file system API (`.src`/`.dest` etc), you can optionally return a stream instead of calling a callback. Either a callback must be called, or a stream must be returned, otherwise update has no way of knowing when a task is complete. + +#### .build + +Run one more tasks. + +**Params** + +* `names` **{String|Array|Function}**: names of one or more tasks to run, or callback function if you only want to run the [default task](#default-task) +* `callback`: callback function, invoked after all tasks have finished executing. The callback function exposes `err` as the only argument, with any errors that occurred during the execution of any tasks. + +**Example** + +```js +app.task('foo', function(cb) { + // do task stuff + cb(); +}); +app.task('foo', function(cb) { + // do task stuff + cb(); +}); + +app.build(['foo', 'bar'], function(err) { + if (err) return console.log(err); + console.log('done'); +}); +``` + +#### .update + +The `.update` may also be used to run tasks. However, `.update` can be used to run _tasks and updaters_, thus it will also look for updaters to run when a task is not found. + +_To ensure that only tasks are run, use the `.build` method._ + +See the [updaters documentation](#updaters) for more details. + +### Task composition + +#### Task dependencies + +When a task has "dependencies", this means that one or more other tasks need to finish before the task is executed. + +Dependencies can be passed as the second argument to the `.task` method. + +**Example** + +In the following example, task `foo` has dependencies `bar` and `baz`: + +```js +app.task('foo', ['bar', 'baz'], function(cb) { + // do task stuff + cb(); +}); +``` + +Task `foo` will not execute until tasks `bar` and `baz` have completed. + +#### Alias tasks + +An "alias" task is a task with one or more dependencies and _no callback_. + +**Example** + +In this example, task `foo` is an alias for tasks `bar` and `baz`: + +```js +app.task('foo', ['bar', 'baz']); +``` + +In this example, task `foo` is an alias for task `baz` + +```js +app.task('foo', ['baz']); +``` + +### default task + +The `default` task is run automatically when a callback is passed as the only argument: + +```js +app.task('default', function(cb) { + // do task stuff + cb(); +}); + +// no need to specify "default", but you can if you want +app.build(function(err) { + if (err) return console.log(err); + console.log('done'); +}); +``` + +## Related + +* [running-updaters](running-updaters.md) +* [updaters](updaters.md) +* [updatefile](updatefile.md) \ No newline at end of file diff --git a/support/docs/tutorial.md b/support/docs/tutorial.md new file mode 100644 index 0000000..e8720c6 --- /dev/null +++ b/support/docs/tutorial.md @@ -0,0 +1,111 @@ +# Quickstart + +The following intro only skims the surface of what update has to offer. For a more in-depth introduction, we highly recommend visiting the [getting started guide][getting-started]. + +**Create an updater** + +Add a `updatefile.js` to the current working directory with the following code: + +```js +module.exports = function(app) { + console.log('success!'); +}; +``` + +**Run an updater** + +Enter the following command: + +```sh +update +``` + +If successful, you should see `success!` in the terminal. + +**Create a task** + +Now, add a task to your updater. + +```js +module.exports = function(app) { + app.task('default', function(cb) { + console.log('success!'); + cb(); + }); +}; +``` + +Now, in the command line, run: + +```sh +$ update +# then try +$ update default +``` + +When a local `updatefile.js` exists, the `update` command is aliased to automatically run the `default` task if one exists. But you can also run the task with `update default`. + +**Run a task** + +Let's try adding more tasks to your updater: + +```js +module.exports = function(app) { + app.task('default', function(cb) { + console.log('default > success!'); + cb(); + }); + + app.task('foo', function(cb) { + console.log('foo > success!'); + cb(); + }); + + app.task('bar', function(cb) { + console.log('bar > success!'); + cb(); + }); +}; +``` + +Now, in the command line, run: + +```sh +$ update +# then try +$ update foo +# then try +$ update foo bar +``` + +**Run task dependencies** + +Now update your code to the following: + +```js +module.exports = function(app) { + app.task('default', ['foo', 'bar']); + + app.task('foo', function(cb) { + console.log('foo > success!'); + cb(); + }); + + app.task('bar', function(cb) { + console.log('bar > success!'); + cb(); + }); +}; +``` + +And run: + +```sh +$ update +``` + +You're now a master at running tasks with update! You can do anything with update tasks that you can do with [gulp][] tasks (we use and support gulp libraries after all!). + +**Next steps** + +Update does much more than this. For a more in-depth introduction, we highly recommend visiting the [getting started guide](https://github.com/update/getting-started). \ No newline at end of file diff --git a/support/docs/updatefile.md b/support/docs/updatefile.md new file mode 100644 index 0000000..5abbfd9 --- /dev/null +++ b/support/docs/updatefile.md @@ -0,0 +1,46 @@ +# updatefile.js + +If an `updatefile.js` exists in the current working directoy, Update's CLI will attempt to load it and run any updaters or tasks you've specified for it to run. + +Moreover, Update's CLI will use `updatefile.js` to create the ["default" updater](default-updater.md). + +## Creating an updatefile.js + +An `updatefile.js` may contain any custom JavaScript code, but must export a function that takes an instance of Update (`app`): + +**Example** + +```js +// -- updatefile.js -- +module.exports = function(app) { + // custom code here +}; +``` + +Inside this function, you can define [tasks](tasks.md), additional [updaters](updaters.md), or any other custom JavaScript code necessary for your updater: + +```js +module.exports = function(app) { + // register a task + app.task('default', function(cb) { + // do task stuff + cb(); + }); + + // register an updater + app.register('foo', function() { + + }); + + // register another updater + app.register('bar', function() { + + }); +}; +``` + +## Related + +* [installing-updaters](installing-updaters.md) +* [updaters](updaters.md) +* [tasks](tasks.md) \ No newline at end of file diff --git a/support/docs/updaters.md b/support/docs/updaters.md new file mode 100644 index 0000000..ce605e1 --- /dev/null +++ b/support/docs/updaters.md @@ -0,0 +1,185 @@ +# Updaters + +Updaters are [plugins](plugins.md) that are registered by name. This document describes how to create, register and run updaters. + +- [What is an updater?](#what-is-an-updater) +- [Creating updaters](#creating-updaters) +- [Registering updaters](#registering-updaters) +- [Running updaters](#running-updaters) +- [Resolving updaters](#resolving-updaters) + * [Tasks and updaters](#tasks-and-updaters) + * [Naming tips](#naming-tips) + * [Order of precendence](#order-of-precendence) +- [Discovering updaters](#discovering-updaters) +- [Default updater](#default-updater) +- [Related](#related) + +## What is an updater? + +Updaters are just plugins. The only difference between "updaters" and "plugins" is how they're registered. + +**Comparison to plugins** + +Here is a comparison to illustrate the differences between the two in detail: + +| | **Plugin** | **Updater** | +| --- | --- | --- | +| Registered with | [.use](plugins.md#use) method | [.register](#register) method or [.updater](#updater) method | +| Instance | Loaded onto "current" `Update` instance | A `new Update()` instance is created for every updater registered | +| Invoked | Immediately | `.register` deferred (lazy), `.updater` immediately | +| Run using | [.run](plugins.md#run): all plugins are run at once | `.update`: only specified plugin(s) are run | + +**Which method should I use?** + +In general, it's recommended that you use the `.register` method. In most cases update is smart enough to figure out when to invoke updater functions. + +However, there are always exceptions. If you create custom code and notice that update can't find the information it needs. Try using the `.updater` method to to invoke the function when the updater is registered. + +## Creating updaters + +An updater function takes an instance of `Update` as the first argument. + +**Example** + +```js +var update = require('update'); +var app = update(); + +function updater(app) { + console.log(app); +} +``` + +## Registering updaters + +Updaters may be registered using either of the following methods: + +* `.register`: if the plugin should not be invoked until it's called by `.update` (stays lazy while it's cached, this is preferred) +* `.updater`: if the plugin needs to be invoked immediately when registered + +**Example** + +```js +var Update = require('update'); +var app = new Update(); + +function updater(msg) { + return function(app) { + // "app" is the instance of update we created + console.log(msg); + }; +} + +app.register('foo', updater('One!!!')); +app.updater('bar', updater('Two!!!')); // <= invoked now + +// `updater` foo won't be invoked until called by `.update` +app.update(['foo', 'bar'], function(err) { + if (err) return console.log(err); + // 'One!!!' + // 'Two!!!' +}); +``` + +## Running updaters + +Updaters and their tasks can be run by command line or API. + +todo + +## Resolving updaters + +Updaters can be published to npm and installed globally or locally. But you there is no requirement that updaters must be published. You can also create custom updaters and register them using the [.register](#register) or [.updater](#updater) methods. + +This provides a great deal of flexibility, but it also means that we need a strategy for _finding updaters_ when `update` is run from the command line. + +### Tasks and updaters + +1. When both a task and an updater have the same name _on the same instance_, Update will always try to run the task first (this is unlikely to happen unless you intend for it to - there are [reasons to do this](#naming-tips)) + +### Naming tips + +Since the [.build](tasks.md#build) method only runs tasks, you can use this to your advantage by aliasing sub-generators with tasks. + +**Don't do this** + +```js +module.exports = function(app) { + app.register('foo', function(foo) { + foo.task('default', function(cb) { + // do task stuff + cb(); + }); + }); + + // `.build` doesn't run updaters + app.build('foo', function(err) { + if (err) return console.log(err); + }); +}; +``` + +**Do this** + +```js +module.exports = function(app) { + app.register('foo', function(foo) { + foo.task('default', function(cb) { + // do task stuff + cb(); + }); + }); + + // `.build` doesn't run updaters + app.update('foo', function(err) { + if (err) return console.log(err); + }); +}; +``` + +**Or this** + +```js +module.exports = function(app) { + app.register('foo', function(foo) { + foo.task('default', function(cb) { + // do task stuff + cb(); + }); + }); + + app.task('foo', function(cb) { + app.update('foo', cb); + }); + + // `.build` will run task `foo`, which runs the updater + app.build('foo', function(err) { + if (err) return console.log(err); + }); +}; +``` + +### Order of precendence + +When the command line is used, Update's CLI resolves updaters in the following order: + +1. [default updater](#default-updater): attempts to match given names to updaters and tasks registered on the `default` updater +2. built-in updaters: attempts to match given names to Update's [built-in updaters](built-in-updaters.md) +3. locally installed updaters +4. globally installed updaters + +## Discovering updaters + +todo + +## Default updater + +If an updater is registered with the name `default` it will receive special treatment from Update and Update's CLI. More specifically, when Update's CLI looks for updaters or tasks to run, it will search for them on the `default` updater first. + +There is a catch... + +**Registering the "default" updater** + +_The only way to register a `default` updater is by creating an [updatefile.js](updatefile.md) in the current working directory._ + +When used by command line, Update's CLI will then use node's `require()` system to get the function exported by `updatefile.js` and use it as the `default` updater. \ No newline at end of file diff --git a/support/lib/common.js b/support/lib/common.js new file mode 100644 index 0000000..2a7fd9e --- /dev/null +++ b/support/lib/common.js @@ -0,0 +1,41 @@ +'use strict'; + +var isValid = require('is-valid-app'); +var path = require('path'); + +module.exports = function(options) { + return function(app) { + if (!isValid(app, 'update-support-common')) return; + app.use(require('generate-collections')); + app.use(require('generate-defaults')); + app.use(require('verb-toc')); + if (!app.docs) app.create('docs'); + app.option('renameKey', function(key, file) { + return file ? file.basename : path.basename(key); + }); + + app.helper('links', function(arr) { + arr = arr ? (Array.isArray(arr) ? arr : [arr]) : []; + var links = arr.map(function(link) { + return createLink(link); + }); + return links.join('\n'); + }); + }; +}; + +function createLink(link) { + var filepath = name; + var name = link; + var anchor = ''; + var segs = link.split('#'); + if (segs.length > 1) { + name = segs.shift(); + anchor = '#' + segs.pop(); + } + var filename = name; + if (!/\.md$/.test(filename) && !/#/.test(filename)) { + filename += '.md'; + } + return `- [${name}](${filename}${anchor})`; +} diff --git a/support/lib/middleware.js b/support/lib/middleware.js new file mode 100644 index 0000000..bfa3d97 --- /dev/null +++ b/support/lib/middleware.js @@ -0,0 +1,35 @@ +'use strict'; + +var isValid = require('is-valid-app'); +var path = require('path'); + +module.exports = function(options) { + return function(app) { + if (!isValid(app, 'update-support-middleware')) return; + app.cache.views = {docs: []}; + + app.onLoad(/\.md$/, function(view, next) { + view.data.related = view.data.related || {}; + view.data.layout = 'default'; + if (view.data.toc === true) { + view.data.toc = {render: true}; + } + if (view.stem.indexOf('docs.') === 0) { + app.cache.views.docs.push(view); + } + next(); + }); + + app.preWrite(/\.md$/, function(file, next) { + var segs = file.stem.split('.').filter(Boolean); + if (segs[0] === 'docs') { + segs.shift(); + file.stem = segs[0]; + } + if (segs.length > 1) { + file.path = path.resolve(file.base, segs.join('/') + file.extname); + } + next(); + }); + }; +}; diff --git a/support/lib/paths.js b/support/lib/paths.js new file mode 100644 index 0000000..9c14dad --- /dev/null +++ b/support/lib/paths.js @@ -0,0 +1,20 @@ +'use strict'; + +var path = require('path'); +exports.cwd = require('memoize-path')(path.resolve(__dirname, '..')); +exports.memo = require('memoize-path')(path.resolve(__dirname, '..')); + +exports.docs = function(fp) { + var res = exports.memo('../docs')(fp); + return fp ? res() : res; +}; + +exports.site = function(fp) { + var res = exports.memo('../_gh-pages')(fp); + return fp ? res() : res; +}; + +exports.tmpl = function(fp) { + var res = exports.memo('templates')(fp); + return fp ? res() : res; +}; diff --git a/support/package.json b/support/package.json new file mode 100644 index 0000000..c818a75 --- /dev/null +++ b/support/package.json @@ -0,0 +1,43 @@ +{ + "name": "update-docs", + "description": "Documentation for Update.", + "version": "0.0.0", + "homepage": "https://github.com/update/update-docs", + "author": "Jon Schlinkert (https://github.com/jonschlinkert)", + "repository": "update/update-docs", + "bugs": { + "url": "https://github.com/update/update-docs/issues" + }, + "private": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + }, + "scripts": { + "test": "mocha" + }, + "devDependencies": { + "assemble": "^0.14.0", + "base-generators": "^0.4.1", + "delete": "^0.3.2", + "gulp-drafts": "^0.2.0", + "gulp-format-md": "^0.1.9", + "gulp-reflinks": "^0.1.0", + "is-valid-app": "^0.2.0", + "memoize-path": "^0.1.2", + "template-helpers": "^0.6.3", + "verb-toc": "^0.2.3" + }, + "dependencies": { + "generate-collections": "^0.3.4", + "generate-defaults": "^0.3.1" + }, + "verb": { + "plugins": [ + "gulp-format-md" + ], + "lint": { + "reflinks": true + } + } +} diff --git a/support/verbfile.js b/support/verbfile.js new file mode 100644 index 0000000..da68592 --- /dev/null +++ b/support/verbfile.js @@ -0,0 +1,35 @@ +'use strict'; + +var path = require('path'); +var drafts = require('gulp-drafts'); +var reflinks = require('gulp-reflinks'); +var format = require('gulp-format-md'); +var del = require('delete'); +var middleware = require('./lib/middleware'); +var common = require('./lib/common'); +var paths = require('./lib/paths'); + +module.exports = function(app) { + app.use(middleware()); + app.use(common()); + + app.task('clean', function(cb) { + // del(paths.docs(), {force: true}, cb); + cb() + }); + + console.log(paths.docs()) + app.task('docs', ['clean'], function(cb) { + app.layouts('docs/layouts/*.md', {cwd: paths.cwd()}); + app.docs('docs/*.md', {cwd: paths.cwd(), layout: 'default'}); + + return app.toStream('docs') + .pipe(drafts()) + .pipe(app.renderFile('*')) + // .pipe(reflinks()) + .pipe(format()) + .pipe(app.dest(paths.docs())); + }); + + app.task('default', ['docs']); +}; From 53724dff2138905ff9a69bef6fc1bb18caa9841a Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 28 Jun 2016 09:20:51 -0400 Subject: [PATCH 177/274] remove unused file --- examples/eslint/updatefile.js | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 examples/eslint/updatefile.js diff --git a/examples/eslint/updatefile.js b/examples/eslint/updatefile.js deleted file mode 100644 index a2b175b..0000000 --- a/examples/eslint/updatefile.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; - -module.exports = function(app) { - app.task('foo', function(cb) { - app.update('bar', cb); - }); - - app.register('foo', function(gen) { - gen.task('default', function(cb) { - console.log(gen.name, 'task >', this.name); - cb(); - }); - }); - - app.task('default', function(cb) { - console.log(app.name, 'task >', this.name); - cb(); - }); -}; From 3deae1c08a7f22d450937a8d5be6a99f6b2d5f5a Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 28 Jun 2016 09:21:31 -0400 Subject: [PATCH 178/274] update defaults, support `updatefile.js` in cwd --- bin/update.js | 39 +++------------ lib/cli.js | 123 +++++++++++++++++----------------------------- lib/list.js | 17 +++++++ lib/updatefile.js | 41 ++++++++++------ lib/utils.js | 18 ++++++- 5 files changed, 114 insertions(+), 124 deletions(-) create mode 100644 lib/list.js diff --git a/bin/update.js b/bin/update.js index fc7fd2d..3ceef11 100755 --- a/bin/update.js +++ b/bin/update.js @@ -5,15 +5,7 @@ require('set-blocking')(true); var Update = require('..'); var commands = require('../lib/commands'); var utils = require('../lib/utils'); -var argv = require('yargs-parser')(process.argv.slice(2), { - alias: { - add: 'a', - config: 'c', - configfile: 'f', - global: 'g', - remove: 'r' - } -}); +var argv = require('yargs-parser')(process.argv.slice(2), utils.opts); /** * Listen for errors @@ -36,11 +28,16 @@ Update.on('update.postInit', function(app) { Update.cli(Update, argv, function(err, app) { if (err) return console.log(err); - var tasks = resolveTasks(app, argv); app.cli.process(argv, function(err) { if (err) app.emit('error', err); + var tasks = argv._.length ? argv._ : ['default']; + if (app.updatefile !== true) { + tasks = Update.resolveTasks(app, argv); + } + + app.log.success('running:', tasks); app.update(tasks, function(err) { if (err) return console.log(err); app.emit('done'); @@ -48,25 +45,3 @@ Update.cli(Update, argv, function(err, app) { }); }); }); - -/** - * Get the updaters to run from user config - */ - -function resolveTasks(app, argv) { - var tasks = utils.arrayify(argv._); - if (tasks.length && utils.contains(['help', 'new', 'default'], tasks)) { - app.enable('silent'); - return tasks; - } - - if (tasks.length && !utils.contains(['help', 'new', 'default'], tasks)) { - return tasks; - } - - tasks = app.getUpdaters(argv.add, argv); - if (!tasks || !tasks.length) { - return ['init']; - } - return tasks; -}; diff --git a/lib/cli.js b/lib/cli.js index a0deeba..e755a78 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -5,11 +5,8 @@ process.ORIGINAL_CWD = process.cwd(); var path = require('path'); var find = require('find-pkg'); var log = require('log-utils'); -var exists = require('fs-exists-sync'); -var extend = require('extend-shallow'); -var argv = require('yargs-parser')(process.argv.slice(2), { - alias: {configfile: 'f', template: 't'} -}); +var utils = require('./utils'); +var argv = require('yargs-parser')(process.argv.slice(2), utils.opts); module.exports = function(Update, config, cb) { if (typeof cb !== 'function') { @@ -19,7 +16,7 @@ module.exports = function(Update, config, cb) { throw new TypeError('expected config to be an object'); } - var opts = extend({}, config, argv); + var opts = utils.extend({}, config, argv); function logger() { if (opts.silent) return; @@ -42,18 +39,6 @@ module.exports = function(Update, config, cb) { process.chdir(cwd); } - /** - * Check for config file - */ - - var configfile = path.resolve(__dirname, 'updatefile.js'); - - /** - * Log configfile - */ - - logger(log.timestamp, 'using file', log.green('~' + configfile)); - /** * Get the Base ctor and instance to use */ @@ -68,18 +53,35 @@ module.exports = function(Update, config, cb) { */ base.set('cache.argv', opts); - var app = require(configfile); + + /** + * Check for config file + */ + + var defaults = path.resolve(__dirname, 'updatefile.js'); + var updatefile = path.resolve(cwd, 'updatefile.js'); + var app = new Update(opts); + var fn = require(defaults); /** * Invoke `app` */ - if (typeof app === 'function' && typeof base.register === 'function') { - base.register('default', app); - } else if (typeof app === 'function') { - base.use(app); - } else if (app) { - base = app; + if (utils.exists(updatefile)) { + logger(log.timestamp, 'using file', log.green('~' + updatefile)); + var val = require(updatefile); + if (typeof val === 'function') { + app.use(fn); + app.use(val); + app.parent = base; + base.register('default', app); + } else if (val && val.isApp) { + base = val; + } + base.updatefile = true; + } else { + logger(log.timestamp, 'using file', log.green('~' + defaults)); + base.register('default', fn); } /** @@ -87,7 +89,7 @@ module.exports = function(Update, config, cb) { */ if (!base) { - handleError(base, new Error('cannot run config file: ' + configfile)); + handleError(base, new Error('cannot run config file: ' + updatefile)); } if (typeof base !== 'function' && Object.keys(base).length === 0) { handleError(base, new Error('expected a function or instance of Base to be exported')); @@ -103,68 +105,35 @@ module.exports = function(Update, config, cb) { * Setup listeners */ - // base.on('build', function(event, build) { - // var prefix = event === 'finished' ? log.success + ' ' : ''; - // logger(log.timestamp, event, build.key, prefix + log.red(build.time)); - // }); + base.on('build', function(event, build) { + var prefix = event === 'finished' ? log.success + ' ' : ''; + logger(log.timestamp, event, build.key, prefix + log.red(build.time)); + }); - // base.on('task', function(event, task) { - // logger(log.timestamp, event, task.key, log.red(task.time)); - // }); + base.on('task', function(event, task) { + logger(log.timestamp, event, task.key, log.red(task.time)); + }); - // base.on('error', function(err) { - // logger(err.stack); - // }); + base.on('error', function(err) { + logger(err.stack); + }); /** * Resolve the "app" to use */ function resolveBase() { - var paths = [ - { name: 'updater', path: path.resolve(cwd, 'node_modules/updater')} - ]; - - var len = paths.length; - var idx = -1; - - while (++idx < len) { - var file = paths[idx]; - if (opts.app && file.name !== opts.app) { - continue; - } - if ((base = resolveApp(file))) { - return base; - } - } - - if (opts.app) { - base = resolveApp({name: opts.app, path: path.resolve(cwd, 'node_modules', opts.app)}); - } - return base; - } + var file = {path: path.resolve(cwd, 'node_modules/update'), name: 'update'}; + if (utils.exists(file.path)) { + var Update = require(file.path); + var update = new Update(opts); - /** - * Try to resolve the path to the given module, require it, - * and create an instance - */ - - function resolveApp(file) { - if (exists(file.path)) { - var Base = require(file.path); - var base = new Base({isApp: true}, opts); - base.define('log', log); - - if (typeof base.name === 'undefined') { - base.name = file.name; + if (typeof update._name === 'undefined') { + update.is('update'); } - // if this is not an instance of base-app, load - // app-base plugins onto the instance - if (file.name !== 'core') { - Update.plugins(base); - } - return base; + Update.plugins(update); + return update; } } diff --git a/lib/list.js b/lib/list.js new file mode 100644 index 0000000..3ed456e --- /dev/null +++ b/lib/list.js @@ -0,0 +1,17 @@ +'use strict'; + +var through = require('through2'); + +module.exports = function() { + var paths = []; + + return through.obj(function(file, enc, next) { + paths.push(file.basename); + next(); + }, function(cb) { + console.log('- ' + paths.join('\n- ')); + console.log(); + console.log(paths.length + ' updaters installed'); + cb(); + }); +}; diff --git a/lib/updatefile.js b/lib/updatefile.js index 749456c..e476c18 100644 --- a/lib/updatefile.js +++ b/lib/updatefile.js @@ -1,32 +1,35 @@ 'use strict'; -var util = require('util'); -var wrap = require('word-wrap'); -var size = require('window-size'); +var path = require('path'); +var isValid = require('is-valid-app'); var choose = require('gulp-choose-files'); var through = require('through2'); -var gm = require('global-modules'); +var utils = require('./utils'); +var list = require('./list'); +var Update = require('..'); +var argv = require('yargs-parser')(process.argv.slice(2), utils.opts); module.exports = function(app, base) { + if (!isValid(app, 'update-builtins')) return; + var gm = path.resolve.bind(path, require('global-modules')); + var cwd = path.resolve.bind(path, app.cwd); /** * Prompts the user to select the updaters to run with the `update` command. Use `--add` to * add addtional updaters, and `--remove` to remove them. * * ```sh - * $ update defaults:help - * # aliased as - * $ update help + * $ update init * ``` - * @name help + * @name init * @api public */ app.task('init', { silent: true }, function() { var updaters = []; - return app.src('updater-*', {cwd: gm}) + return app.src([gm('updater-*'), cwd('node_modules/updater-*')]) .pipe(through.obj(function(file, enc, next) { - file.basename = file.basename.split('updater-').join(''); + file.basename = app.toAlias(file.basename); next(null, file); })) .pipe(choose({message: 'Choose the updaters to run with the `update` command:'})) @@ -39,6 +42,15 @@ module.exports = function(app, base) { })); }); + app.task('list', { silent: true }, function() { + return app.src([gm('updater-*'), cwd('node_modules/updater-*')]) + .pipe(through.obj(function(file, enc, next) { + file.basename = app.toAlias(file.basename); + next(null, file); + })) + .pipe(list()); + }); + /** * Display a help menu of available commands and flags. * @@ -68,7 +80,9 @@ module.exports = function(app, base) { */ app.task('show', { silent: true }, function(cb) { - base.cli.process({ help: true }, cb); + argv._ = []; + app.log.success('queued tasks:', Update.resolveTasks(app, argv)); + cb(); }); /** @@ -104,8 +118,6 @@ function save(app, list) { var gray = app.log.gray; var cyan = app.log.cyan; var bold = app.log.bold; - var configname = 'updater'; - var appname = 'update'; var command = 'update'; var msg = gray('\n ---') @@ -118,7 +130,8 @@ function save(app, list) { + gray(' ---') + '\n' + '\n' - + ' To make changes, run: ' + bold(`$ ${command} init`) + + ' To make changes, run: ' + bold(`$ ${command} init`); console.log(msg.split('\n').join('\n')); console.log(); } + diff --git a/lib/utils.js b/lib/utils.js index 1a49a05..b143694 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -14,11 +14,27 @@ require('base-config-process', 'config'); require('base-questions', 'questions'); require('base-runtimes', 'runtimes'); require('base-store', 'store'); +require('extend-shallow', 'extend'); +require('fs-exists-sync', 'exists'); require('global-modules', 'gm'); require('log-utils', 'log'); require('os-homedir', 'home'); require = fn; // eslint-disable-line +/** + * argv options + */ + +utils.opts = { + alias: { + add: 'a', + config: 'c', + configfile: 'f', + global: 'g', + remove: 'r' + } +}; + utils.remove = function(arr, names) { return arr.filter(function(ele) { return names.indexOf(ele) === -1; @@ -70,7 +86,7 @@ utils.logger = function(prop, color) { return console.log .bind(console, utils.log.timestamp, utils.log[prop]) .apply(console, [utils.log[color](msg), ...rest]); - } + }; }; /** From 44d3216c1fcefea2930a97715bb30907b3fdea66 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 28 Jun 2016 09:22:04 -0400 Subject: [PATCH 179/274] expose static resolveTasks method --- index.js | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 46e7313..1cf82af 100644 --- a/index.js +++ b/index.js @@ -68,7 +68,7 @@ Update.prototype.initDefaults = function() { }); function isUpdater(name) { - return /^updater?-/.test(name); + return /^(updater|generate)?-/.test(name); } this.option('lookup', function(name) { @@ -149,6 +149,28 @@ Update.plugins = function(app) { app.use(utils.cli()); }; +/** + * Get the updaters or tasks to run from user config + */ + +Update.resolveTasks = function(app, argv) { + var tasks = utils.arrayify(argv._); + if (tasks.length && utils.contains(['help', 'list', 'new', 'default'], tasks)) { + app.enable('silent'); + return tasks; + } + + if (tasks.length && !utils.contains(['help', 'list', 'new', 'default'], tasks)) { + return tasks; + } + + tasks = app.getUpdaters(argv.add, argv); + if (!tasks || !tasks.length) { + return ['init']; + } + return tasks; +}; + /** * Expose logging methods */ From 2e1abac6487841d560f89dd92bb8184c7fa2998a Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 28 Jun 2016 09:38:44 -0400 Subject: [PATCH 180/274] update configs, re-add front-matter --- support/assemblefile.js | 43 +++++++-------------------- support/docs/_drafts/notes.md | 17 +++++------ support/docs/api.plugins.md | 7 ++++- support/docs/api.register.md | 7 ++++- support/docs/api.updater.md | 7 ++++- support/docs/cli.built-in-updaters.md | 7 ++++- support/docs/faq.md | 7 ++++- support/docs/features.md | 7 ++++- support/docs/installing-the-cli.md | 7 ++++- support/docs/installing-updaters.md | 13 ++++---- support/docs/introduction.md | 14 ++++----- support/docs/layouts/default.md | 13 +++++++- support/docs/tasks.md | 13 ++++---- support/docs/tutorial.md | 7 ++++- support/docs/updatefile.md | 13 ++++---- support/docs/updaters.md | 7 ++++- support/lib/common.js | 28 +++-------------- support/lib/helpers.js | 35 ++++++++++++++++++++++ support/lib/index.js | 1 + support/lib/matter.js | 29 ++++++++++++++++++ support/package.json | 1 + support/verbfile.js | 8 ++--- 22 files changed, 182 insertions(+), 109 deletions(-) create mode 100644 support/lib/helpers.js create mode 100644 support/lib/index.js create mode 100644 support/lib/matter.js diff --git a/support/assemblefile.js b/support/assemblefile.js index 868cd64..6723e60 100644 --- a/support/assemblefile.js +++ b/support/assemblefile.js @@ -1,46 +1,25 @@ 'use strict'; var path = require('path'); -var helpers = require('template-helpers'); var generators = require('base-generators'); -var common = require('./lib/common'); var paths = require('./lib/paths'); +var lib = require('./lib'); module.exports = function(app) { - app.helpers(helpers()); app.use(generators()); - app.use(common()); - // app.register('verb', require('./verbfile')); + app.use(lib.common()); + app.register('verb', require('./verbfile')); - // app.task('verb', function(cb) { - // app.generate('verb', cb); - // }); - - // app.task('default', ['verb'], function() { - // app.layouts(paths.tmpl('layouts/*.hbs')); - // app.includes(paths.tmpl('includes/*.hbs')); - // app.pages(paths.docs('**/*.md')); - // return app.toStream('pages') - // .pipe(app.renderFile()) - // // .pipe(app.dest(paths.site())); - // }); - - app.onLoad(/\.md$/, function(view, next) { - var data = '---\ntitle: '; - data += view.stem.charAt(0).toUpperCase() + view.stem.slice(1); - data += '\n'; - data += 'layout: default'; - data += 'related:'; - data += ' docs: []'; - data += '---\n\n'; - view.content = data + view.content; - next(); + app.task('verb', function(cb) { + app.generate('verb', cb); }); - app.task('default', function() { - app.pages('docs/**/*.md'); + app.task('default', ['verb'], function() { + app.layouts(paths.tmpl('layouts/*.hbs')); + app.includes(paths.tmpl('includes/*.hbs')); + app.pages(paths.docs('**/*.md')); return app.toStream('pages') - // .pipe(app.renderFile()) - .pipe(app.dest('docs')); + .pipe(app.renderFile()) + .pipe(app.dest(paths.site())); }); }; diff --git a/support/docs/_drafts/notes.md b/support/docs/_drafts/notes.md index 5e549a6..fc61f12 100644 --- a/support/docs/_drafts/notes.md +++ b/support/docs/_drafts/notes.md @@ -1,5 +1,11 @@ -Visit the [getting started guide][getting-started] to learn more. +--- +title: Notes +layout: default +related: + docs: [] +--- +Visit the [getting started guide][getting-started] to learn more. Not all generators are created for rendering templates and writing files to the file system. Some generators are created to handle a specific part of the build workflow. For example, [generate-dest][] does one specific thing: when `gen dest` is run in the command line, it will prompt you for the destination directory to use for any generated files. So you can run `gen dest foo` to set the destination directory for files written by `generator-foo`. You can run a generator more than once in the same command, so it's also possible to do: `gen dest foo dest bar`, if both `foo` and `bar` require different destinations. @@ -13,7 +19,6 @@ maintain a separation of concerns between configuration and application logic. use of [generators](#generators) and [tasks](#tasks) - ## tldr Install `update` globally with the following command: @@ -22,24 +27,16 @@ Install `update` globally with the following command: $ npm install --global update ``` - - Update is installed globally and run with the `update` command. - - - download - git commit! - run `update` - - ```js var updater = require('{%= name %}'); ``` - - **Example** This example shows two updaters working together seamlessly. diff --git a/support/docs/api.plugins.md b/support/docs/api.plugins.md index aa36d89..ddea64f 100644 --- a/support/docs/api.plugins.md +++ b/support/docs/api.plugins.md @@ -1,4 +1,9 @@ -# Plugins API +--- +title: Plugins +layout: default +related: + api: ['updater', 'register'] +--- A plugin is function that takes an instance of `Update` and is registered with the `.use` method. See the [base-plugins][] documentation for additional details. diff --git a/support/docs/api.register.md b/support/docs/api.register.md index 900eb8b..904e5cf 100644 --- a/support/docs/api.register.md +++ b/support/docs/api.register.md @@ -1,4 +1,9 @@ -# .register +--- +title: Register +layout: default +related: + api: ['updater', 'plugin'] +--- Register an updater function by name. Similar to [.updater](updater.md) but does not invoke the updater function. diff --git a/support/docs/api.updater.md b/support/docs/api.updater.md index f146caf..0414116 100644 --- a/support/docs/api.updater.md +++ b/support/docs/api.updater.md @@ -1,4 +1,9 @@ -# .updater +--- +title: Updater +layout: default +related: + api: ['register', 'plugin'] +--- Register an updater function by name. Similar to [.register](register.md) but immediately invokes the updater function upon registering it. diff --git a/support/docs/cli.built-in-updaters.md b/support/docs/cli.built-in-updaters.md index c6970df..6d45772 100644 --- a/support/docs/cli.built-in-updaters.md +++ b/support/docs/cli.built-in-updaters.md @@ -1,4 +1,9 @@ -# Built-in updaters +--- +title: Built in updaters +layout: default +related: + docs: [] +--- Update only has a few built-in [updaters](docs/updaters.md) (these might be externalized at some point): diff --git a/support/docs/faq.md b/support/docs/faq.md index f29b00d..8cca06f 100644 --- a/support/docs/faq.md +++ b/support/docs/faq.md @@ -1,4 +1,9 @@ -# FAQ +--- +title: Faq +layout: default +related: + docs: ['features', 'introduction'] +--- diff --git a/support/docs/features.md b/support/docs/features.md index 040d776..7ab600c 100644 --- a/support/docs/features.md +++ b/support/docs/features.md @@ -1,4 +1,9 @@ -# Features +--- +title: Features +layout: default +related: + docs: ['faq', 'introduction'] +--- Update offers an elegant and robust suite of methods, carefully organized to help you accomplish common activities in less time, including: diff --git a/support/docs/installing-the-cli.md b/support/docs/installing-the-cli.md index 912b886..03a0582 100644 --- a/support/docs/installing-the-cli.md +++ b/support/docs/installing-the-cli.md @@ -1,4 +1,9 @@ -# Installing the CLI +--- +title: Installing the cli +layout: default +related: + docs: ['installing-updaters'] +--- To run update from the command line, you'll need to install Update's CLI globally first. You can do that now with the following command: diff --git a/support/docs/installing-updaters.md b/support/docs/installing-updaters.md index 73e9f8d..e802855 100644 --- a/support/docs/installing-updaters.md +++ b/support/docs/installing-updaters.md @@ -1,4 +1,9 @@ -# Installing updaters +--- +title: Installing updaters +layout: default +related: + docs: ['installing-the-cli'] +--- Updaters are responsible for all of the "updating" that happens in update. You can find [updaters to install](#updaters to install) on npm, or create your own. @@ -18,8 +23,4 @@ If `updater-license` installed successfully, you should now be able to run it wi ```sh $ update license -``` - -## Related - -* [installing-the-cli](installing-the-cli.md) \ No newline at end of file +``` \ No newline at end of file diff --git a/support/docs/introduction.md b/support/docs/introduction.md index 9edf11b..9d01b26 100644 --- a/support/docs/introduction.md +++ b/support/docs/introduction.md @@ -1,4 +1,9 @@ -# Introduction +--- +title: Introduction +layout: default +related: + docs: ['updaters', 'updatefile', 'tasks', 'features', 'faq'] +--- ## What is update? @@ -92,10 +97,3 @@ For example, if we were to sift the files in the average code project into major * **everything else**: LICENSE and copyright files, dotfiles, manifests, config files, and so on. Update maintains **everything else**. - -## Related - -* [updaters](updaters.md) -* [tasks](tasks.md) -* [features](features.md) -* [faq](faq.md) \ No newline at end of file diff --git a/support/docs/layouts/default.md b/support/docs/layouts/default.md index c10d48e..7d88648 100644 --- a/support/docs/layouts/default.md +++ b/support/docs/layouts/default.md @@ -1,7 +1,18 @@ -# <%= title %> +--- +title: Default +layout: default +related: + docs: [] +--- {% body %} # Related +**Docs** +<%= links(related.docs) %> +**API** +<%= links(related.docs) %> + +**URL** <%= links(related.docs) %> \ No newline at end of file diff --git a/support/docs/tasks.md b/support/docs/tasks.md index 3d91241..a1def99 100644 --- a/support/docs/tasks.md +++ b/support/docs/tasks.md @@ -1,4 +1,9 @@ -# Tasks +--- +title: Tasks +layout: default +related: + docs: ['running-updaters', 'updaters', 'updatefile'] +--- Tasks are used for wrapping code that should be executed at a later point, either when specified by command line or explicitly run when using the API. @@ -157,9 +162,3 @@ app.build(function(err) { console.log('done'); }); ``` - -## Related - -* [running-updaters](running-updaters.md) -* [updaters](updaters.md) -* [updatefile](updatefile.md) \ No newline at end of file diff --git a/support/docs/tutorial.md b/support/docs/tutorial.md index e8720c6..18e7d56 100644 --- a/support/docs/tutorial.md +++ b/support/docs/tutorial.md @@ -1,4 +1,9 @@ -# Quickstart +--- +title: Tutorial +layout: default +related: + docs: [] +--- The following intro only skims the surface of what update has to offer. For a more in-depth introduction, we highly recommend visiting the [getting started guide][getting-started]. diff --git a/support/docs/updatefile.md b/support/docs/updatefile.md index 5abbfd9..3fde3a0 100644 --- a/support/docs/updatefile.md +++ b/support/docs/updatefile.md @@ -1,4 +1,9 @@ -# updatefile.js +--- +title: Updatefile +layout: default +related: + docs: ['installing-updaters', 'updaters', 'tasks'] +--- If an `updatefile.js` exists in the current working directoy, Update's CLI will attempt to load it and run any updaters or tasks you've specified for it to run. @@ -38,9 +43,3 @@ module.exports = function(app) { }); }; ``` - -## Related - -* [installing-updaters](installing-updaters.md) -* [updaters](updaters.md) -* [tasks](tasks.md) \ No newline at end of file diff --git a/support/docs/updaters.md b/support/docs/updaters.md index ce605e1..909d465 100644 --- a/support/docs/updaters.md +++ b/support/docs/updaters.md @@ -1,4 +1,9 @@ -# Updaters +--- +title: Updaters +layout: default +related: + docs: [] +--- Updaters are [plugins](plugins.md) that are registered by name. This document describes how to create, register and run updaters. diff --git a/support/lib/common.js b/support/lib/common.js index 2a7fd9e..f95b396 100644 --- a/support/lib/common.js +++ b/support/lib/common.js @@ -1,5 +1,6 @@ 'use strict'; +var helpers = require('./helpers'); var isValid = require('is-valid-app'); var path = require('path'); @@ -9,33 +10,12 @@ module.exports = function(options) { app.use(require('generate-collections')); app.use(require('generate-defaults')); app.use(require('verb-toc')); + app.use(helpers()); + app.use(helpers()); + if (!app.docs) app.create('docs'); app.option('renameKey', function(key, file) { return file ? file.basename : path.basename(key); }); - - app.helper('links', function(arr) { - arr = arr ? (Array.isArray(arr) ? arr : [arr]) : []; - var links = arr.map(function(link) { - return createLink(link); - }); - return links.join('\n'); - }); }; }; - -function createLink(link) { - var filepath = name; - var name = link; - var anchor = ''; - var segs = link.split('#'); - if (segs.length > 1) { - name = segs.shift(); - anchor = '#' + segs.pop(); - } - var filename = name; - if (!/\.md$/.test(filename) && !/#/.test(filename)) { - filename += '.md'; - } - return `- [${name}](${filename}${anchor})`; -} diff --git a/support/lib/helpers.js b/support/lib/helpers.js new file mode 100644 index 0000000..e8ac3b3 --- /dev/null +++ b/support/lib/helpers.js @@ -0,0 +1,35 @@ +'use strict'; + +var helpers = require('template-helpers'); +var isValid = require('is-valid-app'); +var path = require('path'); + +module.exports = function(options) { + return function(app) { + if (!isValid(app, 'update-support-helpers')) return; + app.helpers(helpers()); + app.helper('links', function(arr) { + arr = arr ? (Array.isArray(arr) ? arr : [arr]) : []; + var links = arr.map(function(link) { + return createLink(link); + }); + return links.join('\n'); + }); + }; +}; + +function createLink(link) { + var filepath = name; + var name = link; + var anchor = ''; + var segs = link.split('#'); + if (segs.length > 1) { + name = segs.shift(); + anchor = '#' + segs.pop(); + } + var filename = name; + if (!/\.md$/.test(filename) && !/#/.test(filename)) { + filename += '.md'; + } + return `- [${name}](${filename}${anchor})`; +} diff --git a/support/lib/index.js b/support/lib/index.js new file mode 100644 index 0000000..23b2930 --- /dev/null +++ b/support/lib/index.js @@ -0,0 +1 @@ +module.exports = require('export-files')(__dirname); diff --git a/support/lib/matter.js b/support/lib/matter.js new file mode 100644 index 0000000..bdb7694 --- /dev/null +++ b/support/lib/matter.js @@ -0,0 +1,29 @@ + + // app.onLoad(/\.md$/, function(view, next) { + // if (/^#/.test(view.content)) { + // view.content = view.content.replace(/^#[^\n]+/, ''); + // } + // var data = '---\ntitle: '; + // var title = view.stem; + // var segs = title.split('.'); + // if (segs.length > 1) { + // title = segs[1]; + // } + // title = title.split('-').join(' '); + // title = title.charAt(0).toUpperCase() + title.slice(1); + // data += title; + // data += '\n'; + // data += 'layout: default\n'; + // data += 'related:\n'; + // data += ' docs: []\n'; + // data += '---\n\n'; + // view.content = data + view.content; + // next(); + // }); + + // app.task('default', function() { + // app.pages('docs/**/*.md'); + // return app.toStream('pages') + // // .pipe(app.renderFile()) + // .pipe(app.dest('docs')); + // }); \ No newline at end of file diff --git a/support/package.json b/support/package.json index c818a75..7ef1840 100644 --- a/support/package.json +++ b/support/package.json @@ -20,6 +20,7 @@ "assemble": "^0.14.0", "base-generators": "^0.4.1", "delete": "^0.3.2", + "export-files": "^2.1.1", "gulp-drafts": "^0.2.0", "gulp-format-md": "^0.1.9", "gulp-reflinks": "^0.1.0", diff --git a/support/verbfile.js b/support/verbfile.js index da68592..17c2779 100644 --- a/support/verbfile.js +++ b/support/verbfile.js @@ -5,20 +5,18 @@ var drafts = require('gulp-drafts'); var reflinks = require('gulp-reflinks'); var format = require('gulp-format-md'); var del = require('delete'); -var middleware = require('./lib/middleware'); -var common = require('./lib/common'); var paths = require('./lib/paths'); +var lib = require('./lib'); module.exports = function(app) { - app.use(middleware()); - app.use(common()); + app.use(lib.middleware()); + app.use(lib.common()); app.task('clean', function(cb) { // del(paths.docs(), {force: true}, cb); cb() }); - console.log(paths.docs()) app.task('docs', ['clean'], function(cb) { app.layouts('docs/layouts/*.md', {cwd: paths.cwd()}); app.docs('docs/*.md', {cwd: paths.cwd(), layout: 'default'}); From ca514dc52dae290fba9bfb6e3261480f7de28c3a Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 28 Jun 2016 09:54:26 -0400 Subject: [PATCH 181/274] build docs --- docs/api/plugins.md | 71 ++++++++++ docs/api/register.md | 35 +++++ docs/api/updater.md | 35 +++++ docs/cli/built-in-updaters.md | 51 +++++++ docs/faq.md | 18 +++ docs/features.md | 21 +++ docs/installing-the-cli.md | 50 +++++++ docs/installing-updaters.md | 27 ++++ docs/introduction.md | 116 ++++++++++++++++ docs/tasks.md | 167 ++++++++++++++++++++++ docs/tutorial.md | 111 +++++++++++++++ docs/updatefile.md | 48 +++++++ docs/updaters.md | 193 ++++++++++++++++++++++++++ support/docs/_drafts/notes.md | 3 +- support/docs/api.plugins.md | 8 +- support/docs/api.register.md | 6 - support/docs/api.updater.md | 6 - support/docs/cli.built-in-updaters.md | 3 +- support/docs/faq.md | 3 +- support/docs/features.md | 3 +- support/docs/installing-the-cli.md | 7 +- support/docs/installing-updaters.md | 3 +- support/docs/introduction.md | 5 +- support/docs/layouts/default.md | 19 +-- support/docs/tasks.md | 16 +-- support/docs/tutorial.md | 3 +- support/docs/updatefile.md | 3 +- support/docs/updaters.md | 15 +- support/lib/{ => _drafts}/matter.js | 4 +- support/lib/helpers.js | 4 + support/lib/middleware.js | 15 +- support/lib/paths.js | 2 +- support/lib/utils.js | 5 + support/package.json | 2 +- 34 files changed, 992 insertions(+), 86 deletions(-) create mode 100644 docs/api/plugins.md create mode 100644 docs/api/register.md create mode 100644 docs/api/updater.md create mode 100644 docs/cli/built-in-updaters.md create mode 100644 docs/faq.md create mode 100644 docs/features.md create mode 100644 docs/installing-the-cli.md create mode 100644 docs/installing-updaters.md create mode 100644 docs/introduction.md create mode 100644 docs/tasks.md create mode 100644 docs/tutorial.md create mode 100644 docs/updatefile.md create mode 100644 docs/updaters.md rename support/lib/{ => _drafts}/matter.js (95%) create mode 100644 support/lib/utils.js diff --git a/docs/api/plugins.md b/docs/api/plugins.md new file mode 100644 index 0000000..e6a3fae --- /dev/null +++ b/docs/api/plugins.md @@ -0,0 +1,71 @@ +# Plugins + +A plugin is function that takes an instance of `Update` and is registered with the `.use` method. See the [base-plugins][] documentation for additional details. + +### .use + +The `.use` method is used for registering plugins that should be immediately invoked. + +**Example** + +```js +var Update = require('update'); +var app = new Update(); + +function plugin(app) { + // "app" and "this" both expose the instance of update we created above +} + +app.use(plugin); +``` + +Once a plugin is invoked, it will not be called again. + +### .run + +If a plugin returns a function after it's invoked by `.use`, the function will be pushed onto an array allowing it to be called again by the `.run` method. + +**Example** + +```js +var Update = require('update'); +var app = new Update(); + +function plugin(app) { + // "app" and "this" both expose the instance of update we created above + return plugin; +} + +app.use(plugin); +``` + +We can now run all plugins that were pushed onto the `.fns` array on any arbitrary object: + +```js +var obj = {}; +app.run(obj); +``` + +Additionally: + +* If `obj` has a `.use` method, it will be used on each plugin (e.g. `obj.use(fn)`). Otherwise `fn(obj)`. +* If the plugin returns a function again and `obj` has a `.run` method, the plugin will be pushed onto the `obj.fns` array + +This can continue indefinitely as long as the plugin returns a function and the receiving object has `.use`/`.run` functions. + +## Updaters + +When plugins are [registered by name](../docs/updaters.md), they are referred to as "updaters". See the [updater documentation](../docs/updaters.md) for more details. + +# Related + +**Docs** + +* [default-updater](default-updater.md) +* [resolving-updaters](resolving-updaters.md) +* [registering-updaters](registering-updaters.md) + +**API** + +* [updater](updater.md) +* [register](register.md) diff --git a/docs/api/register.md b/docs/api/register.md new file mode 100644 index 0000000..38d3551 --- /dev/null +++ b/docs/api/register.md @@ -0,0 +1,35 @@ +# Register + +Register an updater function by name. Similar to [.updater](updater.md) but does not invoke the updater function. + +```js +app.register(name, fn); +``` + +**Params** + +* `name` **{String}**: name of the updater to register +* `fn` **{Function}**: updater function + +**Example** + +```js +var Update = require('update'); +var app = new Update(); + +// not invoked until called by `.update` +app.register('foo', function(app) { + // do updater stuff +}); + +app.update('foo', function(err) { + if (err) return console.log(err); +}); +``` + +# Related + +**API** + +* [updater](updater.md) +* [plugin](plugin.md) diff --git a/docs/api/updater.md b/docs/api/updater.md new file mode 100644 index 0000000..f9d5a41 --- /dev/null +++ b/docs/api/updater.md @@ -0,0 +1,35 @@ +# Updater + +Register an updater function by name. Similar to [.register](register.md) but immediately invokes the updater function upon registering it. + +```js +app.updater(name, fn); +``` + +**Params** + +* `name` **{String}**: name of the updater to register +* `updater` **{Function}**: updater function + +**Example** + +```js +var Update = require('update'); +var app = new Update(); + +// immediately invoked +app.updater('bar', function(app) { + // do updater stuff +}); + +app.update('bar', function(err) { + if (err) return console.log(err); +}); +``` + +# Related + +**API** + +* [register](register.md) +* [plugin](plugin.md) diff --git a/docs/cli/built-in-updaters.md b/docs/cli/built-in-updaters.md new file mode 100644 index 0000000..de53fd5 --- /dev/null +++ b/docs/cli/built-in-updaters.md @@ -0,0 +1,51 @@ +# Built in updaters + +Update only has a few built-in [updaters](docs/updaters.md) (these might be externalized at some point): + +* [init](#init): Choose the updaters to run by default each time `update` is run from the command line +* [list](#list): List all globally and locally installed updaters +* [show](#show): show the list of updaters that will run on the current project when the `update` command is given +* [new](#new): create a new `updatefile.js` in the current working directory +* [help](#help): show a help menu with all available commands + +## Usage + +### init + +Choose the updaters to run by default each time `update` is run from the command line: + +```sh +$ update init +``` + +### list + +List all globally and locally installed updaters: + +```sh +$ update list +``` + +### show + +Show the list of updaters that will run on the current project when the `update` command is given: + +```sh +$ update show +``` + +### new + +Create a new `updatefile.js` in the current working directory: + +```sh +$ update new +``` + +### help + +Display a help menu with all available commands: + +```sh +$ update help +``` diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 0000000..6e1e61f --- /dev/null +++ b/docs/faq.md @@ -0,0 +1,18 @@ +# Faq + + + +**What's an alias, and what do they do?** + +Update tries to find globally installed updaters using an "alias" first, falling back on the updater's full name if not found by its alias. + +A updater's alias is created by stripping the substring `update-` from the _full name_ of updater. Thus, when publishing a updater the naming convention `update-foo` should be used (where `foo` is the alias, and `update-foo` is the full name). + +Note that **no dots may be used in published updater names**. Aside from that, any characters considered valid by npm are fine. + +# Related + +**Docs** + +* [features](features.md) +* [introduction](introduction.md) diff --git a/docs/features.md b/docs/features.md new file mode 100644 index 0000000..93bb748 --- /dev/null +++ b/docs/features.md @@ -0,0 +1,21 @@ +# Features + +Update offers an elegant and robust suite of methods, carefully organized to help you accomplish common activities in less time, including: + +* **unparalleled flow control**: through the use of [updaters][getting-started], [sub-updaters][getting-started] and [tasks][getting-started] +* **templates, scaffolds and boilerplates**: update a single file, initialize an entire project, or provide ad-hoc "components" throughout the duration of a project using any combination of [templates, scaffolds and boilerplates](#templates-scaffolds-and-boilerplates). +* **any engine**: use any template engine to render templates, including [handlebars][], [lodash][], [swig][] and [pug][] +* **prompts**: asks you for data when it can't find what it needs, and it's easy to customize prompts for any data you want. +* **data**: gathers data from the user's environment to populate "hints" in user prompts and render templates +* **streams**: interact with the file system, with full support for [gulp][] and [assemble][] plugins +* **smart plugins**: Update is built on [base][], so any "smart" plugin can be used +* **stores**: persist configuration settings, global defaults, project-specific defaults, answers to prompts, and so on. + +Visit the [getting started guide][getting-started] to learn more. + +# Related + +**Docs** + +* [faq](faq.md) +* [introduction](introduction.md) diff --git a/docs/installing-the-cli.md b/docs/installing-the-cli.md new file mode 100644 index 0000000..7409ed0 --- /dev/null +++ b/docs/installing-the-cli.md @@ -0,0 +1,50 @@ +# Installing the cli + +To run update from the command line, you'll need to install Update's CLI globally first. You can do that now with the following command: + +```sh +$ npm install --global update +``` + +This adds the `update` command to your system path, allowing it to be run from any directory. + +You should now be able to use the `update` command to execute code in a local `updatefile.js` file, or to execute globally installed updaters by their [aliases](#aliases). + +**Init** + +If it's your first time using update, run `update init` to set your global defaults. + +**CLI help** + +``` +Usage: update [options] + +Command: Generator or tasks to run + +Examples: + + # run the "foo" updater + $ update foo + + # run the "bar" task on updater "foo" + $ update foo:bar + + # run multiple tasks on updater "foo" + $ update foo:bar,baz,qux + + # run a sub-updater on updater "foo" + $ update foo.abc + + # run task "xyz" on sub-updater "foo.abc" + $ update foo.abc:xyz + + Update attempts to automatically determine if "foo" is a task or updater. + If there is a conflict, you can force update to run updater "foo" + by specifying a task on the updater. Example: `update foo:default` +``` + +# Related + +**Docs** + +* [installing-updaters](installing-updaters.md) diff --git a/docs/installing-updaters.md b/docs/installing-updaters.md new file mode 100644 index 0000000..1314df7 --- /dev/null +++ b/docs/installing-updaters.md @@ -0,0 +1,27 @@ +# Installing updaters + +Updaters are responsible for all of the "updating" that happens in update. You can find [updaters to install](#updaters to install) on npm, or create your own. + +If you author and publish a updater, please name your updater using the pattern `updater-foo`, where `foo` is the `alias` to be passed to Update's CLI for executing your updater (example: `update foo` would run `updater-foo`). + +_(Before proceding, please keep in mind that update's job is to make changes to files that are often neglected. You will get used to this quickly, but to prevent surprises always make sure your work is committed before running `update`)_ + +Let's install an updater so you can see how this works. + +```sh +$ npm install --global updater-license +``` + +_(The `license` updater will update the copyright statement your `LICENSE` or `LICENSE-MIT` file. If you don't use MIT, that's okay. This is just an example intended to give you the gist of running update. Just be sure to `git commit` first so you can revert any changes afterwards.)_ + +If `updater-license` installed successfully, you should now be able to run it with the following command: + +```sh +$ update license +``` + +# Related + +**Docs** + +* [installing-the-cli](installing-the-cli.md) diff --git a/docs/introduction.md b/docs/introduction.md new file mode 100644 index 0000000..d6b3ea8 --- /dev/null +++ b/docs/introduction.md @@ -0,0 +1,116 @@ +# Introduction + + * [What is update?](#what-is-update) + * [How does it work?](#how-does-it-work) + * [Updaters](#updaters) + * [Command line](#command-line) + + [updatefile.js](#updatefilejs) + + [Running updaters](#running-updaters) + + [Aliases](#aliases) + * [Resolving updaters](#resolving-updaters) + * [API](#api) + * [Update](#update) +- [Related](#related) + +## What is update? + +Update is a new, open-source developer framework for automating updates of any kind to code projects. + +* update files that are typically excluded from the automated parts of the software lifecycle, and thus are mostly forgotten about after they're created. +* fix dates in copyrights, licenses and banners, removing deprecated fields from project manifests, updating settings in runtime config files, preferences in dotfiles, and so on. +* normalize configuration settings, verbiage, or preferences across all of your projects + +## How does it work? + +**Updaters** + +All "updates" are accomplished using plugins called [updaters](#updaters). Updaters are functions that are registered by name, and can be run by [command line](#command-line) or [API](#api). + +**Update core** + +Update itself is system for [creating](#creating-updaters), [registering](#registering-updaters), [resolving](#resolving-updaters) and [running](#running-updaters) updaters. + +## Updaters + +**What are updaters?** + +Updaters are plugins that provide all of the "updating" capabilities to update. Technically speaking, updaters are functions that are either registered by name using the [.register](#register) method, or directly using the [.use](#use) method. + +Updaters may be published to [npm](https://www.npmjs.com) using the `updater-foo` naming convention, where `foo` is the [alias](#aliases) of your updater. Published updaters can be installed locally or globally. + +1. **Plugins**: + +Since updaters tend to be used on unstructured data, or things that are often updated "by hand", the first few times you run update (depending on the updaters you run) you might be suprised at the number of inconsistencies and errors that are uncovered. + +## Command line + +### updatefile.js + +Each time `update` is run, Update's CLI looks for an [updatefile.js](docs/updatefile.md) in the current working directory: + +**If `updatefile.js` exists** + +If found, Update's CLI will attempt to load a local installation of the Update library using node's `require()` system, falling back to a global installation if necessary. Next, Update's CLI loads the configuration from your `updatefile.js`, and executes any tasks or updaters you've specified for it to run. + +**If `updatefile.js` does not exist** + +If not found, Update's CLI attempts to find any updaters you've specified for it to run by using node's `require()` system to search for locally installed modules with the name `updater-*`, + +### Running updaters + +To run updaters by command line, pass the [aliases](#aliases) or full [npm](https://www.npmjs.com) package names (if published) of the updaters to run after the `update` command. + +**Example** + +Run updaters `foo`, `bar` and `baz` in series: + +```sh +$ update foo bar baz +# or +$ update updater-foo updater-bar updater-baz +``` + +Note that _updaters are run in series_, so given the previous example, updater `bar` will not run until updater `foo` is completely finished executing. + +### Aliases + +Get the alias of an updater by removing the `updater-` substring from the begining of the full name. + +## Resolving updaters + +When run by command line, Update's CLI will attempt to find and run updaters matching the names you've given, by first searching in the local `updatefile.js`, then using node's `require()` system to find locally installed updaters, and last by searching for globally installed updaters. + +If any of the updaters specified is not found, _an error is thrown and the process will exit_. + +## API + +by passing the names of updaters to run to the `.update` + +## Update + +Updaters via CLI or API. (tasks are powered by [bach][], the same library used in [gulp][] v4.0). + +The main export of the library is a constructor function, `Update`. + +Updaters themselves are just functions that take an instance of `Update`. wrap code to be executed when the + +Update gives you a way to automate the maintenance of files that are typically excluded from the automated parts of the software lifecycle, and thus are mostly forgotten about after they're created. + +For example, if we were to sift the files in the average code project into major generic buckets we would end up with something like this: + +* **code**: the actual source code of the project (compiled, lib, src, and so on) +* **dist**: the "deliverable" of the project (this could be HTML, CSS, minified JavaScript, or something similar for non-web projects) +* **docs**: documentation for the project +* **everything else**: LICENSE and copyright files, dotfiles, manifests, config files, and so on. + +Update maintains **everything else**. + +# Related + +**Docs** + +* [updaters](updaters.md) +* [updatefile](updatefile.md) +* [tasks](tasks.md) +* [features](features.md) +* [faq](faq.md) diff --git a/docs/tasks.md b/docs/tasks.md new file mode 100644 index 0000000..6209138 --- /dev/null +++ b/docs/tasks.md @@ -0,0 +1,167 @@ +# Tasks + +Tasks are used for wrapping code that should be executed at a later point, either when specified by command line or explicitly run when using the API. + + * [Creating tasks](#creating-tasks) + * [Running tasks](#running-tasks) + + [Command line](#command-line) + + [Task API](#task-api) + - [.task](#task) + - [.build](#build) + - [.update](#update) + + [Task composition](#task-composition) + - [Task dependencies](#task-dependencies) + - [Alias tasks](#alias-tasks) + + [default task](#default-task) +- [Related](#related) + +## Creating tasks + +Tasks are asynchronous functions that are registered by name using the `.task` method, and can be run using the `.build` method. + +## Running tasks + +Tasks can be run by command line or API. + +### Command line + +Pass the names of the tasks to run after the `update` command. + +**Example** + +Run tasks `foo`, `bar` and `baz`: + +```sh +update foo bar baz +``` + +**Conflict resolution** + +You might notice that [updaters](updaters.md) can also be run from the command line using the same syntax. Update can usually determine whether you meant to call tasks or updaters. Visit the [running updaters](updaters.md#running-updaters) documentation for more information. + +### Task API + +#### .task + +Create a task: + +```js +app.task(name, fn); +``` + +**Params** + +* `name` **{String}**: name of the task to register +* `fn` **{Function}**: asynchronous callback function, or es6 generator function + +**Example** + +```js +app.task('default', function(cb) { + // do task stuff (be sure to call the callback) + cb(); +}); +``` + +**Stream or callback** + +When using update's file system API (`.src`/`.dest` etc), you can optionally return a stream instead of calling a callback. Either a callback must be called, or a stream must be returned, otherwise update has no way of knowing when a task is complete. + +#### .build + +Run one more tasks. + +**Params** + +* `names` **{String|Array|Function}**: names of one or more tasks to run, or callback function if you only want to run the [default task](#default-task) +* `callback`: callback function, invoked after all tasks have finished executing. The callback function exposes `err` as the only argument, with any errors that occurred during the execution of any tasks. + +**Example** + +```js +app.task('foo', function(cb) { + // do task stuff + cb(); +}); +app.task('foo', function(cb) { + // do task stuff + cb(); +}); + +app.build(['foo', 'bar'], function(err) { + if (err) return console.log(err); + console.log('done'); +}); +``` + +#### .update + +The `.update` may also be used to run tasks. However, `.update` can be used to run _tasks and updaters_, thus it will also look for updaters to run when a task is not found. + +_To ensure that only tasks are run, use the `.build` method._ + +See the [updaters documentation](#updaters) for more details. + +### Task composition + +#### Task dependencies + +When a task has "dependencies", this means that one or more other tasks need to finish before the task is executed. + +Dependencies can be passed as the second argument to the `.task` method. + +**Example** + +In the following example, task `foo` has dependencies `bar` and `baz`: + +```js +app.task('foo', ['bar', 'baz'], function(cb) { + // do task stuff + cb(); +}); +``` + +Task `foo` will not execute until tasks `bar` and `baz` have completed. + +#### Alias tasks + +An "alias" task is a task with one or more dependencies and _no callback_. + +**Example** + +In this example, task `foo` is an alias for tasks `bar` and `baz`: + +```js +app.task('foo', ['bar', 'baz']); +``` + +In this example, task `foo` is an alias for task `baz` + +```js +app.task('foo', ['baz']); +``` + +### default task + +The `default` task is run automatically when a callback is passed as the only argument: + +```js +app.task('default', function(cb) { + // do task stuff + cb(); +}); + +// no need to specify "default", but you can if you want +app.build(function(err) { + if (err) return console.log(err); + console.log('done'); +}); +``` + +# Related + +**Docs** + +* [running-updaters](running-updaters.md) +* [updaters](updaters.md) +* [updatefile](updatefile.md) diff --git a/docs/tutorial.md b/docs/tutorial.md new file mode 100644 index 0000000..970cd16 --- /dev/null +++ b/docs/tutorial.md @@ -0,0 +1,111 @@ +# Tutorial + +The following intro only skims the surface of what update has to offer. For a more in-depth introduction, we highly recommend visiting the [getting started guide][getting-started]. + +**Create an updater** + +Add a `updatefile.js` to the current working directory with the following code: + +```js +module.exports = function(app) { + console.log('success!'); +}; +``` + +**Run an updater** + +Enter the following command: + +```sh +update +``` + +If successful, you should see `success!` in the terminal. + +**Create a task** + +Now, add a task to your updater. + +```js +module.exports = function(app) { + app.task('default', function(cb) { + console.log('success!'); + cb(); + }); +}; +``` + +Now, in the command line, run: + +```sh +$ update +# then try +$ update default +``` + +When a local `updatefile.js` exists, the `update` command is aliased to automatically run the `default` task if one exists. But you can also run the task with `update default`. + +**Run a task** + +Let's try adding more tasks to your updater: + +```js +module.exports = function(app) { + app.task('default', function(cb) { + console.log('default > success!'); + cb(); + }); + + app.task('foo', function(cb) { + console.log('foo > success!'); + cb(); + }); + + app.task('bar', function(cb) { + console.log('bar > success!'); + cb(); + }); +}; +``` + +Now, in the command line, run: + +```sh +$ update +# then try +$ update foo +# then try +$ update foo bar +``` + +**Run task dependencies** + +Now update your code to the following: + +```js +module.exports = function(app) { + app.task('default', ['foo', 'bar']); + + app.task('foo', function(cb) { + console.log('foo > success!'); + cb(); + }); + + app.task('bar', function(cb) { + console.log('bar > success!'); + cb(); + }); +}; +``` + +And run: + +```sh +$ update +``` + +You're now a master at running tasks with update! You can do anything with update tasks that you can do with [gulp][] tasks (we use and support gulp libraries after all!). + +**Next steps** + +Update does much more than this. For a more in-depth introduction, we highly recommend visiting the [getting started guide](https://github.com/update/getting-started). diff --git a/docs/updatefile.md b/docs/updatefile.md new file mode 100644 index 0000000..a170d63 --- /dev/null +++ b/docs/updatefile.md @@ -0,0 +1,48 @@ +# Updatefile + +If an `updatefile.js` exists in the current working directoy, Update's CLI will attempt to load it and run any updaters or tasks you've specified for it to run. + +Moreover, Update's CLI will use `updatefile.js` to create the ["default" updater](default-updater.md). + +## Creating an updatefile.js + +An `updatefile.js` may contain any custom JavaScript code, but must export a function that takes an instance of Update (`app`): + +**Example** + +```js +// -- updatefile.js -- +module.exports = function(app) { + // custom code here +}; +``` + +Inside this function, you can define [tasks](tasks.md), additional [updaters](updaters.md), or any other custom JavaScript code necessary for your updater: + +```js +module.exports = function(app) { + // register a task + app.task('default', function(cb) { + // do task stuff + cb(); + }); + + // register an updater + app.register('foo', function() { + + }); + + // register another updater + app.register('bar', function() { + + }); +}; +``` + +# Related + +**Docs** + +* [installing-updaters](installing-updaters.md) +* [updaters](updaters.md) +* [tasks](tasks.md) diff --git a/docs/updaters.md b/docs/updaters.md new file mode 100644 index 0000000..edad0b9 --- /dev/null +++ b/docs/updaters.md @@ -0,0 +1,193 @@ +# Updaters + +Updaters are [plugins](plugins.md) that are registered by name. This document describes how to create, register and run updaters. + + * [What is an updater?](#what-is-an-updater) + * [Creating updaters](#creating-updaters) + * [Registering updaters](#registering-updaters) + * [Running updaters](#running-updaters) + * [Resolving updaters](#resolving-updaters) + + [Tasks and updaters](#tasks-and-updaters) + + [Naming tips](#naming-tips) + + [Order of precendence](#order-of-precendence) + * [Discovering updaters](#discovering-updaters) + * [Default updater](#default-updater) +- [Related](#related) + +## What is an updater? + +Updaters are just plugins. The only difference between "updaters" and "plugins" is how they're registered. + +**Comparison to plugins** + +Here is a comparison to illustrate the differences between the two in detail: + +| | **Plugin** | **Updater** | +| --- | --- | --- | +| Registered with | [.use](plugins.md#use) method | [.register](#register) method or [.updater](#updater) method | +| Instance | Loaded onto "current" `Update` instance | A `new Update()` instance is created for every updater registered | +| Invoked | Immediately | `.register` deferred (lazy), `.updater` immediately | +| Run using | [.run](plugins.md#run): all plugins are run at once | `.update`: only specified plugin(s) are run | + +**Which method should I use?** + +In general, it's recommended that you use the `.register` method. In most cases update is smart enough to figure out when to invoke updater functions. + +However, there are always exceptions. If you create custom code and notice that update can't find the information it needs. Try using the `.updater` method to to invoke the function when the updater is registered. + +## Creating updaters + +An updater function takes an instance of `Update` as the first argument. + +**Example** + +```js +var update = require('update'); +var app = update(); + +function updater(app) { + console.log(app); +} +``` + +## Registering updaters + +Updaters may be registered using either of the following methods: + +* `.register`: if the plugin should not be invoked until it's called by `.update` (stays lazy while it's cached, this is preferred) +* `.updater`: if the plugin needs to be invoked immediately when registered + +**Example** + +```js +var Update = require('update'); +var app = new Update(); + +function updater(msg) { + return function(app) { + // "app" is the instance of update we created + console.log(msg); + }; +} + +app.register('foo', updater('One!!!')); +app.updater('bar', updater('Two!!!')); // <= invoked now + +// `updater` foo won't be invoked until called by `.update` +app.update(['foo', 'bar'], function(err) { + if (err) return console.log(err); + // 'One!!!' + // 'Two!!!' +}); +``` + +## Running updaters + +Updaters and their tasks can be run by command line or API. + +todo + +## Resolving updaters + +Updaters can be published to npm and installed globally or locally. But you there is no requirement that updaters must be published. You can also create custom updaters and register them using the [.register](#register) or [.updater](#updater) methods. + +This provides a great deal of flexibility, but it also means that we need a strategy for _finding updaters_ when `update` is run from the command line. + +### Tasks and updaters + +1. When both a task and an updater have the same name _on the same instance_, Update will always try to run the task first (this is unlikely to happen unless you intend for it to - there are [reasons to do this](#naming-tips)) + +### Naming tips + +Since the [.build](tasks.md#build) method only runs tasks, you can use this to your advantage by aliasing sub-generators with tasks. + +**Don't do this** + +```js +module.exports = function(app) { + app.register('foo', function(foo) { + foo.task('default', function(cb) { + // do task stuff + cb(); + }); + }); + + // `.build` doesn't run updaters + app.build('foo', function(err) { + if (err) return console.log(err); + }); +}; +``` + +**Do this** + +```js +module.exports = function(app) { + app.register('foo', function(foo) { + foo.task('default', function(cb) { + // do task stuff + cb(); + }); + }); + + // `.build` doesn't run updaters + app.update('foo', function(err) { + if (err) return console.log(err); + }); +}; +``` + +**Or this** + +```js +module.exports = function(app) { + app.register('foo', function(foo) { + foo.task('default', function(cb) { + // do task stuff + cb(); + }); + }); + + app.task('foo', function(cb) { + app.update('foo', cb); + }); + + // `.build` will run task `foo`, which runs the updater + app.build('foo', function(err) { + if (err) return console.log(err); + }); +}; +``` + +### Order of precendence + +When the command line is used, Update's CLI resolves updaters in the following order: + +1. [default updater](#default-updater): attempts to match given names to updaters and tasks registered on the `default` updater +2. built-in updaters: attempts to match given names to Update's [built-in updaters](built-in-updaters.md) +3. locally installed updaters +4. globally installed updaters + +## Discovering updaters + +todo + +## Default updater + +If an updater is registered with the name `default` it will receive special treatment from Update and Update's CLI. More specifically, when Update's CLI looks for updaters or tasks to run, it will search for them on the `default` updater first. + +There is a catch... + +**Registering the "default" updater** + +_The only way to register a `default` updater is by creating an [updatefile.js](updatefile.md) in the current working directory._ + +When used by command line, Update's CLI will then use node's `require()` system to get the function exported by `updatefile.js` and use it as the `default` updater. + +# Related + +**Docs** + +* [tasks](tasks.md) +* [updatefile](updatefile.md) +* [installing-updaters](installing-updaters.md) diff --git a/support/docs/_drafts/notes.md b/support/docs/_drafts/notes.md index fc61f12..a2eca69 100644 --- a/support/docs/_drafts/notes.md +++ b/support/docs/_drafts/notes.md @@ -1,8 +1,7 @@ --- title: Notes -layout: default related: - docs: [] + doc: [] --- Visit the [getting started guide][getting-started] to learn more. diff --git a/support/docs/api.plugins.md b/support/docs/api.plugins.md index ddea64f..e3b135a 100644 --- a/support/docs/api.plugins.md +++ b/support/docs/api.plugins.md @@ -1,8 +1,8 @@ --- title: Plugins -layout: default related: api: ['updater', 'register'] + doc: ['default-updater', 'resolving-updaters', 'registering-updaters'] --- A plugin is function that takes an instance of `Update` and is registered with the `.use` method. See the [base-plugins][] documentation for additional details. @@ -61,9 +61,3 @@ This can continue indefinitely as long as the plugin returns a function and the ## Updaters When plugins are [registered by name](../docs/updaters.md), they are referred to as "updaters". See the [updater documentation](../docs/updaters.md) for more details. - -## Related - -* [default-updater](default-updater.md) -* [resolving-updaters](resolving-updaters.md) -* [registering-updaters](registering-updaters.md) \ No newline at end of file diff --git a/support/docs/api.register.md b/support/docs/api.register.md index 904e5cf..1ee1054 100644 --- a/support/docs/api.register.md +++ b/support/docs/api.register.md @@ -1,6 +1,5 @@ --- title: Register -layout: default related: api: ['updater', 'plugin'] --- @@ -31,8 +30,3 @@ app.update('foo', function(err) { if (err) return console.log(err); }); ``` - -## Related - -* [plugin](plugin.md) -* [updaters](updaters.md) \ No newline at end of file diff --git a/support/docs/api.updater.md b/support/docs/api.updater.md index 0414116..3942c16 100644 --- a/support/docs/api.updater.md +++ b/support/docs/api.updater.md @@ -1,6 +1,5 @@ --- title: Updater -layout: default related: api: ['register', 'plugin'] --- @@ -31,8 +30,3 @@ app.update('bar', function(err) { if (err) return console.log(err); }); ``` - -## Related - -* [plugin](plugin.md) -* [updaters](updaters.md) \ No newline at end of file diff --git a/support/docs/cli.built-in-updaters.md b/support/docs/cli.built-in-updaters.md index 6d45772..05352e2 100644 --- a/support/docs/cli.built-in-updaters.md +++ b/support/docs/cli.built-in-updaters.md @@ -1,8 +1,7 @@ --- title: Built in updaters -layout: default related: - docs: [] + doc: [] --- Update only has a few built-in [updaters](docs/updaters.md) (these might be externalized at some point): diff --git a/support/docs/faq.md b/support/docs/faq.md index 8cca06f..b7e8eb4 100644 --- a/support/docs/faq.md +++ b/support/docs/faq.md @@ -1,8 +1,7 @@ --- title: Faq -layout: default related: - docs: ['features', 'introduction'] + doc: ['features', 'introduction'] --- diff --git a/support/docs/features.md b/support/docs/features.md index 7ab600c..422e4b3 100644 --- a/support/docs/features.md +++ b/support/docs/features.md @@ -1,8 +1,7 @@ --- title: Features -layout: default related: - docs: ['faq', 'introduction'] + doc: ['faq', 'introduction'] --- Update offers an elegant and robust suite of methods, carefully organized to help you accomplish common activities in less time, including: diff --git a/support/docs/installing-the-cli.md b/support/docs/installing-the-cli.md index 03a0582..df75256 100644 --- a/support/docs/installing-the-cli.md +++ b/support/docs/installing-the-cli.md @@ -1,8 +1,7 @@ --- title: Installing the cli -layout: default related: - docs: ['installing-updaters'] + doc: ['installing-updaters'] --- To run update from the command line, you'll need to install Update's CLI globally first. You can do that now with the following command: @@ -47,7 +46,3 @@ Examples: If there is a conflict, you can force update to run updater "foo" by specifying a task on the updater. Example: `update foo:default` ``` - -## Related - -* [installing-updaters](installing-updaters.md) \ No newline at end of file diff --git a/support/docs/installing-updaters.md b/support/docs/installing-updaters.md index e802855..f3051f2 100644 --- a/support/docs/installing-updaters.md +++ b/support/docs/installing-updaters.md @@ -1,8 +1,7 @@ --- title: Installing updaters -layout: default related: - docs: ['installing-the-cli'] + doc: ['installing-the-cli'] --- Updaters are responsible for all of the "updating" that happens in update. You can find [updaters to install](#updaters to install) on npm, or create your own. diff --git a/support/docs/introduction.md b/support/docs/introduction.md index 9d01b26..3e03260 100644 --- a/support/docs/introduction.md +++ b/support/docs/introduction.md @@ -1,10 +1,11 @@ --- title: Introduction -layout: default related: - docs: ['updaters', 'updatefile', 'tasks', 'features', 'faq'] + doc: ['updaters', 'updatefile', 'tasks', 'features', 'faq'] --- + + ## What is update? Update is a new, open-source developer framework for automating updates of any kind to code projects. diff --git a/support/docs/layouts/default.md b/support/docs/layouts/default.md index 7d88648..6c915a7 100644 --- a/support/docs/layouts/default.md +++ b/support/docs/layouts/default.md @@ -1,18 +1,13 @@ ---- -title: Default -layout: default -related: - docs: [] ---- +# <%= title %> {% body %} # Related -**Docs** -<%= links(related.docs) %> +<%= hasValue(related.doc, '**Docs**') %> +<%= links(related.doc) %> -**API** -<%= links(related.docs) %> +<%= hasValue(related.api, '**API**') %> +<%= links(related.api) %> -**URL** -<%= links(related.docs) %> \ No newline at end of file +<%= hasValue(related.url, '**Links**') %> +<%= links(related.url) %> \ No newline at end of file diff --git a/support/docs/tasks.md b/support/docs/tasks.md index a1def99..a410fae 100644 --- a/support/docs/tasks.md +++ b/support/docs/tasks.md @@ -1,24 +1,12 @@ --- title: Tasks -layout: default related: - docs: ['running-updaters', 'updaters', 'updatefile'] + doc: ['running-updaters', 'updaters', 'updatefile'] --- Tasks are used for wrapping code that should be executed at a later point, either when specified by command line or explicitly run when using the API. -- [Creating tasks](#creating-tasks) -- [Running tasks](#running-tasks) - * [Command line](#command-line) - * [Task API](#task-api) - + [.task](#task) - + [.build](#build) - + [.update](#update) - * [Task composition](#task-composition) - + [Task dependencies](#task-dependencies) - + [Alias tasks](#alias-tasks) - * [default task](#default-task) -- [Related](#related) + ## Creating tasks diff --git a/support/docs/tutorial.md b/support/docs/tutorial.md index 18e7d56..0836181 100644 --- a/support/docs/tutorial.md +++ b/support/docs/tutorial.md @@ -1,8 +1,7 @@ --- title: Tutorial -layout: default related: - docs: [] + doc: [] --- The following intro only skims the surface of what update has to offer. For a more in-depth introduction, we highly recommend visiting the [getting started guide][getting-started]. diff --git a/support/docs/updatefile.md b/support/docs/updatefile.md index 3fde3a0..e5e41d7 100644 --- a/support/docs/updatefile.md +++ b/support/docs/updatefile.md @@ -1,8 +1,7 @@ --- title: Updatefile -layout: default related: - docs: ['installing-updaters', 'updaters', 'tasks'] + doc: ['installing-updaters', 'updaters', 'tasks'] --- If an `updatefile.js` exists in the current working directoy, Update's CLI will attempt to load it and run any updaters or tasks you've specified for it to run. diff --git a/support/docs/updaters.md b/support/docs/updaters.md index 909d465..96937df 100644 --- a/support/docs/updaters.md +++ b/support/docs/updaters.md @@ -1,23 +1,12 @@ --- title: Updaters -layout: default related: - docs: [] + doc: ['tasks', 'updatefile', 'installing-updaters'] --- Updaters are [plugins](plugins.md) that are registered by name. This document describes how to create, register and run updaters. -- [What is an updater?](#what-is-an-updater) -- [Creating updaters](#creating-updaters) -- [Registering updaters](#registering-updaters) -- [Running updaters](#running-updaters) -- [Resolving updaters](#resolving-updaters) - * [Tasks and updaters](#tasks-and-updaters) - * [Naming tips](#naming-tips) - * [Order of precendence](#order-of-precendence) -- [Discovering updaters](#discovering-updaters) -- [Default updater](#default-updater) -- [Related](#related) + ## What is an updater? diff --git a/support/lib/matter.js b/support/lib/_drafts/matter.js similarity index 95% rename from support/lib/matter.js rename to support/lib/_drafts/matter.js index bdb7694..9597eaa 100644 --- a/support/lib/matter.js +++ b/support/lib/_drafts/matter.js @@ -15,7 +15,7 @@ // data += '\n'; // data += 'layout: default\n'; // data += 'related:\n'; - // data += ' docs: []\n'; + // data += ' doc: []\n'; // data += '---\n\n'; // view.content = data + view.content; // next(); @@ -26,4 +26,4 @@ // return app.toStream('pages') // // .pipe(app.renderFile()) // .pipe(app.dest('docs')); - // }); \ No newline at end of file + // }); diff --git a/support/lib/helpers.js b/support/lib/helpers.js index e8ac3b3..eab3ffa 100644 --- a/support/lib/helpers.js +++ b/support/lib/helpers.js @@ -1,5 +1,6 @@ 'use strict'; +var hasValue = require('has-value'); var helpers = require('template-helpers'); var isValid = require('is-valid-app'); var path = require('path'); @@ -8,6 +9,9 @@ module.exports = function(options) { return function(app) { if (!isValid(app, 'update-support-helpers')) return; app.helpers(helpers()); + app.helper('hasValue', function(val, str) { + return hasValue(val) ? str : ''; + }); app.helper('links', function(arr) { arr = arr ? (Array.isArray(arr) ? arr : [arr]) : []; var links = arr.map(function(link) { diff --git a/support/lib/middleware.js b/support/lib/middleware.js index bfa3d97..365c25f 100644 --- a/support/lib/middleware.js +++ b/support/lib/middleware.js @@ -1,7 +1,8 @@ 'use strict'; -var isValid = require('is-valid-app'); var path = require('path'); +var isValid = require('is-valid-app'); +var utils = require('./utils'); module.exports = function(options) { return function(app) { @@ -9,7 +10,15 @@ module.exports = function(options) { app.cache.views = {docs: []}; app.onLoad(/\.md$/, function(view, next) { - view.data.related = view.data.related || {}; + var related = view.data.related || (view.data.related = {}); + related.doc = utils.arrayify(related.doc); + related.api = utils.arrayify(related.api); + related.url = utils.arrayify(related.url); + + if (view.content.indexOf('') !== -1) { + view.data.toc = true; + } + view.data.layout = 'default'; if (view.data.toc === true) { view.data.toc = {render: true}; @@ -29,6 +38,8 @@ module.exports = function(options) { if (segs.length > 1) { file.path = path.resolve(file.base, segs.join('/') + file.extname); } + file.content = file.content.trim(); + file.content += '\n'; next(); }); }; diff --git a/support/lib/paths.js b/support/lib/paths.js index 9c14dad..d87fa88 100644 --- a/support/lib/paths.js +++ b/support/lib/paths.js @@ -10,7 +10,7 @@ exports.docs = function(fp) { }; exports.site = function(fp) { - var res = exports.memo('../_gh-pages')(fp); + var res = exports.memo('../_gh_pages')(fp); return fp ? res() : res; }; diff --git a/support/lib/utils.js b/support/lib/utils.js new file mode 100644 index 0000000..4edb2a5 --- /dev/null +++ b/support/lib/utils.js @@ -0,0 +1,5 @@ +'use strict'; + +exports.arrayify = function(val) { + return val ? (Array.isArray(val) ? val : [val]) : []; +}; diff --git a/support/package.json b/support/package.json index 7ef1840..91711c8 100644 --- a/support/package.json +++ b/support/package.json @@ -17,13 +17,13 @@ "test": "mocha" }, "devDependencies": { - "assemble": "^0.14.0", "base-generators": "^0.4.1", "delete": "^0.3.2", "export-files": "^2.1.1", "gulp-drafts": "^0.2.0", "gulp-format-md": "^0.1.9", "gulp-reflinks": "^0.1.0", + "has-value": "^0.3.1", "is-valid-app": "^0.2.0", "memoize-path": "^0.1.2", "template-helpers": "^0.6.3", From fcd93b0888f1225d33519e96c2b7fe8137c765f4 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 28 Jun 2016 11:59:56 -0400 Subject: [PATCH 182/274] add patterns --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 80a228c..8dc5b44 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ vendor .idea benchmark coverage +_notes.md +_draft From ee5c3378a0caba561894ab4df38348320b09e2b9 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 28 Jun 2016 12:00:11 -0400 Subject: [PATCH 183/274] update deps --- package.json | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index badc184..06363e4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "update", - "description": "Update your projects.", + "description": "Update is a developer framework and CLI for automating updates of any kind in code projects, using globally or locally installed `updaters`.", "version": "0.1.0", "homepage": "https://github.com/jonschlinkert/update", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", @@ -26,25 +26,26 @@ "test": "mocha" }, "dependencies": { - "base-app": "^0.2.5", - "base-cli-process": "^0.1.11", - "base-config-process": "^0.1.6", + "arr-union": "^3.1.0", + "base-app": "^0.2.6", + "base-cli-process": "^0.1.13", + "base-config-process": "^0.1.7", "base-questions": "^0.6.6", "base-runtimes": "^0.1.11", "base-store": "^0.4.4", + "export-files": "^2.1.1", "extend-shallow": "^2.0.1", "find-pkg": "^0.1.2", "fs-exists-sync": "^0.1.0", "global-modules": "^0.2.2", - "gulp-choose-files": "^0.1.0", + "gulp-choose-files": "^0.1.2", + "is-valid-app": "^0.2.0", "lazy-cache": "^2.0.1", "log-utils": "^0.1.4", "os-homedir": "^1.0.1", "resolve-file": "^0.2.0", "set-blocking": "^2.0.0", "through2": "^2.0.1", - "window-size": "^0.2.0", - "word-wrap": "^1.1.0", "yargs-parser": "^2.4.0" }, "devDependencies": { @@ -53,6 +54,7 @@ "gulp-format-md": "^0.1.9", "gulp-istanbul": "^0.10.4", "gulp-mocha": "^2.2.0", + "gulp-reflinks": "^0.1.0", "gulp-unused": "^0.1.2", "mocha": "^2.5.3" }, @@ -75,10 +77,8 @@ ] }, "verb": { - "related": { - "list": [] - }, - "toc": false, + "run": true, + "toc": true, "layout": "default", "tasks": [ "readme" @@ -86,19 +86,21 @@ "plugins": [ "gulp-format-md" ], - "lint": { - "reflinks": true + "related": { + "list": [ + "assemble", + "base", + "generate", + "verb" + ] }, "reflinks": [ - "assemble", "base", - "gulp", - "handlebars", - "lodash", - "pug", - "swig", "verb", "verb-readme-generator" - ] + ], + "lint": { + "reflinks": true + } } } From 46f80907c26f212c243a7ee22eeb224fbafaa41c Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 28 Jun 2016 12:00:27 -0400 Subject: [PATCH 184/274] link linter, rebuild-docs --- docs/api/plugins.md | 14 ++--- docs/api/register.md | 6 +- docs/api/updater.md | 6 +- docs/cli/built-in-updaters.md | 2 + docs/faq.md | 2 +- docs/features.md | 12 ++-- docs/installing-the-cli.md | 2 +- docs/installing-updaters.md | 2 +- docs/introduction.md | 25 ++++---- docs/tasks.md | 27 ++++----- docs/tutorial.md | 6 +- docs/updatefile.md | 4 +- docs/updaters.md | 29 +++++---- support/assemblefile.js | 5 +- support/docs/api.plugins.md | 4 +- support/docs/api.register.md | 2 +- support/docs/api.updater.md | 2 +- support/docs/layouts/default.md | 8 +-- support/docs/tasks.md | 2 +- support/docs/updatefile.md | 2 +- support/docs/updaters.md | 4 +- support/lib/helpers.js | 40 +++++++++---- support/lib/index.js | 1 + support/lib/plugins.js | 80 +++++++++++++++++++++++++ support/lib/utils.js | 101 +++++++++++++++++++++++++++++++- support/package.json | 8 ++- support/verbfile.js | 3 +- 27 files changed, 304 insertions(+), 95 deletions(-) create mode 100644 support/lib/plugins.js diff --git a/docs/api/plugins.md b/docs/api/plugins.md index e6a3fae..07c34d5 100644 --- a/docs/api/plugins.md +++ b/docs/api/plugins.md @@ -1,6 +1,6 @@ # Plugins -A plugin is function that takes an instance of `Update` and is registered with the `.use` method. See the [base-plugins][] documentation for additional details. +A plugin is function that takes an instance of `Update` and is registered with the `.use` method. See the [base-plugins](https://github.com/node-base/base-plugins) documentation for additional details. ### .use @@ -55,17 +55,15 @@ This can continue indefinitely as long as the plugin returns a function and the ## Updaters -When plugins are [registered by name](../docs/updaters.md), they are referred to as "updaters". See the [updater documentation](../docs/updaters.md) for more details. +When plugins are [registered by name](docs/updaters.md), they are referred to as "updaters". See the [updater documentation](docs/updaters.md) for more details. -# Related +true **Docs** -* [default-updater](default-updater.md) -* [resolving-updaters](resolving-updaters.md) -* [registering-updaters](registering-updaters.md) +* [updaters](../docs/updaters.md) **API** -* [updater](updater.md) -* [register](register.md) +* [updater](api/updater.md) +* [register](api/register.md) diff --git a/docs/api/register.md b/docs/api/register.md index 38d3551..c465bef 100644 --- a/docs/api/register.md +++ b/docs/api/register.md @@ -27,9 +27,9 @@ app.update('foo', function(err) { }); ``` -# Related +true **API** -* [updater](updater.md) -* [plugin](plugin.md) +* [updater](api/updater.md) +* [plugins](api/plugins.md) diff --git a/docs/api/updater.md b/docs/api/updater.md index f9d5a41..924ecdf 100644 --- a/docs/api/updater.md +++ b/docs/api/updater.md @@ -27,9 +27,9 @@ app.update('bar', function(err) { }); ``` -# Related +true **API** -* [register](register.md) -* [plugin](plugin.md) +* [register](api/register.md) +* [plugins](api/plugins.md) diff --git a/docs/cli/built-in-updaters.md b/docs/cli/built-in-updaters.md index de53fd5..3738d7f 100644 --- a/docs/cli/built-in-updaters.md +++ b/docs/cli/built-in-updaters.md @@ -49,3 +49,5 @@ Display a help menu with all available commands: ```sh $ update help ``` + +true diff --git a/docs/faq.md b/docs/faq.md index 6e1e61f..c891a07 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -10,7 +10,7 @@ A updater's alias is created by stripping the substring `update-` from the _full Note that **no dots may be used in published updater names**. Aside from that, any characters considered valid by npm are fine. -# Related +true **Docs** diff --git a/docs/features.md b/docs/features.md index 93bb748..e9fba2a 100644 --- a/docs/features.md +++ b/docs/features.md @@ -2,18 +2,18 @@ Update offers an elegant and robust suite of methods, carefully organized to help you accomplish common activities in less time, including: -* **unparalleled flow control**: through the use of [updaters][getting-started], [sub-updaters][getting-started] and [tasks][getting-started] +* **unparalleled flow control**: through the use of [updaters](https://github.com/taunus/getting-started), [sub-updaters](https://github.com/taunus/getting-started) and [tasks](https://github.com/taunus/getting-started) * **templates, scaffolds and boilerplates**: update a single file, initialize an entire project, or provide ad-hoc "components" throughout the duration of a project using any combination of [templates, scaffolds and boilerplates](#templates-scaffolds-and-boilerplates). -* **any engine**: use any template engine to render templates, including [handlebars][], [lodash][], [swig][] and [pug][] +* **any engine**: use any template engine to render templates, including [handlebars](http://www.handlebarsjs.com/), [lodash](https://lodash.com/), [swig](https://github.com/paularmstrong/swig) and [pug](http://jade-lang.com) * **prompts**: asks you for data when it can't find what it needs, and it's easy to customize prompts for any data you want. * **data**: gathers data from the user's environment to populate "hints" in user prompts and render templates -* **streams**: interact with the file system, with full support for [gulp][] and [assemble][] plugins -* **smart plugins**: Update is built on [base][], so any "smart" plugin can be used +* **streams**: interact with the file system, with full support for [gulp](http://gulpjs.com) and [assemble](https://github.com/assemble/assemble) plugins +* **smart plugins**: Update is built on [base](https://github.com/node-base/base), so any "smart" plugin can be used * **stores**: persist configuration settings, global defaults, project-specific defaults, answers to prompts, and so on. -Visit the [getting started guide][getting-started] to learn more. +Visit the [getting started guide](https://github.com/taunus/getting-started) to learn more. -# Related +true **Docs** diff --git a/docs/installing-the-cli.md b/docs/installing-the-cli.md index 7409ed0..1c125b0 100644 --- a/docs/installing-the-cli.md +++ b/docs/installing-the-cli.md @@ -43,7 +43,7 @@ Examples: by specifying a task on the updater. Example: `update foo:default` ``` -# Related +true **Docs** diff --git a/docs/installing-updaters.md b/docs/installing-updaters.md index 1314df7..b26ab6b 100644 --- a/docs/installing-updaters.md +++ b/docs/installing-updaters.md @@ -20,7 +20,7 @@ If `updater-license` installed successfully, you should now be able to run it wi $ update license ``` -# Related +true **Docs** diff --git a/docs/introduction.md b/docs/introduction.md index d6b3ea8..af35309 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -1,16 +1,15 @@ # Introduction - * [What is update?](#what-is-update) - * [How does it work?](#how-does-it-work) - * [Updaters](#updaters) - * [Command line](#command-line) - + [updatefile.js](#updatefilejs) - + [Running updaters](#running-updaters) - + [Aliases](#aliases) - * [Resolving updaters](#resolving-updaters) - * [API](#api) - * [Update](#update) -- [Related](#related) +- [What is update?](#what-is-update) +- [How does it work?](#how-does-it-work) +- [Updaters](#updaters) +- [Command line](#command-line) + * [updatefile.js](#updatefilejs) + * [Running updaters](#running-updaters) + * [Aliases](#aliases) +- [Resolving updaters](#resolving-updaters) +- [API](#api) +- [Update](#update) ## What is update? @@ -88,7 +87,7 @@ by passing the names of updaters to run to the `.update` ## Update -Updaters via CLI or API. (tasks are powered by [bach][], the same library used in [gulp][] v4.0). +Updaters via CLI or API. (tasks are powered by [bach](https://github.com/gulpjs/bach), the same library used in [gulp](http://gulpjs.com) v4.0). The main export of the library is a constructor function, `Update`. @@ -105,7 +104,7 @@ For example, if we were to sift the files in the average code project into major Update maintains **everything else**. -# Related +true **Docs** diff --git a/docs/tasks.md b/docs/tasks.md index 6209138..0043498 100644 --- a/docs/tasks.md +++ b/docs/tasks.md @@ -2,18 +2,17 @@ Tasks are used for wrapping code that should be executed at a later point, either when specified by command line or explicitly run when using the API. - * [Creating tasks](#creating-tasks) - * [Running tasks](#running-tasks) - + [Command line](#command-line) - + [Task API](#task-api) - - [.task](#task) - - [.build](#build) - - [.update](#update) - + [Task composition](#task-composition) - - [Task dependencies](#task-dependencies) - - [Alias tasks](#alias-tasks) - + [default task](#default-task) -- [Related](#related) +- [Creating tasks](#creating-tasks) +- [Running tasks](#running-tasks) + * [Command line](#command-line) + * [Task API](#task-api) + + [.task](#task) + + [.build](#build) + + [.update](#update) + * [Task composition](#task-composition) + + [Task dependencies](#task-dependencies) + + [Alias tasks](#alias-tasks) + * [default task](#default-task) ## Creating tasks @@ -158,10 +157,10 @@ app.build(function(err) { }); ``` -# Related +true **Docs** -* [running-updaters](running-updaters.md) +* [updaters](updaters.md#running-updaters) * [updaters](updaters.md) * [updatefile](updatefile.md) diff --git a/docs/tutorial.md b/docs/tutorial.md index 970cd16..909a914 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -1,6 +1,6 @@ # Tutorial -The following intro only skims the surface of what update has to offer. For a more in-depth introduction, we highly recommend visiting the [getting started guide][getting-started]. +The following intro only skims the surface of what update has to offer. For a more in-depth introduction, we highly recommend visiting the [getting started guide](https://github.com/taunus/getting-started). **Create an updater** @@ -104,8 +104,10 @@ And run: $ update ``` -You're now a master at running tasks with update! You can do anything with update tasks that you can do with [gulp][] tasks (we use and support gulp libraries after all!). +You're now a master at running tasks with update! You can do anything with update tasks that you can do with [gulp](http://gulpjs.com) tasks (we use and support gulp libraries after all!). **Next steps** Update does much more than this. For a more in-depth introduction, we highly recommend visiting the [getting started guide](https://github.com/update/getting-started). + +true diff --git a/docs/updatefile.md b/docs/updatefile.md index a170d63..8f23e24 100644 --- a/docs/updatefile.md +++ b/docs/updatefile.md @@ -2,7 +2,7 @@ If an `updatefile.js` exists in the current working directoy, Update's CLI will attempt to load it and run any updaters or tasks you've specified for it to run. -Moreover, Update's CLI will use `updatefile.js` to create the ["default" updater](default-updater.md). +Moreover, Update's CLI will use `updatefile.js` to create the ["default" updater](updaters.md#default-updater). ## Creating an updatefile.js @@ -39,7 +39,7 @@ module.exports = function(app) { }; ``` -# Related +true **Docs** diff --git a/docs/updaters.md b/docs/updaters.md index edad0b9..7113361 100644 --- a/docs/updaters.md +++ b/docs/updaters.md @@ -1,18 +1,17 @@ # Updaters -Updaters are [plugins](plugins.md) that are registered by name. This document describes how to create, register and run updaters. - - * [What is an updater?](#what-is-an-updater) - * [Creating updaters](#creating-updaters) - * [Registering updaters](#registering-updaters) - * [Running updaters](#running-updaters) - * [Resolving updaters](#resolving-updaters) - + [Tasks and updaters](#tasks-and-updaters) - + [Naming tips](#naming-tips) - + [Order of precendence](#order-of-precendence) - * [Discovering updaters](#discovering-updaters) - * [Default updater](#default-updater) -- [Related](#related) +Updaters are [plugins](api/plugins.md) that are registered by name. This document describes how to create, register and run updaters. + +- [What is an updater?](#what-is-an-updater) +- [Creating updaters](#creating-updaters) +- [Registering updaters](#registering-updaters) +- [Running updaters](#running-updaters) +- [Resolving updaters](#resolving-updaters) + * [Tasks and updaters](#tasks-and-updaters) + * [Naming tips](#naming-tips) + * [Order of precendence](#order-of-precendence) +- [Discovering updaters](#discovering-updaters) +- [Default updater](#default-updater) ## What is an updater? @@ -164,7 +163,7 @@ module.exports = function(app) { When the command line is used, Update's CLI resolves updaters in the following order: 1. [default updater](#default-updater): attempts to match given names to updaters and tasks registered on the `default` updater -2. built-in updaters: attempts to match given names to Update's [built-in updaters](built-in-updaters.md) +2. built-in updaters: attempts to match given names to Update's [built-in updaters](cli/built-in-updaters.md) 3. locally installed updaters 4. globally installed updaters @@ -184,7 +183,7 @@ _The only way to register a `default` updater is by creating an [updatefile.js]( When used by command line, Update's CLI will then use node's `require()` system to get the function exported by `updatefile.js` and use it as the `default` updater. -# Related +true **Docs** diff --git a/support/assemblefile.js b/support/assemblefile.js index 6723e60..cc0d92a 100644 --- a/support/assemblefile.js +++ b/support/assemblefile.js @@ -6,6 +6,7 @@ var paths = require('./lib/paths'); var lib = require('./lib'); module.exports = function(app) { + var dest = paths.site(); app.use(generators()); app.use(lib.common()); app.register('verb', require('./verbfile')); @@ -19,7 +20,9 @@ module.exports = function(app) { app.includes(paths.tmpl('includes/*.hbs')); app.pages(paths.docs('**/*.md')); return app.toStream('pages') + .pipe(lib.plugins.buildPaths(dest)) + .pipe(lib.plugins.lintPaths(dest)) .pipe(app.renderFile()) - .pipe(app.dest(paths.site())); + .pipe(app.dest(dest)); }); }; diff --git a/support/docs/api.plugins.md b/support/docs/api.plugins.md index e3b135a..3ac6b67 100644 --- a/support/docs/api.plugins.md +++ b/support/docs/api.plugins.md @@ -2,7 +2,7 @@ title: Plugins related: api: ['updater', 'register'] - doc: ['default-updater', 'resolving-updaters', 'registering-updaters'] + doc: ['updaters'] --- A plugin is function that takes an instance of `Update` and is registered with the `.use` method. See the [base-plugins][] documentation for additional details. @@ -60,4 +60,4 @@ This can continue indefinitely as long as the plugin returns a function and the ## Updaters -When plugins are [registered by name](../docs/updaters.md), they are referred to as "updaters". See the [updater documentation](../docs/updaters.md) for more details. +When plugins are [registered by name](docs/updaters.md), they are referred to as "updaters". See the [updater documentation](docs/updaters.md) for more details. diff --git a/support/docs/api.register.md b/support/docs/api.register.md index 1ee1054..d704e12 100644 --- a/support/docs/api.register.md +++ b/support/docs/api.register.md @@ -1,7 +1,7 @@ --- title: Register related: - api: ['updater', 'plugin'] + api: ['updater', 'plugins'] --- Register an updater function by name. Similar to [.updater](updater.md) but does not invoke the updater function. diff --git a/support/docs/api.updater.md b/support/docs/api.updater.md index 3942c16..9619bdd 100644 --- a/support/docs/api.updater.md +++ b/support/docs/api.updater.md @@ -1,7 +1,7 @@ --- title: Updater related: - api: ['register', 'plugin'] + api: ['register', 'plugins'] --- Register an updater function by name. Similar to [.register](register.md) but immediately invokes the updater function upon registering it. diff --git a/support/docs/layouts/default.md b/support/docs/layouts/default.md index 6c915a7..fa40124 100644 --- a/support/docs/layouts/default.md +++ b/support/docs/layouts/default.md @@ -2,12 +2,12 @@ {% body %} -# Related +<%= hasAny([related.doc, related.api, related.url], '# Related') %> <%= hasValue(related.doc, '**Docs**') %> -<%= links(related.doc) %> +<%= links(related, 'doc') %> <%= hasValue(related.api, '**API**') %> -<%= links(related.api) %> +<%= links(related, 'api') %> <%= hasValue(related.url, '**Links**') %> -<%= links(related.url) %> \ No newline at end of file +<%= links(related, 'url') %> \ No newline at end of file diff --git a/support/docs/tasks.md b/support/docs/tasks.md index a410fae..1685e7d 100644 --- a/support/docs/tasks.md +++ b/support/docs/tasks.md @@ -1,7 +1,7 @@ --- title: Tasks related: - doc: ['running-updaters', 'updaters', 'updatefile'] + doc: ['updaters#running-updaters', 'updaters', 'updatefile'] --- Tasks are used for wrapping code that should be executed at a later point, either when specified by command line or explicitly run when using the API. diff --git a/support/docs/updatefile.md b/support/docs/updatefile.md index e5e41d7..949297c 100644 --- a/support/docs/updatefile.md +++ b/support/docs/updatefile.md @@ -6,7 +6,7 @@ related: If an `updatefile.js` exists in the current working directoy, Update's CLI will attempt to load it and run any updaters or tasks you've specified for it to run. -Moreover, Update's CLI will use `updatefile.js` to create the ["default" updater](default-updater.md). +Moreover, Update's CLI will use `updatefile.js` to create the ["default" updater](updaters.md#default-updater). ## Creating an updatefile.js diff --git a/support/docs/updaters.md b/support/docs/updaters.md index 96937df..19f015f 100644 --- a/support/docs/updaters.md +++ b/support/docs/updaters.md @@ -4,7 +4,7 @@ related: doc: ['tasks', 'updatefile', 'installing-updaters'] --- -Updaters are [plugins](plugins.md) that are registered by name. This document describes how to create, register and run updaters. +Updaters are [plugins](api/plugins.md) that are registered by name. This document describes how to create, register and run updaters. @@ -158,7 +158,7 @@ module.exports = function(app) { When the command line is used, Update's CLI resolves updaters in the following order: 1. [default updater](#default-updater): attempts to match given names to updaters and tasks registered on the `default` updater -2. built-in updaters: attempts to match given names to Update's [built-in updaters](built-in-updaters.md) +2. built-in updaters: attempts to match given names to Update's [built-in updaters](cli/built-in-updaters.md) 3. locally installed updaters 4. globally installed updaters diff --git a/support/lib/helpers.js b/support/lib/helpers.js index eab3ffa..4b07b36 100644 --- a/support/lib/helpers.js +++ b/support/lib/helpers.js @@ -1,28 +1,43 @@ 'use strict'; -var hasValue = require('has-value'); -var helpers = require('template-helpers'); -var isValid = require('is-valid-app'); var path = require('path'); +var utils = require('./utils'); module.exports = function(options) { return function(app) { - if (!isValid(app, 'update-support-helpers')) return; - app.helpers(helpers()); + if (!utils.isValid(app, 'update-support-helpers')) return; + app.helpers(utils.helpers()); app.helper('hasValue', function(val, str) { - return hasValue(val) ? str : ''; + return utils.hasValue(val) ? str : ''; }); - app.helper('links', function(arr) { - arr = arr ? (Array.isArray(arr) ? arr : [arr]) : []; + app.helper('hasAny', function(arr) { + arr = utils.arrayify(arr); + return utils.hasValue(arr); + }); + app.helper('links', function(related, prop) { + var arr = related[prop] || (related[prop] = []); + arr = utils.arrayify(arr); + if (arr.length === 0) { + return ''; + } + + var fp = this.view.stem; + var dir = 'docs'; + var segs = fp.split('.'); + if (segs.length > 1) { + dir = segs[0]; + } var links = arr.map(function(link) { - return createLink(link); + return createLink(dir, prop, link); }); return links.join('\n'); }); }; }; -function createLink(link) { +function createLink(dir, prop, link) { + var key = (prop === 'doc') ? 'docs' : prop; + var filepath = name; var name = link; var anchor = ''; @@ -35,5 +50,10 @@ function createLink(link) { if (!/\.md$/.test(filename) && !/#/.test(filename)) { filename += '.md'; } + if (dir !== key) { + filename = path.join('..', key, filename); + } else if (key !== 'docs') { + filename = path.join(key, filename); + } return `- [${name}](${filename}${anchor})`; } diff --git a/support/lib/index.js b/support/lib/index.js index 23b2930..3559238 100644 --- a/support/lib/index.js +++ b/support/lib/index.js @@ -1 +1,2 @@ +require('set-blocking')(true); module.exports = require('export-files')(__dirname); diff --git a/support/lib/plugins.js b/support/lib/plugins.js new file mode 100644 index 0000000..18e0bdf --- /dev/null +++ b/support/lib/plugins.js @@ -0,0 +1,80 @@ +'use strict'; + +var path = require('path'); +var utils = require('./utils'); + +exports.buildPaths = function(dest) { + var paths = {api: [], cli: [], docs: [], url: [], path: [], dest: [], anchors: {}}; + var files = []; + + return utils.through.obj(function(file, enc, next) { + setDir(file, paths); + getAnchors(file, paths); + createDest(file, paths, dest); + paths.path.push(file.path); + files.push(file); + next(); + }, function(cb) { + var len = files.length; + var idx = -1; + while (++idx < len) { + var file = files[idx]; + getLinks(file, paths); + file.paths = paths; + this.push(file); + } + cb(); + }); +}; + +exports.lintPaths = function(dest) { + return utils.through.obj(function(file, enc, next) { + // console.log(file.paths); + next(null, file); + }); +}; + +function setDir(file, paths) { + file.name = file.stem; + var segs = file.relative.split('/'); + var dir = 'docs'; + var rest = file.relative; + if (segs.length > 1) { + dir = segs.shift(); + rest = segs.join('/'); + } + paths[dir] = paths[dir] || []; + paths[dir].push(rest); + file.rest = rest; + file.dir = dir; +} + +function createDest(view, paths, dest) { + var file = view.clone(); + file.dirname = path.join(file.dirname, file.stem); + file.basename = 'index.html'; + paths.dest.push(path.resolve(dest, file.relative)); +} + +function getAnchors(file, paths) { + var str = file.contents.toString(); + var matches = str.match(/^#+\s+([^\n]+)/gm); + if (matches) { + paths.anchors[file.rest] = paths.anchors[file.rest] || []; + matches.reduce(function(acc, str) { + var anchor = utils.slugify(str.replace(/^#+\s+/, '')); + if (acc.indexOf(anchor) === -1) { + acc.push(anchor); + } + return acc.sort(); + }, paths.anchors[file.rest]); + } +} + +function getLinks(file, paths) { + var md = new utils.Remarkable({paths: paths, file: file}); + md.use(utils.prettify); + md.use(utils.lintLinks); + md.render(file.content); +} + diff --git a/support/lib/utils.js b/support/lib/utils.js index 4edb2a5..67c9684 100644 --- a/support/lib/utils.js +++ b/support/lib/utils.js @@ -1,5 +1,104 @@ 'use strict'; -exports.arrayify = function(val) { +var utils = module.exports = require('lazy-cache')(require); +var rules = require('pretty-remarkable/lib/rules'); +var fn = require; +require = utils; + +require('has-value'); +require('is-valid-app', 'isValid'); +require('pretty-remarkable', 'prettify'); +require('remarkable', 'Remarkable'); +require('strip-color'); +require('template-helpers', 'helpers'); +require('through2', 'through'); + +utils.arrayify = function(val) { return val ? (Array.isArray(val) ? val : [val]) : []; }; + +/** + * Slugify the url part of a markdown link. + * + * @param {String} `anchor` The string to slugify + * @return {String} + * @api public + */ + +utils.slugify = function(anchor) { + anchor = utils.stripColor(anchor); + anchor = anchor.toLowerCase(); + anchor = anchor.split(/ /).join('-'); + anchor = anchor.split(/\t/).join('--'); + anchor = anchor.split(/[|$&`~=\\\/@+*!?({[\]})<>=.,;:'"^]/).join(''); + return anchor; +}; + +utils.links = function(rules) { + var open = rules.link_open; + rules.link_open = function(tokens, idx, options, env) { + open.apply(rules, arguments); + var token = tokens[idx]; + if (options.paths && !/http/.test(token.href)) { + var href = token.href.replace(/^[.\\\/]+/, '').split(/[\\\/]+/).join('/'); + var paths = options.paths; + var anchors = paths.anchors; + var file = options.file; + if (/#/.test(href)) { + var idx = href.indexOf('#'); + var val = href.slice(idx + 1); + var pre = href.slice(0, idx); + var anc = anchors[pre]; + if (anc && anc.indexOf(val) === -1) { + throw new Error('cannot find anchor: #' + val + ' in (' + token.href + ') in ' + file.path); + } + } else { + var segs = href.split('/'); + if (segs.length === 1) { + segs.unshift(file.dir); + } + var seg = segs.shift(); + var rest = segs.join('/'); + var group = paths[seg]; + if (typeof group === 'undefined') { + throw new Error('directory group: ' + seg + ' is not defined'); + } + if (group.indexOf(rest) === -1) { + throw new Error(`cannot find filepath: ${rest} in "${seg}" (${file.path})`); + } + } + } + return ''; + }; + return rules; +}; + +utils.lintLinks = function(md) { + md.renderer.renderInline = function(tokens, options, env) { + utils.links(rules); + var _rules = rules; + var len = tokens.length, i = 0; + var str = ''; + + while (len--) { + str += _rules[tokens[i].type](tokens, i++, options, env, this); + } + return str; + }; + + md.renderer.render = function(tokens, options, env) { + utils.links(rules); + var _rules = rules; + var len = tokens.length, i = -1; + var str = ''; + + while (++i < len) { + if (tokens[i].type === 'inline') { + str += this.renderInline(tokens[i].children, options, env); + } else { + str += _rules[tokens[i].type](tokens, i, options, env, this); + } + } + return str; + }; +}; diff --git a/support/package.json b/support/package.json index 91711c8..12b3b62 100644 --- a/support/package.json +++ b/support/package.json @@ -26,12 +26,18 @@ "has-value": "^0.3.1", "is-valid-app": "^0.2.0", "memoize-path": "^0.1.2", + "pretty-remarkable": "^0.3.6", + "remarkable": "^1.6.2", + "set-blocking": "^2.0.0", "template-helpers": "^0.6.3", + "through2": "^2.0.1", "verb-toc": "^0.2.3" }, "dependencies": { "generate-collections": "^0.3.4", - "generate-defaults": "^0.3.1" + "generate-defaults": "^0.3.1", + "lazy-cache": "^2.0.1", + "strip-color": "^0.1.0" }, "verb": { "plugins": [ diff --git a/support/verbfile.js b/support/verbfile.js index 17c2779..5de3f60 100644 --- a/support/verbfile.js +++ b/support/verbfile.js @@ -9,6 +9,7 @@ var paths = require('./lib/paths'); var lib = require('./lib'); module.exports = function(app) { + var dest = paths.site(); app.use(lib.middleware()); app.use(lib.common()); @@ -24,7 +25,7 @@ module.exports = function(app) { return app.toStream('docs') .pipe(drafts()) .pipe(app.renderFile('*')) - // .pipe(reflinks()) + .pipe(reflinks()) .pipe(format()) .pipe(app.dest(paths.docs())); }); From 367d113ec4f6f6969ff3c0462f32e25f18877df4 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 28 Jun 2016 12:39:19 -0400 Subject: [PATCH 185/274] add `hasAny` helper --- docs/api/plugins.md | 2 +- docs/api/register.md | 2 +- docs/api/updater.md | 2 +- docs/cli/built-in-updaters.md | 2 -- docs/faq.md | 2 +- docs/features.md | 2 +- docs/installing-the-cli.md | 2 +- docs/installing-updaters.md | 2 +- docs/introduction.md | 2 +- docs/tasks.md | 2 +- docs/tutorial.md | 2 -- docs/updatefile.md | 2 +- docs/updaters.md | 2 +- support/lib/helpers.js | 12 ++++++++++-- support/verbfile.js | 3 +-- 15 files changed, 22 insertions(+), 19 deletions(-) diff --git a/docs/api/plugins.md b/docs/api/plugins.md index 07c34d5..cbe509b 100644 --- a/docs/api/plugins.md +++ b/docs/api/plugins.md @@ -57,7 +57,7 @@ This can continue indefinitely as long as the plugin returns a function and the When plugins are [registered by name](docs/updaters.md), they are referred to as "updaters". See the [updater documentation](docs/updaters.md) for more details. -true +# Related **Docs** diff --git a/docs/api/register.md b/docs/api/register.md index c465bef..2db0016 100644 --- a/docs/api/register.md +++ b/docs/api/register.md @@ -27,7 +27,7 @@ app.update('foo', function(err) { }); ``` -true +# Related **API** diff --git a/docs/api/updater.md b/docs/api/updater.md index 924ecdf..51918ee 100644 --- a/docs/api/updater.md +++ b/docs/api/updater.md @@ -27,7 +27,7 @@ app.update('bar', function(err) { }); ``` -true +# Related **API** diff --git a/docs/cli/built-in-updaters.md b/docs/cli/built-in-updaters.md index 3738d7f..de53fd5 100644 --- a/docs/cli/built-in-updaters.md +++ b/docs/cli/built-in-updaters.md @@ -49,5 +49,3 @@ Display a help menu with all available commands: ```sh $ update help ``` - -true diff --git a/docs/faq.md b/docs/faq.md index c891a07..6e1e61f 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -10,7 +10,7 @@ A updater's alias is created by stripping the substring `update-` from the _full Note that **no dots may be used in published updater names**. Aside from that, any characters considered valid by npm are fine. -true +# Related **Docs** diff --git a/docs/features.md b/docs/features.md index e9fba2a..db8a236 100644 --- a/docs/features.md +++ b/docs/features.md @@ -13,7 +13,7 @@ Update offers an elegant and robust suite of methods, carefully organized to hel Visit the [getting started guide](https://github.com/taunus/getting-started) to learn more. -true +# Related **Docs** diff --git a/docs/installing-the-cli.md b/docs/installing-the-cli.md index 1c125b0..7409ed0 100644 --- a/docs/installing-the-cli.md +++ b/docs/installing-the-cli.md @@ -43,7 +43,7 @@ Examples: by specifying a task on the updater. Example: `update foo:default` ``` -true +# Related **Docs** diff --git a/docs/installing-updaters.md b/docs/installing-updaters.md index b26ab6b..1314df7 100644 --- a/docs/installing-updaters.md +++ b/docs/installing-updaters.md @@ -20,7 +20,7 @@ If `updater-license` installed successfully, you should now be able to run it wi $ update license ``` -true +# Related **Docs** diff --git a/docs/introduction.md b/docs/introduction.md index af35309..54f8ce8 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -104,7 +104,7 @@ For example, if we were to sift the files in the average code project into major Update maintains **everything else**. -true +# Related **Docs** diff --git a/docs/tasks.md b/docs/tasks.md index 0043498..830d2e8 100644 --- a/docs/tasks.md +++ b/docs/tasks.md @@ -157,7 +157,7 @@ app.build(function(err) { }); ``` -true +# Related **Docs** diff --git a/docs/tutorial.md b/docs/tutorial.md index 909a914..c4a8b4e 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -109,5 +109,3 @@ You're now a master at running tasks with update! You can do anything with updat **Next steps** Update does much more than this. For a more in-depth introduction, we highly recommend visiting the [getting started guide](https://github.com/update/getting-started). - -true diff --git a/docs/updatefile.md b/docs/updatefile.md index 8f23e24..fafe66e 100644 --- a/docs/updatefile.md +++ b/docs/updatefile.md @@ -39,7 +39,7 @@ module.exports = function(app) { }; ``` -true +# Related **Docs** diff --git a/docs/updaters.md b/docs/updaters.md index 7113361..6a5bcbb 100644 --- a/docs/updaters.md +++ b/docs/updaters.md @@ -183,7 +183,7 @@ _The only way to register a `default` updater is by creating an [updatefile.js]( When used by command line, Update's CLI will then use node's `require()` system to get the function exported by `updatefile.js` and use it as the `default` updater. -true +# Related **Docs** diff --git a/support/lib/helpers.js b/support/lib/helpers.js index 4b07b36..92dd7c0 100644 --- a/support/lib/helpers.js +++ b/support/lib/helpers.js @@ -10,9 +10,17 @@ module.exports = function(options) { app.helper('hasValue', function(val, str) { return utils.hasValue(val) ? str : ''; }); - app.helper('hasAny', function(arr) { + app.helper('hasAny', function(arr, str) { arr = utils.arrayify(arr); - return utils.hasValue(arr); + var len = arr.length; + var idx = -1; + while (++idx < len) { + var ele = arr[idx]; + if (ele.length) { + return str; + } + } + return ''; }); app.helper('links', function(related, prop) { var arr = related[prop] || (related[prop] = []); diff --git a/support/verbfile.js b/support/verbfile.js index 5de3f60..c912482 100644 --- a/support/verbfile.js +++ b/support/verbfile.js @@ -14,8 +14,7 @@ module.exports = function(app) { app.use(lib.common()); app.task('clean', function(cb) { - // del(paths.docs(), {force: true}, cb); - cb() + del(paths.docs(), {force: true}, cb); }); app.task('docs', ['clean'], function(cb) { From f360b4ca5afad88d8dbdf8930c0b92f2744c4672 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 28 Jun 2016 17:26:29 -0400 Subject: [PATCH 186/274] support `run` flag to force tasks to run --- bin/update.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/update.js b/bin/update.js index 3ceef11..1d18a4a 100755 --- a/bin/update.js +++ b/bin/update.js @@ -33,7 +33,7 @@ Update.cli(Update, argv, function(err, app) { if (err) app.emit('error', err); var tasks = argv._.length ? argv._ : ['default']; - if (app.updatefile !== true) { + if (app.updatefile !== true || argv.run) { tasks = Update.resolveTasks(app, argv); } From 3de66f359ac6a642ec8083e8ae0214a227fa9dc4 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 28 Jun 2016 17:28:47 -0400 Subject: [PATCH 187/274] add summary, commands, .github templates --- .github/contributing.md | 43 +++++++++++++ .github/issue_template.md | 13 ++++ docs/installing-the-cli.md | 4 +- docs/introduction.md | 14 ++-- support/docs/cli.commands.md | 15 +++++ support/docs/installing-the-cli.md | 4 +- support/docs/introduction.md | 12 ++-- support/docs/summary.md | 100 +++++++++++++++++++++++++++++ 8 files changed, 190 insertions(+), 15 deletions(-) create mode 100644 .github/contributing.md create mode 100644 .github/issue_template.md create mode 100644 support/docs/cli.commands.md create mode 100644 support/docs/summary.md diff --git a/.github/contributing.md b/.github/contributing.md new file mode 100644 index 0000000..31109e8 --- /dev/null +++ b/.github/contributing.md @@ -0,0 +1,43 @@ +# Contributing to Generate + +First and foremost, thank you! We appreciate that you want to contribute to Generate, your time is valuable, and your contributions mean a lot to us. + +**What does "contributing" mean?** + +Creating an issue is the simplest form of contributing to a project. But there are many ways to contribute, including the following: + +- Updating or correcting documentation +- Feature requests +- Bug reports + +## Issues + +**Before creating an issue** + +Please make sure you're creating one in the right place: + +- do you have a template syntax question? Like how to accomplish something with handlebars? The best place to get answers for this is [stackoverflow.com](https://github.com/stackoverflow.com), the [handlebars docs](handlebarsjs.com), or the documentation for the template engine you're using. +- Are you having an issue with an Generate feature that is powered by an underlying lib? This is sometimes difficult to know, but sometimes it can be pretty easy to find out. For example, if you use a glob pattern somewhere and you found what you believe to be a matching bug, that would probably be an issue for [node-glob][] or [micromatch][] + +**Creating an issue** + +Please be as descriptive as possible when creating an issue. Give us the information we need to successfully answer your question or address your issue by answering the following in your issue: + +- what version of assemble are you using? +- is the issue helper-related? If so, this issue should probably be opened on the repo related to the helper being used. +- do you have any custom helpers defined? Is the issue related to the helper itself, data (context) being passed to the helper, or actually registering the helper in the first place? +- are you using middleware? +- any plugins? + + +## Above and beyond + +Here are some tips for creating idiomatic issues. Taking just a little bit extra time will make your issue easier to read, easier to resolve, more likely to be found by others who have the same or similar issue in the future. + +- take some time to learn basic markdown. This [markdown cheatsheet](https://gist.github.com/jonschlinkert/5854601) is super helpful, as is the GitHub guide to [basic markdown](https://help.github.com/articles/markdown-basics/). +- Learn about [GitHub Flavored Markdown](https://help.github.com/articles/github-flavored-markdown/). And if you want to really go above and beyond, read [mastering markdown](https://guides.github.com/features/mastering-markdown/). +- use backticks to wrap code. This ensures that code will retain its format, making it much more readable to others +- use syntax highlighting by adding the correct language name after the first "code fence" + +[node-glob]: https://github.com/isaacs/node-glob +[micromatch]: https://github.com/jonschlinkert/micromatch \ No newline at end of file diff --git a/.github/issue_template.md b/.github/issue_template.md new file mode 100644 index 0000000..1409b5d --- /dev/null +++ b/.github/issue_template.md @@ -0,0 +1,13 @@ +Before opening an issue, please: + +- read the [contributing guidelines](https://github.com/generate/generate/blob/master/.github/contributing.md) +- [search for existing duplicate or closed issues](https://github.com/generate/generate/issues?utf8=%E2%9C%93&q=is%3Aissue) +- Do not open issues to ask for implementation help or to ask "how to" questions here. Instead ask on [StackOverflow](stackoverflow.com) or one of the services listed on the [readme](https://github.com/generate/generate/blob/master/README.md#community) + +For bug reports, please provide the following details: + +- **version**: what version of generate you were using when you experienced the bug? +- **[reduced test case](https://css-tricks.com/reduced-test-cases/)**: the minimum amount of detail and code to reproduce the bug +- **error messages**: please paste any error reports into the issue or a gist + +Please wrap all code and error messages in [markdown code fences](https://help.github.com/articles/creating-and-highlighting-code-blocks/). \ No newline at end of file diff --git a/docs/installing-the-cli.md b/docs/installing-the-cli.md index 7409ed0..9ce27dc 100644 --- a/docs/installing-the-cli.md +++ b/docs/installing-the-cli.md @@ -8,7 +8,7 @@ $ npm install --global update This adds the `update` command to your system path, allowing it to be run from any directory. -You should now be able to use the `update` command to execute code in a local `updatefile.js` file, or to execute globally installed updaters by their [aliases](#aliases). +You should now be able to use the `update` command to execute code in a local `updatefile.js` file, or to run any locally or globally installed updaters by their [aliases](tasks.md#alias-tasks) or full names. **Init** @@ -19,7 +19,7 @@ If it's your first time using update, run `update init` to set your global defau ``` Usage: update [options] -Command: Generator or tasks to run +Command: Updater or tasks to run Examples: diff --git a/docs/introduction.md b/docs/introduction.md index 54f8ce8..5b0b165 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -11,13 +11,15 @@ - [API](#api) - [Update](#update) -## What is update? +## What is "Update"? -Update is a new, open-source developer framework for automating updates of any kind to code projects. +Update is a new, open-source developer framework for automating updates of any kind in code projects. -* update files that are typically excluded from the automated parts of the software lifecycle, and thus are mostly forgotten about after they're created. -* fix dates in copyrights, licenses and banners, removing deprecated fields from project manifests, updating settings in runtime config files, preferences in dotfiles, and so on. * normalize configuration settings, verbiage, or preferences across all of your projects +* update files that are typically excluded from the automated parts of the software lifecycle, and are often forgotten about after they're created. +* fix dates in copyrights, licenses and banners +* removing deprecated fields from project manifests +* updating settings in runtime config files, preferences in dotfiles, and so on. ## How does it work? @@ -89,9 +91,9 @@ by passing the names of updaters to run to the `.update` Updaters via CLI or API. (tasks are powered by [bach](https://github.com/gulpjs/bach), the same library used in [gulp](http://gulpjs.com) v4.0). -The main export of the library is a constructor function, `Update`. +The main export of the library is the `Update` constructor function. -Updaters themselves are just functions that take an instance of `Update`. wrap code to be executed when the +Updaters themselves are just functions that take an instance of `Update`. Update gives you a way to automate the maintenance of files that are typically excluded from the automated parts of the software lifecycle, and thus are mostly forgotten about after they're created. diff --git a/support/docs/cli.commands.md b/support/docs/cli.commands.md new file mode 100644 index 0000000..e14de4e --- /dev/null +++ b/support/docs/cli.commands.md @@ -0,0 +1,15 @@ +--- +title: Command line flags +related: + doc: [] +--- + +Supported command line flags. + +## --run + +Force stored tasks to run. Stored tasks are "skipped" if you have an `updatefile.js` in the current working directory. + +```sh +$ update --run +``` diff --git a/support/docs/installing-the-cli.md b/support/docs/installing-the-cli.md index df75256..b12e37d 100644 --- a/support/docs/installing-the-cli.md +++ b/support/docs/installing-the-cli.md @@ -12,7 +12,7 @@ $ npm install --global update This adds the `update` command to your system path, allowing it to be run from any directory. -You should now be able to use the `update` command to execute code in a local `updatefile.js` file, or to execute globally installed updaters by their [aliases](#aliases). +You should now be able to use the `update` command to execute code in a local `updatefile.js` file, or to run any locally or globally installed updaters by their [aliases](tasks.md#alias-tasks) or full names. **Init** @@ -23,7 +23,7 @@ If it's your first time using update, run `update init` to set your global defau ``` Usage: update [options] -Command: Generator or tasks to run +Command: Updater or tasks to run Examples: diff --git a/support/docs/introduction.md b/support/docs/introduction.md index 3e03260..52ce77b 100644 --- a/support/docs/introduction.md +++ b/support/docs/introduction.md @@ -8,11 +8,13 @@ related: ## What is update? -Update is a new, open-source developer framework for automating updates of any kind to code projects. +Update is a new, open-source developer framework for automating updates of any kind in code projects. -* update files that are typically excluded from the automated parts of the software lifecycle, and thus are mostly forgotten about after they're created. -* fix dates in copyrights, licenses and banners, removing deprecated fields from project manifests, updating settings in runtime config files, preferences in dotfiles, and so on. * normalize configuration settings, verbiage, or preferences across all of your projects +* update files that are typically excluded from the automated parts of the software lifecycle, and are often forgotten about after they're created. +* fix dates in copyrights, licenses and banners +* removing deprecated fields from project manifests +* updating settings in runtime config files, preferences in dotfiles, and so on. ## How does it work? @@ -84,9 +86,9 @@ by passing the names of updaters to run to the `.update` Updaters via CLI or API. (tasks are powered by [bach][], the same library used in [gulp][] v4.0). -The main export of the library is a constructor function, `Update`. +The main export of the library is the `Update` constructor function. -Updaters themselves are just functions that take an instance of `Update`. wrap code to be executed when the +Updaters themselves are just functions that take an instance of `Update`. Update gives you a way to automate the maintenance of files that are typically excluded from the automated parts of the software lifecycle, and thus are mostly forgotten about after they're created. diff --git a/support/docs/summary.md b/support/docs/summary.md new file mode 100644 index 0000000..dc700f0 --- /dev/null +++ b/support/docs/summary.md @@ -0,0 +1,100 @@ + +
+
+
+![separator](sep.png) +
+
+
+ +## Introduction +### What is update? +{%%= doc("what-is-update.md") %} +todo + +### What are updaters? +{%%= doc("what-are-updaters.md") %} +todo + +### Why use update? +{%%= doc("why-use-update.md") %} +todo + +
+
+
+![separator](sep.png) +
+
+
+ +## Getting started +### Installing the CLI +{%%= doc("intro.installing-the-cli.md") %} +todo + +### Installing updaters +{%%= doc("intro.installing-updaters.md") %} +todo + +
+
+
+![separator](sep.png) +
+
+
+ +## Usage +Update can be used by command line or API. We recommend you start out with the CLI to become familiarized with how update works before diving into the API. + +### Using tasks +{%%= doc("getting-started.using-tasks.md") %} +todo + +### Using updaters +{%%= doc("getting-started.using-updaters.md") %} +todo + +### Using plugins +{%%= doc("getting-started.using-plugins.md") %} +todo + +
+
+
+![separator](sep.png) +
+
+
+ +## Docs + +### Authoring updaters +todo + +### Publishing updaters +todo + +### Feature highlights +{%%= doc("features.md") %} + +### FAQ +{%%= doc("faq.md") %} + +
+
+
+![separator](sep.png) +
+
+
+ +## Developers +Hacking on update. + +### Base +todo + +### Architecture +todo From 0b3bbffdd136b33327e10e24d7edfabbf06b417f Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 28 Jun 2016 17:29:17 -0400 Subject: [PATCH 188/274] improve default updater handling --- lib/cli.js | 10 ++++++---- lib/utils.js | 7 +++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/cli.js b/lib/cli.js index e755a78..31e10c8 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -71,10 +71,12 @@ module.exports = function(Update, config, cb) { logger(log.timestamp, 'using file', log.green('~' + updatefile)); var val = require(updatefile); if (typeof val === 'function') { - app.use(fn); - app.use(val); - app.parent = base; - base.register('default', app); + base.register('default', function fn() { + if (!utils.isValid(app, 'default-updater')) return; + this.use(fn); + this.use(val); + return fn; + }); } else if (val && val.isApp) { base = val; } diff --git a/lib/utils.js b/lib/utils.js index b143694..da8f22e 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -16,11 +16,18 @@ require('base-runtimes', 'runtimes'); require('base-store', 'store'); require('extend-shallow', 'extend'); require('fs-exists-sync', 'exists'); +require('is-valid-app', 'isValid'); require('global-modules', 'gm'); require('log-utils', 'log'); require('os-homedir', 'home'); require = fn; // eslint-disable-line + +utils.stripPrefixes = function(file) { + file.basename = file.basename.replace(/^_/, '.'); + file.basename = file.basename.replace(/^\$/, ''); +}; + /** * argv options */ From 553486415e2a19adc2f163133528f856b6afd89e Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 28 Jun 2016 17:30:46 -0400 Subject: [PATCH 189/274] generate docs --- .verb.md | 210 +++++++++++---------------------------- README.md | 271 ++++++++++++++------------------------------------- package.json | 17 ++-- 3 files changed, 143 insertions(+), 355 deletions(-) diff --git a/.verb.md b/.verb.md index f2852e2..b39982d 100644 --- a/.verb.md +++ b/.verb.md @@ -1,195 +1,101 @@ -## What is update? +--- +toc: false +--- -Update is a new, open-source developer framework for automating updates of any kind to code projects. +{%= include("logo") %} -## CLI +## The basics -**Installing the CLI** +- All of the actual _updating_ is accomplished by plugins called "updaters". +- [Updaters](docs/updaters.md) can be published to npm using the `updater-foo` naming convention, where `foo` is the updater's [alias](docs/faq.md#aliases). +- Run multiple updaters by passing a list of names after `update`. Example `$ update foo bar baz` will run updaters foo, bar and baz _in series_. The "next" updater always waits for the previous to finish. +- Updaters can have tasks, which are powered by [bach][] and use the same conventions as [gulp][] +- To run a task on an updater, do `$ update foo:abc`, where `abc` is the task name +- To run multiple tasks on an updater, do `$ update foo:abc,xyz`, where `abc` and `xyz` are task names +- Updaters that are published to npm can be [installed locally or globally](docs/installing-updaters.md) +- If you add an [updatefile.js](docs/updatefile.md) to the current working directory, Update's CLI will load it and register it as the ["default" updater](docs/updaters.md#default-updater). +- Update's CLI will look on the default updater for tasks and (other) updaters to run before looking elsewhere. -To run update from the command line, you'll need to install it globally first. You can do that now with the following command: +More [features](#features) listed below. See the [docs](docs) for more detail. -```sh -$ npm i -g update -``` - -This adds the `update` command to your system path, allowing it to be run from any directory. +## TOC -You should now be able to use the `update` command to execute code in a local `updatefile.js` file, or to execute globally installed updaters by their [aliases](#aliases). - -**Init** +- [1 minute](#fast-track) +- [4 minutes](#getting-started) +- [???](docs) -If it's your first time using update, run `update init` to set your global defaults. +## Fast track +Install `update` and the example "updater" with the following command: -**CLI help** - +```sh +$ npm install --global update updater-license ``` -Usage: update [options] - -Command: Generator or tasks to run - -Examples: - # run the "foo" updater - $ update foo +Make sure your work is committed, then run: - # run the "bar" task on updater "foo" - $ update foo:bar - - # run multiple tasks on updater "foo" - $ update foo:bar,baz,qux - - # run a sub-updater on updater "foo" - $ update foo.abc - - # run task "xyz" on sub-updater "foo.abc" - $ update foo.abc:xyz - - Update attempts to automatically determine if "foo" is a task or updater. - If there is a conflict, you can force update to run updater "foo" - by specifying a task on the updater. Example: `update foo:default` +```sh +$ update license ``` -## Quickstart +## Getting started -The following intro only skims the surface of what update has to offer. For a more in-depth introduction, we highly recommend visiting the [getting started guide][getting-started]. - -**Create a updater** - -Add a `updatefile.js` to the current working directory with the following code: - -```js -module.exports = function(app) { - console.log('success!'); -}; -``` +This section just covers the essentials, more detail is [provided below](#table-of-contents) and in the [docs/](docs) folder. -**Run a updater** +**Installing Update** -Enter the following command: +To use the CLI, update must first be installed globally with [npm](https://www.npmjs.com/): ```sh -update +$ npm install --global update ``` -If successful, you should see `success!` in the terminal. +This adds the `update` command to your system path, allowing it to be run from anywhere. -**Create a task** +**Installing updaters** -Now, add a task to your updater. - -```js -module.exports = function(app) { - app.task('default', function(cb) { - console.log('success!'); - cb(); - }); -}; -``` - -Now, in the command line, run: +To see how updaters work, install `updater-example`: ```sh -$ update -# then try -$ update default +$ npm install --global updater-example ``` -When a local `updatefile.js` exists, the `update` command is aliased to automatically run the `default` task if one exists. But you can also run the task with `update default`. - -**Run a task** - -Let's try adding more tasks to your updater: - -```js -module.exports = function(app) { - app.task('default', function(cb) { - console.log('default > success!'); - cb(); - }); - - app.task('foo', function(cb) { - console.log('foo > success!'); - cb(); - }); - - app.task('bar', function(cb) { - console.log('bar > success!'); - cb(); - }); -}; -``` +**Running updaters** -Now, in the command line, run: +Add a file named `example.txt` to the current working directory, then run `updater-example` with the following command: ```sh -$ update -# then try -$ update foo -# then try -$ update foo bar +$ update example ``` -**Run task dependencies** - -Now update your code to the following: +Next, try the following steps to get familiarized with how update works: +- [ ] run `$ update example` to execute the default task, which will append the string `foo` to the file's contents. +- [ ] run `$ update example:foo` to execute the `foo` task, appending the string `foo` to the file's contents +- [ ] run `$ update example:bar` to execute the `bar` task, appending the string `bar` to the file's contents +- [ ] run `$ update example.abc` to execute the default task on the `abc` (sub-)updater, appending the string `abc:one` to the file's contents +- [ ] run `$ update example.abc:one` to execute the `one` task on the `abc` (sub-)updater, appending the string `abc:one` to the file's contents -```js -module.exports = function(app) { - app.task('default', ['foo', 'bar']); - - app.task('foo', function(cb) { - console.log('foo > success!'); - cb(); - }); - app.task('bar', function(cb) { - console.log('bar > success!'); - cb(); - }); -}; -``` +**Init** -And run: +Now that you know how to run an updater directly, you can tell update to store a list of one or more updaters to run each time the `update` command is given: ```sh -$ update +$ update init ``` -You're now a master at running tasks with update! You can do anything with update tasks that you can do with [gulp][] tasks (we use and support gulp libraries after all!). - -**Next steps** - -But update does much more than this. For a more in-depth introduction, we highly recommend visiting the [getting started guide][getting-started]. - -## More info - -### Feature highlights - -Update offers an elegant and robust suite of methods, carefully organized to help you accomplish common activities in less time, including: - -- **unparalleled flow control**: through the use of [updaters][getting-started], [sub-updaters][getting-started] and [tasks][getting-started] -- **templates, scaffolds and boilerplates**: update a single file, initialize an entire project, or provide ad-hoc "components" throughout the duration of a project using any combination of [templates, scaffolds and boilerplates](#templates-scaffolds-and-boilerplates). -- **any engine**: use any template engine to render templates, including [handlebars][], [lodash][], [swig][] and [pug][] -- **prompts**: asks you for data when it can't find what it needs, and it's easy to customize prompts for any data you want. -- **data**: gathers data from the user's environment to populate "hints" in user prompts and render templates -- **streams**: interact with the file system, with full support for [gulp][] and [assemble][] plugins -- **smart plugins**: Update is built on [base][], so any "smart" plugin can be used -- **stores**: persist configuration settings, global defaults, project-specific defaults, answers to prompts, and so on. - -Visit the [getting started guide][getting-started] to learn more. - - -### FAQ - -
-**What's an alias, and what do they do?** - -Update tries to find globally installed updaters using an "alias" first, falling back on the updater's full name if not found by its alias. +Whenever you install new updaters you can run `update init` to update your preferences. -A updater's alias is created by stripping the substring `update-` from the _full name_ of updater. Thus, when publishing a updater the naming convention `update-foo` should be used (where `foo` is the alias, and `update-foo` is the full name). +Visit the [documentation](docs) for details about authoring an publishing updaters. -Note that **no dots may be used in published updater names**. Aside from that, any characters considered valid by npm are fine. +## Features -[getting-started]: https://github.com/update/getting-started +* **unparalleled flow control**: through the use of [updaters][getting-started], [sub-updaters][getting-started] and [tasks][getting-started] +* **templates, scaffolds and boilerplates**: update a single file, initialize an entire project, or provide ad-hoc "components" throughout the duration of a project using any combination of [templates, scaffolds and boilerplates](#templates-scaffolds-and-boilerplates). +* **any engine**: use any template engine to render templates, including [handlebars][], [lodash][], [swig][] and [pug][] +* **prompts**: asks you for data when it can't find what it needs, and it's easy to customize prompts for any data you want. +* **data**: gathers data from the user's environment to populate "hints" in user prompts and render templates +* **streams**: interact with the file system, with full support for [gulp][] and [assemble][] plugins +* **smart plugins**: Update is built on [base][], so any "smart" plugin can be used +* **stores**: persist configuration settings, global defaults, project-specific defaults, answers to prompts, and so on. diff --git a/README.md b/README.md index 2bcae1c..c44a0c4 100644 --- a/README.md +++ b/README.md @@ -1,253 +1,120 @@ # update [![NPM version](https://img.shields.io/npm/v/update.svg?style=flat)](https://www.npmjs.com/package/update) [![NPM downloads](https://img.shields.io/npm/dm/update.svg?style=flat)](https://npmjs.org/package/update) [![Build Status](https://img.shields.io/travis/jonschlinkert/update.svg?style=flat)](https://travis-ci.org/jonschlinkert/update) -Update your projects. +Update is a developer framework and CLI for automating updates of any kind in code projects. All updating is accomplished using plugins called _updaters_, which can be installed globally, locally, or in a local updatefile.js -## Install +

+ + + +

-Install with [npm](https://www.npmjs.com/): +## The basics -```sh -$ npm install --save update -``` - -## What is update? - -Update is a new, open-source developer framework for automating updates of any kind to code projects. Using a combination of hrough the use of [updaters](#updaters) and [tasks](#tasks). intuitive CLI, and a powerful and expressive API, Update is easy to learn, and enjoyable to use. - -## tldr - -Install `update` globally with the following command: - -```js -$ npm install --global update -``` - -Update is installed globally and run with the `update` command. - -* download -* git commit! -* run `update` - -```js -var updater = require('update'); -``` - -**Example** - -This example shows two updaters working together seamlessly. +* All of the actual _updating_ is accomplished by plugins called "updaters". +* [Updaters](docs/updaters.md) can be published to npm using the `updater-foo` naming convention, where `foo` is the updater's [alias](docs/faq.md#aliases). +* Run multiple updaters by passing a list of names after `update`. Example `$ update foo bar baz` will run updaters foo, bar and baz _in series_. The "next" updater always waits for the previous to finish. +* Updaters can have tasks, which are powered by [bach][] and use the same conventions as [gulp][] +* To run a task on an updater, do `$ update foo:abc`, where `abc` is the task name +* To run multiple tasks on an updater, do `$ update foo:abc,xyz`, where `abc` and `xyz` are task names +* Updaters that are published to npm can be [installed locally or globally](docs/installing-updaters.md) +* If you add an [updatefile.js](docs/updatefile.md) to the current working directory, Update's CLI will load it and register it as the ["default" updater](docs/updaters.md#default-updater). +* Update's CLI will look on the default updater for tasks and (other) updaters to run before looking elsewhere. -![Example of how to update a gulpfile.js](demo.gif) +More [features](#features) listed below. See the [docs](docs) for more detail. -**Want to know more?** +* [1 minute](#fast-track) +* [4 minutes](#getting-started) +* [???](docs) -You can use update from the command line, or as a node.js library as a part of your own application. +## Fast track -* Jump to [feature highlights](#feature-highlights) -* Visit the [getting started guide](https://github.com/update/getting-started) - -Continue on, and start updating! - -## CLI - -**Installing the CLI** - -To run update from the command line, you'll need to install it globally first. You can do that now with the following command: +Install `update` and the example "updater" with the following command: ```sh -$ npm i -g update -``` - -This adds the `update` command to your system path, allowing it to be run from any directory. - -You should now be able to use the `update` command to execute code in a local `updatefile.js` file, or to execute globally installed updaters by their [aliases](#aliases). - -**Init** - -If it's your first time using update, run `update init` to set your global defaults. - -**Usage** - +$ npm install --global update updater-license ``` -Usage: update [options] - -Command: Generator or tasks to run - -Examples: - # run the "foo" updater - $ update foo +Make sure your work is committed, then run: - # run the "bar" task on updater "foo" - $ update foo:bar - - # run multiple tasks on updater "foo" - $ update foo:bar,baz,qux - - # run a sub-updater on updater "foo" - $ update foo.abc - - # run task "xyz" on sub-updater "foo.abc" - $ update foo.abc:xyz - - Update attempts to automatically determine if "foo" is a task or updater. - If there is a conflict, you can force update to run updater "foo" - by specifying a task on the updater. Example: `update foo:default` +```sh +$ update license ``` -## Quickstart - -The following intro only skims the surface of what update has to offer. For a more in-depth introduction, we highly recommend visiting the [getting started guide](https://github.com/update/getting-started). +## Getting started -**Create a updater** +This section just covers the essentials, more detail is [provided below](#table-of-contents) and in the [docs/](docs) folder. -Add a `updatefile.js` to the current working directory with the following code: - -```js -module.exports = function(app) { - console.log('success!'); -}; -``` +**Installing Update** -**Run a updater** - -Enter the following command: +To use the CLI, update must first be installed globally with [npm](https://www.npmjs.com/): ```sh -update +$ npm install --global update ``` -If successful, you should see `success!` in the terminal. +This adds the `update` command to your system path, allowing it to be run from anywhere. -**Create a task** +**Installing updaters** -Now, add a task to your updater. - -```js -module.exports = function(app) { - app.task('default', function(cb) { - console.log('success!'); - cb(); - }); -}; -``` - -Now, in the command line, run: +To see how updaters work, install `updater-example`: ```sh -$ update -# then try -$ update default +$ npm install --global updater-example ``` -When a local `updatefile.js` exists, the `update` command is aliased to automatically run the `default` task if one exists. But you can also run the task with `update default`. - -**Run a task** - -Let's try adding more tasks to your updater: - -```js -module.exports = function(app) { - app.task('default', function(cb) { - console.log('default > success!'); - cb(); - }); - - app.task('foo', function(cb) { - console.log('foo > success!'); - cb(); - }); - - app.task('bar', function(cb) { - console.log('bar > success!'); - cb(); - }); -}; -``` +**Running updaters** -Now, in the command line, run: +Add a file named `example.txt` to the current working directory, then run `updater-example` with the following command: ```sh -$ update -# then try -$ update foo -# then try -$ update foo bar +$ update example ``` -**Run task dependencies** - -Now update your code to the following: +Next, try the following steps to get familiarized with how update works: -```js -module.exports = function(app) { - app.task('default', ['foo', 'bar']); - - app.task('foo', function(cb) { - console.log('foo > success!'); - cb(); - }); +* [ ] run `$ update example` to execute the default task, which will append the string `foo` to the file's contents. +* [ ] run `$ update example:foo` to execute the `foo` task, appending the string `foo` to the file's contents +* [ ] run `$ update example:bar` to execute the `bar` task, appending the string `bar` to the file's contents +* [ ] run `$ update example.abc` to execute the default task on the `abc` (sub-)updater, appending the string `abc:one` to the file's contents +* [ ] run `$ update example.abc:one` to execute the `one` task on the `abc` (sub-)updater, appending the string `abc:one` to the file's contents - app.task('bar', function(cb) { - console.log('bar > success!'); - cb(); - }); -}; -``` +**Init** -And run: +Now that you know how to run an updater directly, you can tell update to store a list of one or more updaters to run each time the `update` command is given: ```sh -$ update +$ update init ``` -You're now a master at running tasks with update! You can do anything with update tasks that you can do with [gulp](http://gulpjs.com) tasks (we use and support gulp libraries after all!). - -**Next steps** - -But update does much more than this. For a more in-depth introduction, we highly recommend visiting the [getting started guide](https://github.com/update/getting-started). +Whenever you install new updaters you can run `update init` to update your preferences. -## More info +Visit the [documentation](docs) for details about authoring an publishing updaters. -### Feature highlights +## Features -Update offers an elegant and robust suite of methods, carefully organized to help you accomplish common activities in less time, including: - -* **unparalleled flow control**: through the use of [updaters](https://github.com/update/getting-started), [sub-updaters](https://github.com/update/getting-started) and [tasks](https://github.com/update/getting-started) +* **unparalleled flow control**: through the use of [updaters][getting-started], [sub-updaters][getting-started] and [tasks][getting-started] * **templates, scaffolds and boilerplates**: update a single file, initialize an entire project, or provide ad-hoc "components" throughout the duration of a project using any combination of [templates, scaffolds and boilerplates](#templates-scaffolds-and-boilerplates). -* **any engine**: use any template engine to render templates, including [handlebars](http://www.handlebarsjs.com/), [lodash](https://lodash.com/), [swig](https://github.com/paularmstrong/swig) and [pug](http://jade-lang.com) +* **any engine**: use any template engine to render templates, including [handlebars][], [lodash][], [swig][] and [pug][] * **prompts**: asks you for data when it can't find what it needs, and it's easy to customize prompts for any data you want. * **data**: gathers data from the user's environment to populate "hints" in user prompts and render templates -* **streams**: interact with the file system, with full support for [gulp](http://gulpjs.com) and [assemble](https://github.com/assemble/assemble) plugins -* **smart plugins**: Update is built on [base](https://github.com/node-base/base), so any "smart" plugin can be used +* **streams**: interact with the file system, with full support for [gulp][] and [assemble][] plugins +* **smart plugins**: Update is built on [base][], so any "smart" plugin can be used * **stores**: persist configuration settings, global defaults, project-specific defaults, answers to prompts, and so on. -Visit the [getting started guide](https://github.com/update/getting-started) to learn more. - -### FAQ - - +## Related projects -**What's an alias, and what do they do?** +You might also be interested in these projects: -Update tries to find globally installed updaters using an "alias" first, falling back on the updater's full name if not found by its alias. - -A updater's alias is created by stripping the substring `update-` from the _full name_ of updater. Thus, when publishing a updater the naming convention `update-foo` should be used (where `foo` is the alias, and `update-foo` is the full name). - -Note that **no dots may be used in published updater names**. Aside from that, any characters considered valid by npm are fine. +* [assemble](https://www.npmjs.com/package/assemble): Assemble is a powerful, extendable and easy to use static site generator for node.js. Used… [more](https://github.com/assemble/assemble) | [homepage](https://github.com/assemble/assemble "Assemble is a powerful, extendable and easy to use static site generator for node.js. Used by thousands of projects for much more than building websites, Assemble is also used for creating themes, scaffolds, boilerplates, e-books, UI components, API docum") +* [base](https://www.npmjs.com/package/base): base is the foundation for creating modular, unit testable and highly pluggable node.js applications, starting… [more](https://github.com/node-base/base) | [homepage](https://github.com/node-base/base "base is the foundation for creating modular, unit testable and highly pluggable node.js applications, starting with a handful of common methods, like `set`, `get`, `del` and `use`.") +* [generate](https://www.npmjs.com/package/generate): The Santa Claus machine for GitHub projects. Scaffolds out new projects, or creates any kind… [more](https://github.com/generate/generate) | [homepage](https://github.com/generate/generate "The Santa Claus machine for GitHub projects. Scaffolds out new projects, or creates any kind of required file or document from any given templates or source materials.") +* [verb](https://www.npmjs.com/package/verb): Documentation generator for GitHub projects. Verb is extremely powerful, easy to use, and is used… [more](https://github.com/verbose/verb) | [homepage](https://github.com/verbose/verb "Documentation generator for GitHub projects. Verb is extremely powerful, easy to use, and is used on hundreds of projects of all sizes to generate everything from API docs to readmes.") ## Contributing -This document was generated by [verb-readme-generator](https://github.com/verbose/verb-readme-generator) (a [verb](https://github.com/verbose/verb) generator), please don't edit directly. Any changes to the readme must be made in [.verb.md](.verb.md). See [Building Docs](#building-docs). - -Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new). Or visit the [verb-readme-generator](https://github.com/verbose/verb-readme-generator) project to submit bug reports or pull requests for the readme layout template. - -## Building docs +Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new). -Generate readme and API documentation with [verb](https://github.com/verbose/verb): - -```sh -$ npm install -g verb verb-readme-generator && verb -``` +Please read the [contributing guide](.github/contributing.md) for avice on opening issues, pull requests, and coding standards. ## Running tests @@ -267,8 +134,18 @@ $ npm install -d && npm test ## License Copyright © 2016, [Jon Schlinkert](https://github.com/jonschlinkert). -Released under the [MIT license](LICENSE). +Released under the [MIT license](https://github.com/jonschlinkert/update/blob/master/LICENSE). *** -_This file was generated by [verb](https://github.com/verbose/verb), v0.9.0, on June 13, 2016._ \ No newline at end of file +_This file was generated by [verb](https://github.com/verbose/verb), v0.9.0, on June 28, 2016._ + +[bach]: https://github.com/gulpjs/bach +[gulp]: http://gulpjs.com +[getting-started]: https://github.com/taunus/getting-started +[handlebars]: http://www.handlebarsjs.com/ +[lodash]: https://lodash.com/ +[swig]: https://github.com/paularmstrong/swig +[pug]: http://jade-lang.com +[assemble]: https://github.com/assemble/assemble +[base]: https://github.com/node-base/base \ No newline at end of file diff --git a/package.json b/package.json index 06363e4..331c4e7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "update", - "description": "Update is a developer framework and CLI for automating updates of any kind in code projects, using globally or locally installed `updaters`.", + "description": "Update is a developer framework and CLI for automating updates of any kind in code projects. All updating is accomplished using plugins called _updaters_, which can be installed globally, locally, or in a local updatefile.js", "version": "0.1.0", "homepage": "https://github.com/jonschlinkert/update", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", @@ -54,7 +54,6 @@ "gulp-format-md": "^0.1.9", "gulp-istanbul": "^0.10.4", "gulp-mocha": "^2.2.0", - "gulp-reflinks": "^0.1.0", "gulp-unused": "^0.1.2", "mocha": "^2.5.3" }, @@ -78,8 +77,8 @@ }, "verb": { "run": true, - "toc": true, - "layout": "default", + "toc": false, + "layout": "common-minimal", "tasks": [ "readme" ], @@ -95,9 +94,15 @@ ] }, "reflinks": [ + "assemble", + "bach", "base", - "verb", - "verb-readme-generator" + "getting-started", + "gulp", + "handlebars", + "lodash", + "pug", + "swig" ], "lint": { "reflinks": true From 4e073d8614de0df6be0bd118462a80608059fca5 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 28 Jun 2016 17:34:16 -0400 Subject: [PATCH 190/274] logo --- docs/logo.png | Bin 0 -> 100073 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100755 docs/logo.png diff --git a/docs/logo.png b/docs/logo.png new file mode 100755 index 0000000000000000000000000000000000000000..3009a36bcf98e418c87b84d4e55c58e456ac63fb GIT binary patch literal 100073 zcmeEui9eL<`~NLMCuvbpiBb_2bJ|oyC6z2?3kgH1BWubQW}ed~(MpyS+9=7A?8{6^ zolulQ)}cf(Ln$L;_+9t&(D{76|H1F|`poNfPMvw~<+`uyeZ8;ce&&SkHrYPqkGX#! zgr*qm*lLE*0?8es1lzBq}LKIV!=ndQj!js59AM@J4jjovRD&Z+t}*}0~6grYijRs-f(nZ z)uv*}i`%=ste61|KD&2B(A@jAcl5?17r)QsENk_@weT$VGi|Rm#ghEe{$7nc38!CZ zB$dYa2>kd7z4kFI*|&wtQ}HNT@Yo(QJ1vXmCCIj|wHR&n(sh!(U)q1HjUhkEeO={O zI+*6x@OmE}k3KH&lE8niOYA-Ivru<*=%3!dzjZxzpV`VB?XT{g_zgcqFBVaw216~% zl$z}KIP|joG*VlIN9pT%_9s~R(^!q8Bk8&talVI?@pvLxCXbXp%tvKkZ~Fb#XJ0j= zYkNneIcs>^dhPOxrC^0E{^Zb&)cI(J_s?|;F9zRfdGVEXifQjCAr}!?*V-@elshpv z!Yxl~z9xys$7qd>U$1+%&dyFEDzSyhy2vSf}Ihew<;SkY6S2{W`)dPkQ)@6fiuNJs;2VH`kS{w{wv2*ZMgk;PP}QbFf(n^RaVC zM5pAnjQaNn%Zl|NbF+}f$$gr#ZFtifJR7}OOl=y^eLZWyX_WDS>8YM6N^@u)`T0h` z<3SV1WK;uw&r@NoYIKPSv3W3wmghF+QCnS zu06d^r)4DAh!c|@lycwmw(}opr16%U&_^mxc?U-tKUPsbAVFxpIW1@tj@@?r+ zsaC&(q%A(qATh;W)|_A764G->ko!mqRhr~7Tc3`65B%`hHVd!CZle^$2?YiHReZ6o z?w4?e$9`W#|L~P2);l_>^e(?)F&^2slG@dOQ;O4c577ZX$GmELYjNa%%RTRWOE>LQ zuNJl9Mam-9MG{T}BZaW37u`71|0aU*_Mcq`FetSIzqvb zo}-XgS(5VyqKVi|E5^{v*iCJJut#~wlbl{DEvO@S((!fN-PRo5Eg3W=Bvk-B+;f%nlC6`)fIkisF zoVpJpXxn{O7el6~Zd_cB1@%b+=+oW>QG_ebDZL}Bv*w{oW(I1 z*eux4B+fl;iBR5c)>r$G#;)K)Gw?p~h9oN4CjkUuJ>6dO9;c^HK|#|y+lvd4`tSRSAB-ezrbU0+;pn0ZFZ zjDypG?(dEi1m0+^;}qa1$Tp>xv8OZd7Ef(x<+7Gx#kA+~Fb8?jsRdLT99$ea`8s(3 zbJWyH%@m<#DrBn=)>3XWvR#cy5bdxj3B%PK#Ze6Gg(u`Z{C}dCa?{ zN;z8#GFUwOweIeCBl;K8#B8NIYO{gA1%_PLF!*}WqGBP*opnkn5!Z6mj;+>_F7 zGg35FubR{e-HG>{cVj)4cM0VzSyxj0`8xw0L1A>|oL|9H^m!{{o2b+nY(|q-w^ulQ4KuQ!7N|aBeC|IX3&oCPcV%dXbt^AujP0T4RQ-NUk^>T2X zzC@N2f5dhz_cewh`yHuU;>}LW9=!pbzj{jtFho$*+ZVjnr_&1kNQfkDBF_?j?jAS@ zXKBtayNQ|DrZjI-M9wF0)fi#!aP8)Iu&bAw6ZS6pV@f!KrqBjKZ#iC7<;@E~jg@na z2Z@^N*GTF7SKR`qBuu-%7fO>eIj5+Zrl9~vqvAdpgPcgscqt?Oxe4^}dE2T08)VdTU5v;p_3&{puF;lFf z5FhY>1-{sz3`&cWAs;DE2&3(D)y=|bktaQ(weFEix-oX7rX@HTAtp9#=L+u*v|X%)#aU+THC_U1kt0GgZPqpm?X#9a8OmpR4K zJ+O)#qZc<^5(W6f9gG+ik~)m`L72kYGgfpkM^P#c*0h61NhcyoS^)5xN7YY}#YKBk2)nFa-GS%tGH*QtTv( z#TP}T)H@u4btwICI*0O#FE*#+#E@KRA*sXZ5DUS+w5{A_Sa5cj&o8@CU$U+tgdoM2^xwHaLBfQKPj^Tl`$(XA;9<2PY6syQn*06VBJ{0Q7spn7vXVppYqMuk|w_Uf6v}w`qly-f& zwCr?jQWSRV=zC#`TLT%s`i_%c?=N15-ILgU%rWn4$&vRscG{bexZ1_pi?QD)6~{@z zezT~VC z13Zg;3&K@M+g?Hb8kkFHzI(x~qYjWqf`r}Plb-ExYN4A3X_4#JH(D(}Jf3F`gENki z^3Psmd1U^Xd%~x4;XAhEPpf84I0zX9h$UpcN^`w`?!oZ-s$O$Sj+hJGw0%CTwZOy3 zPzi&K9%VKw3=kIFM7bHw)Q%DT%N;5nj8`I`JOypp{d2Ov`gK^@q^PkUKKa?K<<`!G zRVvB6jd*Ej*`x88DWz9Wk`1u@2G)Z|E}BU&gziCF)Cl6p1}Rg)X>yH;4PyW!nTKL| z;-vvb>Qq1%Mk+@m4umCHWvtSTIfYa7=mtSXurov(L`b;z7cP(U-H%;4iL3?K_wK5t z^>+r#wF4tVDt0is)tM0orP5LOFWM|pZ`)KYzbQ%8HB_ZyWb~=U4X1(UVXLw0N!FD+ zk1OvWsnRkkmLQ4jSG~Z#=P}OZmXYvMi3S&T=>-#6lfrft45xh;n2klH@KB5wBErB@ zd#YZ2Nsc&7WE;4B7wlW>eh5qZoTO5A9JqMLF2x*Xc{+~ch`sckqhzxyHnP`FQrMQQ zDNdJlWgYYCDFd;W`oIWMv7DKtV&yF%K+D}4nCuF(kkNBYN+pqy?_rkkBoWFs1^*6N z=;mOHWu?DeOX36^o!Czr*o%g7@}+cO*RNUFC&A-3NZq6;@&dKO18(WS#ebzp{a1== zQi>vLoZ!1se2%QItFs>bD+QG_b3Q;N=9pJYIW(ClRy9^-GzsWFb3umCoIzp;`JebLWIP!TaK>%*tN%~d)L#!tr(sWa`xhYb zTS&Lth*SGp17Qo?uzonS0@}V~tua^^ie&!P7ad$bqAKNx-5$x8{+BjeNo_6PhmkDX zNCpeUo|iwVSrh6V@)T#V-RU<690Qq|!PFT{{)%WlbQ3Q}b{p7F@2J_IYI73AVn;x7 zm;WV=3^EBNPS=NUg#bP5)+pS1{+Tcy$*@r*FVOarK0Wm*3Gk$3=`BEdL>Vc2Wwnmm zXiRI!@E+2N^rw*ML>U+U3I5I=CZT$nL9U9JU{al9qe|pl9cr#5IgMjV ziaCc2H%3GT=1UZB)CI!O95K*|=`uMOob0ek&9Dzu+0(W_Uxs#y>q5>iBxi3UX~Js)HCt{9Ir}U*yX6uk);2?O z_5^aa<|W857P_%AunVK`YZ#1U>D~6lB!q~UGYUZR>jXAENAeU@Z6Mw}^1Pa42wu0q zkwjBv6ghhextsKutYL3na1$z$gib&UL@5cdqAe-yYEq7Z?bNYv9UJ%fb@HT6C-;;N z=o}us)Jo>0D8t%r8kRsloRnY=B>{U8WkdfpCYMzpbHv#ikhuWG9wko+StO=vhq1^*RWa77!SyW; z5_i)>#$BVWZ6Jx zF{0S+wDsUY!@m}>R#93Wc9uC)UAzj8NpmKTxf0_4@Gn0GnLRdyn+(B!g1gm z+P-lkuy_4D5(u?M7{o&2KFK<_v!^!de&#P^ky$ag_1%hI{#ZR=MCwWobOpOaI#6N~ zR`*M%6?o!^8-4@pQtm3&<{rEutB z#~DKFX`!1g1!eY9x2Mwz{E}d#37M}I#p2|{kxJJf_dWEVa9uYMtIF#brsiKUttynV z!|i3yxbjGIdXk=mL$yAflcIJMVb;mVy+eFXDp1Song$4@^T~3 zkVpo&?F_PN0t=mo#CUn!fCCou0x70EG*emMJM|dLC>bivqFYCC;ROa=Cyu4;J4`~t zZ!C(U**B&2x>{oK^Tp(ICmbPv8)NpDe-W`xj2z(h-^W~TAC1jL<|OuH?@QY-r9EI| z`0|*&5$|5Vpcopn7&BBL-vv#B*=I7wLL_2vDn_&XH0qAbcd6ZUo3p9(WVuU|L0iJJ z#|tL^@hA~ZKKGc2oOowBIv}Dt`O=zVnS;jd(#sl8x=kICY&o^bEQoXYqM$zTq36WA zd-$Bx_@(__2fAdNHE)4l6bzpL#0Jx@x4Q(`UkzMHdJ0mw7uDE(O>llGi1cDNcGwct zWdAus?+46XU=pZFtfE@B*@|3Y3%NqX^>&-CFmC5t@;P0iB}#DDrI1{q*A%ZXa>+c^ zkegm!e2MgDq_wAl>+?#u4H9K`?o!;`QS6whp~q@qs8K=;{55CjmS87-9z7FSjsDsD z+@YHqISGa&4HKfrZF$j2+_hK;9$tWL)G@Q$Oz^;4;|@3vOuPKfJJ9YZYdSz=w<;bW z1v_kEHI$5rAetc+{3dt_5JrsVMDT)`%h%g)Y1ehUzDvzV#Vh!RHNNvqVJ+<&XJN_D z3zG*6v`43uQ^YD&7v`;)!`ccfR9+%i$h+Pi&}G#qJEn%MQ5|X5a%{r@KcDrmo|M&+ z{pSuj6<(c(B}C5;oAc_Dxc20L&E$Zk$=1xLKP~geG))ufu1?R_&;6juB11O8OX? zSa#JlVaO7W=J=2zFX4~?H+4$N`uyAuhD(FTA~2?$GB&2%+3W4y--nl3yOX3z1n&)D zyb4yu&lT|Vb&oLSts1K+xFLz7=DfRW@Wk2V<15#l><%+cc~>blbCw>?FHXy6$pRdu zPM!d=k>70cP5Z|K?wFz2Z{D8(ygFboTp;~Y`Y$-7Y@@>t67fjK-9hwE^ zy9(yx&`9%5O(k))L#q_?b}KY6WqbtCxtIrm~1_nn_bTs<2_C&Dd-|nP^&T3BHw?m!4b7DVUqk6E(-06`|8Ji?|zH9mxq9X!50 zJL(DEDrg(|upupnrN-YvMLYKN_iOmj;A_S{f;Wx1`QPZ1dM1FyJ-3P=_I1ov#QK-2 zCF)fniA|sx_()ag=Z}7#sX-2~0SBPZ9V`B5?f^d`=c?+{n!28_?*JF{RKL53o}3zV9## z(^MPZ5A6L-F0_Tem10in?7*j)ReBB{EtY0jm?L*a7H|z0YZs_op=7r~HzL-5V{)cPYdk z5HkVeaGMH-g->?#E>BQ4e0~5i@UrE=v$e?QOOmY(x!Fw~V{qdf^(h0-#&&00lSljm zj%d|kKjycbxV0T{zu%=xNT8_22V|`gob$ZUYnm9JUWfa9SiXSRm{5@^_jL6r?hL_?}m zA=={gWttuNBwoPcP5%?2#UodSFXq*dH7plE`g?okeD3dxUWNPymk zFcOulo@-0-BU%@3iE*+A%eVvMWZgB4le+i5x~C_Rdi0X_c`1B|_4o&t@q$va|DJfi zj@eYSMC(%cHyw`RC_wDj!j=w+!hvS$=6s3)=#pR4xg(m%{_3E}3$L(0N<^4zPl0Hd zPuUWQe}R<1J`8H0{n*x=1vz1g*-hgq1+m#Ss_<}w#|oG)&D{!Z+8$ezZ$1GYC%s`U z_^{=!M++{Q7fk~f4LOJ{RXi3Y#$p*^dc5&*vms#JYbnNhPnqwVj~m9BLaxvfV-?4H zBA7fuA)G*FsKd8|Y)HV}+XGguz){N!whna0>)*iZJGkjaP!z#l+#Cs6Z!=DdddMK5 zm1)*OwWZ0#~|XL;6FQQu3tP~}_Wd>|5c?2zi1>G}W)Vc+*jjFB%onycpb{&6$TytL~=C4rYyueZllYve8? zo8*v8F}eJ;$^JMJ##dk~OpP+%cdDeGF{UI`(w@`=oqwE~aa1u9{4ZT^*AGZL`JPe( zD)F@Bby6t@C(~prDE{K7nXRW7`F3NbCKT_p#D^WV5CU{bj(CF`JYxH zp1FF)5SU}Y*NE7EKz)HL#n% zNE&})PQ)c~)1Cnj1!LpMfE!N@&KFvY>E7s{6ukXIU{snNx%~y4i#3Ul`4LJ=9y$Ik zgv*hEx!8~B6THu5S2OH*W8o52vH^fC=V1Tic>itckQA#O&L58nsPdn2{ThW}`OiY_ z;Pf9^5iw`)RU8#CV1bu|8*Dl^~oQ#NHDJ*XQ4JISPB?B92<@&O{f2BVdPf8J? zTjbWj!+iJL7|RHGvCrSPqJAfgi0Lf@B@XuWk2ry$%>ani+gEUt#~?y@{wu`uPAo)e z^nVF~7V<#R3j#XGfR^FZT^kEsL+hL<`-+dJ}X`d(Y&xRMX#6_ zsaIt$rYueG{K|d@mV!fy{*(pAl6Nr4WW?;TYFx368o?eZc5mR_1vQ<)xj*Nh9Nm+H zX-(sn^rHRtGq%@tz&gi2N0(_GJziuX;^YPmzry)q2tjK!?S%AVWQZgSY%{lOv`Om4+)9QVU2`8Q1t)IYGjY z(|$tqCFIWn_wfP^vFQ)Xh3_cJ4)Ie0>KEd`*!eql6E`1}S^^X7fwRY89mjXfY>n*P zJA+!DHaE;02zxTPx_qs}R-kP~fI%%3H#$G4BM{$?LrAa%8_A!+^A#Y4?g)?_O3(mA z;LmgJm<@Xp@be1*f&0Ro-@%{064(&J9^@Z{a6JT8Ht*9+jf(z-<7~lx7bIWMAZ;&# zGE}eywXR^4xf)Qbvkx?1ZK?uS%ur|CkzWo?12@Z4t-tXI+A4?n!v(P5UyoodmGp)Z2$M zh2!92PaStr1ri$)IUNIy-iOBg4=HqwDOUReR;=GAP;Bvkc!&1e-!$1r!#p)u0D*2i zX+!b6q9*Qu)oloX2!{ZN%jQMPe|rWo|K4Pt?ih>naY{b8#pIju@7cb|>UpgmO#QhC zsvJ;VMz+v~z1M>^R!xAd#weE4jTQG)@xMeL829VpSP-2Sh}F)L9;a`18|1`ec?KV5 z6E=;|^6?go<-~yUajmz2(-ZmSs`kIRm*cM^?d=|tkB}tf7es7BRAc%Q$ky;jYLlH; zx*=~FfcfYlaJHBmkOJn6Ck{x@l<70%tMzUoqT$?x_<-$ky5)C;glN0R&J?o@>_wmb z-{uhKx!3q=ltE0pjL#IXzDz%$W#V{Dplf?v%PE`zxv?gIn0*I(L9zPpIv2^sLUwkI zY?Z`>;oQmms(I1xfX4O@mj6+%I`mow#Y_QXu@^(LsN(1fDszPZ^A(D7Z->wcC}snG z{OQw72eIES( zkVE#b2^5or%LBP$_k3Z_^@NX9=8ZnLj~(0SJU;f}-$o2?UhVF;L>$p~ViUgkKZQM{ z6nxx%!o4A~u%Sne=O$<*LyaGC1s0o#QTtpOx_iH$@q*+fD^!Q1XCb+b_@wvTD!6r5 zDEp$physp%G}%XaUXG=i3t7*2Ohg>pfAz2j>tX4C=>e5=r+s29`~%U$M}R2lU0`0r zF_uBvWzDEu7Wa|dRa{PBbqX;+L9^mAO*%gK*h zg}7nvErGV-=*_-_UH9L53f14OxZVyW9dR=YTubVCPLqA~zfmtk_JKF{OvvqMxQoDE zFiw?lk%ZM|jn#Hx#eb?zy5l3VF?r+n7_m9l;LH~e+j_8~#u%FYSkVZylSi^p7zCh50?kE6zm0UTCC z9Ey{>bijYx%Ce) z0-MHqj8GAGj3H3|3g|yWa~y%_wqMQT=BuDaJ)FZ1Y#B@65q)JOAo3&*HW{v%ddBM$ z6mw=w@p_okdfusYj5n2`b5Q=fH0cS$8?E}iQA92U*bEIUy9s%qj72{yzP;BRIW)>> zA8k1VjaKQw*7R$TMP0#bR>)|T-$?Iu0dp0gHy#I(`guSroDU?{4|*0CYyGU? zjs|_JO3h)8X0bZM`ko9I3U6tLX^afijkL6~2D?`!;O<{#6zDAjw>Q{X(4VO2S07wC z57O>Gq7sJl83saTqb@W5LJ2N8)Jq-REZ{c)Y%f*lf@vkg zskLoPzCF?=eM;`mF2gU~-lKw-33Yy>Mm4_6OpmxVjuIoUlb8Hr1%K~6Y5?|@x`MYi z$J2Plam3c5_-bXv3Cm4=aQ{x_2ay zFK#aTug0E16z46wpI#b#Bsf2~4(Hfn7bDDzY)_6p{xQPm7E1Cv{IRyu8EM}zL-g&# zyWS>u)qXh?@u1`9(COY68i6a)N{<9*N-|4=duutpe|(2Y+SoVx-0*~xs<;vfs%aiN z9z5Dx!vf7LQjp7qQnX2MpZvnNg4R;u46LboY@Ca@0TmSC4J9qj zteY#;vUZZ#))UsjXPv1E>w^XM-vkcQ#vIgViJ{4s3|gKR!Z=?%5&(W+ww(PAg3goT z+0C+zSwkR(l--Ja7Np;h;ud`!hE-wtf zPF;8h4jFV64oSAydY&r((D!}NgJgbQ&E_Ay4eiLG%6jzoV>N-SY5;p~ePHh+P}j{gLF-l=dLnmMUc ztJIS#wNPN<4-i$GzyQF}zVZ^@Y4^MK<14a8wU%aydWj`Ow)Fnd+i3D;H^sSUc=;2Tr zv@x26o{fC0Ia&Z7y&sR3AxFDZWY`^?8hkT&(F~NNmYgS8E|Wdr)PI?6O{FYY7-)^->rmg-1QZ?!oQ~-sh~rPXGz(lthT$3RT8l z|8%DP4(KRsGX%Cei2H2HMwnE$xy;dV_^b7-7>ZY+_N{_~2i?8}mRN=R6Eb(PX>Vsr z@tp_mu0!-ue3X(tdxz7|X_M0d9mh)7uIY92Q^X|VSh|Q+{a4ql#XrGt^5rzoG zPDnhl5kKvVtH~mNHCxgfek2cvEs2tsc@%Y+hRoX1egp4wFNJn1;v#l-f02sB)1;# zRA1;bSX5mydlKr~xV|i_@7Gh9ouP{j@+UTk6E;YOW^XB2V~I54tcULCO^u?_+fA`s zftWiZ0SsF(BJPlgNIo1IY{TONQMMyA05$AfUIp)`9agpP7ErY^4tv{nU{%Mv%jm#+ z>S1DN-5E$ij_FS2p9URrzThP%;3W@upG&Xk-#G)2{g=;OA>X?fntq|8NbVMHbB)-i zl}ru=bKl0G_y?Q&EUvf6T+;v2E~^ID`xroUBV3BhHUoxXa4E*dSv+_LiJ+73oCloq zTT|gD2aSFfg6!dmP0hgDFph~z@TK+?y&y3QF zfO0(uGiW%zyO1JXb@=6mAIrEbO?SS%JYqMw*EK`HC=lGo>rTbWU5S-T$I9&svFx&H zXoVKn3<%qEKTofDR8+#=~~wVbi{D5U}(d4m*U2BF;Cs`oj)nq=Xw!cuO)eFE@a?V`7a5i{6z?pG20RvTY?j40Xs|4(}Z4nj$HydaRun3jUHvBN;vT!K{L%+E*Ex)Jr@cJRMOfrF$ z#f|XFI5#0eALtLXRuo&o3*K^Tox!RHvGK)s;sKk;0VyzG3w0WLP)>r~$f_UC{L=ub zn3&N+mqe)^#Rcq>;4ZLC%u>9J@@J}0WcLBfv!|1#2i52So5EY_mEn&7zKfEQ2P#iUgVYk|m}DmSYjF*(FF}kvphmwAiJPkC(dyy{wnQ8oXNl8t_-S zg%kbII)pt}{Bznn2EOY!L$`(3oB37Vhh_jLO8{0W)K-xfFx$X=q9Y-Z?TY|3w1@d!HG4>3uhE_bF)zv4{?Ke&d{!SXs`dADPE z>$CVbHL}Uu;UHV+kZlW`Gvv%)1!yjX!gYOIi7d}?!yXNyLr$-mF z<}2UV=U33Liy``9yz(trnG;K{oN3%u*T8gd@W3L}pTFMj957-^c1mw!K+0f1R*|Ta ze!o)CyjQRdeo2A5Q2OwiNcSVb7Er=%j87Si&nmbdkrv8AbF6P>_CE}nq3nU|b%2Nr zK*TFZeICN=cH#I!r4mC{`!a@XRmO6@EmXL-Abb1RII_3XIiET40bpAXN3B)(%6b(1d{3h;Cs>cLNlO&lQ;1Q9N{iTj(1P|6P+_^;VifJ!#m@28b0npeB9~yxc&=V zLG84Kd*FS@3)=wb2XRfU06>SX-W3p3E9wb;Q{AZ6_q|ZRtAd9arRORZ7>6nCO(x$` zwR6dr8`!d6_X^-zH9H+3g!(h)>w}K*oq7*;$x7VJ8z!S#Ylb@C5F5jt92iOBxI_J9 z)nyS#y|E1VzPDt*;AocOiG*tt5vRpCzZEB76QE~gVe)MykPNP8wL@WH-0^RHv?8~z z!^%U(sMZSK}lg4QPV~=wfdNob^qCeqmD@sEOtY_s`knYZU&e+)L$Q z-lI|s)tQJB2#O(eF@&u?%}hV(4CJ-GP?hhB%MG0Ifk`EWDda7h4mLD-16_hGLbL7m zW?jX11sD`;9kTUknKrv+*OCcT_Sc77hJB$JRcX)$qAjcN1|}viJ2|@{xol$08M4F- zO3X2XETIH*Z4Nfn!OkSoJ-32xw*tsii>(QfaMR#m#}k_XogL6B_`bG- zF+~`y;(7$+)q5mNEl@9TfD7sv8c+_W5Ud|l%#{_V>_)Qzp`kT`Nr>Ksqil4O>xbq; zb%-5_gR3s&!t7M2e`Q5Sr<5!Jk56%imzNB&F7{^$r3AN{9(ZF|kc6#$7VMmrEp$qR zW)if`>E0f2V@JNGfjc=X3hMOvN5OWBaB{7UL6HR|N5{ZX0|41`IAcf4T8iLk`s>R! zG?<0)qTddV_B6k;ocEzXob$9>oHOlN#$mCDYdJIjUU*{J_UdVWFPzXNS$!U@-1PAp z``+Qdoarmq@f-L2dc2E&^VLPh*Uo_22G^s#Uvk=J6rm#aJP8pkw$F!Hw+_z+ zA4Sho)cY+w5)Og$Z&gB@LYC%w_Rgi*jXK*aO3?3tvgJN5?8O9L>?_-S@z!DdZU7oI z7t9@uHhKe9wr@pJ}?~>&z+IUNI1OtQw+Qhsg+ademxRYQf zAQEZR&2Apmtc6=U_5~1gbgsrvMFqwXvswRh@|Cg4rETh08(0V7&?xxIIWF^A!P)>Q zwK&0FS0)_j4&`C%+1_a?iE}Ls8))Di`oc?=eJes=iVr&U1m@O!8dwBlemqsHbdO|U{YzjC)6qGv@m`uQ10C*_XjNn>^C!Ust zZayPp<<86%3RxtsH?y^RoCb$ z_(*+m&Vn^nwfg)}@FmUN9Zzj@JI8_NQctQ%oaC(wh5%pn6 ze*k9&h|RduZl2n09rBHt`l;E6N zF=o@Q_<=P88nK}~#MU+UXsya_$gS5Y;V5q0rFbAx4h2KQpIt3|^h?b@Q}G$E>!<3T zO9_|>hup-^*5|io{hYZQ5ecffy`4jDa~p-rMy~Epe<@mR1)L%Eh35B8;j<2SapJ1k zq(Lzrgf+A8zun4*PBdYZADmFfkeK=Vb)8`BLz;E zA3CfXO$F~m)ec9JY@c5%i*{K7{B%31sl=hiYNY?Z?h%c(Xy_j?KO}xry+5SV9g4;R z`sG+`NSd{BfhhYgkdZ&vZAN3A=16zlxB44~(+QnFu_fd`z5Rr*6bRwo1IqXU;6(9MxREkJq}_=0x}n zPv4b!@SC;HP8mW6imt>eiM#+eP0(I_n0^wLXOkhgkhnp9u~yyQRwu#WYu$shy4NYx zsDw_a#?9p)Kl^-VHlBoZ+SlW4zwV%K{HZVtJ9+jYw}3@9LE2iVz^Anl=<`3k~DhGm6y zLh-#RAllk@&Wst2mZ_nuQKETdV_^Vy^+oPML+0@QR2yyHt}Eh+t~zHCp)kMUGuGNQ z=N!huz$EfiInQGI)oUGtV*fbyIssL~+bzvMUBZ|Hra2>=h3MD?IMy-yz{av+!ZUR?gmQhiGnHyXzTn&GZLu zc5b?EaD&-q+d1zhE*=JHElN{^-~ib{$k%1CcTn`GZRGh`a~%|NI(svI#N zPXy{fUFQobRWgHL7Qs(WgB&7Snapl`lbTu-RRV_stAIMg6p3KW8z!MA%{JW0=gvZG!E&b? z$+m^~6-MIbJxuH>1^6yG9pg&}ii+6@_f(Y-xgml-EgUtnc=NCfSejO$WbYfoZFT4y_ z5n*?n1d5qyVJUycf+wPpTk8D=IqLaUcSWF9%;gLAes+CP^6&@a*^yzBhuT|E5AHV# zv}26-7e?dh%OCxG=UawtGkCh7@}g7WxiweCp;BvV>2;0C8kn2ps*v?_v0tw#%8*gq z3hh5>=ZJa_@UcF_3hR(BQzmK1181-+p3NEhvvzrjk?WG_XxoHfZ2q6GC9x@Xd7gP=1!qNf- zgB#~U;)5IOzG?aM-iXrYVIG<<|IFmvAID2i`=Fe(5tAN9Tm`8^J^`yTfHiAn&A6U}ex!Mw)G0YE+ zshh93Ur_r|vF*=21^XXrwV{kZmEpLr^y;?xDeY`7VU8%>o3RI7=EHqu*`pg3<}zqJpGgKPCLn-iA{F8F-$y$;i8@;e_W*JE$$A~(!^jq8G%57{?OM9*DfA13GE7f9j#7mMg$t8HbQRo)_c1&GPC zI}-U&LQ-0P?(or`;2Ef;`=lhQ?`!K;!zX-xXjb>*d)H!R-C-`h{;kDZ$bdSWbJUNB zu-7ily;%B^&4^zeI+CN_e^FsCYJnGIi1VSM<=Uij7e%yGn$+yzksE(BwRolP_9C-L z8L^X8b1%L~78x;eQ4?MbnN9H50h`2Cx2)~fv1QJcMRcjCOr1C!9uOqm=ef$YKtx3m z-U9zLMKe}=Bf)(3L!tcXqA6@!48Qw&b$XxmZ4q65Pv&c5@@I1+kbj5OGpeM-CUD#% z8(;V|)2eC}FU@;&OrmM~#kDB?=VmeF#&*Y1FT4~2;GcU){~C;VEn zjCoJ0{9C1s#Rw~$%lL(!{KwMm5HOdxN9;q-yZLY*3B)x6jFI)zq84m4II&~ki2bsl ziswh_qMe5sRod;S2)}jGyWZ_pR$Ht3WF(hsnR-+P5-&vLrc?|o<+NrUnOTqgH{zL! z@?`_GTZ+FnFsiCHen&;`AO-2WP+AKYH-t!GxDT17p`z`kYh*L$zxX~Eu0-xO>Da3* z5(Gd<(fyVo+GhqJ?K^y=YlqCXw<@7vX(ayFobIEhYsgv^#gPzbo$seKg?MA`p~zpe z`RhLU^F1#Z=hlR^3bwOE(C|43^hKv-?L~-%J2g_1oCIVK9o3(KwD!aCYXSi4Hsk!@ zXAL(`Y+p3|rlu;mW@H%kU4)F?pS!tRX()UF+&N<77aK$u3|4glV)h)6Tj3 zeiD+3^Va&3ulrN|xsElhSo0`1SA^&U2=u)btzDcGQW^FDv1!_6L$_~4L3x5QtbAgV z`3`0;gVm=HPs}_zU-XaU8u684=WG&VAH}l1Y*U+`%FFus5cq1y<3E&SX9j+f6K4(7 zC3VhQOINq9)v&#Ab$AzTq|qOY&cpyJ+H_40xAuP69sm0W_7hwxplW@Wb1tc+gWsY1 z0?n7#RXU-ZR)&XPv4hX&KbT>UP^W@skPG&ZfFqmPe=CsujQ+4b`sY z+~M~YVxw*M)cfJZvOX)Uob^Y}?l5muTr7fq)bCI2UoA8u=P72Fo;CZ{eS0-JuAhH{ zrIWnNNuU-w`t{uRj8j==_iU<1g8TlgE%G3Pjz9^EVJ97N>) zglIhYI{CB+p5q7&hI`5in?aOIunA$5s+XAi|zE58K zf>U9Y(RYLj+^@!xKN{(CO3D{?gC<*gfJw=#$fnjsQ*@5c6lLedaHMHGJ&WU;`<45j zDx+%mB=UC}MOR#S$o6*AYgg4=R+W3BrwDy30RA~wt@bil_QKw>Yj9wNZUfu`Kdwu zckwQtvPmq(C=tq2;AOAM&>is6h;wdO`zxsYVS<8>UAMjM$ zcO?#yJ9$8AgG)MmTZV$PLb(qXuT?7!Io4IYm}nCyPDkOdEitbjt5B;wBm;FD&SO@b zzH#ldbe|z?7c%vVyM4jI{PSyMCbBbvK~jH+GSu1h()Q1%UbgD7H(leGIsf_IouVAi z$i{&5)~y+{Y6>bfo*|kW;8rdqFK-v+kjI{>ts;NPkhlsM7}{V5uW4<%)%039-gb1! z>4(<>5+>2}jJzb|UflfhV#PJED*GMIK8XveDW3(dYh%GwAeXJ(XBD&hg2tSwqHF}M zrSMYYRP2QtZJoPa=5}VrKf?jMdWHV`R3}I^`mOC*BF}f^-!T5Q_{2lQj)8f9A}M$k zna$45jrj4Bj_9qtmF76gpyO@HF1(9x;l=_+Upq%vF=?=uLZU|(Od#6Mg$>@W2L?>u zBZ3AN8V?Qavv@^%5gJOyF|=gI1i5t`zH5Iq#TLw+dZqsU>H{w&`jPlEF+ZYBA%@X( z&Hy53eYC}4T?<^3p{{fYp(|^e{&qC?I8>9QRG;6Nqaw=Q1Q?R4vrJtC`C8zmq?`m< z%cFD0;3teyzV&?P627Yo5(UVv4Vv-fweh2$({>>7_Dy1_6w>9U)A0U=3Y56xsVUtb zvbBTV{F};e(Q%Xf8}8b>l2qGU7iVg^popoTM5G7^VWnEj@80KH0r?GZfcz+aW_!)D z%nDy&d=!5A(e-DCZ;rFtiQ^$Y#?`_5vor4K@5 z=5W}}8b5DWCH890Qx;MZ4#qd`Pt@QhjTV$`v?7pt>{6x2VOS-i4y z3(1TEDS=kpDk5JJ9-zX; zs?+zag$qJo56X|e0`yhy0*;XScxiPg7aEA{3qc8$bj`&~vzNlXb8(|G zYgxk9YSsJOk1i7@7|@dFg7{nWP~jp(9ygn7MQdDDj$ll&v(BZgzvv$IP(m5?z#AW0 zrqKav=DU`>S-;&qU7U!AP}jv5QJsI`HJMqx zZ=6lcdfmX7f;K`mTW!uwW;XAfgzA%4JG$1IG-2HnR^TbBwJt%i_6{pw&94X7_vLC% zKi1&hm?e^^0>pMa=-d2?GEJ1R!sSt|qn68Le6p6*d)K<_4`wzkoK_ESISxD#<9n>y zIwSEA6am$`KCua9zDp({`sBBZA65()Z^u*W@4;yU_d$l21I`kxojbp5U_q$;I=+J3 zCSBm#>Z+6YLMCI1%Zb{`$$@x^o1y8N&deJJyEjk9t%27iF)b~MR_?vx#09u}>$;rx zk8j0dM7J$zRW7(UH#d~D@Pl(H`C*Xto6TjP0iEvxoiEdL9~CF0@to+@`N2x?TH?vK zcT#IsuU{5*AKt))Ta&HFS74ehiWKiNDd+YW+eWUPUgYrh<4=7l)C{*!)d_lUv~%(y zmQLAO({Qmdl$@4v6fpeESKf8W6wVaDMXt>BA8&2!J}YQZyZxPaFaALH7YKuK-ZmD- zYPnRYz}A5<6+BIE`AIpVZnsDBWQNRU&*sN6gCbq*zQXJh$} zZ82T5E7Rm-1@wb85}Qn{VkWdW+z-J>^1w*S1NwB*x*~=$M4*t?f4^KGBWaT?M4}rH zlk;_Viu#}N*1EAfI^e6f+@5;R8h`mJ0Q^QkZ?le+@qU34SW6VDQ*gC}t>oP3S(Xvy z+$?LjRASz2GKr39(UG#^hRi};mqe$dW9Iv`lXv~nd3FtcN#-A7@t5uzlBfxtl!_pQ zM$3a+)Im&s{B5&jy+4o3l`!*OCoUy;v?ucu3q4jss>BJ7%9OrJl4#ch=}kMk|J2qrDc%)1nGD)6 z)~D^bpMG?aIQa|Iu1n+k?-P&Ke_v@7i3Oy0olAjy&%zt%P~0&8#%oZ`HQ4Qd$~U=$ zP!T!{J4>wSZnYfU9TSlM-G7z%=i$kJSzUpbU_6q$t2TZK*e-Is{;j|>Ye3Hj_52O= z+q`c0&aQzL=qmW%{c)?|CYc_i{gxK5e4rBC^Oo2Qu$l@QDSG!Yf4V4p1Y*Q7y`f{h zTcx1Ez7HaVe76CaF@)P_*{!+S)O$yyIH}2`6H@3RRtL

Xc1xq^44}~8Zr)U@dQ2}p+5SrG9oP`T8r=WV{tI4lgKc+1fxeYY*uEhLx zMD*q}l{Kexmy1fh0L+v&{a7xOtR+I!tw}c>xraNx2nk-64{ca({P%#(T$&o}^d?mD zAh)EvW@ z)d^We>AD#r>yn)(=v;0E z5<(A@UOz8h3BNtXco<;({WOJ?V;al4JH6~qF4VAVCe22ixPw>pR=sJX@&_+}kWRY7<@?H* z3I4huowxb~hNDf#m-E0mev2m%BmTy-0Aa@WgPV=cklhFrZg@30>{CZ?prH%9C9*M; z8`$<>v47c%zWm%-qMQ666Yc6%e(?ZWZ_I*Mj$kxWOl#eggwqbs zP#*(Qn`6wAS&|iy+HC4u5)g2S%59L$yQp_M${Ht^6A9)XDtk^#*1ri3q)j0Fa9nZB zx)#GhXz|O=)YLd{K^AFOdcmVdj6W(|@)Yih=QzZ%B zXSu$nrXlV=*WL$oZdkzFabn}f)7`foc{Lf+;8DN>|JzmEgDZhZVNkk&RcxF3LXL>S{0z z-Xr{cXPU@-ig=o#CMnXpnyl$)1B`yKQ*~dmjg2l@(V~CKhmKmSUx|JqH*FqF`fMb2 ztShCwW{VUmgha63ygq1cnQ!uP5jrF1WyJ%r+*_n*B`y+9*i%<7m|f1TG+j7hgReu! zmrOa-0ynsv`Tvi*FORFKd;4CebCgIWQ|<;yBvZ#&sW=Ep%2cLsGGwk3niO`rDTGof zQZi2=gcD&CA|baB9UPf5C8R=lzw4a6Pu;)g_dfr;|Gn$^e4hK~?7i2z)-`^wYYnA( zHpdMBPFA)FrK5Z)3?wUx^sm*X>c-_az_Aq<$W!V@hJTzlb$=IubilU3 z;o}ynZaaa`_Fm*r>uuzYO|wg?M~JG*)gCbygh@Bu+Siy!`)ynNbDsAD1NBsb@9W>5 zyYBK+y&L1JkJw+jNrmP8>Sp zdM7r3#*rYXSjj{!wp(WD!$!2)MymD|-1Cz^a5IX<#+=-U9=3U3qOFWe@~gYslDa4a z^?-A9mn$QO{@c>kW1#c)*{f&louNM}$L#mbs$l)+(?nz-b~;s_b!<)~#cDoHYpT*- zy?ti#yNT;}?7bxDdzN_;1?iLMj*dwg^jtASH#{Cj7>>xRnys9Q1cB z-(7VvFQ{8HZPeSIt<<;MadD;FD&Y^~0m)}(kXl<*XjUv2s{MxH5nH+m2u&tkwDaT4 zFTLV~KTr@D*uRZHC)ietq3o?&@pZ177P)dTj5(%`gRz6XA9(sYjGCssL^3_G^!Xcj z=g97A+E&spTuS;(TfH|)cx|}Zo0!VzB}Z$5wcC8DD9q{ z3llKqPSNlCbbYTgM*Yb{{dc2X_cbl}SQ!o{=CZDf=dmla zFQyZW>0GHe?9}%_i9&BZfD z?U>g=TY3~2ZCbymrag(Is zJMwlFBkBLJ-%!xv_<=7~!}M4M6!qkN|KF`Q6J`X8ws405DjZG*EMunY_HE~tZ+ zn{({!G^@o5|6he#Cz0kBe)HS=sA>^%@j}XSe-pH~&=Nv)`ERpb$&) z8v9JM-U@NFO|?WpGVTxPy2!1<^Pd#tw4pnC~!02RT!B~YS5w{tg2=@*B?WSl8fw)lARp5Nwk zo5Qz5P16oJdHK&N*q9e&tu57Wsff(USzs4V*s4$&PME+|uDh6ojH7w0JJ)xbSX)ZV zZkrX8*Gg9r>yw>&ZsiDc{kES*|8u7%?XU5jG-Ae=#L}JT?p!_QJIQs`+VCb1hXx(Q+LEN~ z*{Are2Eoj)>{sOrM!0WY?6~y3kI3aE!q8r0hF&Xjw`pq5u1Cd`puJ&fPdSju{+vDg zmqWL8|J;LXd4fwP;&a*v3~_o=s$b!P8&NQBJ-U?GJ&v?&a_=5?_WcrHN-Kfy>RZ(! z28X%~Pi3t=H?Q9k=)#R!Z~tfORE{fS%Ay;;`hG62aQXFh>Z^I}wJor_T$1>B!pKK*0o$kUTXbS(=CAfD zHD}<67fP^mIM{jlM4Tx8oWa2N+L2m!5kh9<_MF#mK@C-D?jWsw-07FUdqSCS0#6V5 z;~p?azxDI8MnXgQD~n;NGKcua#TVEc1Il$>vu<^C6jZ@Y_*GBdv zJ&_m~CIlx`MQzPS(-S2bBI!6!renG2-I(b%K=sEfkR@ ziuE|Vac79C(!#{|ys>Pwlu5yW&g2=BkuZJa*I# zXP=;F4BLX?h_TKQhO8W_M^^fnp5;U*4&1dWMS58X1CY%d)K8-za|-K zYxX>fO&h62hAl`-`Zh=H^WE2P4xa-{5`*j|uxQnrm(Bx3WFt668B;658&w4c%hU%S#SG$Za>|K=}C8@qQ#_a{&_gD)YtU( zQ1YzjRkM_qu$#q5h~JqK;~mJ|IIXzidE-|y-V;%ty_#rLnKrxji5xR*)ue)L**EnF z;H$Kc8YyhrMSCVGIvs1;!3~C{Jk=j~W}eP^t=M$T%f>h1)ZBhkpr3c^%}hEiQSk?R z@~^#e7gdfMmGjF(k8KI>JZ_1t7BOyyLJk_Ub==|!{?R?>?H^~`QE)N4$wlkc#>{cl z+x9JO@lWpH0_~>c8g~+xxmYNFi534tMc{FQ17$xv`T=T>9Fz>o=CjCUS3nu)4hY++xSrXImRf=j@XUcQ3Wll4e4I zrmg-uT#lQAm?+iBL7E2Rz7~_Z8Pt4tg#?4@gJ>>-)&8rOXgHSZHI}JO3-$NAd9KZB(ZiYuq@fQvj_WOQp|VLiI#^VcbF;d#F^&&|?}q(`-fEVQGf!f9 z&`%vzZIBo%aIg=oSp?SH?kjZrbcN6Z!s6Ebh7vLSCshifU%{yQyMX@Ie+`z}u!GQ_ zPMdy14rg_vByzSW?V~@Ms@d8FH_zm6W}X^PY+@R_%28IH+U!`ifO*c(xxa7==GV&)!6vCj@BbbNIq>_d~QV>F^;<_<0^*Y>_Lu~+61IsmZ|#lfuD$alpXCxwBdJ5 zT}rB2uAMMcz>eM)U2UhvWKy^Ol+!~ZJX}cBSqRq(y^TgOiSZ3p`*wC#(CEcA+fB=6 zt#Z_>b9|`Zr;#y^?ug}Ck8*Tx4mKi1j+|3Z$qh+VI9o?I_6j?s(2w?A=O{LYj+Up! zzR2?pnIQl?;>sFeBfTRIH$u8;lk)p*xE^C}=~H3E zr0Q14>_a{>>vC}jhw_iRF;DEEdlvK>b_xnW@I7SNnK0E%oIF`9P zS37HYb74{RpXLHFd+X{b8YJjPo#>t`n8e7BRv}XDLr%`gTCoM4d{CCDdj2Pdlz)F+ zX5THDPBEhE84l+X$;;a=WcS$Y0BoMd;#tv|*L$8Kf6rLL*#iCmQhP1qv{8If97aO27rqr)`3vXAnJ_z}>S}X9^Uz#*$ z=T6;LroR+-0hJFe$!_W2gJiVP40H<)j)qhC%N`KP^5gRaOocNSe~8%ng1b_U}ts9`}7f^BOFvueQbr|l4QcRvCtgVh-8U(4CyqdgO>6<9Fs@3wI7x< zQNmJ7i%2$aUyH|MXhD#v0e(BX*jkH}Upd29l5w~X?ld2ptHoZ)A3urNY3Qbhf9EE4 zEm78A!1);B3d2mepI8bbtj50LrI)ILN%=LTtvYH5LnNp)q+}!1w80zj%(){={?5F7 zA`!b_N{{fg)&waR??=B+0DmW$@(f2lJKA(NB6mgq;I?)VSjJltQPwpba z4ZCQeiS2=RmiM)Ozws&-n8V$sO}kp%O;veDvHGt5RG9lNutth22-pF3Mvw{efNJDk zToD6dTIY==Qw~Hwb*&(j;{SFgs6U-U&G-x_S*9JXPujI-iFPiogvm-khMAR#@Etyd zzn_J*ML8{SvZ{Ej><)saI*h=c*K@ZEehgBTKaef$rrPeXMdY%e#fm$>HVTiyu?FX? z?RNYL9;{Xis^V5sV?K!~>F=1lJg3pk;$0cmT=u8?`_94r5VmmGC@ z-;7qO^4FYF8RzZ+D#nMMbzM46st@Np=r9`qH48X}dnG=6a8On7=w4&mRvb1I6ka>z zT&z_IQ)Lfd_ABestzS!$g^Hf~jnH$wo3ok5@+Niv#5ZQ19sH6V*0J#6%UAcsge{$l z)V{OY^N~;_c`G~puo2s}J9z?^h}BvHKRoow3B!GTM|iteE<*jo=vT{>-6#_cL^!Cc zc)c?xP!5&)@Unfq=CP@mq~F20267UC`>krzMwKn?Mn06Gj>a3U)OvMe>FIu!LFF_r(+92;nNcZ_YnF6(8rEIOWFslgx*m`o78r7V@3*NMRKQf^h)n%*X#|< z)ef$ocVx^Hudv5U$8YQ?5H%gAh@}0jTx^7XUSrO8cuhMGOj&F4q^}a!b+9X;`b|gR zaMI~Xla4{XRqYd20+a#nfjYn8sOr*M$Vlbc{jxON*rsZ@KJ%?5+za_5Ycrr7jN&We zPQwSKCQs}D|B=`S zrH4Oe*4gMv4X`S z4Gh(PIMn;Won7j_m}zk1CHGLPSZz!%{PSHw0nd&pb7s?f)l{u&pP`^qAKkcPQR?;X zw5PxOq9mYR!l8GE(N?sHI+yQ5j9ACz5bp>*L%S9V-)Xe3cFtY+knyQWb7CgGYUYOn z<9@r+o~}Usq4st^AKt4-J|U_@3BU7vGL{;7pIYwem~uQ$RMdQ& zqW$6~72+MKBGp^i721{a0_7+@W(?UUAA&>0zUGvOPs`y6$8f}oV|s&*-dD{mgRQh; zrZ}GewE&t-o<(4hY&`!?EMiDS!C)gv{s8zFEA5y~x)l^636vMNnUpr={UFvur*aRVaJ7*SQP?#eb^O;@qy))GLh&U}wy`^CvQ~vLrnWO)q-6a$d%Iv6PqlHeb#vm} z3uGZviW$d~wIMn)^C7Rv&$E9eA?2?60QL5q!GvOC$md>E85e4tn*{ z&cBv{$*$=da7gT9aD$d`qE{Hu$Q7*qmCb^>=gKSp>ZKN?y)j(o&Xh9!<_M)%_VVJc zU)RB2?!#ZO95v8NpjRH%KGaaBg`)iciXJvQ@uFr2A%f`L0u{i=E_>9X_N%bbh?*S) z-)dYER=QKz33ZgR86d*y231Ucs(Evp?i)FR2p0?pKYE4S12zkMVhp=w6-QLAT}`e2 zJ0B4n=>v2|-}o#(Z^q<=Lj(D!CzHXT^sj&roN0Dp0WMs(9sH=_=NRm`u(ukg@GWbp zXs?esi0AxU+w3zDY+W`rvhSvT$@hm{Bl=p%L)2V(kGNabNlPk%2H;AK=(lhVj6*{j z#DBpzH;3MLLfvrRCls_(4lZgCOYIRS8{Zsj#JZM0xa49Z&&v*I;!SZW({`EuLlh`f zO*$Ud)8WKi$6)pf>^Xap3+~MaE8qdtFT@sXZOp{qJszbC7qu2z!L+5WWjFu*Qu;ky z6g`mco}4h76yAjo$?d#}ajoWeUx@ex>Y3n}IU6?0$T zViqa91=v1?x6A`+>q$z?$mVP*%acTSgD#};Tvec=(Z4%p+2Sk0sC7@)9!Tt- z@`^E8viL9`zh6AmU*zMem68E+LNQVwe-UyvX{QUbW-sr+%{k@oC792tLtm;U>WMwz z*j9L~6NuX&UK(-&cE7f^F|!tz$W55R1S0o}oe)0w!-Ai)n~>w@pz<2`*32vY_pz3Y z?gH)4)EQ4|Delopo+s~XeFf=RO4J=5nTXB2($hx@Qsnzm-<;GNh9${s;Vp_H!lt=F z^Gepd=L4761~z1w)m#v}X_aH_geAQSVMa3^=DuF=F$ky9XcIf^GZ*%p2t_s$#^M67 z*yIFXQg{hku93%5m6pSyTJ;!8UclkL$km;ggMC%n<|j}5Mk=FfDW(XLDZs+ruaFl& z`!A?>Iv%e7a5Ar9Bj$jZ-6wTApoxTt3vVIN;hKcv-!h?KTt32YMfWN<-V3 zZb7Uz2fQ~tZmSVP7;NUcS@e51J=sKzqy_XgeI3x7#n@X1Uu$&n3u0p-Yu%jng#y|4 zvAzIkCE0Vdfdg=#;K(8}iiPVdx1KQ)nquiGHg9iZ5>pK1u0eq}x}u*Lk9MYT1Ev$h z|G~m3Dh8$+rMGAT2K&vLL^Dl$y+$GSxf^H~fMk4nAZdvZQy6PgA+u;wA(MbylRvAi zNK6rSLA2w2;yu8?UfuFH6TjC^5x*hLJov`E#h05h#@L&{tCnFih&$(T3|Ry3t@BU% z!tPcA$=?mRyekk6XCF$z%c2_T!GG!_H3Pj4s_acAZ^l0R>g~N&Oxtrt0zLfo1^tts zr~f@0`ztL^Vv`B4H-iI`SU_y~jWU zu^!pAr{p5^pI|{I;-KK)Lmg;kNosBK{CCa)!Ldoug6osj>JS_24OHT6-rtY-<_$M9;qg&?uYm3gzXQBS`Ke%ZOkgk|$v7ihe(D4SSZ{nvAPSte=v!A51l? z_c+B)WE(}BzFwKdc?gr8UBC=4ku@o zi6wT}LC|IfQv>W!u@LyhrSG`Xhdki>K5u;r;^@+AEki=B(T(5k=_TbtvF{&j#F^jmxb_cb9sovxPZl;`(~xYin@z$5&0nAeDH*^u=MpiJer;Eh+b zR6I9B60jQ3iMSU5foKryBzW4QRM7fkbruengtoKJBq_V{8#u|Ys`VC3x@vw$trDDo z`f%|^K)+fz)07my`9C$jzZ%f3s$#4t%dt^N3AGRRBKRIpo^y{-9*}%$jcC4J5DaYz zZ#>;T-}m!gAzQq#VQf8f`X_2VQbQ;?c1K13>hr1^eoWkRL36bp5R}2#xZ5a1#R%w5 zsA{+v;F<=!VU+M2QIprIHcAC!gIDt{l-Rg8vL}50KNnturZQ})jXcWsFH#nQ9QY(> z?F`bywa+(_AyfXo^K3~^wiL|uH>rDZZ!ZBd^598JW47x$f86gxEUE-)({Y4zE& z&gWsQ_IKwFF+-d~IUIU1bPG|o-|VC>Y^xj~44Hea#duP9BRsLyUQVgmN#@b1*4A>N z)8=ND#ZsmIw}HxwD@U)g_RP`}x0{#PN~f*57iM6|MdFm67yMjb7;@mvC~R|k zxzdUoVMS8@^V4S+CX!J3%f;nAWUC_`i(jm@!R^A%1?$Fgz)5M~5ulT@@s1H2q+KB! zZXW%{peE9Y6iO!jilBFp&{oTy6p@6_odiB}l=?LeG*_xMntgQy-^GM5Q~yIZy>*!z zCF5&e+qG5BL{~ramRG*~=1vNS{5AXPApTWhwJo{#;?`f37)5m_;O5T*jnt!%2_K*m z+mmm$&t==`DW0F6fL#WWHJ!V<@ssVk9cRZz1sJgx+sgA=g_))gEYWcz;=tM8zIyQk zbO0@fZw}LCMl5b@8^N+>b zJCc+>(Vs6y;Wgf7B2ohY;7oU3fO zrGV`wO3n+5v=)Zcj{2rEW8BoIi9~vN;}~y^XymVO?`~&-*hK%$>ZW+@Nh*w7p&|7z zI*lZ-^nBJb?TSiAPl-T0JGyh%u{=xvGJ;yZa`ImpFTh0GshH;tr&mZ%SJ1{z4KvmD z3>K3mRg<*DGM$yiBxQX=p{RP87ar!S>wSwgOaHxW{{6$wxKV9fy-+gt&A}))ea~m6 zM>^I$3{zZ)7cgsoG*F?8ec;!lWeLnF7{i}slo$PnO*)>;Dd2$)n}+B(YZaEhUThnE zzn3gO`iH$!`-u_fSYhi#Ob_Z8s?zzyDO0=0E2uTw8=xJGx7AIpo`B6*vZE77eZ&^^x)u+ z?_D?@FB%IS?_#e=zAu)|Srm&bb6da|@9ncVN+&Oag|vEVCGD=Kn3PJn-5!kkmnOIFsx~d&VsDJo<6?R1vBjgMoZmb zLmzlU?C)wtCMKP6(zolCwG@+GJ7EbCcbv|<(f*4!+p<4O2A>GUv%z45$uern@xvy? z^S(Sg$l;RR7%gcKxbYUWHuXHLCGMz^=vg-m@aEURO?eXSEro2_)pJfq-X)n5_p1X6 z*6rq%XSjZZXrgVZYwKHV+9}0#yIw@x{1fjRohgk+?igjw0uhn>y#y)izP|m)ha5>8 zYPeK;=Qm2e*eJ_YyQ15#yt?mExf23$<2zJzJwJ*84Bq>bm)4d7Hf?Wje;sL;3q$Lp zPI=^*4x~E1+m=Yj0fj=g@^)VJF@lSy@wPqiyOVLwNjl2+yt)U`VWboF zsj4QK9@|z>TY6H|Ek4q5;uBiFb|{Q^g9%yltpabTTE_5x zJxSdnm1cK?X0IRrBrD;aQD*@gmDE63Kj2JNK~32P+~@-m?nC+A*>k*sY=VNa7k~GI zT0FOIeU&w9_gWw|*2v<=>%?hfz>MBEc0m{H5kmrs(}!s(?_O|KlR>IpE$;sbE^t6) z7s1FG%G*X^V_Px9NAAwFzs|pL{cSGsNq8A(e4QPOUOFdRdp%1e%1ci>a&Iglwao? z&ywomoqu$`e~k|HJfv0Ws+q*beY5>PZ{BSqexq5fpK@^tPU2>-l%XOvksen@P-Pf2!|0%(T<} z*}FxgnM0qHN$5m(z(~Rh>pLj`(#Wo~6fE&mZZzphvO@-(KhOIVV^@sOT|L54jBx5^ zGyBn6j7}i0UE&xg{it^{wl|hcIc6lDpj{ys2qlj7JWF&X5*^$B_5_}in4#xRy~M+{ zD+;}XUN~P|WkWlcX*%ztGf=At%2o))_Rl3niVz+U?0gwGSD`OySLf`z1-|e_BgXHa z2A)*#5%SP6F&Nd8_B}Qz?};}>gXYSqT;45}UWA0k- zi+~s?>z{c%YQ#QkO+&ZcWFo)An=z~WVxPTUAZ={2a%M^kVaV(1_A91L)#`}9siD!O zp1^DDGvf!hL39|1j;|Nv`g=iwO1m}pd&}~hwI=>Q0rXT&oJ}P}jastmUBE4G?bj@u z=CC~G*$+bl_Ig3*uKK*U=^(4@K!$`TF%DvQidN)kI3K@^Z$L-%3iHMnu7V&kN#${P z!f+nY>{NNFwMIm*+kxM&7tWTxgQnb!uKn7qFT~lrp2U$*rVe3A%t1VxJGy+II~~=uRF~XeLqY;}JLz%EG31l+K474@iwQzp^++8GgO}Rd-m%xGx}4=-2?I6c)o{8ONDfC!mYO&@Vd7&am0-xM@sD7M>pq zK3(re#pSdO;u`(LP;ILW46~YNNbff-L&%z!Su@OULtHN8!R2Hpbsy}9sruDjpwT6xpxc7^h3G+C2%<3;6AZSxy! znvJZ3NIDnxq;4Yv9hC2Zzl4uQupOY%!3V}G$Ma%pun>M@@ zn;Dqg&HS^>n~~G^g4Ied_2Y0v$;j=8+G*nWNEMT^ZoQCo5N-mu^TyjJ*D>~DYwInQCUmA4OYc^VPc_TbqRJjUDf@Xay0oxZ(HU-ArNw1!Jol8IUxVb$#z@@>gxT$baYOu0(|n+|LAfroOn z$2xbt{h}I%Rn__W3vpazPej>m@2>eN^jX8|2B~4)YiL9m8kz71y z+Hy-5fn?JMNm~>?W`4ona9G}Tccv@v18d`id89nStn+RpLM@ST-|vWMl^(Vi*Jjej zwgLn`d~*b*xS38d3ec&W|6HQyYs)c#9Tko!f7e564Mk3WktL@#owz)LbCcvuSnqyh zxto;~W*K`GhvKSkC5zQvlMt6lW4x28j-0qfC)gflBY!sx6O=qdnqd>|fpN%Esv_+KxAm z9z@L{V>nXu?cF#^iel|%3yYFRQQI_zs~$2Ez-i8frQ8(fcIKjQQ9>JC)$?%FUChnk zKX^x3Q~(!OzIJ)%DCodk*nZV@KCF}O8X7ODe4HLq#!KVvj_x@t4-*t6e@*VjSJg=W zME(@58tA`Bl=c(r{&6Kf9bk)mu(<{_9}I{0A|9=kYz7#;%{QmhC|$kcEEC~EaDl}Q-8Xfj%WNv^%eI;tSaIYVcv$+ax?L!T z){OsY*mAETaB%qungm4OOzx!ap2U8N7Llcit`g)aRkN)z|H|3?_famqIkD-9NT%N2 zT#t8`{@#4$eFgWq`OQCz1m%|T3^Jj&-METQ(Os&joVlRXLXE*f{=sc$U+_2%95;x- zCQ?uO)F4Ma4GO{BuBs2`b>}C3sX??M2~|V1 z$mAwH9LLQ?wWS9t9-yr~N87zuxD)!Ut5rrbVaQpk);e!+4^mr>E#fiBc8fnD-gR^w zHyMhz9;Vsf-sgfI$5iRr+^r_eX(Qdad2E_DAB)UtjdA0vPrgQ1-myV5j65jkdN3xS#CF8U!Xcdjoujci#aE;wkVbPZJHXAT^{DC537QyE| zQu7`G!Y8S0-y=423bE%YMB#L6gx%>ykTnm~cCqJ<%_EUP8zy&TNc+=z@?Y@0Hb5>6=k4n5d zJa=b4x}@~gNfi{W`DJqo6CaO>cMtR8Dhq7g_h;zZ%4l?Qq~j@7euou`?3Xqx)U-2O z*PkexIMofk&+RG*!uKxp`-Z(w6zOai6iqabUfZM?lWPHYGWSN;cih~!Wy>PgCs-2X*LpNe@HJ{#ma2PQnbE@bMLp`BA9jf4Q}!m^lRn-5m3!)KS+MTwpBPVYMq(*=NMV_l3Xf=N@tNzQipbm<*7V^L zFFw$&(Io3x{Somp1V6_gMRI&g$XozoSGUZONNigu(D&$joi+_|^csNtwL46u&v?|( z`Ervq39Oro`h-DS`I4L9%wPTa8e!ICRoC2y>9K;0gP$~=Zy9oz%)RW9gwMY2bke_L zw*kSg&*+9dr-6CSZF7X9cSJh219hW7UF~K*!tXF5qbE>Rtpku+5Dd1&+1hZV%k2Lm zqmV*jN$6-GZoV9~eOG2>$42U>Y{5_p<#waGS2aTGHzsxQWuQE)7p`e<`+_U8lHb*E zj`Q8Dl%CoZ5m)49%)ST*-o~GqZq}AeMJ_9BTZ@?0j>fE=<2ky>ZC>Q_7mIsegvdHj zGg2DXuLP16@oq#iD)?|63R#;%jkK9gi1QqA?dojXGG{+{e@7|cBC-*+wpb)_&zb3{ zLys0P+l+MS*!~FqfwA>T0ApYbevLGb_6U5_PP?M+Z33-QCN1W^_Ia_gfD|XWvcNpw z6Rf3>z`bhzSpp?Wf9`Ag9gXDMkmzmf#XYTz@7%TEUD+Re{+#jqjpTgb7kl^dBqLHY z@rv5^0@4@xmjO}-Q!@8vG7mHqOAsUbn-&++VebPX9_U)oWcpyPYuUKOR{8H1kJVzZ zNM_$#_GSXsLo#}3MDgy+KtJ+MHoEspFw+*5({w$B-~h!1>Q?-6Wg#i{%`cb^zch|y zY|v0NAI>ggjdHQHch@<}+t1)r4!mt(b|eTOaTx_+<~seb0yJ;{;@qf%e7}8Ye4OuU z1>L?FR@R6)tFal$5WG~ZWc`q)Eh~5-8%G2Cf#)sjS2oEudEK!bl${(+OJX4=ZPI6P z97iv+1r{UkAU9PMu7k~NK?E2zkPqD%d)q;;qoPtL0JEM%`cV85fmq!htES_!DhNC( zOz1(1A$iS0C|Tt5`^|P#rp$I&O|%o%B;Dk6=fI`IWn2g4@Aiv`;<@s55~_Kl2y+$) zLfwku|2PX}o^km*^HL`ivSz{%^3EQb##=elqsO1{0XFEp`bbwlJYDt$Jglg-y0omV{1^fY7M?l_B+WEDuChYL>u9RXouOT^ z;=4eZ-Y{HLJ^wPW9A>I8#43g1o1|W{>2{PbR}Hh~pd*Fvo3mrJE8bTFv(2X}sBX?b zZ3HC5$|y4-^5@yG6OEX2yE0w9xQ7|44?nlleQ<+az8BD26{aifc`KCALL_xPvec;U z6AIWTw$4ORmL%go*Sq>>k3fF)LC{`3#lx;JmA@cSbu)HFJ?YHLz?1`713=U_5LMB+ zLF&aCf(%uP>MvXOvq@X|&(%82g8I)V;&Z#Y;j6y&lubV-p}fO1)J^#H;ymIXnDG@~ z9UDzOA1RJ=;~ugsRjT&!(&ZrO#=UMtv816}Xb)Lt(#%s1Pk!!U5_;c{NFp1K;nb~z zgDYX1MT*|fP*$`tBLlu@WH_vBWie(is!@G~&|0ApfYgc#AS*#SRZlSiJ$oJ}GEXe< zkKk%1QBE1lkHtiun=T`@UYgHlu0OFWvj=+i`8;A3?KWPO zdtf=mF7%ZNH{DH}QHO1X>G~=!^uRiSGF{^}%&+WRh=nWHEOY2&jzHNCDL@zQvq}q0 z*e4<1H{)`DjD2$@s9F=ndkiPH#8$Iu%BWm-MD$*Yo+H5?rB?v=wbNt}IRW4O4V^@o zmkrSnZSY#wj~xAOb%}P1d^mlwK)D5uQG-}Rvq0d{##R$`gdrD;iEN8T^1D^x4B{W4 z%zBd~meq`GM&|sP&c~xpmN2nRDvgYQMt+7y`qvB4()1r~wr`~9<{ae(bNF55p$0ro ziQ&e?4xWoeq+Z#^!*QzAsh0Iv^{TgQdZd$ni&jMb9R5Y!+}$y?b|9C2TG-&zEB2Yq zm`x(1ZaW}1KjPg36*hHXuAN?8VSG4ncGJAhz31Lx-1drwyWK4sdg!{<*vW-#i1r_ya{#MG8WkPw${n#|HarSNyE(EFe3KtvD)_59AnaU}vM3 ze#(kyJ@&%)J-h;(nU9#%b)6`ut=!r!=+-5Jw&V|2ha(mi$)^RfTf6DQHzA3H8E8L( z>yh2?#lwVIp!)6&_W7{;|1_SP|GgWBIy4-+9EC*ui*PZ8CZ}TbZ6(S}LgIQW-)nPwo-m*j2y|qo0QioJOkOPU1&g1wKi4 zyT&C)o@-}I%ztyPK)EI7N;Ad)BOQuMhz$4N1Vz1?WA=tGQxZ{6I@-`x+hNZ#K}kDJ zq`o{iKMdHUt3sKch!2NCVM8}_(_^GQueI3+Q6dL0yf4J1)j&=b#_;Q>^^IQ6Ql?M9 zRuuzoWrA2eU+{`7{Kh|Ex_W%H-!Yr((!w}7M^JgGxbM)_5qgTR%Mf4<>bPgKiZ*_I z|I@$CSwWVU)%AS#i9u^-`R5z`xW~H|A2cCThU%K~jadbwWiUO_U=I$poZKH1cgF;mG?-78}ian+PL zl~>%iM~uf6)_DhtJ0-HkT=L1dqmL5Vprw}R(>_v*#nuRpnysM*`mA@ng@LdpVi{hl zoW~2{9~e%=_i9T{#p?xBjD?LJ&yX<_o|vS4C(9R1b@%Z*w0H-63Fwf9Et zHV3kx$688EGO{ick4%oIad(m2cReo(GnT&@OPK^~QZ*(v*FcBa13rhr_?zvR#eHCr z7QFpV@<1Nd5)0@G6n3>x`M|x{xKV1-lNlaWo6`kvJPsDvNX1B<} zSZ>`jXu~*bve4lmxBExp2VrI^M$#_LJ<@PXGmgTkn#2z~mb}-|uYQivic9u9&h;0k zQkXZHBGNQ@sI{*$eK-I{GdnpyxwMcMB-g=J)ng5K@}MD4oS|H)2Nx*H(isd01N7cL znq#Ka;1EcWUv(A;Yn-;*3#3@5;AY@lxdJnowyG9ZBr6j!r`qreas8;-9GIr-HP$&& zrAZ|k-zv3^#a~+Z+IbM73Q<1i`XZKJH<~hbI=W@OWp{Qa6DFyl&IV&QCuu!g_3%0V zXip~U>FQPzKS+ym|3&K>CFCP}3@VT)@DJhRGfVDk&rzmFC3GU8GJa0Uj{^rx|Ggb5 zzJ8nIqc zCMcQdh#s-lr*2HzVIv~J`qpuWsamX~^<9nzY!GEuelxlJz~dm@b@ka%DE4+)RAL#+M?0W zrnn(ILF9K3H(0DY5vwix6D#u%0k{C8LSMptGk&Bp;~tcU%m zTP@q>t{@bmih~cnqA>>M*{K&1-U4R*Zqa9Bd43gq?gsX2Gj!EN$bpTr=YE|v z9!cVD*4b28Bi(oOtrw|(RJWgUYZJ)DaaZ;0%u-nPVZvVBEjkWr$6*hRrl*J-p#91W zrMV}C3QDg3%8orY2{T+lCAuMeH<_TYRQxSo<_WK^- z7$q(yk9AH*Pzk|vb*pRz(ZwL5s($uJ;oLF1a(;f!A<#K!l|80w@mg3rsS)={?Pf&% z3bT&1vp97e)_70GmRg#rrh7tWK~g2>;W)*zO|&}Q2Kkw~vB~FMNmh72c_cWo7;tp` zN#4*ZXH%+ZfGcwt!M#qVt9qJ737Sa*oukGJu_17{7W%@PiOBqmHl8$5aN<|4e;jzU z3lRrxF!RR>JHa_4Zp$xG{)Abd3rV!l#%G9q!FN7#n;1#uayt4YjP4}k_#p`fws_*` zYKVYmIPPKX7(wMbn1H&~_WM*Suy@0=bM0D@;~M9$EQN(g^ys)FHL|$y5eITAo{XeD znIam?d-GnO*&&0s8M?|CCTWkNmE&zM4a?Jmw?Bbj9Ynp1sYe1w6(M!*@pJ;z;bV|8Kf=LG`i8n$#Q=0vmq92M>Q<+KU4EDr zudGHwKS*F0o$!)-Qn>^yk}q!{=MRGrt!ng*XfMz;cx^yc4XUXtnMC>LCi@BDb>=ky z^!qs8wr)k}078kL`3c@^iR&HKnP#Bp-t?Zwk6Gg**2xOkq#D8)xaq?*4Of3}Yx;0GgLQrI(W;NAe|xRSzOOjTWUFgHD~Am!@;IcxHLoafrG2;=WlBg9uljNA7AY| z-qE;1;u`sPY}~q$`FmnAM6OdDnczyRaTPkup_DBRL7qwd7)f1&!7Hb;&=7t$b5li4tM!NdChS{4eyj^%^M7X!_GzG$g#1nEJRUaFlpBRGeW(GP=2z{B zJm(XP6agUe$jp^vHL5^}f59+2aH9k+IZ(mlMj6L!?2a%~*S;d_+y!yFJ4d8;Ah z(Fb*x$AgT>p4W@`07973pPdGQzW?~W_AeX;D)yi_q`uQGdAY4x5j6%_&_M3)l>DO_ z`z7Q$5{1(`^{RB?ij_bzIZZ@GA)#@aYfS2UtC*YArFN;8Z5(#J@h_0JA3-+aF-11A zI6fh}aAmhDiiIzPzJB@Z^`bEvCMl~Q9RD<41v@mIT;G?VBAWl9*JE`o6wFY3S<@sq-Vlh{x-P2k%?JN;Mo*Y-N-)n<<;_+wiT-Z? z5;BRqHHbp1fA?=jW=#H%*}yknUyyQUs>Ycwrn5)LJ&JNO*BIMxa6<_G8U-LkWuW{E zMAFY%cLo-7n9xo;L(`Na}wTtSQPD`9khaeMp_l*G zibvR7Wr#A8djqxlvlgy{;_rQkuA25gz1{zdC}w=;_w8eVNi>*rLjd60^@9JaoUdm> zAprL^ieqt7P3`}cn*YL+Ry3K^^uXZ&esDqtBb%7V?~S29L+_mTA&h1tT&o+qa;Di& z#06F6bfrwmSHb_7A&*pLNZgC~j|wGd{`qgm`yUTyWsm%q7nuYHLEZb%f4jtYygR>? zIrG`U`mU*GHT2Z%(t`9+0qD~ZUi??5!gmmNe~c=2XjsW)V#0e)jr`BhWH|_sh8W(_ zRK?-u|I?QF1z4DQ{}F`HgtRaI%eBhA=vmUOfji>(^jX25h`?2BdxN>Cr19R$1prc` z1s11eNcPLCOT)joQCQ#Nco}+@3|A9ID;{Sy? zRQmCs%F}qR+0T=jLdITmbzR`awub(lM#6gXPS_Y-nN{y1CBt-70O-g86|=7_-ORsw zA@R>^frSNyB{!y&@xPKisA15uzouOWR6F4N6yoEQmf#u&R)q@GsWGgEDJrlF0IS)GL+p?S5YW*;YBWfd00oSt zd{8&Lno$7QooLcwu#h4#rnufs+{%&Z|F@*0@j|okfDIhu*+qcz5=`uKcbHo+Ur#0e zhZ2y6d@{P?r!;@2Nw_d-TqHPsC0L^({%}UnK9pDK&zkRv(hmkibD;*_-R}7=2hy7U ztizN*Pt}b{rKOPL|1-hDmj}Q1;R{=e+cqIX-f?f ztKAdy;SN&O2-?>UA-^0#h>fxQ;U$#Y__hOocEI91m^Mc_Et;l28c4x-qHaq*(_j;U zO(N=6Iqw;4wU#=90ReHmK;EQ5Ap_zqun=p|g6NmRHKFJeF%<2{5A^5r}fn=`QQ;M+v}&-#K(-DwPS0NAD@?T<*!!q4!)h_#)Z ze_@<}X%j4OMbneB@~eEzTQL>0w>9rbKb(pT=O0?6SGT(UZEgr8v!N>NB@^zq$*(LO z$h20vnBlbKshA6PwsY;`2P~_j7V6bgcI+my+_$uNuWl>SP(;k=1yxomxosWfNPir+ zp}8h@UmP2Wnu>FM1=1FZ)p0Qy-CkI8wk3+|4h&Kpbz?nqT=Z!j{w!t8^7=rzA7W<@ zTMoholCHf}5THR_7OTxrSDNtsSgPT#=Mzw{{*zJhRrdS<*!^2G-mNk_kkTZS|;fy%3vgRp%^kl}%KJ#}NB zeVM(KO#5h5oX?)`m6toJ3z5AUH=lc2>3JJ#128?@fItq=+O@jX-tAwV)`aASC1zkj zItXQqJq{L%4qHMvOiOQM6C$P#t(1*~IBS%{cYnn9s`Z`jmd{RH*anSStXgXG0 z&SP2%jq_*sf~e8h54Q}JiO@j)91#A@H;AxH5K&P)L_7-d=R+ei^7n8Zrl76-;d?;H zirg?>KY)4W1wl8Y9ic|3aY#3hOWW}IHBZMy-rua zq5^_--_2G|JLvngQ!5gBPh)Ps2kIz2{bk1}2r0f}!Y`7^LPD?-Xm`h`_$GVa|7j0_ z(n~|Y)*~|maMr4$Nh$UeK3s&gN5_B+Ug`ea7RYDKoYE5}7j{m>3=D0-SugVv1j$ER zQPDm@y_csJxtCQ)v~llPX@5Urv$8nIhFpv%vegP!Oq zoEc(&fE!_BK?W8H zK_48k8=_BPs)?d2q^0W9^f4#J>#y;48^zP7L|F5Ks>?kHGLC!5xWF>yFRQDy;X3Z5 zb|Ut?2UevTJxcEPW7+eI^K!%PiI|Zv2X&iOwsJA);XHEmKw(Y4&{7c-^3+{N{m5c9 zih8yK)38x3#p=M0$Z!)ynLg{N!(+Y^vT#Sz=Q?}78FW+mL<3MaBV$zBt_e#WulfjL zQ$FcmtzXHHD8mX-({rLlVp%{Qolrf`*hVtqGudh`&iW90mY2?kx`F!aUdurydI}S< z%DcAFa4ci?`;r}AL|Kra>MtLbQZIwKKG1^+>(oymolnuVm{Bi|j-o8+g^!I<_9gbo z5M4>C2XE*_;y>$!vRkw7ZUBY(33$n#53!Ow>d;UTo4UH6vHZ+#tW7uCF@qyCW5oNC zb9B2(g6Jc@q}w8zYVbxMq}j{@|A2s)$Z$u1T3HIuWx@2mI?BhpRo`3z4s#MHZPl(@ zMW`g||8Vu)fmFT!Sk7MWkg6Rt`XjuCCVsDN?AAB$;!$+QY1=7MrJ7*G8$Im zmRW=-DWi+5WE3GIzt=hUUVXm5^XI+iJkM*d=XqX_)eMpLTw=RAsl83n070Sq3gfv3 zQ92E_$zXqUuku9X{z$T1<(|XUoyVOJ-(!(!BE*q%7ch$R{APaltSX=jnS)>^Zl`Jm z?d29IVhVp}O3|s-Aiy_4@)URzOAuQttP;;M+sj=(uSGrqJr$gtYJr|Slgts7>lWyU zl}}U}9IaTsy6@Z}*kiSe#Mb(=7n7tk{h`*f z>ur8T!l)Q&{NRC8;JXqrjkvnF0n1djpn!dhUucKwTiVM#AZ}_um;%*!1tiorZFeJ# z$-5c(PZ)~y$C#8$+kpd{&T&rB*{KoiASWU`&0^xEoLKg;NWF_Hs|$Tj5o*dev@yTB z|90Et$(7LI9n=rLgPazX?L5W8Uj-Yo1`7Zfi*+VE>`I9_|2Z^U@4PORs z*}#5zEPeI6q!dvjHjX9J*^eHE&0Bw*2%jHVnfgG|LXLf^7hA1g_{ju|ntfGr+x&H( zdmKn=t5GhbZp&8oAJ2g8d1D{XG4ZOqyxtM9+HZjU`}Y?cJ%F`Wka|_vg`FN{c(GT} zc`8l_&CLpvJW|X2iblE%);vK?UTI})EzR#U9N(fdCxz=P_C)S{G?RCb4K+C+usJdC zMHvmb)W5*8;?!HlD~qO@E{p=KUEMpAOij`46^|yVLb5?yV_uLbbYPy<>RoQ z6_O=HYaB$M@kqmYeRSC-?$`#J-=Dv-u*G!D+>!a8@y+yeIBg)EtL=xwCaH&48XSY8 z%lJcEfo*YqWrOdMEyPHH6#hHRv2kORj5#PXK5_?ahke~E+rb<+e$7{X9@^HM*w)+; zc_pOlmoAG28C7|J$JKE~v!j1V?d7@P?M?m<;V%Kxih4(s6ipKvljZd#kg z0LK328p$P)tf={(;Y5uVVfM;}m1<`_sqJbY3zb9^B&W2GWvh0<=5qQ~F@Z}^$VDT>7I<=Ko$XATEv+XI}6c7MlS2f2`qh ziCK=GeW)fMp@hMg#@Y#WgOnK%5w?mMun*|F7#4_1|O zA)Rr)mgCFRb*cllX`Z^U2JAIjap8jRudT}wt3KJUOrXe#_6|tWE&aFKep+NJ z?5BT8tbzY-0wE#|nIF=2W31~c!#|_A*vv>SLq7cG2kH;-+*USus9Kkq;;_A0l-a~LT!=dSC8cb+)vGXdyo9b)RNk`$|PrQJ92F@|Y3o~wi zttSk*5l82jnp6gFy4%306TfGTQgsU)){9;Tb~5nl z_%93XO>p$V&P*~g=Q5M~X+r&9w>!bPbV5iHJM2oA*G8s2o6&+ZT7_>zG)@vx;nd-}=yeW2$1n^uIC%U&2M|0#kJ7~P0j|S(L4hx;9*e4L!ZCmLkAF}l z0`yY=fm1D6boYlq|5LE3SBE4jI9G?k%KcSII8nuzH|-G!>zWyH0&xUXps`|cTj{_6 zM6+w&!qF;CXBV4;zkBY(;fz97a;nvcwdDH%F}{-ydNuvwx9^I%TGP z5Iga>vj1NCJ2-F_0%xWgQIii$5;w+v-OAn=Gc-I0C)T5lVW#Ot0&l^P(dAJYhhr3b zeY-hPY2Sy;qx@(UOU9LB3}~?4Z{lJjIkpfb*`h6L9|L!Eq(+hsNBQT(>uY@HM(3S%bjR%tARI=uv zHD2n4|B%L=M?ml*nN*>h=?R?sUVQ`(y1td9pM*hvgaEAHqFGU<6tS;nJ0&PyEe z+Kx0S=cZ6{-HlKP)+0Hy7dR>>VMM!1m=-M zD64-aGJQGdH1feW67|1GFXwUs6qbh)T zOrUxy#y8GX8VfiO<0Gy&5=;Q6)u~qqY0I_mGN+}vC?j8<9O{L`>vKj)al7P~!vPL$lriPiEue0($Nz$=Q;IINYlkx#1x>z|N zO8t|pfSjAZff|w8iz+xhkuPJXeHk;O2#lRAhMh6q7>)h^2ZSOsa26WBtU0pMAm%L- zKt5qEIDxwbql+9ItXy*l`UR99s%Hx8rGAX%PfU8DvsO0N=VO`HP3EBzz8m0=e%nL% zy5+clGpy^|tV1S_TT4cYSuuycR4%TJ6vJ6O?9B1C92800Vynp{86QvCdSY6bF&lQ8 z`3|^O6a5k7Sx@sH=C=e;Z*y(mS~x{-4yHj7n*jV7()hY~w}dv0`IdziS0gg;y6doXdy15Lyyhg84W#2&mu>+Pf+0)Nf33JC5OJtq)aRIQ%gJju<}6 z-57Oh3sX~QcB>268!Hv+Yazk0gLCj;YM*@zVF^X;Zzv9mkPDijX~7;$WbPeexrW%x z-FH4lr=LXJ>>~`&b-1J<^0daDM*-6y=V5Sz+*uwN;#|0L?)MzOTdo zJpilHBGX+=vq^IWOQExiXG=2_mG=DkMnbGhSl*u!(C;+n@r{ILD{`L#J<#ti(a?g! zkByyubGpnp7U_EjYL?B@FS1D)NU|14;zkb)VWgl%!`Ig{x2FTNbm0EB(Y?12PO%-{8=d^g^dF*^lD9r%Obt`mnIeRIBqg!&v4 zz#6AmL-UWc_1YXv#Nu!5mGdzOgK}?*8gMJ>c&~_z>`V5{XasjSh2fqaH_ofaBK;~j`89IWfTjsX2x~Mm zLJyH({?|MXN;h)M_AS)xi-5q0rx$AvDs>z9*-v;Z2?ojsn!_AsQ9gZQuAHAaLdH>y zZ4c{b_YXjO5+5wy3|VB1bh)YvyK{~)+X$HSmylRGb7>2+a7QIVRT#8=BHIf^#dg_f zz}2B1u2f=-P0uR@SI4U*NwO%I$4n$>zw1)3Z)_uNQOXb81HD*Lm)5#eQ9g}zYvH)M!tox6q(ml z^8KLei}YjA-6$ICj&r_1P5#l$KJ0jPe!ti)<}8$K5RVBuUw`jD;tZ9(*H#O{(7kf3 zoHQKT(<0uv(P$~m5C>A&H+*6`aqmv5CV~GjLmsJMe z&ax_*!B0U(Q`D{-!-0;$HjpoG`hMp4G-oi2X&a!m5*(8+{UZsD!WMfC>MUH9G#tVL z9q%Xuyt|WmQe4Q43w9d+9EcvPJkhBB39O|N6g#BY{UEYk0!*Hutz?#8QBiQC55+98 zW?dm1SQjjD%+_;RvTVKgZwM+ip9md?{qm$&8(fira!6htvyzvWKlgk=CP2Vp=UVd% zCVzE!Q&>V!Xp5z$d9VhZatw^#aVqSgBTaTgvMyTkKPH{-m<_%tGns<~J$mKkkPJuA z3jERWgxLFgbr{xiEgHQItv@O`X&<1yB;#?8YcBMYVn;OptGaOEMj-sJbY-T}Jxk2S z2AP0>I?{+GNF7+$IZ;1G%dsX{xbg0034sn59y96#DWApR9nJ27D0~0f4{>EJQ_@d0 z{PsX*Vh!+)WyTO{2(?0};r)cA?rBBi;(0m02ofH8ZOTiEu!`UuF0CRi`VL(j*R9sG;twyFyW zZ)lBJ-?Vv&N8ho)UGp=@)6h8cUB~lKJuZbwi0qzKW6&u;^UET&KL2rU#YpL}fnX2K z9jP0FY$?2rp#$jq#5>;84EmyJ$-F`L zpam#u%vJ?GB3i*LH$FZ0FPuiv|42ldX(!%6G5j4wtur2L3d(Aj?71}H%jWA1Nrk_JyH4toT-7$J3yb(Xc>2)8AE=W|% zf2lMz5ZoXQ6_)Sof_I*QW)l~+)*@>Bb`EGQxzUDA6haTYx2KNP7A6g1``{7{V&6q| zUTCbM3vsO85}N5jFcvJ8jZMQMVN6_xAoWUTv`Y07hxv4@MO84#3yo5GOTm$7APP^p>TyIz~P1xK7z$I?QcvHod9tK7Ywx_l%aAkfQHrl4viF> za1g;8asLx@D@|fjUn@!71l@11dO};x#zbZuL=%RLs3ihUA`wqizc~g*awT{H(tukf zW;^kRq6-$7@(ij)K_uhL(D2pSUjeEQlpD>3^NCE6p(TMHXJgKS+!}&+$hEnFTzYNJ z9D?fD2}d9~e7NHU*gZ3j!KxYi5g~(TEHvYTu$t_F`8y!{>Qxs8brhl@<2&|2zUIdS zl>1Nu7-+^5AL_&qjJm?)H(0$z0KMS=-i}4}6PT#$Lrn01@n>TRD`YV~9J#X%S8sk$ z@2yw|-lRCWLO|@W$rSm`UbYNa(39Vb>~LRG5D?A!6N=XmH-xT1fBNpmS~mAoa_iJs zX)~#l(G5bW?|KERGCY7Tt5Nmx^+6!>4p2T~sUSTP0w1jLH03y`ZXg@qwnMCtcUK!o z=M^nU>RYfXY@F-dGG#{|uo%1^82nJo7LIO7hG43TwXBvNnq&Jdz)2-#vzCG*em(yC z6ndma{*c5wYHtGzOxFF*vau8uh&a5E7sk@iH2wj!>P90Mld$j~48odKV(uE+ZJ%ta zmv4fgTtD}J3^3)6Dv&2~Xc+72ZF3(P(>}|LM^FWT(C-(lc~t$DBbXAO?QNvi?N z4Qb9#J5E&l4`n6uqe2#EuYgVCRip?(_x9I+D!oB)1BJf_oSjo=&L7@|v8Qir{ZlL; z-Hd2x4ofmQ7u*N^<9UAp+#vW#WHC0%3Nbhofj?Dr_|N_k$Yc#I31o8Z?`4z9S{l%k zvf=vQ^~~BG`4*TOjVt95b=QF|&yeW?PR7?%FVNnxCBSc0fa@I=-Q+`g6MxA2Aq3li z5yh546llkF_<`-SOxYKhPlyEXhd;DN%ucemR#ISH->Zv_?}=_woR%7M1`-tR+~-?C zwAo9HXu{yNb^+POoR%^^rU*L0A{YW8DA(%v_3WM}GX1+!=+Bj3uIVK9vf!b}g`6e5bgSAv&-dWGIotXJse%3i9qm$Kd5*ic%mS`IP^d4Ki}6&&}>jnKS35y zD=?s90N0zOI_SUt)VvJk{atQEM&&$MzDCy>hUYr35szJUFailm8YCP{z}I1b@AkM^ z2iMu0?~m3;o|z|?aWV~W+9B*;Dnpml0r+7J(+Ndl+O_}h^XtT?5EyR`%xQ+kUaZxA zov)Y@v;Ek382mykZNqh_@4@~X{2^m8ibm5e4C>9WxAf_uvKtuGt5NAEGheSQ-XoF< zW-}EdU!I3R?h2UVvg`;l&3l0{3Vk2;*LyGq*!2@vM4xM$l8 zJ|WDsEdJ4q3p68^eK_-V*}_34Fn(CX39geC1s7mQ!YIy{bB;3Uc7;o5TOsbmA2bCE6VgBLeWC`i>0=3U4W#q0%LM=0Gi83@8enQ%v6n3txxvHG@|8UeUHN7?2B;(G#$6Zq2zzA@hfBfr;IDQYO-7sD*W%_RmfcQ~M0 z*z_wvpt0|Ff<-ix%rqDh5ox+7w*{Ev*t+}tDCEKTLwi~OsjUo@a%bzVC#PVF&#(*duRVmk z_>;y3_)}Jj;hEPPAVJm)r#~Jcz1sF+VbwHi4^E*`gzr1irLH0LzV`CHW=jb$bIkuC7~B`kZOp2ktWC zJ>%NJ!qE^C{>n6mpboOIUF@F^YJAV+**9Ewu(zlmC;t*=7Q%jb)MKnLCs0c^4#i z6Gu3p!t-{S7&VWle~MWY)UjNE#ar$fYqlNGeEAC4Gp{lxCJ@RoB0_2D^FJa@R~HH< z&0Jo1=?^ziSO|Y*wdHjCiIJ=kXaVuL>FVuln``lUF%yBc5DE`fL*6Xi=T`BlK9UN< zJcJ_~7AqFE^D^Ff2FoVju~;f&kUmsP=*m~Y0aGliXRQ5M z8{|&i!6NeA0LvW@0W?rO2?T_i4`!4B>Kdp%{l>ZGzHMUKnh3YKMT4lHIJ;;O8bLH* zl}{Ra<&Qzx=%P@6?`?l*brmlq#H`n%iFoA@I(<-%mKh-du<3V!g4CI>!b)U}Y0?6j z0`?uxQlU4f$lq^l6xtm0k7GQJu;=127OKZnc6oO}Y1UNT7WtdaLCb>5G~-zgVKePg z@?_ZciPt7@;afl_ynPMN02nXOwqpF<^$^U$q>EP5vLv`^@9;eD&V$%`?+J01FTxg; z&F820fEbK;a?_#8^-6mMa{k%k|6P{zCn3)eJctE-gufWDkUFA=q0-8DO~{4iCZM+|;q8PBxkq0=vAV;dvVYgZC4pnsY}3#oak>BqD}9c&H`Lhx48ZU8 zm>HTKsafv}`||zwEd;p~(`_~qD2G`}M_XA2Sd-MMmI;vyg96l$zPqr3y^ejMp>tli z;My^QaI8|AQd-2d=*gy&!D)OdLHmeL*56o7xV>bvKBaMGTo<<*&kpL6w)U?2dN0Yu zsA;Z_>C#OfUM%piS^pkm2GR|KcGF2!RuI-Y7O*$hAPrCHJPnT^3YF6S!Sos@-4*JZ zr}elI#oQRg=EN(Jlzq?VosWkKP{UhT9Fi|IX)q=til#=0Jd7aka|5ATJheW-EE}|o&-@8>qhqi4N(j>~s?X_s zoYY+d+a!cke=M*9@41hD-X|218=*(orvFK^V?LyCDtR`+0d{tcBZqM&XEgFR9+=#4xI9#Ae>jJ6-HZ9B8 zYj4U%&kCMjxWk0k*h;8;On9R!4l0E|#+{oql~yA)#%9mw*Rhve156T+u%XdkrX!iQ9KO%p-2>)c z@!J)-k|1~?2#9kiwwo2O8bOdoA8=l58CPJi# zRW#4TOgB1qcxCbm(AwM;=3=YB>+X8~_{(2b>`K{VO?qJ+{uMZBCiW=*>s!PL_MMe9 zz@D!3vrNl@8esjWj6cSYyDPM3$H&Q=ASrb`;=++2 z-WMMZdoD|Wj1`3jDtt_lfOXY&wl(V57 zJ93@=nj17<@ctM7M15fQcG$dw4}iYL%r3@mHf8fRRYlsM1h+m%avaugzT8q`oaCJ2qR$5OfG&Gy&~rfcELb?Htp2`b=)i z9Ky_yIv{f?Sx1c>fZZ`G|C}g=q-_1MP_T?KQJU4GiMnG*8lSBc+6)tA7C`cIn5EOE z*%&@xgiNm#pP7A3a`pxek>sCGcs(G@H6LM3gxfo8q8cG?x&{CeI$YKYbqTuI8fI9V zX2F~W7#&1%7KLFne2X~88pP)RdNM(gF%>F5w`Lg(b45f)Z9TwCoRu$tZ9mgx&zVwX znj#l`VT~uibK~}zS$(3K7`&xr#Ety@wu@PuiW#)TMAww>Q(IS^)^4P~xpK^B&3P#_ zw9W71i~-Y>$ruTh_sfDn%hcwdnT=4ST_~dS^!FOcAuJ(JQv;)TC>GuTbyw4ddGyWO zKAnbE`_hhEnD%PLN!aejIkSn7lnSb(ZRD+Evh`(u?7w~I-lwJ`P8YnKIRmq_Mq0jz@GuTPligiv3mG2_f^ zz#Am4=c2mRb@>wXfeY&fwWW<(BgLbdtxi!@8ugs6ZwrS&K{nytdEEza&bb+_~+{?Fb1& zcJbEp*vy8{_Lyv8w@TskEG*&TM!6R|nLe4?g1G~k4^ME}y8tu&Dz%ya*(;>;W>K5M zS@!kfM**4|sM4~wfS$2MiIJTS?ZL;2sXB`0C8;77B>pQDLw%oKQ7jaN<`! z?v0mD>ame|myJ-^X64JyNTEBR%_HNv@ZGM^1T!H^CkMRM!^1c|$-IZ!0|m}C!?r?} zW|Q2p00ZK`*PlCrBJ=1x>mi`R)HaRsH=7|dI>E){6Q{Fa^Mf;P^C)rvixup&UU7mY zRmV;jv}=L8ehrm@^~YX`=pS2fsUcuhIPu zP1hlhzTFNx8d>!ia#ot7>gXd9#Rdkt1m^yw zPMjgKdeK#CV6r@o<@kB+qXUHXAYfHzr!L3G$=rCvQt=zt%rX6?5V8w&iGSBaC&7q7fncf+&R8t)NxV$C~Qz znYVLl`S#dU83pAi+$m4-kgHCtl-Y|qCoQ#1Re%};ZNH;@(XmX!w$*vM#( z1QTML7opGPA-%i%eVZ3e2i|e61Ef&6Wea~A&GsZ@z8MaQf~)A=Myb>l?MK%cyKoVy zqN(N)i=do-J7@2ZiM?a&6hheV9CGwe>Jn7+g&@=PO_-SVaphSXu%XqmRAY>5= zn3$AwQJwVZ_~y&|(|G6T%vZecY#fisOE!e$4iMJY0fZkW#?I*p=5r{!PKc+iYC!*yiy=a z^Clb=9w3J=kmH5h1~vwyt&cP6BJ9;;fgG{h+~Vv|7gOhuK&48N);yM6=c)sP9}Eit z^SVpjthZ4)0B2!fvBz!Kql{6I7tJw${2dPEP2Pi}0~K?jm^B zmV{8j?B2xe+{O`_$|A{!L1!fL*Bc-xz<2^+R0MKan*RAM%lO*ikXc_f#x|rB1JW2Q z&Knp#dU5nNpu|xJAQn0b&_$X#hwv)`bh(yH>7sckA1@VaGtcrnjnF)Stu-KkUcgp| z*LP<5nMV|IMIN;KgD_3kPk6*5Ab=Y|0GBJUyi5MwjpO4OrI5cMUdAY3kqB56`>kYZ zlY@;}--Fh_hhYIj9)wf^7*+ucQyD9=W96L=7EFah{Ow?jOE5+%Hiir2U-nL7iTav>&wg&f$b{z4B;06QkY@Q z=HdosAWdbzrMM8k1-q$G8>lI*|4JmIe1#`n)0kO&LYITxpkNw6lgB^+UWZaDMZ7W> zLHK?4t*^FaY}?d50#c?1gJkT2K`LR8J>5HAv2NgK4L{(hy@DN?v)lJ!_&J&C4FuT% z1nJz(%CS|_SS0nY)j1R|!|6G;ON=h%$x_ANxGik}wtfY5l?J{`XC!uKf8#09mtv}t zMQTPJulXZ$j6ubgbTYcK?uA$tQ~1s{Vu}Jfi@}RA4{U?6>D*6~-bwi= z%XwTC- z0?d@F#Fj<;5vnFlMdg~AK*@WQf1K_0~gSrsrAdbbmi zTsnd=@BAi~7`Nf#!4P9H(D*qcAW3#&_eh)*E#O_x_)&4Dq4SDU#^c3W@xwkCSeIG zHXMpfcd^Y{I&ejyZ+td?pOj}AhoP;<|0?9K1-uNi!-_Te*~iukwg-v<`6LTi%>0D= zU`(psy0xITGKK65o4!wO%P*3o{wlr7Fa5-c^fCZ!VVfYq zIECf?#+F(a%>%2H5vo)~LiU<55~nA9gI>qMJad5TR&_ruoy-`JZZ2mha+QD_oYdRJ z>2>|0`k1;BKkZlti{l4B#r(P8P!06HdCzY49vSTixXM7 zYbqzeZUZ*r4A^1$ea>9M(&z?TJtqDE5F9W~I+7EWQD7TJ@B%3r%ThDmtZ3GL)X)E|~#9D9^er(NX%xBYoN1?+ObHD*CTF%AI*y6+)N@XA1|DQ1pb=v1NH zcMo9jRv7D3d?=znxF~r~L96XvL_CL0{$A15#{DEOc1};~alHn2nOIP+-A+XRc1SX+A>uKWhV^Z^D8*+KbIsi?_ZM@lpcG`&hhj z-^{DN`8e?FvtFDa%`qy=?avZo3TIL^~edadZN3LI50T zmnDZQ`VZUxvK0rqXlSCE;U`<8;t^^W+MjJgITC{|k(JiOm_ z76t-xW$h6@xGL|zT%EFwOdm_xh*`BsaMQUS83pWXF(GX`oV80q$7#`(o4vo;mII~a zv!2!;&Pfref*@4Suj(3YDhP<=n22TBA>$opeY-F=J6r_=S_P&f(RuY*apFL-9;jd< zLl(k8y%-EqvC9ZzbBMaAp6k)E0<1HyCXY3pT2NLEswd6_SGslDBGJ7yD zjj7t61kWH7f(pcCt?_Dg2l6(4*rDwu|C-~jtB*r*a!eb6dbYlpkcn7AaO}_=x`a9;+9Q!Jz#d+!?)XKPu>qap1xla0b=)y`Bkdq) zk7e^s2U!E!!3zzqYO=aAUNZ3x`UuVfvovk4(ri>AApJQA&GLu#a!^FiChbmV;!Jm& z%GZVfEWbN{?Wv`??tsm~%G)RT+l&Qrx5_C~B~``>&7~M?=EU?DnSNj|Ti1YVNd|A= z(S=1X)SkYM2tzcmP3IrgUuI%dS8e`};DO0TvD(~S*B7?aulmd-(5^P>|Gan)d$FJ< zmfe`(0nuG+C?-aUMr8%Uaz~jz$VjSC#7+|E5^`FUu0=Wsxcp!ltPHX?W^9^2uL6cdDHmzl~#nOq3 z7u6d>bR&0tq0NBH4L%+6kBvW!4>^|M2EO&-&>hEqJ7fmZvDOD_E4J9pN^o1Ko9sdf z-?#rm%M!z1J(?JEtCMFU7zXBw%UflKK2H#GA9}NdLebo`h!^!NhF9i^VriXJkYV*t zBG>2G_*(h_{mq)2|Gn0OzqSgj)>XoQAg5=VQ1=bDwU$!c7Gaiv*hJw!#IEfFElO7B zCp4-;G?g0P%`-Y>@nP7CL?N;NBPewk8?oOptoU&SG65prxYl~(ryfUovXH0*dX#MX z51q5=7&=q&t9LJ*0FpMUJ{=k3D1m%fXJL8nzj-||?X{WS|6zCsNkkC1dB3_#g13owPjAQuDA?>IK!etY1I7bLc6w$cawnV2#ld75W^abP;~62>HT zJ4>;7?UP~~h~SQ0oEo!O^XDx;3_m8Lx51lv%kvyEha?w0Y3_e?*}Ov6BHk!BM)6LA zWC3>l!0n)s!QENv4urB|LBd3k>ued*VoBpTV_pq#(Y*un?odtkmgb&*Y{-%8^LDt* zl%Znrd>GF6z(Rq92ZJ_}!_^$Dt00J(a~1FQBdBMz|IIq!TE>U_Xj&n7DIF(pY-9;A ztfwHb(|)iMd2z4(@1173^|)ErzwdJ^6XSKmxqSsU$*4GlDa3o~ZcrBc>%M`lRittzbcZZT{d?aG{~BNsB$&qN61 zhqPC?6lYG&!;yK!A;EPvaU(-|!clDcFD1CB>4K9b?H7J6*L>7iz_>km33WE6?du+f zJ)qJs=1c(^q~we0bPBQDJQT?{D)KwWBqI?g<7hD+Q#L|^L4JZ4ac*)5KD)9ks;m;R zoKH+|FZCZhy;-b#{Kvi(I1T0@Oupcd3emvJo28!fZBsckay-D%k)@vW>O=cgB!5?t zpFNL#$F9=ZJNOp^ISdao(FS@#Sp_yZ%{}Cak^yBqc(=CO?WHbE_oCUS^&djoVUDG& zXdZ|PF-JYgJc??U<0II9bXoheuWJ}a5fyb!6-J2Vs44ZpigrSXGFi0Oqm@l{xM1YL zH~)3`hDNmGJob+LLCjLn;=vZBH;Oc!h@whB#-mcZXExbDYAU2=!IXj4i^!Dx)k9j? zH^Ef?q5ULUD|}+_`dQ=)^#{23<@+7K7D0OS#+pgq+WkeE`H*Et5fRdB13bd2!es2) zNUn!8x4t8P&hN`)s!~jwk1p#Af0=?8s>_gaXI!zV;h?emLUk=~Ih-rrnqI2LPjCo! z^%>Q;*{*#RPqC>_F_Y%pw60N50kH?U2!DUr^GcAy%qBJq?cT{wL)-i96$=py_*OV{ z`Cv-MpDmaMi6^Xdeh{W&8~Ysr)? zfN~}ln!dV!wRnCVky;0tj{Zg-m$ykXJ2SA+-D|*k0r(oNb?6 zd9o*62A8gbm@yBUyp4qt1{dwqZ~ z(-fLD-fU~0Hw9}+b4Ms%65QTD_Hv9V_IezQF&z2Y{o9qnE9yX#PK3o&!fX#hk&c5*)D8h_BQ3{s zxgf8@@M4PVdp3Df$Nrm598*3Fi$kp`&NR<(osERLhjLR|eF7PY1I0Rbb;}X*%{Dwe9Sgwb8I_uBzgA zq$|TkbX9|Q?_2swp$kBPfr_Ip{B1%8jqFf`bCM}|x?Rk^gULj{=8m;xQ0oid5A}@e zOK47D9_QBaTD!!fQSuhG@ZWqn&vhA+`XZL-dNT4mOxx$ciOPMqzKyu!n*toHRnZ>HeBbYq2xYJNz5)ncyizWz)0`^A ze`!FOt2Gd`JIYmE-D73WD+toCr>!aG&%XSm_EIQxkRK~gjO`>L0pL-`N|g9>*`apU z>*3vVe(DG=Uaj{>9zfwOv^5QLCKPt5xxEKxbot65rP{=c}I{ecFVfK6av`S)%NC?W;8iZQ5L~ zw5?8i^0x%7Qv4Tv)4YXB71MPdqkFv=2mwV(-FEmNb|uJA@4S+L@cv2 z+r&@V`gS;+R&6o$^N1E7Cl1_(4ow%6U=H;31F)i7*}Q^))LGeyO*4E$I%~4tOURx2 zyNX?KR>R!!q;XTUjaJ3UKUNH4L==w-joOP~w2<-(nT{A7hK*E!mu$stB@rzX$yc|` zC8&mg*zHkmH0)FUr>*#eIaso;xx8UN7_-3y2m?zXVxq}bc0IlNt|!PMQ=Ol(5}<51 zFR9YB!@cJ2sdDWTwQq*Gz;eIAz}&*-#RZaGkWhK#N}#)S>lJh(2)D%Ep6C9}njZK! z@o5=#CiXAh#F_+fAQ`<1Eumzj6N>lrk#dk z+f=#cAU~3EmciUn$fd{hUC58^#?{0-A^#0ec2yU2Cl86+EQH|k7W&i3#6A4?jUq8v z=8O#%{w_es3Iu#+>+H5v(0t?^chcfPAY2B2mOuD5qKQ!eS>Lj}csh0qmo0)cPmqT| z6NaERGsdIWu`TJBXqeaXca=1YjRoap*~L?Mv`kU#K*MGo{u^UM;74TCn@byR4Z+#?{-Qw-RiZ(u`z!%#AoTK;?o_kVa? zRv!NIscDgj&E&lFp*fMlmAE94j8th79m`Z}*pS^?e>!=o^AOlrM5|>T%)8Le_sG?y zd_bn4{B2XuRi|#v^K!TwCtrs;>WSLGL!j(Bu8-Pza#t@MLC&rSX?fb(7e(c?)q_~F zpghrcZ-m0$2&Gl{L=%>nq<0*vC9Fb_uc==)6VZ2IPA#-U4jt?gDhUsERWGTX3N`7l zzlP(KV!*zsc$O~IgdqOTkY7T{Tt)Yl2=!g}*e&V82FX`qo4ehg@Uh!N?sPU1%2ORZ z%nt$Sb@{V(e$EK}+y-r9dpGuIJB=W6A1`dJ##YARo%hHTfn&ZzrIikCQG?e9rRE)-nn#T zSUW1*{Jmwu1KmOa?1H*YeO5z;@9!+j77Ufid}+;8 z$DffVqQgMM){ITFObOnx5TH^cuae244?$FjYMzakGsVWJ6=Urr+TO zE~*h27?sUD1z~maYJaq)3zc@i9nLk&{3%*8DjI=*lm&LFALCY_AcB-BfuE6ssXhx5 zu%*fr$M6exyPj?7zS!@iK%q6r_j(Mx`5I}F+K%6NgH8<*4eySQUcXB~=C(to!li&X z#6eNA?Yuy3`qhkg6CT7}MIzq4>soY676kME2cBtxLSiu}6REFBO6@bVO?`lb4X$dOgTV%I@E#J6{MfwAa?1T>^xNwgf@V zlicP-5f6xTcH8u#{S`hL|sfo~nEDg8RNH%Q#^NpBkVmDMVD zA6X*W0Sh^_5#{5KM=F%-db<0?6dK1H*28=u$_CwLQ})|1q~h;7^6lJ|FJ{1wcwN4b z#R~=FAJ{U~3-_okVW+|^rPyXbR`jL(T;imQQt^xl-3Zx^OuiGAb&Pp$02Q8xy(GE1 zE;4(|=8_ETCPsGZ*n$~5WnD2ln~`_`r*<@CDi<84KD+X9Ahfe}#iG23W-d@0HX~?s zoKlafkkPE-Y_zpY%S_dLJ52Ep16c^ij3BkHcss9Z!e|@xe+C}_nG^aKC z^hg_yP*iKbW5^q|E5G?h|Cr(SoT3jE@XARnO+z}Rf4hS$G|Gc4XqtX_T>i_eBNQ#& z=NMv8d}ZqI9@RIvMbf{$_Q#Esm>;D=$k;(xX_I4p!XMJx1}okDBo~Yg2vV)YaFePg{1A+1quFp@t`ESDK z3BJ!X5;|Of(2oJ%xtR**h^?Ml*%YBcg47 z`NH2o4n29sblnNXwkui8PvY>?vMvKx(nKBF#$?y~hsUY3Iq3=%`Q^!rhJ^tkLd+)ntup ze27H|EH&`jAp@!qRLu-Y#_ked9yd7rBgs{LY-wt9#88vuW}N1YLSW0PV?hU;IFT&f z?_Eh(2x$voCcI&Q%eueQAb5Rox1Fct)xIpJj-8t^;cHF6)I+mE8wW5wIy(aBhY`a< z_+j&kv9kuGDDIIT;V>1?X@M3$)zx@Pl%I|5ak+;t4We6%e|W1QBwET61l!;=s`s>R zPlQfcwq5z%xaR}reo$3>=^fX~-2Wcfo|hn^u$hUAd;rWS6bk{x& zyD?h!%#BN63aujl`xGcN#TR1$_y&o5C4&h=DK-@Y_%JWX;jpW=5KeppSi8#e3;Vqf z;_uibH@`Iq(Ggs0dB)h6wPA(3XDmrxIqbd*ui_FzFj5HK%*d~v{LfPB4=+NslnX1~ zDkDIZ1OrblbyKswavts~f)9Q8#XSi&BtU{IW0 zbOOSMqa{5?_HsB}gle4WX~R$EzQG-Cf-kVI$VIS@XYT{MT$Ur$u!$20wkwIsakV%Z zQ{^AvNm_si(<1^v0(Z2!~ak>SG+ zyX^*p$QI+XhvDPIT8pP6gG$(R;W`jEeSXq9ve6%*$yo`x`+#WtgxV0*s_C|$c<^J` zE;j_48Gc~q$+);ykH(!f@D-JQOY!R{QQjI`FQz%x*(Za)O9Fk?zngev#z4UGP$=pS zYXnvm28}Q7EVhq)!OzwbM4RbQmn*1T+zn|Zz=ub>Crrqx4HAJ;u)w80^Zy~l! zbYi6;$vq!;tBUsHes7>m`MIX$UI*=1B9d!AsGb4qo8g$xU%w(`dc5wy&vTJVXK%Oj z7&jh0aSL0fqpbs*xS~zBPfG=E!~KL2AFAUNo75-0>l0eD)cMD3fLj%Q*Bf5VT5vJu zh=Vx}-umJ%Hl!ng(5~Oxz@7?N&0$!9m4fwT$ zjYh5qT<4p99|aadx)crpX@8zyY=wS%)#Ye0{;`xPs9V!=9y(k&?<$U&dvw^ytnF#9 zr_oi2b5rk*Mr2nal;vP-!mWfy8kEecRC^yS@sjQJKu?Ms&%le%KQ29hcTKEi3)qdr zAl6sDyRk>~{WJdp3HlZ&RNBO3T(Yj{hk6#f3rv7G*i0Ru1AY+XdLI)VGA1F-m{6P^ zkalc0vO5P$KYzCnJI8DeIC6O9jAKhFPIe5M2fLZIi9w|YNlUE?ELX)iN$5?<$I^?g?=9P z0YSi<)|JJ9@)utT=&as$V+qoDKBapZY;K|*sH~(NKOykP^363NF#Qhiz<2#D^1+iB zG&SK14|KygzOP{0YUG%Y1mtMSdFw4ZKWN;jdb`R+;dcSqtLr7KRpzc8clht4J^mG( zh!{niK1|vJHQ4v9bf4W$LL@;Tj4iCl8dZ}90y*7ruje8G#JwU_-jsKk0gQ(w1MRBG zE$4IAAY`8Tm54uPWn4NgH7glp0Y+ZeUDb7*e6KfrbLv=Mh{X%iw=rv>`m^ELBy1!h z)nq#EUXS=fbc+a7w%^pK;`|786rUwjQ#t#0?YKBpW#QW-PlBM*#*$XL`)nj4{>Na` z-GU9J;#OE!#3ueRbr6J?v8Ck@q^*1pABJHHbG9Y1hMdkR2v;Fevq*t z&sf#3GkM80g!bt6-L@m0cXzpE%KKGf;Dv9;6(`JH5)2@^>hqIKHlz%)5W1~i2b7>r zMtL2SV)F-+L{U3^byjz(#t}Y3w_{;vi|*X`Q^zJeNt^A+3aJEZmZf?#dL1OdIBh?`u*1{4!XX@*v^5Gk<$ip$v8hp-X{)jAa$xzYisDiAOtOb&4kG8}Px? zx6kK;Dtwm#u-(v6}SMpwS!H(4f_4JWFZ(hOH_V*_98VyIje!0ZK zmQxaIeeaX)E5Ev)EUvjJ+ZjHx91!i*t*Sp{ZQhcO(jqK6$W=9ueAoSRBeqv`{ygXn zP4_bp`sD#VmC$N;a%Vc_(9a!12@&Wk|8}sBH?nk}9{y&CCDJrGb3K*oyNZQ0ZxWG4 z-9dw*%nOH|b`og1t=Iq+Vb|Q=yJ*qMW||&p8-ozHD_$QL_jfGpEFHuq9+Mmlb#bu? zIdOItHZtn)-rGM5MQ*ejlgQ3)y=nKWa*gsA-`EQ;iJSDn+tQMook%w1<+S$i8p)OpBsO3xzCg zie$-B*1Hf|bhiw$-l3?h(M{c!@9TB0Tkm(D|KR(0yssZ-bYJH>=e0av%Xyu1u1m@E zCzdcjauV^nnNP1D9#2s_l=Y!Kllczwe%#n2$WT-!`Ss z8QPG1huOdRf;yw+;Z=rX12nU1$W zPNtAE_gM9={q;hfiEz)>;U~#j3t%+Omu>5V9hy@sO2WBc42J|NJzO|Txe)V=9ztP?`za2)5m(4agY*<%o3G)Jpo9?!ACLIP2 zm0+2gwY%o9jQ^{1MWNG@wIHOh!#B40or();Jlr&da@A0>rt>BkSFVzEk5ji&rMllg`G0X#bgOh|0d$T~pbO zL!gn4mmZ7la?{sz^;ouuH|?o^5@rNeUAv^A-$*TxVu$6C9BjWJEXO$(CU(EFINSrC zK=hef{BtPT^qjt%e>)XGyl@EYt>!Km;)?2F0bZCg@ z!QYR$W}05FkMEo)eJwlJyhDzJ<*4{MBS3K?5rzDRRS+Vsk?a-Y$qmcxKj;pXI-(Vk zU4H!vpP~9y>7fq07&Cj5feW)iW?fYm3WbSQpYjai^qZoxv@+oIi~QHYaV*hoWvd5% z`wNEix|dY`C_yd@Ev`o|TKzf5He?)2?=Fm{XVCO--oX!XohHgAp5W(IR9e)fo7;*$ zWjYvRD%0cIJI^PxATlO9J1-V?(cd=(Ifj-_^dzDO^X9&@wry(Be&s2dn!on)&_$@l zTeZ(|ZoF54ez3`)l;@Rys&7$dcfn-B%8qKYDS&MoWf<-rnCtU2rKETCuisHN^{!>X z^2u|y^7SE6~si%(v= zvRcFX61QI43CQB+y0e=DLwwm*uNf7%^prc@1{ZZ!`W&cep>Ht`tW^KLZoEeml*eDSezWk zT7};KX40bQlEn_Db0t57In7q)_zAmsYFi))&JoUyzvY6r;>gzR_4PUXW+P0ROk6%{ z{X}Y8!N)Cl#mftP9yYjs@JTXf8aRcXUtg(L_x61V>Wm%*qB+|?OiqWG^Ik4}#YtfK zr0j!n9F0}*TKp;I;n&v(6>2^l{Z{RjkjdM~BYY zPXn!dn}lpG59=cPUsHvEGNfGKB# zJDs&lrEl;{t%2r_5_w#qwOu+NUKM@;0^Y=dFyU`am*3f>v^F+|L4lm8-|jzsyt{c< zO*@>YWT!*qI$c)hR3lpiN$KxCVq8__PZxfJ9wvB_9f|T8nH%~$3rBT&_daWTzzYyT zRrTI($v;#99&eZ0OU=?ZNx9SVw=!L_L8e!d~lJQ)>*~&GVhYwv$)gn$h zpW^9wfB%*}o-8P-_YWkwT+m78Yws9;ZP8E4GjhT}BAJiXD{Y2`hU7w@KL_qQ^z+}x zl-v*1*|-7@I9KBB<4O>Tg=1w#x-*)sc0pO;@vuVY@Q_~9ah9P`w>FtA(M^2+)8xIa=#4`L&i zyTutm*LY$a>kcgP6NfprQUf5TMs@FI$uT4WsTf5Q>Exdlce70k^FdPJ& z4D1=mXT^aV;H*UKOa|;Ic0Kp(ueUtrDCE&Ly8a(UD2 zH@`|%JFhbtbhy(zq5hI;*vJlf*wq!Ncmyq(Z6ya{&wS`@Q3Xr5@yRf`Za<$?zQ6aL z-$Tc@YtDy_&V$x$6$L?SR8r=nMarMEND?3+r`PLix#h^A{4+ZZdu>x;^^J%; zUrr6KKVt71qMLjhJn)iH!d=zK8TGCrW<*6D=x-j@L!tUSm)UijlgpT&c?J1B+$_xZUG>^kc4>&K(De&>@VK7C}22y23Rh8k3(F{xAo|mZS(Xks$gxNvmPGqUu(Xv#mn#1if@&HpAY4y z?KJ%OODZ~xw5h?k7g;z{F+1@z^~sryj8Eol#pfRJoNrZo4kerCr}-{PIFdgR931-& zlw{3g)C%{^0H2(bZStqT6q1{}Z+6>j7YVu%lVx!$xWnD{a7j}{c0jD*f$leu$P<3{ zBs!c)wqkY>gde}riktAkSQGiI(WH7&-6n`3oHgd*{*#_Px-=gqD3NF19KvE7d*Sue z&c?xcFm>YYfn+o2#%t}VsJh*#fzdS70F;DuyY1DUgM_Y4&G&L12r=VDo2YVNT#l2ilb|@YIG{jGPfKamY9IOzp9l9RKQl*Wcq+;5-xQhlKjBTW)#X&O)G3x9(cm-NF#p8ATK8Pe8bk zNa`H!)%gb@EPZW)aw^2gGq7Sn#IRhExl2ArxjFyI6CXwJmsXQ7fo}1(U6maMxaCZp zYLX&sbpq^(B@Bf(qOpHQo4-XBjbi?0v)`zz4%l0k%-!%4iC2s8-X0e$M7K_oE_CJX zX*Ra2axG5+yxWf?l|z9b3)3jhnq+msTxmLpnG8T_oAj4xR7R_BZNvrj+?(KLD$40FR`9(OL?MA!qM zFg6QZBk6dCQib2(i@XePobz?-e-c6eSdf8LJp4sO_e!?;#~e!mKY$afXKMlI zu*$`f{_`F0IxyCTEm_zybdiH=pkp$)em6_-C!F)O2Oea@OML2L^55Q@mih>}) zVf~FFvdwaj8Ay2Zv2YIfw*_&u)u!AgT*1sw?*v-&5VdPyDU@iTnmC3ZjKY)EyCHW!35G9qLq5ZKFeAkura#03 zcLn3RF7d&?tfs$=mg#O$$j6sK>VOd`NEHR4Lm+t(T`yvHY#c}OQ0ZD2IaL0%OXl-o z@`EYSX)xv9!NsZrhNwd;EP7d$fo9^@qh=;57lda_GJ4YxEk}rmF)GL2&ZLxg60EHT z7cFYw&>3l;41bZ4g#`(pwJQaN)P5Hu_Mnz3-cmom!lddaRs&4)SW+l_uI$VC3W4uo zb^zAozlox5k?M6GTCsZCecwAIO&WC0aYmhM>ejZ#KJMy$Dkf_( zgZs2AFJ!=<4v?9Amz+ad`4b<9ScZ^nEl@aX%(#LYMelqHEUR27IMKEn>bX4Iu-Gq4o= zUyoUiL8qN5e;Z&YbDK}?JIIc?PN;ozkiN5BAoO7VH`o`DNWK z;-roe6GDk)zSaODAsb085h84z2KlT+ht8Q`(VT^F+X;gV#|0a@m1z8yH=ayC51*`U zr};K)swcL~kXem`lIJ1oo2Y+F5?(BiQeKSui-1WRlsl?^ebC^9L2B&-(N9eCzBVTK!kHq#H! zu9&%unqtQd*;7|c`^lMFl0_qmf%pEWWNYVpQ@TIf?ofZXvcL)@Za+AsrPw9L`9Wc8I z)E@s10dV9z?7-#*Z2@pwzOyO#S(vE&gu%~rKO9AD18B3>fOF9mMmMt_Pa&`iZnr2D zM|O6hl)%K^sQSHmsT~et8p64=+fg)ti)R5snl;P)J_^xv74~#nahF$|t zLW8*^WgZ1e!m>rP0WON*AD?Wi$U7wA_eLvk~#LCmf&PS zSUrnDSjCZReKpE9gRBY=&Y{#DZH7}QtRuBYQ>S(x3Wri9#Na8G;;Yck%_!&}qot+{Z^ z=_h(?HQqACTffj-8$R{8zr}i$S5#8@y0?ax{C}PWa*xOJUh(MIt*j79@qI7X4cH8B z_Od+r6A8GDktLU-4Jib1ivV==(+GG5(a~SI+{&3it6l8JI1?Z1~ZRHAi(MuB!GFtyKJU8R(fX^f7 zYR99^(NkM$5pO@epvXEAb1i&%3mM#qTj=inFe$zIKIhm!hcIi-+#^ z7gj#;k(tAKyJ!}IWc z5d38^5Q02ddGyeb?`Q8~Ut5E2c%-R%uaIp{Qu&PpxwKJ}!HfhL-3sN$fUJ+rsMw$4Ypxfq@K0gEo5fD1|dpa{8)yyU<@uWxoQ|i5fZk&nvXI ziTe|zq7GZffEU0H+k}#!c+Q~fQ(6O0%HB_S@Xngx4mBNUW;-66&Bta1RLqiTqLz++ zCg-vX9sd~LzuSD@E3Z>)?;aa&N|4K_D+ja9#5CMV7A4h)x@LtMOc;nx=^mYb;8K7R zRBUWnO82u)Yt61Uy)>NDsS|b`B<}{hbV$!`9Oj4_lnPa424ELIM4oMqvKJagJ$>(> zgPf>-9Qq<{#3meQcFyoyk?=Fu;Q-Tlh>)G8r@W_X3byYbZ9F7+1IAGpHe#D0X@~{ zPkdn6fqMWYzaH^O2Jd`H*~AKp{rdag!|&s;E)s?OIwI-k=-uiwALiqHq6-Esk`_Ki$*cm~#44j^RhO z(l7hq-Lc@qFe{RR(mrgKa;O;A>$!Ote$C#|_^sE{bJ!22f6JWh>wHDjn*Pucwd`71 zuawd>;s~tUilg3NAm5jhf)xtVV^-bwy`~0d5(XOVgT&r`n$|ly&G^%OQ)`^ z=8fjxDf-klB3amYMO!qs17N4p$`?xPtqp+5A6E{ED4^#Bt%4$&zJBdL{&iHVx!S*v z-)d7}*SnI}A3OS?v$NgvYiD>7Admz=WuDZ`1qfO>A_VE>1BH1~b89993w}mJfakof zqGh;h3Ky#jt96m1E@LXv0jF|^lhc2G#LTaw0m`H-JxEZc!tVbn=t!eQ9;X zs#sigmYdAnV{O@KFMbKehRyE8$?SV)DLBG{*VvggKx{^s+lmrvge+dQ`oO8WI@BIn zTVRd>3p(nH?U-9+L4#jjnh4>>qfn~^hcH3inEr8X@y9KK!_VL%rE?5?-FkGWnGuLryx5KS zSc(g*7saQ1&KU7qEK|95?5Q=82AyEky2-HeO5C8n6xn-m1NeHptYi;A?!*eT0~(DR zb+Y(7+Zd&n;*K3Ig*kSawbGdM*6&2O9bak>Zw0}+;C4+V837!6ehKN^yNs}^G610M zG5NQ}6W+8QzKR{f#U92!FQY~{+i_jcS@HnvHlP=)JIYb~q!7u3!z+Yi_gyDCs9;*S z<#>)o!%iWth~ z$dbwn;0N(y3GEa{W{Qy+A7JK**VA7G%!q7+GVWpVqe8^(>@?`UyVXW3tn8QoU=U6L z-aZci-tjrGBvIM|Z!x8p?sLWL5H@7{&*?o}gInBClAgfaCGD`ah{SKD1E$JnY|y;Z za_X93$o_J_yuqnZD?-ft{vAj@XnO53XnHDw`WMdo>D7MIDCQNowkw2ugV+DSw$xUe z;jimVVy=&|D2meyA0)==%nx>`yZ+i{{s)hAaiZ%8i@8K(vqe>(*RDSW&}he;-bw`t zotW~2nloST&0|l6DKfpMsQgJ^v$DCyO~7F0Gb~rCs6z3Z9DN>LFWY-Bbj29NIpg{| zA1cAdk#s=FFMf@!g~4Ta)p4z!UhB`tYWt%Zid+DZlbkV)&KzqX9Dx=vVK-zdINx&$ zN-9@sSvK|-Ri|HWy^dX#gN{Yy5&((q32|?gl=2upJGRT`BtD~f*Mlk~=^6inW3Vz2 zq|zL$`LhQATqnx5FNhW>mbKd#v!kAs-~>n)6zPA^T?gAQ&uJ=Q?F8MCF7V)HhfQP# zPL63|y@5%XHn=D`IEa8}tqC{abB3{6lyS>0*yl|zucPiUxH8DvLbsJ<9>&(`Mz8Au?AgZQfG6&TN=AK25J9*wBP(M+9x{9 zZZiXva-EU5n)Iad#z!sJ1-4@4BycjC@G>OAmV#McNZI$BDgvSRsDcp_Uplpir3?Y- zEQ>YIpL~Ob4r1a77R(whfAX!bMOd#-VlBg4eBdf)-1kO2$^halt>j<@2dB~L2Vf5e zz1mO1S>*<$!g0iEJ*r0Z+7DKKdEl#EC0+I=X**$Ulc=BiL9iMQ@CpMV!h3|BkEcrt zV*eq|*!x`M7uXfaR6p1=X!*`2W=!kxUtVVO`oIldfyopWJy?rj@zcS{B)UFvjK3;_ zcRYas*XYnD$bn1Tn1|jWd!g3!zW-vtO)<=yi0$8W;pY5L40~hCG(;3$%SGa4K(mip z^kOj4!;5=UP@ZAGMb&2hNvnbNvSXtksVlkEW`p%$Rtth&*B8QnXqnvtwWop((qJVF z&hnGcCn$jkSk`gZv=}3fcX)1_0@QhbBP5iLUrJq}!}sy6lm**pbl1Q<4yx(q`_6g! zomc@oO6k?{8Wv8D0WfRf7!Z6k7TnwX`qY+`)a5D)l8E_Vwhwpx5y+ja< zyfV!W!VA@K{8~hIy!8*RxW@|CyrD=*Y^m@?p)&H{fty_r1H3~n%JP7TD||v0N)`9v z5b<~Ebk~-w7T7G0wt!6l|K)D{tY=5L@S#XU8k`WDhkr;xxJyIS6Cv)BhP@E+bWJEW z+`mf(K8l`y8!H?fEyTUnW~g)KB%b{q>_s?BDz`IB32x`WY;?uU^*!c>T@u)o#2Itj z$DM~Dvbj}5#zMZyDu43Ue1Db^tJN=a7_H@^-)Bi-rS-ou7LI9~_jXG--xx2ACgF4E z|9GqRa3kC7Y^-K-uxN<8BHz?`<&R-b3-UKu>zy%y=oX#fwgz&Kk-HzKUu+g^cF>T* z_aot@8?w6;a9e;Rz?)Tp1OV161lCxfQS1}UOC+#@LN-_UFNpe&a5Hsls{)|Fwi$F+ zjF}I+=?8DS;Dx;EJ09+tI*zbnrBatw_dVrv8UvPu682sQ^7&Oty7|;~hCH?qByvox zlAVU+IVIAy_$TVm$683GG(V-g8VJ1g5wdGo(~0c$f^Qwri^s+B5!QdAf(CEFT&H(% z`uB9nqm@~2Bu*$CGB5@nEZpOz`QW?wdQ4D*nxLZ8mR@+N7jysNazX$1U_&!3gg}8? zRrNmsgB#ljX~Yijxzut)*^ zxS~&B4MPpw9#+3E(y{ac>6#$ zFEh&@q9u`n7f2DDFm*U+T0jL9FRL>V3354(08q9yA~4O>JRFiUoA1^&vBk)u3Rd!< zrbX|=y5JAVV?jiAR$@x0A>gaW3c1X8+fu9HY&kf5F$lvyQvO07?6)+tYpbkaQB?)idYYQB& z?$T>(`7k9`MiDN=A#jt+>1OA-ZMf+Muh4_Dqf&6Gia6PhKCf(;uXc9dZeldkYTRpu zlbau_{B8XpUePvl!mo@Sb(h|B*=0-~_r=w?a@=ymsuG@4yg0 zQ%$5c@K9}6JW%)k5mKjLA=en;dX>>Fyvp9Na1i$Mvvj;!TNur;|I9!7Eo)dGWQ*T^BP9Q^oJG*LXZ`}=aQ``;+L?0@9*6IY*nNATw38gglJvNQax|=L&^0xQuU#6~cBMwkOJfy=F}KejL}E zEgtlNHS~eYV-Ey8P_dOhPECp>O{uW#2aB_qNxNg4pvZ~iJ|tZFjA{OqFVTDfR#`>8 zSx`7_gpVM>xW6QN?W{b#gg(XEN@-A5L8fN*3J9J=e+)vyVW0deKt;$q6pY^)WYF)Q z9}vOr_=;o8?!~d=eoMSJ6~-1oS;dfcHkooM+b0-de?>KvX$OBo%W+y#$h<0wZLfL0 zp7xYT#ebkxApVUzq(4^kj>v;Aaiv5}7?aky79=VLKHWyhu0M^PC33`?>Ql2ABft8u zFqNY{mE%K~;9|DFL@r;zPs?&Y`y%u(F=6=hDK^+I|7;3lBHU(+wZpX|R{-Ja%4i}I z;W5oizy<^`VZV_yl=6p@cdRGNP;}wm!|wx3GhQ*-am=%Y00(p(To)YM>Kb6IDRP&< zxoJP^$r=Xdfp}rRqk<`aH_K6P7GNA2=xBMGh#s;+QB{Ic| zc?aDb`7!-|mBp!vJ?t8Y$Rz5hyq;U3g#M ziCB}p(TVb$c8Kkc+G=&z#e0o2kvR5B@Cns`sC?bg1lj2g8!S$rrwjxy5K zGDDyQS&yxsIhhR@q}Xf}@*pT7DQ0)YX~ss>|JK70=Y!7&?nl$l-)`)@yVA5`-F)~7 z+~7Zjtq?*n2Ox!I*n?M2Otbsy0U3c6^{n%h5o5wMkmwfTvPt~GP41t%-)HnL}bHF>yK)<3DjiuB2*>}p#u4*7qw*WHJ9L= zcYcc78Edq1=E(q-5X>Y1zBdZ~$Yph@`)W0rolHbfc=+8|i1tG&2UXBCa3KKm#p~8L zi+PkpQ?v%6z1!G%{zr}=;IQ6!eTNd)4BY|iZn>Oj3orWYWk!%oI{*57-7XK|%DzQ; zIB>yqS7e`A#tUk%>}&lpO;OptTR_u2+i4FW<#LDDO6CeYoyx(ovt>6r52L~PtwOYN zueb!~xg0hSBy19w{RJL{RARbD?%ktkUe~pTVe-_&g|GL}jDY1zv9fOQe~XzF(yk|& ztpn*Axd-2ch1lul5*+Za;ePtEfhr?~!$i ziR!27g2MTNd`lMc!5ZpstrRGS8_?%{_XzBuei_TC>@_!m0*U#Qg3SyC0ws7wT2~0t z0`dsHZw+R9Ev7j;CVI@J1$?~InqZelNmz!_6BD|7@&~ql&f0f<2GC7dqv>K_4*8}| z0rsX)8Y+AxbDJXV8GGvq(gQVP5Ms$uB<}w~L{h}vmfkQF}Fqla#dboXnx z1&#$zudCVsbTl=hLw#0H2;xX5SCGmL=}IH1r12>VfqO&*cY8x+S3SaS1#I~xUNji6 zW_nBJ>Rb?b;*cLy$Mr|-5Mzi))4p8g@1VXgO#<~oWUWwbJsX%_t!B`nj4{C{+;ej& z(`_On8uR?jc*DX>(j=kPB4vky=EHkI!}9^ zBp`QtC)4w9Oyi48W4|PS6%?1mY{W5ZvWgr0gTQzFgo`2XV{M{9xHz2w$lw`4D;HC1n*Nif&6T1S<%pDM{};eUgL zee{crr_6KuWS=!^HcjO?B93e{vvjG>Y@jgf!xpavSI5ygd=xttlk*(KU2A5=-*RcIF%Vb zrds=T7!8^v0={|<-E{j%=2}M9pTE5l{10KvsvrNB-jIfObeQK&SC=|4O5(cxAg7){ z3TLmo=nfM2OlZf*ey8;f?Vy_)JGa3isH;feE7|k&&ugw?@+&f0LMgC}0B7{=#c-~` z+m5{d3q^#18{V&vkRs9%KVX?PotF?`AGiq&oODxv$KLR6R!%~II}_WjHif<|D&|=r z?uVwJs)@F~aj(XAr}cD)<>ze~j*!KH0tB&}Wpcr^{(PUWz=_=b{PV{g8oP-)g(|6B zlz2}R>^7UBlU3m(>dtr>l1Lo}dpn&?uX>%%XP|h_Nd#I{XYPjYIlwS9S0m!*OzR_H z7)D-KUq=olkGV%OXiqV_YnQR}Oh^-wr@>>aO?Dns)t|)|{3qgTMz^CZpe@m1Tz051C&wdhk|Az^fInX3jMTW}d z={k&Rj(U=LsT8dWPkR#lB{G{sKB)?;?g?zLkCpL*JpGQ1k|DdcJ(&X1ID-Nvknyo=;PRPI}Jzv6_|(ubN8>pmq ziO@fSz51@xCUi4BSf;}caY9mm#e9q9nT8v~$xx8S?JNK& zJ9;whbYrdgKv9&ZG=0SCHgj(cGvK(SjC)}V@|1T$donRq^bkf-r#a&gN%!KD6mC3g z>t=e@>sS1|ga23#Zg4e!LN=Ua%(vI;TeugwqJg!iqCT=;*L;p$#jv(Zhqfm=g+Xh%eWO+R zLD0+@o3+njnr$?nBj;q2!1wQ4LU~KgIJF>4`7*MZ%aEkCxt3r3 zUh%-E&dSyB?|42Hp$akIeobaz*8NcdYbtx)TeEnncfVwN(9y){Xd*D$tMUZjDUDQ+ z>`B+$d9$)~K#v#jYuca@Yh)&-ChR}W zY@D0?3jYX22*#&^5Kb(Bulm71Nbw>Hqwrgg7b)IvT5sVImj?@I@FueL7KcXV4tuS44%MWnG$eD%S=izzuv={U~QaU5a-P(IWp=Tj0JDhp=ukvzRIIX8TRWE_;zyC z04Lw^3Zyu2et;q@-+=1N>kVqS@t|~8;&omvN|j9KYoY(k+4snbbBKc>ezJIeMC7e5js-FGq1ZqGdMRXI&$H|708$Yw8 z3;uQaub`XA(7lCJ|D4r(0kW**B)!*&3b&(FFzuPEJSX<$f({XH zNQcmMG_zTDa>4x&9H>$0Sch2nRe^An737)O8g|ZZRH7Ez zzJlq?)_nWMXpt2?1>zsJV~5RQdkmhJ)qnv4(K%5Tt33d ze`jBiLVJ=}#PqZSsInf=@4JDP=l0hQyFjo##_vdI&?dT?rCojMxAUIwhPZSuJl8w< zz1Enu5bMPVbE3WpT*l`l-{6MsUDzrOK2B515OeAhC5Ee(lu@IcryQk*IOksWcyT9l zeQ2$xFy zhTr)EVY0(hv*+k&7Y zm48wbaGJ3GQOQar)W?dhD_Ll3Nez(wh2ocJ+>j5^2OisyNAay?eHVC_&mmRy$7u)g z#ejf6EZj{#b%ne)+70onQE@^?@_T>BMwjfyDk?6{Gzu1$6q?)fBLgRsfydWxxCGux(ODw6l8{g?qXDod3M|A=qrSj4k3wmkOHw1Y}Uscg*tv+-WmgFIpRT zG#*Wu#R!?Tbw>Vp(qrtdX$eP!F6TdeRm5@G)#cSou2q2v0iP+T#=57FvIb;(}Fq_cLroTV;1r zYeI+JW&aBS*_G(ObR!vDwO{sl=Pwo1?*A4NVP%75eEH8B{6`w-4#{o?@K)2o1ckff z772H|0%z-PgcfV~2EW-#b*9_I^doUQNnwujiN*mTO~Ri3I^3F<`Lr;2L1uRhrdF&% z8ncLsyhDF%qqa|6?i4ufCAVvIbh&Tq#R88xINWi@l-M^Ru$^3xtGu>fl+?#wqzc@` zJkLIPu6@{=-&bz~?moFr7PsM#X_r{s0uyQF_!spn`3Mt>`P2|Efi}?gl0$1&8c}M&mXWA zuLaNYXgku&ea@hfYkjNMw98YpK z4^ibqkR~xG5sTMVN~a2qVAw5neztylhq_@ z!4VgOsU7Zp0hv18M=4%Z7BDE5NJvuH%+(Il>llGAB>M};PyNWd&Kiz;pR!0fquLmK z7Aajk_9P>0(80*!(1XcU} zV?oRJ##K${o_Bh_)gW7YGjkgQMpusvph)OmZzn~jS$tpUxWQr1BBJ9UO^UJ)7B!iS zC|V(Wh=x9ci+&55;qkTq8RZ0=J2q8le2(SL)aZe`!#8rGQ*!3Fp*+dAKbY319=@0| zr2UVHc23i9;Q00^bG}i+H=S@jUZSmkcH^BG+O@aVt_}~hwl_&z3hPM%7FBs2kK8jOQAKy7C^c+p#>@%F*L=G| Date: Tue, 28 Jun 2016 17:35:13 -0400 Subject: [PATCH 191/274] 0.5.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 331c4e7..bfbbe48 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "update", "description": "Update is a developer framework and CLI for automating updates of any kind in code projects. All updating is accomplished using plugins called _updaters_, which can be installed globally, locally, or in a local updatefile.js", - "version": "0.1.0", + "version": "0.5.0", "homepage": "https://github.com/jonschlinkert/update", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", "repository": "jonschlinkert/update", From 21942a29bc13b1f218f0565dd24e1cd14f4a2a23 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Thu, 30 Jun 2016 09:17:44 -0400 Subject: [PATCH 192/274] ensure array exists --- support/lib/helpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/support/lib/helpers.js b/support/lib/helpers.js index 92dd7c0..9ded6f6 100644 --- a/support/lib/helpers.js +++ b/support/lib/helpers.js @@ -15,7 +15,7 @@ module.exports = function(options) { var len = arr.length; var idx = -1; while (++idx < len) { - var ele = arr[idx]; + var ele = arr[idx] || []; if (ele.length) { return str; } From 4c89838e1461aa46b7a62ba7b6b714f49ec283a7 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Thu, 30 Jun 2016 09:17:51 -0400 Subject: [PATCH 193/274] lint for title --- support/lib/middleware.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/support/lib/middleware.js b/support/lib/middleware.js index 365c25f..2cf44f1 100644 --- a/support/lib/middleware.js +++ b/support/lib/middleware.js @@ -29,6 +29,14 @@ module.exports = function(options) { next(); }); + app.preRender(/docs[\\\/][^\\\/]+\.md$/, function(view, next) { + if (typeof view.data.title === 'undefined') { + next(new Error('`title` is missing in ' + view.path)); + return; + } + next(); + }); + app.preWrite(/\.md$/, function(file, next) { var segs = file.stem.split('.').filter(Boolean); if (segs[0] === 'docs') { From d0b155a07fcf0b2f356eb82de02af08d4c1716b3 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Thu, 30 Jun 2016 09:19:23 -0400 Subject: [PATCH 194/274] add docs --- docs/cli/commands.md | 11 ++++ docs/introduction.md | 117 ---------------------------------- docs/summary.md | 119 +++++++++++++++++++++++++++++++++++ docs/symlinking-updaters.md | 75 ++++++++++++++++++++++ support/docs/introduction.md | 33 +++------- support/docs/summary.md | 4 +- support/docs/updatefile.md | 19 +++++- support/docs/updaters.md | 4 +- 8 files changed, 234 insertions(+), 148 deletions(-) create mode 100644 docs/cli/commands.md delete mode 100644 docs/introduction.md create mode 100644 docs/summary.md create mode 100644 docs/symlinking-updaters.md diff --git a/docs/cli/commands.md b/docs/cli/commands.md new file mode 100644 index 0000000..3c79081 --- /dev/null +++ b/docs/cli/commands.md @@ -0,0 +1,11 @@ +# Command line flags + +Supported command line flags. + +## --run + +Force stored tasks to run. Stored tasks are "skipped" if you have an `updatefile.js` in the current working directory. + +```sh +$ update --run +``` diff --git a/docs/introduction.md b/docs/introduction.md deleted file mode 100644 index 5b0b165..0000000 --- a/docs/introduction.md +++ /dev/null @@ -1,117 +0,0 @@ -# Introduction - -- [What is update?](#what-is-update) -- [How does it work?](#how-does-it-work) -- [Updaters](#updaters) -- [Command line](#command-line) - * [updatefile.js](#updatefilejs) - * [Running updaters](#running-updaters) - * [Aliases](#aliases) -- [Resolving updaters](#resolving-updaters) -- [API](#api) -- [Update](#update) - -## What is "Update"? - -Update is a new, open-source developer framework for automating updates of any kind in code projects. - -* normalize configuration settings, verbiage, or preferences across all of your projects -* update files that are typically excluded from the automated parts of the software lifecycle, and are often forgotten about after they're created. -* fix dates in copyrights, licenses and banners -* removing deprecated fields from project manifests -* updating settings in runtime config files, preferences in dotfiles, and so on. - -## How does it work? - -**Updaters** - -All "updates" are accomplished using plugins called [updaters](#updaters). Updaters are functions that are registered by name, and can be run by [command line](#command-line) or [API](#api). - -**Update core** - -Update itself is system for [creating](#creating-updaters), [registering](#registering-updaters), [resolving](#resolving-updaters) and [running](#running-updaters) updaters. - -## Updaters - -**What are updaters?** - -Updaters are plugins that provide all of the "updating" capabilities to update. Technically speaking, updaters are functions that are either registered by name using the [.register](#register) method, or directly using the [.use](#use) method. - -Updaters may be published to [npm](https://www.npmjs.com) using the `updater-foo` naming convention, where `foo` is the [alias](#aliases) of your updater. Published updaters can be installed locally or globally. - -1. **Plugins**: - -Since updaters tend to be used on unstructured data, or things that are often updated "by hand", the first few times you run update (depending on the updaters you run) you might be suprised at the number of inconsistencies and errors that are uncovered. - -## Command line - -### updatefile.js - -Each time `update` is run, Update's CLI looks for an [updatefile.js](docs/updatefile.md) in the current working directory: - -**If `updatefile.js` exists** - -If found, Update's CLI will attempt to load a local installation of the Update library using node's `require()` system, falling back to a global installation if necessary. Next, Update's CLI loads the configuration from your `updatefile.js`, and executes any tasks or updaters you've specified for it to run. - -**If `updatefile.js` does not exist** - -If not found, Update's CLI attempts to find any updaters you've specified for it to run by using node's `require()` system to search for locally installed modules with the name `updater-*`, - -### Running updaters - -To run updaters by command line, pass the [aliases](#aliases) or full [npm](https://www.npmjs.com) package names (if published) of the updaters to run after the `update` command. - -**Example** - -Run updaters `foo`, `bar` and `baz` in series: - -```sh -$ update foo bar baz -# or -$ update updater-foo updater-bar updater-baz -``` - -Note that _updaters are run in series_, so given the previous example, updater `bar` will not run until updater `foo` is completely finished executing. - -### Aliases - -Get the alias of an updater by removing the `updater-` substring from the begining of the full name. - -## Resolving updaters - -When run by command line, Update's CLI will attempt to find and run updaters matching the names you've given, by first searching in the local `updatefile.js`, then using node's `require()` system to find locally installed updaters, and last by searching for globally installed updaters. - -If any of the updaters specified is not found, _an error is thrown and the process will exit_. - -## API - -by passing the names of updaters to run to the `.update` - -## Update - -Updaters via CLI or API. (tasks are powered by [bach](https://github.com/gulpjs/bach), the same library used in [gulp](http://gulpjs.com) v4.0). - -The main export of the library is the `Update` constructor function. - -Updaters themselves are just functions that take an instance of `Update`. - -Update gives you a way to automate the maintenance of files that are typically excluded from the automated parts of the software lifecycle, and thus are mostly forgotten about after they're created. - -For example, if we were to sift the files in the average code project into major generic buckets we would end up with something like this: - -* **code**: the actual source code of the project (compiled, lib, src, and so on) -* **dist**: the "deliverable" of the project (this could be HTML, CSS, minified JavaScript, or something similar for non-web projects) -* **docs**: documentation for the project -* **everything else**: LICENSE and copyright files, dotfiles, manifests, config files, and so on. - -Update maintains **everything else**. - -# Related - -**Docs** - -* [updaters](updaters.md) -* [updatefile](updatefile.md) -* [tasks](tasks.md) -* [features](features.md) -* [faq](faq.md) diff --git a/docs/summary.md b/docs/summary.md new file mode 100644 index 0000000..74f1913 --- /dev/null +++ b/docs/summary.md @@ -0,0 +1,119 @@ +# Summary + +
+
+
+![separator](sep.png) +
+
+
+ +## Introduction + +### What is update? + +{%= doc("what-is-update.md") %} +todo + +### What are updaters? + +{%= doc("what-are-updaters.md") %} +todo + +### Why use update? + +{%= doc("why-use-update.md") %} +todo + +
+
+
+![separator](sep.png) +
+
+
+ +## Getting started + +### Installing the CLI + +{%= doc("intro.installing-the-cli.md") %} +todo + +### Installing updaters + +{%= doc("intro.installing-updaters.md") %} +todo + +
+
+
+![separator](sep.png) +
+
+
+ +## Usage + +Update can be used by command line or API. We recommend you start out with the CLI to become familiarized with how update works before diving into the API. + +### Using tasks + +{%= doc("getting-started.using-tasks.md") %} +todo + +### Using updaters + +{%= doc("getting-started.using-updaters.md") %} +todo + +### Using plugins + +{%= doc("getting-started.using-plugins.md") %} +todo + +
+
+
+![separator](sep.png) +
+
+
+ +## Docs + +### Authoring updaters + +todo + +### Publishing updaters + +todo + +### Feature highlights + +{%= doc("features.md") %} + +### FAQ + +{%= doc("faq.md") %} + +
+
+
+![separator](sep.png) +
+
+
+ +## Developers + +Hacking on update. + +### Base + +todo + +### Architecture + +todo diff --git a/docs/symlinking-updaters.md b/docs/symlinking-updaters.md new file mode 100644 index 0000000..475f950 --- /dev/null +++ b/docs/symlinking-updaters.md @@ -0,0 +1,75 @@ +# Symlinking updaters + +While developing [updaters](updaters.md), you might find it useful to symlink them to global `node_modules` so that Update's CLI will find them and run them, as if they had been installed from npm using `npm install --global`. + +The following example shows you to do this. + +## Example + +**1. Create an updater project** + +Create a new project named `updater-aaa`. You can expedite this using [generate](https://github.com/generate/generate) or Google's Yeoman or however you prefer. + +**2. Add `index.js`** + +In `index.js`, add the following code: + +```js +// -- index.js -- +module.exports = function(app) { + app.task('default', function(cb) { + console.log('updater', app.name, '> task', this.name); + cb(); + }); +}; +``` + +* `app.name` will display the name of the updater being run +* `this.name` will display the name of the task being run + +_(Also make sure the `index.js` is listed in the `main` property in package.json, so that node's `require()` system finds the file)_ + +**3. Symlink** + +Next, we need to symlink the module to global `node_modules`, so that `updater-aaa` is discoverable by Update's CLI. + +From the root of the `updater-aaa` project, run the following command: + +```sh +$ npm link +``` + +**4. Run** + +To test that `updater-aaa` was symlinked properly, run the following command: + +```sh +$ update aaa +``` + +You should see something like the following in the terminal + +```sh +updater aaa > task default +``` + +If not, review the steps and make sure you did everything described. If you still can't get it working please [create an issue](../../../issues) so we can look into it. + +**Next steps** + +If you'd like to see how multiple updaters can work together, repeat the same steps described above to create and symlnk `updater-bbb` and `updater-ccc`. + +Then run: + +```sh +update aaa bbb ccc +``` + +# Related + +**Docs** + +* [tasks](tasks.md) +* [updatefile](updatefile.md) +* [installing-updaters](installing-updaters.md) +* [updaters](updaters.md) diff --git a/support/docs/introduction.md b/support/docs/introduction.md index 52ce77b..9e14ed4 100644 --- a/support/docs/introduction.md +++ b/support/docs/introduction.md @@ -1,5 +1,6 @@ --- title: Introduction +draft: true related: doc: ['updaters', 'updatefile', 'tasks', 'features', 'faq'] --- @@ -18,40 +19,20 @@ Update is a new, open-source developer framework for automating updates of any k ## How does it work? -**Updaters** - -All "updates" are accomplished using plugins called [updaters](#updaters). Updaters are functions that are registered by name, and can be run by [command line](#command-line) or [API](#api). - -**Update core** - -Update itself is system for [creating](#creating-updaters), [registering](#registering-updaters), [resolving](#resolving-updaters) and [running](#running-updaters) updaters. +Update's API has methods for [creating](#creating-updaters), [registering](#registering-updaters), [resolving](#resolving-updaters) and [running](#running-updaters) updaters. ## Updaters -**What are updaters?** +All "updates" are accomplished using plugins called [updaters](#updaters). -Updaters are plugins that provide all of the "updating" capabilities to update. Technically speaking, updaters are functions that are either registered by name using the [.register](#register) method, or directly using the [.use](#use) method. - -Updaters may be published to [npm](https://www.npmjs.com) using the `updater-foo` naming convention, where `foo` is the [alias](#aliases) of your updater. Published updaters can be installed locally or globally. - -1. **Plugins**: +**What are updaters?** -Since updaters tend to be used on unstructured data, or things that are often updated "by hand", the first few times you run update (depending on the updaters you run) you might be suprised at the number of inconsistencies and errors that are uncovered. +- Updaters are functions that are registered by name, and can be run by [command line](#command-line) or [API](#api). +- Updaters may be published to [npm](https://www.npmjs.com) using the `updater-foo` naming convention, where `foo` is the [alias](#aliases) of your updater. +- Published updaters can be installed locally or globally. ## Command line -### updatefile.js - -Each time `update` is run, Update's CLI looks for an [updatefile.js](docs/updatefile.md) in the current working directory: - -**If `updatefile.js` exists** - -If found, Update's CLI will attempt to load a local installation of the Update library using node's `require()` system, falling back to a global installation if necessary. Next, Update's CLI loads the configuration from your `updatefile.js`, and executes any tasks or updaters you've specified for it to run. - -**If `updatefile.js` does not exist** - -If not found, Update's CLI attempts to find any updaters you've specified for it to run by using node's `require()` system to search for locally installed modules with the name `updater-*`, - ### Running updaters To run updaters by command line, pass the [aliases](#aliases) or full [npm](https://www.npmjs.com) package names (if published) of the updaters to run after the `update` command. diff --git a/support/docs/summary.md b/support/docs/summary.md index dc700f0..f2ed123 100644 --- a/support/docs/summary.md +++ b/support/docs/summary.md @@ -1,4 +1,6 @@ - +--- +title: Summary +---


diff --git a/support/docs/updatefile.md b/support/docs/updatefile.md index 949297c..09dc3df 100644 --- a/support/docs/updatefile.md +++ b/support/docs/updatefile.md @@ -4,9 +4,24 @@ related: doc: ['installing-updaters', 'updaters', 'tasks'] --- -If an `updatefile.js` exists in the current working directoy, Update's CLI will attempt to load it and run any updaters or tasks you've specified for it to run. +Each time `update` is run, Update's CLI looks for an `updatefile.js` in the current working directory. + +**If `updatefile.js` exists** + +Update's CLI attempts to: + +* Load a local installation of the Update library using node's `require()` system, falling back to global installation if not found. +* Load the configuration from `updatefile.js` using node.js `require()` system +* Register it as the ["default" updater](updaters.md#default-updater) +* Execute any tasks or updaters you've specified for it to run. +* If multiple task or updater names are specified on the command line, Update's CLI will attempt to run + +**If `updatefile.js` does not exist** + +Update's CLI attempts to: + +* Find any updaters you've specified for it to run by using node's `require()` system to search for locally installed modules with the name `updater-*`, -Moreover, Update's CLI will use `updatefile.js` to create the ["default" updater](updaters.md#default-updater). ## Creating an updatefile.js diff --git a/support/docs/updaters.md b/support/docs/updaters.md index 19f015f..a2fa296 100644 --- a/support/docs/updaters.md +++ b/support/docs/updaters.md @@ -1,7 +1,7 @@ --- title: Updaters related: - doc: ['tasks', 'updatefile', 'installing-updaters'] + doc: ['tasks', 'updatefile', 'installing-updaters', 'symlinking-updaters'] --- Updaters are [plugins](api/plugins.md) that are registered by name. This document describes how to create, register and run updaters. @@ -176,4 +176,4 @@ There is a catch... _The only way to register a `default` updater is by creating an [updatefile.js](updatefile.md) in the current working directory._ -When used by command line, Update's CLI will then use node's `require()` system to get the function exported by `updatefile.js` and use it as the `default` updater. \ No newline at end of file +When used by command line, Update's CLI will then use node's `require()` system to get the function exported by `updatefile.js` and use it as the `default` updater. From de73198d98646da77bb3d67f0f1edec4adbd49d1 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Thu, 30 Jun 2016 09:23:20 -0400 Subject: [PATCH 195/274] build docs --- docs/api/plugins.md | 2 +- docs/api/register.md | 2 +- docs/api/updater.md | 2 +- docs/faq.md | 2 +- docs/features.md | 2 +- docs/installing-the-cli.md | 2 +- docs/installing-updaters.md | 2 +- docs/logo.png | Bin docs/symlinking-updaters.md | 2 +- docs/tasks.md | 3 ++- docs/updatefile.md | 20 +++++++++++++++++--- docs/updaters.md | 4 +++- support/docs/layouts/default.md | 2 +- support/logo.png | Bin 0 -> 100073 bytes support/package.json | 1 + support/verbfile.js | 7 ++++++- 16 files changed, 38 insertions(+), 15 deletions(-) mode change 100755 => 100644 docs/logo.png create mode 100755 support/logo.png diff --git a/docs/api/plugins.md b/docs/api/plugins.md index cbe509b..aca72e4 100644 --- a/docs/api/plugins.md +++ b/docs/api/plugins.md @@ -57,7 +57,7 @@ This can continue indefinitely as long as the plugin returns a function and the When plugins are [registered by name](docs/updaters.md), they are referred to as "updaters". See the [updater documentation](docs/updaters.md) for more details. -# Related +## Related **Docs** diff --git a/docs/api/register.md b/docs/api/register.md index 2db0016..eb9a6a7 100644 --- a/docs/api/register.md +++ b/docs/api/register.md @@ -27,7 +27,7 @@ app.update('foo', function(err) { }); ``` -# Related +## Related **API** diff --git a/docs/api/updater.md b/docs/api/updater.md index 51918ee..3ed39de 100644 --- a/docs/api/updater.md +++ b/docs/api/updater.md @@ -27,7 +27,7 @@ app.update('bar', function(err) { }); ``` -# Related +## Related **API** diff --git a/docs/faq.md b/docs/faq.md index 6e1e61f..dd6b30f 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -10,7 +10,7 @@ A updater's alias is created by stripping the substring `update-` from the _full Note that **no dots may be used in published updater names**. Aside from that, any characters considered valid by npm are fine. -# Related +## Related **Docs** diff --git a/docs/features.md b/docs/features.md index db8a236..f8424cb 100644 --- a/docs/features.md +++ b/docs/features.md @@ -13,7 +13,7 @@ Update offers an elegant and robust suite of methods, carefully organized to hel Visit the [getting started guide](https://github.com/taunus/getting-started) to learn more. -# Related +## Related **Docs** diff --git a/docs/installing-the-cli.md b/docs/installing-the-cli.md index 9ce27dc..7f384c8 100644 --- a/docs/installing-the-cli.md +++ b/docs/installing-the-cli.md @@ -43,7 +43,7 @@ Examples: by specifying a task on the updater. Example: `update foo:default` ``` -# Related +## Related **Docs** diff --git a/docs/installing-updaters.md b/docs/installing-updaters.md index 1314df7..e97f148 100644 --- a/docs/installing-updaters.md +++ b/docs/installing-updaters.md @@ -20,7 +20,7 @@ If `updater-license` installed successfully, you should now be able to run it wi $ update license ``` -# Related +## Related **Docs** diff --git a/docs/logo.png b/docs/logo.png old mode 100755 new mode 100644 diff --git a/docs/symlinking-updaters.md b/docs/symlinking-updaters.md index 475f950..15ec070 100644 --- a/docs/symlinking-updaters.md +++ b/docs/symlinking-updaters.md @@ -65,7 +65,7 @@ Then run: update aaa bbb ccc ``` -# Related +## Related **Docs** diff --git a/docs/tasks.md b/docs/tasks.md index 830d2e8..15f492c 100644 --- a/docs/tasks.md +++ b/docs/tasks.md @@ -13,6 +13,7 @@ Tasks are used for wrapping code that should be executed at a later point, eithe + [Task dependencies](#task-dependencies) + [Alias tasks](#alias-tasks) * [default task](#default-task) +- [Related](#related) ## Creating tasks @@ -157,7 +158,7 @@ app.build(function(err) { }); ``` -# Related +## Related **Docs** diff --git a/docs/updatefile.md b/docs/updatefile.md index fafe66e..3c45262 100644 --- a/docs/updatefile.md +++ b/docs/updatefile.md @@ -1,8 +1,22 @@ # Updatefile -If an `updatefile.js` exists in the current working directoy, Update's CLI will attempt to load it and run any updaters or tasks you've specified for it to run. +Each time `update` is run, Update's CLI looks for an `updatefile.js` in the current working directory. -Moreover, Update's CLI will use `updatefile.js` to create the ["default" updater](updaters.md#default-updater). +**If `updatefile.js` exists** + +Update's CLI attempts to: + +* Load a local installation of the Update library using node's `require()` system, falling back to global installation if not found. +* Load the configuration from `updatefile.js` using node.js `require()` system +* Register it as the ["default" updater](updaters.md#default-updater) +* Execute any tasks or updaters you've specified for it to run. +* If multiple task or updater names are specified on the command line, Update's CLI will attempt to run + +**If `updatefile.js` does not exist** + +Update's CLI attempts to: + +* Find any updaters you've specified for it to run by using node's `require()` system to search for locally installed modules with the name `updater-*`, ## Creating an updatefile.js @@ -39,7 +53,7 @@ module.exports = function(app) { }; ``` -# Related +## Related **Docs** diff --git a/docs/updaters.md b/docs/updaters.md index 6a5bcbb..1681481 100644 --- a/docs/updaters.md +++ b/docs/updaters.md @@ -12,6 +12,7 @@ Updaters are [plugins](api/plugins.md) that are registered by name. This documen * [Order of precendence](#order-of-precendence) - [Discovering updaters](#discovering-updaters) - [Default updater](#default-updater) +- [Related](#related) ## What is an updater? @@ -183,10 +184,11 @@ _The only way to register a `default` updater is by creating an [updatefile.js]( When used by command line, Update's CLI will then use node's `require()` system to get the function exported by `updatefile.js` and use it as the `default` updater. -# Related +## Related **Docs** * [tasks](tasks.md) * [updatefile](updatefile.md) * [installing-updaters](installing-updaters.md) +* [symlinking-updaters](symlinking-updaters.md) diff --git a/support/docs/layouts/default.md b/support/docs/layouts/default.md index fa40124..3cc8261 100644 --- a/support/docs/layouts/default.md +++ b/support/docs/layouts/default.md @@ -2,7 +2,7 @@ {% body %} -<%= hasAny([related.doc, related.api, related.url], '# Related') %> +<%= hasAny([related.doc, related.api, related.url], '## Related') %> <%= hasValue(related.doc, '**Docs**') %> <%= links(related, 'doc') %> diff --git a/support/logo.png b/support/logo.png new file mode 100755 index 0000000000000000000000000000000000000000..3009a36bcf98e418c87b84d4e55c58e456ac63fb GIT binary patch literal 100073 zcmeEui9eL<`~NLMCuvbpiBb_2bJ|oyC6z2?3kgH1BWubQW}ed~(MpyS+9=7A?8{6^ zolulQ)}cf(Ln$L;_+9t&(D{76|H1F|`poNfPMvw~<+`uyeZ8;ce&&SkHrYPqkGX#! zgr*qm*lLE*0?8es1lzBq}LKIV!=ndQj!js59AM@J4jjovRD&Z+t}*}0~6grYijRs-f(nZ z)uv*}i`%=ste61|KD&2B(A@jAcl5?17r)QsENk_@weT$VGi|Rm#ghEe{$7nc38!CZ zB$dYa2>kd7z4kFI*|&wtQ}HNT@Yo(QJ1vXmCCIj|wHR&n(sh!(U)q1HjUhkEeO={O zI+*6x@OmE}k3KH&lE8niOYA-Ivru<*=%3!dzjZxzpV`VB?XT{g_zgcqFBVaw216~% zl$z}KIP|joG*VlIN9pT%_9s~R(^!q8Bk8&talVI?@pvLxCXbXp%tvKkZ~Fb#XJ0j= zYkNneIcs>^dhPOxrC^0E{^Zb&)cI(J_s?|;F9zRfdGVEXifQjCAr}!?*V-@elshpv z!Yxl~z9xys$7qd>U$1+%&dyFEDzSyhy2vSf}Ihew<;SkY6S2{W`)dPkQ)@6fiuNJs;2VH`kS{w{wv2*ZMgk;PP}QbFf(n^RaVC zM5pAnjQaNn%Zl|NbF+}f$$gr#ZFtifJR7}OOl=y^eLZWyX_WDS>8YM6N^@u)`T0h` z<3SV1WK;uw&r@NoYIKPSv3W3wmghF+QCnS zu06d^r)4DAh!c|@lycwmw(}opr16%U&_^mxc?U-tKUPsbAVFxpIW1@tj@@?r+ zsaC&(q%A(qATh;W)|_A764G->ko!mqRhr~7Tc3`65B%`hHVd!CZle^$2?YiHReZ6o z?w4?e$9`W#|L~P2);l_>^e(?)F&^2slG@dOQ;O4c577ZX$GmELYjNa%%RTRWOE>LQ zuNJl9Mam-9MG{T}BZaW37u`71|0aU*_Mcq`FetSIzqvb zo}-XgS(5VyqKVi|E5^{v*iCJJut#~wlbl{DEvO@S((!fN-PRo5Eg3W=Bvk-B+;f%nlC6`)fIkisF zoVpJpXxn{O7el6~Zd_cB1@%b+=+oW>QG_ebDZL}Bv*w{oW(I1 z*eux4B+fl;iBR5c)>r$G#;)K)Gw?p~h9oN4CjkUuJ>6dO9;c^HK|#|y+lvd4`tSRSAB-ezrbU0+;pn0ZFZ zjDypG?(dEi1m0+^;}qa1$Tp>xv8OZd7Ef(x<+7Gx#kA+~Fb8?jsRdLT99$ea`8s(3 zbJWyH%@m<#DrBn=)>3XWvR#cy5bdxj3B%PK#Ze6Gg(u`Z{C}dCa?{ zN;z8#GFUwOweIeCBl;K8#B8NIYO{gA1%_PLF!*}WqGBP*opnkn5!Z6mj;+>_F7 zGg35FubR{e-HG>{cVj)4cM0VzSyxj0`8xw0L1A>|oL|9H^m!{{o2b+nY(|q-w^ulQ4KuQ!7N|aBeC|IX3&oCPcV%dXbt^AujP0T4RQ-NUk^>T2X zzC@N2f5dhz_cewh`yHuU;>}LW9=!pbzj{jtFho$*+ZVjnr_&1kNQfkDBF_?j?jAS@ zXKBtayNQ|DrZjI-M9wF0)fi#!aP8)Iu&bAw6ZS6pV@f!KrqBjKZ#iC7<;@E~jg@na z2Z@^N*GTF7SKR`qBuu-%7fO>eIj5+Zrl9~vqvAdpgPcgscqt?Oxe4^}dE2T08)VdTU5v;p_3&{puF;lFf z5FhY>1-{sz3`&cWAs;DE2&3(D)y=|bktaQ(weFEix-oX7rX@HTAtp9#=L+u*v|X%)#aU+THC_U1kt0GgZPqpm?X#9a8OmpR4K zJ+O)#qZc<^5(W6f9gG+ik~)m`L72kYGgfpkM^P#c*0h61NhcyoS^)5xN7YY}#YKBk2)nFa-GS%tGH*QtTv( z#TP}T)H@u4btwICI*0O#FE*#+#E@KRA*sXZ5DUS+w5{A_Sa5cj&o8@CU$U+tgdoM2^xwHaLBfQKPj^Tl`$(XA;9<2PY6syQn*06VBJ{0Q7spn7vXVppYqMuk|w_Uf6v}w`qly-f& zwCr?jQWSRV=zC#`TLT%s`i_%c?=N15-ILgU%rWn4$&vRscG{bexZ1_pi?QD)6~{@z zezT~VC z13Zg;3&K@M+g?Hb8kkFHzI(x~qYjWqf`r}Plb-ExYN4A3X_4#JH(D(}Jf3F`gENki z^3Psmd1U^Xd%~x4;XAhEPpf84I0zX9h$UpcN^`w`?!oZ-s$O$Sj+hJGw0%CTwZOy3 zPzi&K9%VKw3=kIFM7bHw)Q%DT%N;5nj8`I`JOypp{d2Ov`gK^@q^PkUKKa?K<<`!G zRVvB6jd*Ej*`x88DWz9Wk`1u@2G)Z|E}BU&gziCF)Cl6p1}Rg)X>yH;4PyW!nTKL| z;-vvb>Qq1%Mk+@m4umCHWvtSTIfYa7=mtSXurov(L`b;z7cP(U-H%;4iL3?K_wK5t z^>+r#wF4tVDt0is)tM0orP5LOFWM|pZ`)KYzbQ%8HB_ZyWb~=U4X1(UVXLw0N!FD+ zk1OvWsnRkkmLQ4jSG~Z#=P}OZmXYvMi3S&T=>-#6lfrft45xh;n2klH@KB5wBErB@ zd#YZ2Nsc&7WE;4B7wlW>eh5qZoTO5A9JqMLF2x*Xc{+~ch`sckqhzxyHnP`FQrMQQ zDNdJlWgYYCDFd;W`oIWMv7DKtV&yF%K+D}4nCuF(kkNBYN+pqy?_rkkBoWFs1^*6N z=;mOHWu?DeOX36^o!Czr*o%g7@}+cO*RNUFC&A-3NZq6;@&dKO18(WS#ebzp{a1== zQi>vLoZ!1se2%QItFs>bD+QG_b3Q;N=9pJYIW(ClRy9^-GzsWFb3umCoIzp;`JebLWIP!TaK>%*tN%~d)L#!tr(sWa`xhYb zTS&Lth*SGp17Qo?uzonS0@}V~tua^^ie&!P7ad$bqAKNx-5$x8{+BjeNo_6PhmkDX zNCpeUo|iwVSrh6V@)T#V-RU<690Qq|!PFT{{)%WlbQ3Q}b{p7F@2J_IYI73AVn;x7 zm;WV=3^EBNPS=NUg#bP5)+pS1{+Tcy$*@r*FVOarK0Wm*3Gk$3=`BEdL>Vc2Wwnmm zXiRI!@E+2N^rw*ML>U+U3I5I=CZT$nL9U9JU{al9qe|pl9cr#5IgMjV ziaCc2H%3GT=1UZB)CI!O95K*|=`uMOob0ek&9Dzu+0(W_Uxs#y>q5>iBxi3UX~Js)HCt{9Ir}U*yX6uk);2?O z_5^aa<|W857P_%AunVK`YZ#1U>D~6lB!q~UGYUZR>jXAENAeU@Z6Mw}^1Pa42wu0q zkwjBv6ghhextsKutYL3na1$z$gib&UL@5cdqAe-yYEq7Z?bNYv9UJ%fb@HT6C-;;N z=o}us)Jo>0D8t%r8kRsloRnY=B>{U8WkdfpCYMzpbHv#ikhuWG9wko+StO=vhq1^*RWa77!SyW; z5_i)>#$BVWZ6Jx zF{0S+wDsUY!@m}>R#93Wc9uC)UAzj8NpmKTxf0_4@Gn0GnLRdyn+(B!g1gm z+P-lkuy_4D5(u?M7{o&2KFK<_v!^!de&#P^ky$ag_1%hI{#ZR=MCwWobOpOaI#6N~ zR`*M%6?o!^8-4@pQtm3&<{rEutB z#~DKFX`!1g1!eY9x2Mwz{E}d#37M}I#p2|{kxJJf_dWEVa9uYMtIF#brsiKUttynV z!|i3yxbjGIdXk=mL$yAflcIJMVb;mVy+eFXDp1Song$4@^T~3 zkVpo&?F_PN0t=mo#CUn!fCCou0x70EG*emMJM|dLC>bivqFYCC;ROa=Cyu4;J4`~t zZ!C(U**B&2x>{oK^Tp(ICmbPv8)NpDe-W`xj2z(h-^W~TAC1jL<|OuH?@QY-r9EI| z`0|*&5$|5Vpcopn7&BBL-vv#B*=I7wLL_2vDn_&XH0qAbcd6ZUo3p9(WVuU|L0iJJ z#|tL^@hA~ZKKGc2oOowBIv}Dt`O=zVnS;jd(#sl8x=kICY&o^bEQoXYqM$zTq36WA zd-$Bx_@(__2fAdNHE)4l6bzpL#0Jx@x4Q(`UkzMHdJ0mw7uDE(O>llGi1cDNcGwct zWdAus?+46XU=pZFtfE@B*@|3Y3%NqX^>&-CFmC5t@;P0iB}#DDrI1{q*A%ZXa>+c^ zkegm!e2MgDq_wAl>+?#u4H9K`?o!;`QS6whp~q@qs8K=;{55CjmS87-9z7FSjsDsD z+@YHqISGa&4HKfrZF$j2+_hK;9$tWL)G@Q$Oz^;4;|@3vOuPKfJJ9YZYdSz=w<;bW z1v_kEHI$5rAetc+{3dt_5JrsVMDT)`%h%g)Y1ehUzDvzV#Vh!RHNNvqVJ+<&XJN_D z3zG*6v`43uQ^YD&7v`;)!`ccfR9+%i$h+Pi&}G#qJEn%MQ5|X5a%{r@KcDrmo|M&+ z{pSuj6<(c(B}C5;oAc_Dxc20L&E$Zk$=1xLKP~geG))ufu1?R_&;6juB11O8OX? zSa#JlVaO7W=J=2zFX4~?H+4$N`uyAuhD(FTA~2?$GB&2%+3W4y--nl3yOX3z1n&)D zyb4yu&lT|Vb&oLSts1K+xFLz7=DfRW@Wk2V<15#l><%+cc~>blbCw>?FHXy6$pRdu zPM!d=k>70cP5Z|K?wFz2Z{D8(ygFboTp;~Y`Y$-7Y@@>t67fjK-9hwE^ zy9(yx&`9%5O(k))L#q_?b}KY6WqbtCxtIrm~1_nn_bTs<2_C&Dd-|nP^&T3BHw?m!4b7DVUqk6E(-06`|8Ji?|zH9mxq9X!50 zJL(DEDrg(|upupnrN-YvMLYKN_iOmj;A_S{f;Wx1`QPZ1dM1FyJ-3P=_I1ov#QK-2 zCF)fniA|sx_()ag=Z}7#sX-2~0SBPZ9V`B5?f^d`=c?+{n!28_?*JF{RKL53o}3zV9## z(^MPZ5A6L-F0_Tem10in?7*j)ReBB{EtY0jm?L*a7H|z0YZs_op=7r~HzL-5V{)cPYdk z5HkVeaGMH-g->?#E>BQ4e0~5i@UrE=v$e?QOOmY(x!Fw~V{qdf^(h0-#&&00lSljm zj%d|kKjycbxV0T{zu%=xNT8_22V|`gob$ZUYnm9JUWfa9SiXSRm{5@^_jL6r?hL_?}m zA=={gWttuNBwoPcP5%?2#UodSFXq*dH7plE`g?okeD3dxUWNPymk zFcOulo@-0-BU%@3iE*+A%eVvMWZgB4le+i5x~C_Rdi0X_c`1B|_4o&t@q$va|DJfi zj@eYSMC(%cHyw`RC_wDj!j=w+!hvS$=6s3)=#pR4xg(m%{_3E}3$L(0N<^4zPl0Hd zPuUWQe}R<1J`8H0{n*x=1vz1g*-hgq1+m#Ss_<}w#|oG)&D{!Z+8$ezZ$1GYC%s`U z_^{=!M++{Q7fk~f4LOJ{RXi3Y#$p*^dc5&*vms#JYbnNhPnqwVj~m9BLaxvfV-?4H zBA7fuA)G*FsKd8|Y)HV}+XGguz){N!whna0>)*iZJGkjaP!z#l+#Cs6Z!=DdddMK5 zm1)*OwWZ0#~|XL;6FQQu3tP~}_Wd>|5c?2zi1>G}W)Vc+*jjFB%onycpb{&6$TytL~=C4rYyueZllYve8? zo8*v8F}eJ;$^JMJ##dk~OpP+%cdDeGF{UI`(w@`=oqwE~aa1u9{4ZT^*AGZL`JPe( zD)F@Bby6t@C(~prDE{K7nXRW7`F3NbCKT_p#D^WV5CU{bj(CF`JYxH zp1FF)5SU}Y*NE7EKz)HL#n% zNE&})PQ)c~)1Cnj1!LpMfE!N@&KFvY>E7s{6ukXIU{snNx%~y4i#3Ul`4LJ=9y$Ik zgv*hEx!8~B6THu5S2OH*W8o52vH^fC=V1Tic>itckQA#O&L58nsPdn2{ThW}`OiY_ z;Pf9^5iw`)RU8#CV1bu|8*Dl^~oQ#NHDJ*XQ4JISPB?B92<@&O{f2BVdPf8J? zTjbWj!+iJL7|RHGvCrSPqJAfgi0Lf@B@XuWk2ry$%>ani+gEUt#~?y@{wu`uPAo)e z^nVF~7V<#R3j#XGfR^FZT^kEsL+hL<`-+dJ}X`d(Y&xRMX#6_ zsaIt$rYueG{K|d@mV!fy{*(pAl6Nr4WW?;TYFx368o?eZc5mR_1vQ<)xj*Nh9Nm+H zX-(sn^rHRtGq%@tz&gi2N0(_GJziuX;^YPmzry)q2tjK!?S%AVWQZgSY%{lOv`Om4+)9QVU2`8Q1t)IYGjY z(|$tqCFIWn_wfP^vFQ)Xh3_cJ4)Ie0>KEd`*!eql6E`1}S^^X7fwRY89mjXfY>n*P zJA+!DHaE;02zxTPx_qs}R-kP~fI%%3H#$G4BM{$?LrAa%8_A!+^A#Y4?g)?_O3(mA z;LmgJm<@Xp@be1*f&0Ro-@%{064(&J9^@Z{a6JT8Ht*9+jf(z-<7~lx7bIWMAZ;&# zGE}eywXR^4xf)Qbvkx?1ZK?uS%ur|CkzWo?12@Z4t-tXI+A4?n!v(P5UyoodmGp)Z2$M zh2!92PaStr1ri$)IUNIy-iOBg4=HqwDOUReR;=GAP;Bvkc!&1e-!$1r!#p)u0D*2i zX+!b6q9*Qu)oloX2!{ZN%jQMPe|rWo|K4Pt?ih>naY{b8#pIju@7cb|>UpgmO#QhC zsvJ;VMz+v~z1M>^R!xAd#weE4jTQG)@xMeL829VpSP-2Sh}F)L9;a`18|1`ec?KV5 z6E=;|^6?go<-~yUajmz2(-ZmSs`kIRm*cM^?d=|tkB}tf7es7BRAc%Q$ky;jYLlH; zx*=~FfcfYlaJHBmkOJn6Ck{x@l<70%tMzUoqT$?x_<-$ky5)C;glN0R&J?o@>_wmb z-{uhKx!3q=ltE0pjL#IXzDz%$W#V{Dplf?v%PE`zxv?gIn0*I(L9zPpIv2^sLUwkI zY?Z`>;oQmms(I1xfX4O@mj6+%I`mow#Y_QXu@^(LsN(1fDszPZ^A(D7Z->wcC}snG z{OQw72eIES( zkVE#b2^5or%LBP$_k3Z_^@NX9=8ZnLj~(0SJU;f}-$o2?UhVF;L>$p~ViUgkKZQM{ z6nxx%!o4A~u%Sne=O$<*LyaGC1s0o#QTtpOx_iH$@q*+fD^!Q1XCb+b_@wvTD!6r5 zDEp$physp%G}%XaUXG=i3t7*2Ohg>pfAz2j>tX4C=>e5=r+s29`~%U$M}R2lU0`0r zF_uBvWzDEu7Wa|dRa{PBbqX;+L9^mAO*%gK*h zg}7nvErGV-=*_-_UH9L53f14OxZVyW9dR=YTubVCPLqA~zfmtk_JKF{OvvqMxQoDE zFiw?lk%ZM|jn#Hx#eb?zy5l3VF?r+n7_m9l;LH~e+j_8~#u%FYSkVZylSi^p7zCh50?kE6zm0UTCC z9Ey{>bijYx%Ce) z0-MHqj8GAGj3H3|3g|yWa~y%_wqMQT=BuDaJ)FZ1Y#B@65q)JOAo3&*HW{v%ddBM$ z6mw=w@p_okdfusYj5n2`b5Q=fH0cS$8?E}iQA92U*bEIUy9s%qj72{yzP;BRIW)>> zA8k1VjaKQw*7R$TMP0#bR>)|T-$?Iu0dp0gHy#I(`guSroDU?{4|*0CYyGU? zjs|_JO3h)8X0bZM`ko9I3U6tLX^afijkL6~2D?`!;O<{#6zDAjw>Q{X(4VO2S07wC z57O>Gq7sJl83saTqb@W5LJ2N8)Jq-REZ{c)Y%f*lf@vkg zskLoPzCF?=eM;`mF2gU~-lKw-33Yy>Mm4_6OpmxVjuIoUlb8Hr1%K~6Y5?|@x`MYi z$J2Plam3c5_-bXv3Cm4=aQ{x_2ay zFK#aTug0E16z46wpI#b#Bsf2~4(Hfn7bDDzY)_6p{xQPm7E1Cv{IRyu8EM}zL-g&# zyWS>u)qXh?@u1`9(COY68i6a)N{<9*N-|4=duutpe|(2Y+SoVx-0*~xs<;vfs%aiN z9z5Dx!vf7LQjp7qQnX2MpZvnNg4R;u46LboY@Ca@0TmSC4J9qj zteY#;vUZZ#))UsjXPv1E>w^XM-vkcQ#vIgViJ{4s3|gKR!Z=?%5&(W+ww(PAg3goT z+0C+zSwkR(l--Ja7Np;h;ud`!hE-wtf zPF;8h4jFV64oSAydY&r((D!}NgJgbQ&E_Ay4eiLG%6jzoV>N-SY5;p~ePHh+P}j{gLF-l=dLnmMUc ztJIS#wNPN<4-i$GzyQF}zVZ^@Y4^MK<14a8wU%aydWj`Ow)Fnd+i3D;H^sSUc=;2Tr zv@x26o{fC0Ia&Z7y&sR3AxFDZWY`^?8hkT&(F~NNmYgS8E|Wdr)PI?6O{FYY7-)^->rmg-1QZ?!oQ~-sh~rPXGz(lthT$3RT8l z|8%DP4(KRsGX%Cei2H2HMwnE$xy;dV_^b7-7>ZY+_N{_~2i?8}mRN=R6Eb(PX>Vsr z@tp_mu0!-ue3X(tdxz7|X_M0d9mh)7uIY92Q^X|VSh|Q+{a4ql#XrGt^5rzoG zPDnhl5kKvVtH~mNHCxgfek2cvEs2tsc@%Y+hRoX1egp4wFNJn1;v#l-f02sB)1;# zRA1;bSX5mydlKr~xV|i_@7Gh9ouP{j@+UTk6E;YOW^XB2V~I54tcULCO^u?_+fA`s zftWiZ0SsF(BJPlgNIo1IY{TONQMMyA05$AfUIp)`9agpP7ErY^4tv{nU{%Mv%jm#+ z>S1DN-5E$ij_FS2p9URrzThP%;3W@upG&Xk-#G)2{g=;OA>X?fntq|8NbVMHbB)-i zl}ru=bKl0G_y?Q&EUvf6T+;v2E~^ID`xroUBV3BhHUoxXa4E*dSv+_LiJ+73oCloq zTT|gD2aSFfg6!dmP0hgDFph~z@TK+?y&y3QF zfO0(uGiW%zyO1JXb@=6mAIrEbO?SS%JYqMw*EK`HC=lGo>rTbWU5S-T$I9&svFx&H zXoVKn3<%qEKTofDR8+#=~~wVbi{D5U}(d4m*U2BF;Cs`oj)nq=Xw!cuO)eFE@a?V`7a5i{6z?pG20RvTY?j40Xs|4(}Z4nj$HydaRun3jUHvBN;vT!K{L%+E*Ex)Jr@cJRMOfrF$ z#f|XFI5#0eALtLXRuo&o3*K^Tox!RHvGK)s;sKk;0VyzG3w0WLP)>r~$f_UC{L=ub zn3&N+mqe)^#Rcq>;4ZLC%u>9J@@J}0WcLBfv!|1#2i52So5EY_mEn&7zKfEQ2P#iUgVYk|m}DmSYjF*(FF}kvphmwAiJPkC(dyy{wnQ8oXNl8t_-S zg%kbII)pt}{Bznn2EOY!L$`(3oB37Vhh_jLO8{0W)K-xfFx$X=q9Y-Z?TY|3w1@d!HG4>3uhE_bF)zv4{?Ke&d{!SXs`dADPE z>$CVbHL}Uu;UHV+kZlW`Gvv%)1!yjX!gYOIi7d}?!yXNyLr$-mF z<}2UV=U33Liy``9yz(trnG;K{oN3%u*T8gd@W3L}pTFMj957-^c1mw!K+0f1R*|Ta ze!o)CyjQRdeo2A5Q2OwiNcSVb7Er=%j87Si&nmbdkrv8AbF6P>_CE}nq3nU|b%2Nr zK*TFZeICN=cH#I!r4mC{`!a@XRmO6@EmXL-Abb1RII_3XIiET40bpAXN3B)(%6b(1d{3h;Cs>cLNlO&lQ;1Q9N{iTj(1P|6P+_^;VifJ!#m@28b0npeB9~yxc&=V zLG84Kd*FS@3)=wb2XRfU06>SX-W3p3E9wb;Q{AZ6_q|ZRtAd9arRORZ7>6nCO(x$` zwR6dr8`!d6_X^-zH9H+3g!(h)>w}K*oq7*;$x7VJ8z!S#Ylb@C5F5jt92iOBxI_J9 z)nyS#y|E1VzPDt*;AocOiG*tt5vRpCzZEB76QE~gVe)MykPNP8wL@WH-0^RHv?8~z z!^%U(sMZSK}lg4QPV~=wfdNob^qCeqmD@sEOtY_s`knYZU&e+)L$Q z-lI|s)tQJB2#O(eF@&u?%}hV(4CJ-GP?hhB%MG0Ifk`EWDda7h4mLD-16_hGLbL7m zW?jX11sD`;9kTUknKrv+*OCcT_Sc77hJB$JRcX)$qAjcN1|}viJ2|@{xol$08M4F- zO3X2XETIH*Z4Nfn!OkSoJ-32xw*tsii>(QfaMR#m#}k_XogL6B_`bG- zF+~`y;(7$+)q5mNEl@9TfD7sv8c+_W5Ud|l%#{_V>_)Qzp`kT`Nr>Ksqil4O>xbq; zb%-5_gR3s&!t7M2e`Q5Sr<5!Jk56%imzNB&F7{^$r3AN{9(ZF|kc6#$7VMmrEp$qR zW)if`>E0f2V@JNGfjc=X3hMOvN5OWBaB{7UL6HR|N5{ZX0|41`IAcf4T8iLk`s>R! zG?<0)qTddV_B6k;ocEzXob$9>oHOlN#$mCDYdJIjUU*{J_UdVWFPzXNS$!U@-1PAp z``+Qdoarmq@f-L2dc2E&^VLPh*Uo_22G^s#Uvk=J6rm#aJP8pkw$F!Hw+_z+ zA4Sho)cY+w5)Og$Z&gB@LYC%w_Rgi*jXK*aO3?3tvgJN5?8O9L>?_-S@z!DdZU7oI z7t9@uHhKe9wr@pJ}?~>&z+IUNI1OtQw+Qhsg+ademxRYQf zAQEZR&2Apmtc6=U_5~1gbgsrvMFqwXvswRh@|Cg4rETh08(0V7&?xxIIWF^A!P)>Q zwK&0FS0)_j4&`C%+1_a?iE}Ls8))Di`oc?=eJes=iVr&U1m@O!8dwBlemqsHbdO|U{YzjC)6qGv@m`uQ10C*_XjNn>^C!Ust zZayPp<<86%3RxtsH?y^RoCb$ z_(*+m&Vn^nwfg)}@FmUN9Zzj@JI8_NQctQ%oaC(wh5%pn6 ze*k9&h|RduZl2n09rBHt`l;E6N zF=o@Q_<=P88nK}~#MU+UXsya_$gS5Y;V5q0rFbAx4h2KQpIt3|^h?b@Q}G$E>!<3T zO9_|>hup-^*5|io{hYZQ5ecffy`4jDa~p-rMy~Epe<@mR1)L%Eh35B8;j<2SapJ1k zq(Lzrgf+A8zun4*PBdYZADmFfkeK=Vb)8`BLz;E zA3CfXO$F~m)ec9JY@c5%i*{K7{B%31sl=hiYNY?Z?h%c(Xy_j?KO}xry+5SV9g4;R z`sG+`NSd{BfhhYgkdZ&vZAN3A=16zlxB44~(+QnFu_fd`z5Rr*6bRwo1IqXU;6(9MxREkJq}_=0x}n zPv4b!@SC;HP8mW6imt>eiM#+eP0(I_n0^wLXOkhgkhnp9u~yyQRwu#WYu$shy4NYx zsDw_a#?9p)Kl^-VHlBoZ+SlW4zwV%K{HZVtJ9+jYw}3@9LE2iVz^Anl=<`3k~DhGm6y zLh-#RAllk@&Wst2mZ_nuQKETdV_^Vy^+oPML+0@QR2yyHt}Eh+t~zHCp)kMUGuGNQ z=N!huz$EfiInQGI)oUGtV*fbyIssL~+bzvMUBZ|Hra2>=h3MD?IMy-yz{av+!ZUR?gmQhiGnHyXzTn&GZLu zc5b?EaD&-q+d1zhE*=JHElN{^-~ib{$k%1CcTn`GZRGh`a~%|NI(svI#N zPXy{fUFQobRWgHL7Qs(WgB&7Snapl`lbTu-RRV_stAIMg6p3KW8z!MA%{JW0=gvZG!E&b? z$+m^~6-MIbJxuH>1^6yG9pg&}ii+6@_f(Y-xgml-EgUtnc=NCfSejO$WbYfoZFT4y_ z5n*?n1d5qyVJUycf+wPpTk8D=IqLaUcSWF9%;gLAes+CP^6&@a*^yzBhuT|E5AHV# zv}26-7e?dh%OCxG=UawtGkCh7@}g7WxiweCp;BvV>2;0C8kn2ps*v?_v0tw#%8*gq z3hh5>=ZJa_@UcF_3hR(BQzmK1181-+p3NEhvvzrjk?WG_XxoHfZ2q6GC9x@Xd7gP=1!qNf- zgB#~U;)5IOzG?aM-iXrYVIG<<|IFmvAID2i`=Fe(5tAN9Tm`8^J^`yTfHiAn&A6U}ex!Mw)G0YE+ zshh93Ur_r|vF*=21^XXrwV{kZmEpLr^y;?xDeY`7VU8%>o3RI7=EHqu*`pg3<}zqJpGgKPCLn-iA{F8F-$y$;i8@;e_W*JE$$A~(!^jq8G%57{?OM9*DfA13GE7f9j#7mMg$t8HbQRo)_c1&GPC zI}-U&LQ-0P?(or`;2Ef;`=lhQ?`!K;!zX-xXjb>*d)H!R-C-`h{;kDZ$bdSWbJUNB zu-7ily;%B^&4^zeI+CN_e^FsCYJnGIi1VSM<=Uij7e%yGn$+yzksE(BwRolP_9C-L z8L^X8b1%L~78x;eQ4?MbnN9H50h`2Cx2)~fv1QJcMRcjCOr1C!9uOqm=ef$YKtx3m z-U9zLMKe}=Bf)(3L!tcXqA6@!48Qw&b$XxmZ4q65Pv&c5@@I1+kbj5OGpeM-CUD#% z8(;V|)2eC}FU@;&OrmM~#kDB?=VmeF#&*Y1FT4~2;GcU){~C;VEn zjCoJ0{9C1s#Rw~$%lL(!{KwMm5HOdxN9;q-yZLY*3B)x6jFI)zq84m4II&~ki2bsl ziswh_qMe5sRod;S2)}jGyWZ_pR$Ht3WF(hsnR-+P5-&vLrc?|o<+NrUnOTqgH{zL! z@?`_GTZ+FnFsiCHen&;`AO-2WP+AKYH-t!GxDT17p`z`kYh*L$zxX~Eu0-xO>Da3* z5(Gd<(fyVo+GhqJ?K^y=YlqCXw<@7vX(ayFobIEhYsgv^#gPzbo$seKg?MA`p~zpe z`RhLU^F1#Z=hlR^3bwOE(C|43^hKv-?L~-%J2g_1oCIVK9o3(KwD!aCYXSi4Hsk!@ zXAL(`Y+p3|rlu;mW@H%kU4)F?pS!tRX()UF+&N<77aK$u3|4glV)h)6Tj3 zeiD+3^Va&3ulrN|xsElhSo0`1SA^&U2=u)btzDcGQW^FDv1!_6L$_~4L3x5QtbAgV z`3`0;gVm=HPs}_zU-XaU8u684=WG&VAH}l1Y*U+`%FFus5cq1y<3E&SX9j+f6K4(7 zC3VhQOINq9)v&#Ab$AzTq|qOY&cpyJ+H_40xAuP69sm0W_7hwxplW@Wb1tc+gWsY1 z0?n7#RXU-ZR)&XPv4hX&KbT>UP^W@skPG&ZfFqmPe=CsujQ+4b`sY z+~M~YVxw*M)cfJZvOX)Uob^Y}?l5muTr7fq)bCI2UoA8u=P72Fo;CZ{eS0-JuAhH{ zrIWnNNuU-w`t{uRj8j==_iU<1g8TlgE%G3Pjz9^EVJ97N>) zglIhYI{CB+p5q7&hI`5in?aOIunA$5s+XAi|zE58K zf>U9Y(RYLj+^@!xKN{(CO3D{?gC<*gfJw=#$fnjsQ*@5c6lLedaHMHGJ&WU;`<45j zDx+%mB=UC}MOR#S$o6*AYgg4=R+W3BrwDy30RA~wt@bil_QKw>Yj9wNZUfu`Kdwu zckwQtvPmq(C=tq2;AOAM&>is6h;wdO`zxsYVS<8>UAMjM$ zcO?#yJ9$8AgG)MmTZV$PLb(qXuT?7!Io4IYm}nCyPDkOdEitbjt5B;wBm;FD&SO@b zzH#ldbe|z?7c%vVyM4jI{PSyMCbBbvK~jH+GSu1h()Q1%UbgD7H(leGIsf_IouVAi z$i{&5)~y+{Y6>bfo*|kW;8rdqFK-v+kjI{>ts;NPkhlsM7}{V5uW4<%)%039-gb1! z>4(<>5+>2}jJzb|UflfhV#PJED*GMIK8XveDW3(dYh%GwAeXJ(XBD&hg2tSwqHF}M zrSMYYRP2QtZJoPa=5}VrKf?jMdWHV`R3}I^`mOC*BF}f^-!T5Q_{2lQj)8f9A}M$k zna$45jrj4Bj_9qtmF76gpyO@HF1(9x;l=_+Upq%vF=?=uLZU|(Od#6Mg$>@W2L?>u zBZ3AN8V?Qavv@^%5gJOyF|=gI1i5t`zH5Iq#TLw+dZqsU>H{w&`jPlEF+ZYBA%@X( z&Hy53eYC}4T?<^3p{{fYp(|^e{&qC?I8>9QRG;6Nqaw=Q1Q?R4vrJtC`C8zmq?`m< z%cFD0;3teyzV&?P627Yo5(UVv4Vv-fweh2$({>>7_Dy1_6w>9U)A0U=3Y56xsVUtb zvbBTV{F};e(Q%Xf8}8b>l2qGU7iVg^popoTM5G7^VWnEj@80KH0r?GZfcz+aW_!)D z%nDy&d=!5A(e-DCZ;rFtiQ^$Y#?`_5vor4K@5 z=5W}}8b5DWCH890Qx;MZ4#qd`Pt@QhjTV$`v?7pt>{6x2VOS-i4y z3(1TEDS=kpDk5JJ9-zX; zs?+zag$qJo56X|e0`yhy0*;XScxiPg7aEA{3qc8$bj`&~vzNlXb8(|G zYgxk9YSsJOk1i7@7|@dFg7{nWP~jp(9ygn7MQdDDj$ll&v(BZgzvv$IP(m5?z#AW0 zrqKav=DU`>S-;&qU7U!AP}jv5QJsI`HJMqx zZ=6lcdfmX7f;K`mTW!uwW;XAfgzA%4JG$1IG-2HnR^TbBwJt%i_6{pw&94X7_vLC% zKi1&hm?e^^0>pMa=-d2?GEJ1R!sSt|qn68Le6p6*d)K<_4`wzkoK_ESISxD#<9n>y zIwSEA6am$`KCua9zDp({`sBBZA65()Z^u*W@4;yU_d$l21I`kxojbp5U_q$;I=+J3 zCSBm#>Z+6YLMCI1%Zb{`$$@x^o1y8N&deJJyEjk9t%27iF)b~MR_?vx#09u}>$;rx zk8j0dM7J$zRW7(UH#d~D@Pl(H`C*Xto6TjP0iEvxoiEdL9~CF0@to+@`N2x?TH?vK zcT#IsuU{5*AKt))Ta&HFS74ehiWKiNDd+YW+eWUPUgYrh<4=7l)C{*!)d_lUv~%(y zmQLAO({Qmdl$@4v6fpeESKf8W6wVaDMXt>BA8&2!J}YQZyZxPaFaALH7YKuK-ZmD- zYPnRYz}A5<6+BIE`AIpVZnsDBWQNRU&*sN6gCbq*zQXJh$} zZ82T5E7Rm-1@wb85}Qn{VkWdW+z-J>^1w*S1NwB*x*~=$M4*t?f4^KGBWaT?M4}rH zlk;_Viu#}N*1EAfI^e6f+@5;R8h`mJ0Q^QkZ?le+@qU34SW6VDQ*gC}t>oP3S(Xvy z+$?LjRASz2GKr39(UG#^hRi};mqe$dW9Iv`lXv~nd3FtcN#-A7@t5uzlBfxtl!_pQ zM$3a+)Im&s{B5&jy+4o3l`!*OCoUy;v?ucu3q4jss>BJ7%9OrJl4#ch=}kMk|J2qrDc%)1nGD)6 z)~D^bpMG?aIQa|Iu1n+k?-P&Ke_v@7i3Oy0olAjy&%zt%P~0&8#%oZ`HQ4Qd$~U=$ zP!T!{J4>wSZnYfU9TSlM-G7z%=i$kJSzUpbU_6q$t2TZK*e-Is{;j|>Ye3Hj_52O= z+q`c0&aQzL=qmW%{c)?|CYc_i{gxK5e4rBC^Oo2Qu$l@QDSG!Yf4V4p1Y*Q7y`f{h zTcx1Ez7HaVe76CaF@)P_*{!+S)O$yyIH}2`6H@3RRtL

Xc1xq^44}~8Zr)U@dQ2}p+5SrG9oP`T8r=WV{tI4lgKc+1fxeYY*uEhLx zMD*q}l{Kexmy1fh0L+v&{a7xOtR+I!tw}c>xraNx2nk-64{ca({P%#(T$&o}^d?mD zAh)EvW@ z)d^We>AD#r>yn)(=v;0E z5<(A@UOz8h3BNtXco<;({WOJ?V;al4JH6~qF4VAVCe22ixPw>pR=sJX@&_+}kWRY7<@?H* z3I4huowxb~hNDf#m-E0mev2m%BmTy-0Aa@WgPV=cklhFrZg@30>{CZ?prH%9C9*M; z8`$<>v47c%zWm%-qMQ666Yc6%e(?ZWZ_I*Mj$kxWOl#eggwqbs zP#*(Qn`6wAS&|iy+HC4u5)g2S%59L$yQp_M${Ht^6A9)XDtk^#*1ri3q)j0Fa9nZB zx)#GhXz|O=)YLd{K^AFOdcmVdj6W(|@)Yih=QzZ%B zXSu$nrXlV=*WL$oZdkzFabn}f)7`foc{Lf+;8DN>|JzmEgDZhZVNkk&RcxF3LXL>S{0z z-Xr{cXPU@-ig=o#CMnXpnyl$)1B`yKQ*~dmjg2l@(V~CKhmKmSUx|JqH*FqF`fMb2 ztShCwW{VUmgha63ygq1cnQ!uP5jrF1WyJ%r+*_n*B`y+9*i%<7m|f1TG+j7hgReu! zmrOa-0ynsv`Tvi*FORFKd;4CebCgIWQ|<;yBvZ#&sW=Ep%2cLsGGwk3niO`rDTGof zQZi2=gcD&CA|baB9UPf5C8R=lzw4a6Pu;)g_dfr;|Gn$^e4hK~?7i2z)-`^wYYnA( zHpdMBPFA)FrK5Z)3?wUx^sm*X>c-_az_Aq<$W!V@hJTzlb$=IubilU3 z;o}ynZaaa`_Fm*r>uuzYO|wg?M~JG*)gCbygh@Bu+Siy!`)ynNbDsAD1NBsb@9W>5 zyYBK+y&L1JkJw+jNrmP8>Sp zdM7r3#*rYXSjj{!wp(WD!$!2)MymD|-1Cz^a5IX<#+=-U9=3U3qOFWe@~gYslDa4a z^?-A9mn$QO{@c>kW1#c)*{f&louNM}$L#mbs$l)+(?nz-b~;s_b!<)~#cDoHYpT*- zy?ti#yNT;}?7bxDdzN_;1?iLMj*dwg^jtASH#{Cj7>>xRnys9Q1cB z-(7VvFQ{8HZPeSIt<<;MadD;FD&Y^~0m)}(kXl<*XjUv2s{MxH5nH+m2u&tkwDaT4 zFTLV~KTr@D*uRZHC)ietq3o?&@pZ177P)dTj5(%`gRz6XA9(sYjGCssL^3_G^!Xcj z=g97A+E&spTuS;(TfH|)cx|}Zo0!VzB}Z$5wcC8DD9q{ z3llKqPSNlCbbYTgM*Yb{{dc2X_cbl}SQ!o{=CZDf=dmla zFQyZW>0GHe?9}%_i9&BZfD z?U>g=TY3~2ZCbymrag(Is zJMwlFBkBLJ-%!xv_<=7~!}M4M6!qkN|KF`Q6J`X8ws405DjZG*EMunY_HE~tZ+ zn{({!G^@o5|6he#Cz0kBe)HS=sA>^%@j}XSe-pH~&=Nv)`ERpb$&) z8v9JM-U@NFO|?WpGVTxPy2!1<^Pd#tw4pnC~!02RT!B~YS5w{tg2=@*B?WSl8fw)lARp5Nwk zo5Qz5P16oJdHK&N*q9e&tu57Wsff(USzs4V*s4$&PME+|uDh6ojH7w0JJ)xbSX)ZV zZkrX8*Gg9r>yw>&ZsiDc{kES*|8u7%?XU5jG-Ae=#L}JT?p!_QJIQs`+VCb1hXx(Q+LEN~ z*{Are2Eoj)>{sOrM!0WY?6~y3kI3aE!q8r0hF&Xjw`pq5u1Cd`puJ&fPdSju{+vDg zmqWL8|J;LXd4fwP;&a*v3~_o=s$b!P8&NQBJ-U?GJ&v?&a_=5?_WcrHN-Kfy>RZ(! z28X%~Pi3t=H?Q9k=)#R!Z~tfORE{fS%Ay;;`hG62aQXFh>Z^I}wJor_T$1>B!pKK*0o$kUTXbS(=CAfD zHD}<67fP^mIM{jlM4Tx8oWa2N+L2m!5kh9<_MF#mK@C-D?jWsw-07FUdqSCS0#6V5 z;~p?azxDI8MnXgQD~n;NGKcua#TVEc1Il$>vu<^C6jZ@Y_*GBdv zJ&_m~CIlx`MQzPS(-S2bBI!6!renG2-I(b%K=sEfkR@ ziuE|Vac79C(!#{|ys>Pwlu5yW&g2=BkuZJa*I# zXP=;F4BLX?h_TKQhO8W_M^^fnp5;U*4&1dWMS58X1CY%d)K8-za|-K zYxX>fO&h62hAl`-`Zh=H^WE2P4xa-{5`*j|uxQnrm(Bx3WFt668B;658&w4c%hU%S#SG$Za>|K=}C8@qQ#_a{&_gD)YtU( zQ1YzjRkM_qu$#q5h~JqK;~mJ|IIXzidE-|y-V;%ty_#rLnKrxji5xR*)ue)L**EnF z;H$Kc8YyhrMSCVGIvs1;!3~C{Jk=j~W}eP^t=M$T%f>h1)ZBhkpr3c^%}hEiQSk?R z@~^#e7gdfMmGjF(k8KI>JZ_1t7BOyyLJk_Ub==|!{?R?>?H^~`QE)N4$wlkc#>{cl z+x9JO@lWpH0_~>c8g~+xxmYNFi534tMc{FQ17$xv`T=T>9Fz>o=CjCUS3nu)4hY++xSrXImRf=j@XUcQ3Wll4e4I zrmg-uT#lQAm?+iBL7E2Rz7~_Z8Pt4tg#?4@gJ>>-)&8rOXgHSZHI}JO3-$NAd9KZB(ZiYuq@fQvj_WOQp|VLiI#^VcbF;d#F^&&|?}q(`-fEVQGf!f9 z&`%vzZIBo%aIg=oSp?SH?kjZrbcN6Z!s6Ebh7vLSCshifU%{yQyMX@Ie+`z}u!GQ_ zPMdy14rg_vByzSW?V~@Ms@d8FH_zm6W}X^PY+@R_%28IH+U!`ifO*c(xxa7==GV&)!6vCj@BbbNIq>_d~QV>F^;<_<0^*Y>_Lu~+61IsmZ|#lfuD$alpXCxwBdJ5 zT}rB2uAMMcz>eM)U2UhvWKy^Ol+!~ZJX}cBSqRq(y^TgOiSZ3p`*wC#(CEcA+fB=6 zt#Z_>b9|`Zr;#y^?ug}Ck8*Tx4mKi1j+|3Z$qh+VI9o?I_6j?s(2w?A=O{LYj+Up! zzR2?pnIQl?;>sFeBfTRIH$u8;lk)p*xE^C}=~H3E zr0Q14>_a{>>vC}jhw_iRF;DEEdlvK>b_xnW@I7SNnK0E%oIF`9P zS37HYb74{RpXLHFd+X{b8YJjPo#>t`n8e7BRv}XDLr%`gTCoM4d{CCDdj2Pdlz)F+ zX5THDPBEhE84l+X$;;a=WcS$Y0BoMd;#tv|*L$8Kf6rLL*#iCmQhP1qv{8If97aO27rqr)`3vXAnJ_z}>S}X9^Uz#*$ z=T6;LroR+-0hJFe$!_W2gJiVP40H<)j)qhC%N`KP^5gRaOocNSe~8%ng1b_U}ts9`}7f^BOFvueQbr|l4QcRvCtgVh-8U(4CyqdgO>6<9Fs@3wI7x< zQNmJ7i%2$aUyH|MXhD#v0e(BX*jkH}Upd29l5w~X?ld2ptHoZ)A3urNY3Qbhf9EE4 zEm78A!1);B3d2mepI8bbtj50LrI)ILN%=LTtvYH5LnNp)q+}!1w80zj%(){={?5F7 zA`!b_N{{fg)&waR??=B+0DmW$@(f2lJKA(NB6mgq;I?)VSjJltQPwpba z4ZCQeiS2=RmiM)Ozws&-n8V$sO}kp%O;veDvHGt5RG9lNutth22-pF3Mvw{efNJDk zToD6dTIY==Qw~Hwb*&(j;{SFgs6U-U&G-x_S*9JXPujI-iFPiogvm-khMAR#@Etyd zzn_J*ML8{SvZ{Ej><)saI*h=c*K@ZEehgBTKaef$rrPeXMdY%e#fm$>HVTiyu?FX? z?RNYL9;{Xis^V5sV?K!~>F=1lJg3pk;$0cmT=u8?`_94r5VmmGC@ z-;7qO^4FYF8RzZ+D#nMMbzM46st@Np=r9`qH48X}dnG=6a8On7=w4&mRvb1I6ka>z zT&z_IQ)Lfd_ABestzS!$g^Hf~jnH$wo3ok5@+Niv#5ZQ19sH6V*0J#6%UAcsge{$l z)V{OY^N~;_c`G~puo2s}J9z?^h}BvHKRoow3B!GTM|iteE<*jo=vT{>-6#_cL^!Cc zc)c?xP!5&)@Unfq=CP@mq~F20267UC`>krzMwKn?Mn06Gj>a3U)OvMe>FIu!LFF_r(+92;nNcZ_YnF6(8rEIOWFslgx*m`o78r7V@3*NMRKQf^h)n%*X#|< z)ef$ocVx^Hudv5U$8YQ?5H%gAh@}0jTx^7XUSrO8cuhMGOj&F4q^}a!b+9X;`b|gR zaMI~Xla4{XRqYd20+a#nfjYn8sOr*M$Vlbc{jxON*rsZ@KJ%?5+za_5Ycrr7jN&We zPQwSKCQs}D|B=`S zrH4Oe*4gMv4X`S z4Gh(PIMn;Won7j_m}zk1CHGLPSZz!%{PSHw0nd&pb7s?f)l{u&pP`^qAKkcPQR?;X zw5PxOq9mYR!l8GE(N?sHI+yQ5j9ACz5bp>*L%S9V-)Xe3cFtY+knyQWb7CgGYUYOn z<9@r+o~}Usq4st^AKt4-J|U_@3BU7vGL{;7pIYwem~uQ$RMdQ& zqW$6~72+MKBGp^i721{a0_7+@W(?UUAA&>0zUGvOPs`y6$8f}oV|s&*-dD{mgRQh; zrZ}GewE&t-o<(4hY&`!?EMiDS!C)gv{s8zFEA5y~x)l^636vMNnUpr={UFvur*aRVaJ7*SQP?#eb^O;@qy))GLh&U}wy`^CvQ~vLrnWO)q-6a$d%Iv6PqlHeb#vm} z3uGZviW$d~wIMn)^C7Rv&$E9eA?2?60QL5q!GvOC$md>E85e4tn*{ z&cBv{$*$=da7gT9aD$d`qE{Hu$Q7*qmCb^>=gKSp>ZKN?y)j(o&Xh9!<_M)%_VVJc zU)RB2?!#ZO95v8NpjRH%KGaaBg`)iciXJvQ@uFr2A%f`L0u{i=E_>9X_N%bbh?*S) z-)dYER=QKz33ZgR86d*y231Ucs(Evp?i)FR2p0?pKYE4S12zkMVhp=w6-QLAT}`e2 zJ0B4n=>v2|-}o#(Z^q<=Lj(D!CzHXT^sj&roN0Dp0WMs(9sH=_=NRm`u(ukg@GWbp zXs?esi0AxU+w3zDY+W`rvhSvT$@hm{Bl=p%L)2V(kGNabNlPk%2H;AK=(lhVj6*{j z#DBpzH;3MLLfvrRCls_(4lZgCOYIRS8{Zsj#JZM0xa49Z&&v*I;!SZW({`EuLlh`f zO*$Ud)8WKi$6)pf>^Xap3+~MaE8qdtFT@sXZOp{qJszbC7qu2z!L+5WWjFu*Qu;ky z6g`mco}4h76yAjo$?d#}ajoWeUx@ex>Y3n}IU6?0$T zViqa91=v1?x6A`+>q$z?$mVP*%acTSgD#};Tvec=(Z4%p+2Sk0sC7@)9!Tt- z@`^E8viL9`zh6AmU*zMem68E+LNQVwe-UyvX{QUbW-sr+%{k@oC792tLtm;U>WMwz z*j9L~6NuX&UK(-&cE7f^F|!tz$W55R1S0o}oe)0w!-Ai)n~>w@pz<2`*32vY_pz3Y z?gH)4)EQ4|Delopo+s~XeFf=RO4J=5nTXB2($hx@Qsnzm-<;GNh9${s;Vp_H!lt=F z^Gepd=L4761~z1w)m#v}X_aH_geAQSVMa3^=DuF=F$ky9XcIf^GZ*%p2t_s$#^M67 z*yIFXQg{hku93%5m6pSyTJ;!8UclkL$km;ggMC%n<|j}5Mk=FfDW(XLDZs+ruaFl& z`!A?>Iv%e7a5Ar9Bj$jZ-6wTApoxTt3vVIN;hKcv-!h?KTt32YMfWN<-V3 zZb7Uz2fQ~tZmSVP7;NUcS@e51J=sKzqy_XgeI3x7#n@X1Uu$&n3u0p-Yu%jng#y|4 zvAzIkCE0Vdfdg=#;K(8}iiPVdx1KQ)nquiGHg9iZ5>pK1u0eq}x}u*Lk9MYT1Ev$h z|G~m3Dh8$+rMGAT2K&vLL^Dl$y+$GSxf^H~fMk4nAZdvZQy6PgA+u;wA(MbylRvAi zNK6rSLA2w2;yu8?UfuFH6TjC^5x*hLJov`E#h05h#@L&{tCnFih&$(T3|Ry3t@BU% z!tPcA$=?mRyekk6XCF$z%c2_T!GG!_H3Pj4s_acAZ^l0R>g~N&Oxtrt0zLfo1^tts zr~f@0`ztL^Vv`B4H-iI`SU_y~jWU zu^!pAr{p5^pI|{I;-KK)Lmg;kNosBK{CCa)!Ldoug6osj>JS_24OHT6-rtY-<_$M9;qg&?uYm3gzXQBS`Ke%ZOkgk|$v7ihe(D4SSZ{nvAPSte=v!A51l? z_c+B)WE(}BzFwKdc?gr8UBC=4ku@o zi6wT}LC|IfQv>W!u@LyhrSG`Xhdki>K5u;r;^@+AEki=B(T(5k=_TbtvF{&j#F^jmxb_cb9sovxPZl;`(~xYin@z$5&0nAeDH*^u=MpiJer;Eh+b zR6I9B60jQ3iMSU5foKryBzW4QRM7fkbruengtoKJBq_V{8#u|Ys`VC3x@vw$trDDo z`f%|^K)+fz)07my`9C$jzZ%f3s$#4t%dt^N3AGRRBKRIpo^y{-9*}%$jcC4J5DaYz zZ#>;T-}m!gAzQq#VQf8f`X_2VQbQ;?c1K13>hr1^eoWkRL36bp5R}2#xZ5a1#R%w5 zsA{+v;F<=!VU+M2QIprIHcAC!gIDt{l-Rg8vL}50KNnturZQ})jXcWsFH#nQ9QY(> z?F`bywa+(_AyfXo^K3~^wiL|uH>rDZZ!ZBd^598JW47x$f86gxEUE-)({Y4zE& z&gWsQ_IKwFF+-d~IUIU1bPG|o-|VC>Y^xj~44Hea#duP9BRsLyUQVgmN#@b1*4A>N z)8=ND#ZsmIw}HxwD@U)g_RP`}x0{#PN~f*57iM6|MdFm67yMjb7;@mvC~R|k zxzdUoVMS8@^V4S+CX!J3%f;nAWUC_`i(jm@!R^A%1?$Fgz)5M~5ulT@@s1H2q+KB! zZXW%{peE9Y6iO!jilBFp&{oTy6p@6_odiB}l=?LeG*_xMntgQy-^GM5Q~yIZy>*!z zCF5&e+qG5BL{~ramRG*~=1vNS{5AXPApTWhwJo{#;?`f37)5m_;O5T*jnt!%2_K*m z+mmm$&t==`DW0F6fL#WWHJ!V<@ssVk9cRZz1sJgx+sgA=g_))gEYWcz;=tM8zIyQk zbO0@fZw}LCMl5b@8^N+>b zJCc+>(Vs6y;Wgf7B2ohY;7oU3fO zrGV`wO3n+5v=)Zcj{2rEW8BoIi9~vN;}~y^XymVO?`~&-*hK%$>ZW+@Nh*w7p&|7z zI*lZ-^nBJb?TSiAPl-T0JGyh%u{=xvGJ;yZa`ImpFTh0GshH;tr&mZ%SJ1{z4KvmD z3>K3mRg<*DGM$yiBxQX=p{RP87ar!S>wSwgOaHxW{{6$wxKV9fy-+gt&A}))ea~m6 zM>^I$3{zZ)7cgsoG*F?8ec;!lWeLnF7{i}slo$PnO*)>;Dd2$)n}+B(YZaEhUThnE zzn3gO`iH$!`-u_fSYhi#Ob_Z8s?zzyDO0=0E2uTw8=xJGx7AIpo`B6*vZE77eZ&^^x)u+ z?_D?@FB%IS?_#e=zAu)|Srm&bb6da|@9ncVN+&Oag|vEVCGD=Kn3PJn-5!kkmnOIFsx~d&VsDJo<6?R1vBjgMoZmb zLmzlU?C)wtCMKP6(zolCwG@+GJ7EbCcbv|<(f*4!+p<4O2A>GUv%z45$uern@xvy? z^S(Sg$l;RR7%gcKxbYUWHuXHLCGMz^=vg-m@aEURO?eXSEro2_)pJfq-X)n5_p1X6 z*6rq%XSjZZXrgVZYwKHV+9}0#yIw@x{1fjRohgk+?igjw0uhn>y#y)izP|m)ha5>8 zYPeK;=Qm2e*eJ_YyQ15#yt?mExf23$<2zJzJwJ*84Bq>bm)4d7Hf?Wje;sL;3q$Lp zPI=^*4x~E1+m=Yj0fj=g@^)VJF@lSy@wPqiyOVLwNjl2+yt)U`VWboF zsj4QK9@|z>TY6H|Ek4q5;uBiFb|{Q^g9%yltpabTTE_5x zJxSdnm1cK?X0IRrBrD;aQD*@gmDE63Kj2JNK~32P+~@-m?nC+A*>k*sY=VNa7k~GI zT0FOIeU&w9_gWw|*2v<=>%?hfz>MBEc0m{H5kmrs(}!s(?_O|KlR>IpE$;sbE^t6) z7s1FG%G*X^V_Px9NAAwFzs|pL{cSGsNq8A(e4QPOUOFdRdp%1e%1ci>a&Iglwao? z&ywomoqu$`e~k|HJfv0Ws+q*beY5>PZ{BSqexq5fpK@^tPU2>-l%XOvksen@P-Pf2!|0%(T<} z*}FxgnM0qHN$5m(z(~Rh>pLj`(#Wo~6fE&mZZzphvO@-(KhOIVV^@sOT|L54jBx5^ zGyBn6j7}i0UE&xg{it^{wl|hcIc6lDpj{ys2qlj7JWF&X5*^$B_5_}in4#xRy~M+{ zD+;}XUN~P|WkWlcX*%ztGf=At%2o))_Rl3niVz+U?0gwGSD`OySLf`z1-|e_BgXHa z2A)*#5%SP6F&Nd8_B}Qz?};}>gXYSqT;45}UWA0k- zi+~s?>z{c%YQ#QkO+&ZcWFo)An=z~WVxPTUAZ={2a%M^kVaV(1_A91L)#`}9siD!O zp1^DDGvf!hL39|1j;|Nv`g=iwO1m}pd&}~hwI=>Q0rXT&oJ}P}jastmUBE4G?bj@u z=CC~G*$+bl_Ig3*uKK*U=^(4@K!$`TF%DvQidN)kI3K@^Z$L-%3iHMnu7V&kN#${P z!f+nY>{NNFwMIm*+kxM&7tWTxgQnb!uKn7qFT~lrp2U$*rVe3A%t1VxJGy+II~~=uRF~XeLqY;}JLz%EG31l+K474@iwQzp^++8GgO}Rd-m%xGx}4=-2?I6c)o{8ONDfC!mYO&@Vd7&am0-xM@sD7M>pq zK3(re#pSdO;u`(LP;ILW46~YNNbff-L&%z!Su@OULtHN8!R2Hpbsy}9sruDjpwT6xpxc7^h3G+C2%<3;6AZSxy! znvJZ3NIDnxq;4Yv9hC2Zzl4uQupOY%!3V}G$Ma%pun>M@@ zn;Dqg&HS^>n~~G^g4Ied_2Y0v$;j=8+G*nWNEMT^ZoQCo5N-mu^TyjJ*D>~DYwInQCUmA4OYc^VPc_TbqRJjUDf@Xay0oxZ(HU-ArNw1!Jol8IUxVb$#z@@>gxT$baYOu0(|n+|LAfroOn z$2xbt{h}I%Rn__W3vpazPej>m@2>eN^jX8|2B~4)YiL9m8kz71y z+Hy-5fn?JMNm~>?W`4ona9G}Tccv@v18d`id89nStn+RpLM@ST-|vWMl^(Vi*Jjej zwgLn`d~*b*xS38d3ec&W|6HQyYs)c#9Tko!f7e564Mk3WktL@#owz)LbCcvuSnqyh zxto;~W*K`GhvKSkC5zQvlMt6lW4x28j-0qfC)gflBY!sx6O=qdnqd>|fpN%Esv_+KxAm z9z@L{V>nXu?cF#^iel|%3yYFRQQI_zs~$2Ez-i8frQ8(fcIKjQQ9>JC)$?%FUChnk zKX^x3Q~(!OzIJ)%DCodk*nZV@KCF}O8X7ODe4HLq#!KVvj_x@t4-*t6e@*VjSJg=W zME(@58tA`Bl=c(r{&6Kf9bk)mu(<{_9}I{0A|9=kYz7#;%{QmhC|$kcEEC~EaDl}Q-8Xfj%WNv^%eI;tSaIYVcv$+ax?L!T z){OsY*mAETaB%qungm4OOzx!ap2U8N7Llcit`g)aRkN)z|H|3?_famqIkD-9NT%N2 zT#t8`{@#4$eFgWq`OQCz1m%|T3^Jj&-METQ(Os&joVlRXLXE*f{=sc$U+_2%95;x- zCQ?uO)F4Ma4GO{BuBs2`b>}C3sX??M2~|V1 z$mAwH9LLQ?wWS9t9-yr~N87zuxD)!Ut5rrbVaQpk);e!+4^mr>E#fiBc8fnD-gR^w zHyMhz9;Vsf-sgfI$5iRr+^r_eX(Qdad2E_DAB)UtjdA0vPrgQ1-myV5j65jkdN3xS#CF8U!Xcdjoujci#aE;wkVbPZJHXAT^{DC537QyE| zQu7`G!Y8S0-y=423bE%YMB#L6gx%>ykTnm~cCqJ<%_EUP8zy&TNc+=z@?Y@0Hb5>6=k4n5d zJa=b4x}@~gNfi{W`DJqo6CaO>cMtR8Dhq7g_h;zZ%4l?Qq~j@7euou`?3Xqx)U-2O z*PkexIMofk&+RG*!uKxp`-Z(w6zOai6iqabUfZM?lWPHYGWSN;cih~!Wy>PgCs-2X*LpNe@HJ{#ma2PQnbE@bMLp`BA9jf4Q}!m^lRn-5m3!)KS+MTwpBPVYMq(*=NMV_l3Xf=N@tNzQipbm<*7V^L zFFw$&(Io3x{Somp1V6_gMRI&g$XozoSGUZONNigu(D&$joi+_|^csNtwL46u&v?|( z`Ervq39Oro`h-DS`I4L9%wPTa8e!ICRoC2y>9K;0gP$~=Zy9oz%)RW9gwMY2bke_L zw*kSg&*+9dr-6CSZF7X9cSJh219hW7UF~K*!tXF5qbE>Rtpku+5Dd1&+1hZV%k2Lm zqmV*jN$6-GZoV9~eOG2>$42U>Y{5_p<#waGS2aTGHzsxQWuQE)7p`e<`+_U8lHb*E zj`Q8Dl%CoZ5m)49%)ST*-o~GqZq}AeMJ_9BTZ@?0j>fE=<2ky>ZC>Q_7mIsegvdHj zGg2DXuLP16@oq#iD)?|63R#;%jkK9gi1QqA?dojXGG{+{e@7|cBC-*+wpb)_&zb3{ zLys0P+l+MS*!~FqfwA>T0ApYbevLGb_6U5_PP?M+Z33-QCN1W^_Ia_gfD|XWvcNpw z6Rf3>z`bhzSpp?Wf9`Ag9gXDMkmzmf#XYTz@7%TEUD+Re{+#jqjpTgb7kl^dBqLHY z@rv5^0@4@xmjO}-Q!@8vG7mHqOAsUbn-&++VebPX9_U)oWcpyPYuUKOR{8H1kJVzZ zNM_$#_GSXsLo#}3MDgy+KtJ+MHoEspFw+*5({w$B-~h!1>Q?-6Wg#i{%`cb^zch|y zY|v0NAI>ggjdHQHch@<}+t1)r4!mt(b|eTOaTx_+<~seb0yJ;{;@qf%e7}8Ye4OuU z1>L?FR@R6)tFal$5WG~ZWc`q)Eh~5-8%G2Cf#)sjS2oEudEK!bl${(+OJX4=ZPI6P z97iv+1r{UkAU9PMu7k~NK?E2zkPqD%d)q;;qoPtL0JEM%`cV85fmq!htES_!DhNC( zOz1(1A$iS0C|Tt5`^|P#rp$I&O|%o%B;Dk6=fI`IWn2g4@Aiv`;<@s55~_Kl2y+$) zLfwku|2PX}o^km*^HL`ivSz{%^3EQb##=elqsO1{0XFEp`bbwlJYDt$Jglg-y0omV{1^fY7M?l_B+WEDuChYL>u9RXouOT^ z;=4eZ-Y{HLJ^wPW9A>I8#43g1o1|W{>2{PbR}Hh~pd*Fvo3mrJE8bTFv(2X}sBX?b zZ3HC5$|y4-^5@yG6OEX2yE0w9xQ7|44?nlleQ<+az8BD26{aifc`KCALL_xPvec;U z6AIWTw$4ORmL%go*Sq>>k3fF)LC{`3#lx;JmA@cSbu)HFJ?YHLz?1`713=U_5LMB+ zLF&aCf(%uP>MvXOvq@X|&(%82g8I)V;&Z#Y;j6y&lubV-p}fO1)J^#H;ymIXnDG@~ z9UDzOA1RJ=;~ugsRjT&!(&ZrO#=UMtv816}Xb)Lt(#%s1Pk!!U5_;c{NFp1K;nb~z zgDYX1MT*|fP*$`tBLlu@WH_vBWie(is!@G~&|0ApfYgc#AS*#SRZlSiJ$oJ}GEXe< zkKk%1QBE1lkHtiun=T`@UYgHlu0OFWvj=+i`8;A3?KWPO zdtf=mF7%ZNH{DH}QHO1X>G~=!^uRiSGF{^}%&+WRh=nWHEOY2&jzHNCDL@zQvq}q0 z*e4<1H{)`DjD2$@s9F=ndkiPH#8$Iu%BWm-MD$*Yo+H5?rB?v=wbNt}IRW4O4V^@o zmkrSnZSY#wj~xAOb%}P1d^mlwK)D5uQG-}Rvq0d{##R$`gdrD;iEN8T^1D^x4B{W4 z%zBd~meq`GM&|sP&c~xpmN2nRDvgYQMt+7y`qvB4()1r~wr`~9<{ae(bNF55p$0ro ziQ&e?4xWoeq+Z#^!*QzAsh0Iv^{TgQdZd$ni&jMb9R5Y!+}$y?b|9C2TG-&zEB2Yq zm`x(1ZaW}1KjPg36*hHXuAN?8VSG4ncGJAhz31Lx-1drwyWK4sdg!{<*vW-#i1r_ya{#MG8WkPw${n#|HarSNyE(EFe3KtvD)_59AnaU}vM3 ze#(kyJ@&%)J-h;(nU9#%b)6`ut=!r!=+-5Jw&V|2ha(mi$)^RfTf6DQHzA3H8E8L( z>yh2?#lwVIp!)6&_W7{;|1_SP|GgWBIy4-+9EC*ui*PZ8CZ}TbZ6(S}LgIQW-)nPwo-m*j2y|qo0QioJOkOPU1&g1wKi4 zyT&C)o@-}I%ztyPK)EI7N;Ad)BOQuMhz$4N1Vz1?WA=tGQxZ{6I@-`x+hNZ#K}kDJ zq`o{iKMdHUt3sKch!2NCVM8}_(_^GQueI3+Q6dL0yf4J1)j&=b#_;Q>^^IQ6Ql?M9 zRuuzoWrA2eU+{`7{Kh|Ex_W%H-!Yr((!w}7M^JgGxbM)_5qgTR%Mf4<>bPgKiZ*_I z|I@$CSwWVU)%AS#i9u^-`R5z`xW~H|A2cCThU%K~jadbwWiUO_U=I$poZKH1cgF;mG?-78}ian+PL zl~>%iM~uf6)_DhtJ0-HkT=L1dqmL5Vprw}R(>_v*#nuRpnysM*`mA@ng@LdpVi{hl zoW~2{9~e%=_i9T{#p?xBjD?LJ&yX<_o|vS4C(9R1b@%Z*w0H-63Fwf9Et zHV3kx$688EGO{ick4%oIad(m2cReo(GnT&@OPK^~QZ*(v*FcBa13rhr_?zvR#eHCr z7QFpV@<1Nd5)0@G6n3>x`M|x{xKV1-lNlaWo6`kvJPsDvNX1B<} zSZ>`jXu~*bve4lmxBExp2VrI^M$#_LJ<@PXGmgTkn#2z~mb}-|uYQivic9u9&h;0k zQkXZHBGNQ@sI{*$eK-I{GdnpyxwMcMB-g=J)ng5K@}MD4oS|H)2Nx*H(isd01N7cL znq#Ka;1EcWUv(A;Yn-;*3#3@5;AY@lxdJnowyG9ZBr6j!r`qreas8;-9GIr-HP$&& zrAZ|k-zv3^#a~+Z+IbM73Q<1i`XZKJH<~hbI=W@OWp{Qa6DFyl&IV&QCuu!g_3%0V zXip~U>FQPzKS+ym|3&K>CFCP}3@VT)@DJhRGfVDk&rzmFC3GU8GJa0Uj{^rx|Ggb5 zzJ8nIqc zCMcQdh#s-lr*2HzVIv~J`qpuWsamX~^<9nzY!GEuelxlJz~dm@b@ka%DE4+)RAL#+M?0W zrnn(ILF9K3H(0DY5vwix6D#u%0k{C8LSMptGk&Bp;~tcU%m zTP@q>t{@bmih~cnqA>>M*{K&1-U4R*Zqa9Bd43gq?gsX2Gj!EN$bpTr=YE|v z9!cVD*4b28Bi(oOtrw|(RJWgUYZJ)DaaZ;0%u-nPVZvVBEjkWr$6*hRrl*J-p#91W zrMV}C3QDg3%8orY2{T+lCAuMeH<_TYRQxSo<_WK^- z7$q(yk9AH*Pzk|vb*pRz(ZwL5s($uJ;oLF1a(;f!A<#K!l|80w@mg3rsS)={?Pf&% z3bT&1vp97e)_70GmRg#rrh7tWK~g2>;W)*zO|&}Q2Kkw~vB~FMNmh72c_cWo7;tp` zN#4*ZXH%+ZfGcwt!M#qVt9qJ737Sa*oukGJu_17{7W%@PiOBqmHl8$5aN<|4e;jzU z3lRrxF!RR>JHa_4Zp$xG{)Abd3rV!l#%G9q!FN7#n;1#uayt4YjP4}k_#p`fws_*` zYKVYmIPPKX7(wMbn1H&~_WM*Suy@0=bM0D@;~M9$EQN(g^ys)FHL|$y5eITAo{XeD znIam?d-GnO*&&0s8M?|CCTWkNmE&zM4a?Jmw?Bbj9Ynp1sYe1w6(M!*@pJ;z;bV|8Kf=LG`i8n$#Q=0vmq92M>Q<+KU4EDr zudGHwKS*F0o$!)-Qn>^yk}q!{=MRGrt!ng*XfMz;cx^yc4XUXtnMC>LCi@BDb>=ky z^!qs8wr)k}078kL`3c@^iR&HKnP#Bp-t?Zwk6Gg**2xOkq#D8)xaq?*4Of3}Yx;0GgLQrI(W;NAe|xRSzOOjTWUFgHD~Am!@;IcxHLoafrG2;=WlBg9uljNA7AY| z-qE;1;u`sPY}~q$`FmnAM6OdDnczyRaTPkup_DBRL7qwd7)f1&!7Hb;&=7t$b5li4tM!NdChS{4eyj^%^M7X!_GzG$g#1nEJRUaFlpBRGeW(GP=2z{B zJm(XP6agUe$jp^vHL5^}f59+2aH9k+IZ(mlMj6L!?2a%~*S;d_+y!yFJ4d8;Ah z(Fb*x$AgT>p4W@`07973pPdGQzW?~W_AeX;D)yi_q`uQGdAY4x5j6%_&_M3)l>DO_ z`z7Q$5{1(`^{RB?ij_bzIZZ@GA)#@aYfS2UtC*YArFN;8Z5(#J@h_0JA3-+aF-11A zI6fh}aAmhDiiIzPzJB@Z^`bEvCMl~Q9RD<41v@mIT;G?VBAWl9*JE`o6wFY3S<@sq-Vlh{x-P2k%?JN;Mo*Y-N-)n<<;_+wiT-Z? z5;BRqHHbp1fA?=jW=#H%*}yknUyyQUs>Ycwrn5)LJ&JNO*BIMxa6<_G8U-LkWuW{E zMAFY%cLo-7n9xo;L(`Na}wTtSQPD`9khaeMp_l*G zibvR7Wr#A8djqxlvlgy{;_rQkuA25gz1{zdC}w=;_w8eVNi>*rLjd60^@9JaoUdm> zAprL^ieqt7P3`}cn*YL+Ry3K^^uXZ&esDqtBb%7V?~S29L+_mTA&h1tT&o+qa;Di& z#06F6bfrwmSHb_7A&*pLNZgC~j|wGd{`qgm`yUTyWsm%q7nuYHLEZb%f4jtYygR>? zIrG`U`mU*GHT2Z%(t`9+0qD~ZUi??5!gmmNe~c=2XjsW)V#0e)jr`BhWH|_sh8W(_ zRK?-u|I?QF1z4DQ{}F`HgtRaI%eBhA=vmUOfji>(^jX25h`?2BdxN>Cr19R$1prc` z1s11eNcPLCOT)joQCQ#Nco}+@3|A9ID;{Sy? zRQmCs%F}qR+0T=jLdITmbzR`awub(lM#6gXPS_Y-nN{y1CBt-70O-g86|=7_-ORsw zA@R>^frSNyB{!y&@xPKisA15uzouOWR6F4N6yoEQmf#u&R)q@GsWGgEDJrlF0IS)GL+p?S5YW*;YBWfd00oSt zd{8&Lno$7QooLcwu#h4#rnufs+{%&Z|F@*0@j|okfDIhu*+qcz5=`uKcbHo+Ur#0e zhZ2y6d@{P?r!;@2Nw_d-TqHPsC0L^({%}UnK9pDK&zkRv(hmkibD;*_-R}7=2hy7U ztizN*Pt}b{rKOPL|1-hDmj}Q1;R{=e+cqIX-f?f ztKAdy;SN&O2-?>UA-^0#h>fxQ;U$#Y__hOocEI91m^Mc_Et;l28c4x-qHaq*(_j;U zO(N=6Iqw;4wU#=90ReHmK;EQ5Ap_zqun=p|g6NmRHKFJeF%<2{5A^5r}fn=`QQ;M+v}&-#K(-DwPS0NAD@?T<*!!q4!)h_#)Z ze_@<}X%j4OMbneB@~eEzTQL>0w>9rbKb(pT=O0?6SGT(UZEgr8v!N>NB@^zq$*(LO z$h20vnBlbKshA6PwsY;`2P~_j7V6bgcI+my+_$uNuWl>SP(;k=1yxomxosWfNPir+ zp}8h@UmP2Wnu>FM1=1FZ)p0Qy-CkI8wk3+|4h&Kpbz?nqT=Z!j{w!t8^7=rzA7W<@ zTMoholCHf}5THR_7OTxrSDNtsSgPT#=Mzw{{*zJhRrdS<*!^2G-mNk_kkTZS|;fy%3vgRp%^kl}%KJ#}NB zeVM(KO#5h5oX?)`m6toJ3z5AUH=lc2>3JJ#128?@fItq=+O@jX-tAwV)`aASC1zkj zItXQqJq{L%4qHMvOiOQM6C$P#t(1*~IBS%{cYnn9s`Z`jmd{RH*anSStXgXG0 z&SP2%jq_*sf~e8h54Q}JiO@j)91#A@H;AxH5K&P)L_7-d=R+ei^7n8Zrl76-;d?;H zirg?>KY)4W1wl8Y9ic|3aY#3hOWW}IHBZMy-rua zq5^_--_2G|JLvngQ!5gBPh)Ps2kIz2{bk1}2r0f}!Y`7^LPD?-Xm`h`_$GVa|7j0_ z(n~|Y)*~|maMr4$Nh$UeK3s&gN5_B+Ug`ea7RYDKoYE5}7j{m>3=D0-SugVv1j$ER zQPDm@y_csJxtCQ)v~llPX@5Urv$8nIhFpv%vegP!Oq zoEc(&fE!_BK?W8H zK_48k8=_BPs)?d2q^0W9^f4#J>#y;48^zP7L|F5Ks>?kHGLC!5xWF>yFRQDy;X3Z5 zb|Ut?2UevTJxcEPW7+eI^K!%PiI|Zv2X&iOwsJA);XHEmKw(Y4&{7c-^3+{N{m5c9 zih8yK)38x3#p=M0$Z!)ynLg{N!(+Y^vT#Sz=Q?}78FW+mL<3MaBV$zBt_e#WulfjL zQ$FcmtzXHHD8mX-({rLlVp%{Qolrf`*hVtqGudh`&iW90mY2?kx`F!aUdurydI}S< z%DcAFa4ci?`;r}AL|Kra>MtLbQZIwKKG1^+>(oymolnuVm{Bi|j-o8+g^!I<_9gbo z5M4>C2XE*_;y>$!vRkw7ZUBY(33$n#53!Ow>d;UTo4UH6vHZ+#tW7uCF@qyCW5oNC zb9B2(g6Jc@q}w8zYVbxMq}j{@|A2s)$Z$u1T3HIuWx@2mI?BhpRo`3z4s#MHZPl(@ zMW`g||8Vu)fmFT!Sk7MWkg6Rt`XjuCCVsDN?AAB$;!$+QY1=7MrJ7*G8$Im zmRW=-DWi+5WE3GIzt=hUUVXm5^XI+iJkM*d=XqX_)eMpLTw=RAsl83n070Sq3gfv3 zQ92E_$zXqUuku9X{z$T1<(|XUoyVOJ-(!(!BE*q%7ch$R{APaltSX=jnS)>^Zl`Jm z?d29IVhVp}O3|s-Aiy_4@)URzOAuQttP;;M+sj=(uSGrqJr$gtYJr|Slgts7>lWyU zl}}U}9IaTsy6@Z}*kiSe#Mb(=7n7tk{h`*f z>ur8T!l)Q&{NRC8;JXqrjkvnF0n1djpn!dhUucKwTiVM#AZ}_um;%*!1tiorZFeJ# z$-5c(PZ)~y$C#8$+kpd{&T&rB*{KoiASWU`&0^xEoLKg;NWF_Hs|$Tj5o*dev@yTB z|90Et$(7LI9n=rLgPazX?L5W8Uj-Yo1`7Zfi*+VE>`I9_|2Z^U@4PORs z*}#5zEPeI6q!dvjHjX9J*^eHE&0Bw*2%jHVnfgG|LXLf^7hA1g_{ju|ntfGr+x&H( zdmKn=t5GhbZp&8oAJ2g8d1D{XG4ZOqyxtM9+HZjU`}Y?cJ%F`Wka|_vg`FN{c(GT} zc`8l_&CLpvJW|X2iblE%);vK?UTI})EzR#U9N(fdCxz=P_C)S{G?RCb4K+C+usJdC zMHvmb)W5*8;?!HlD~qO@E{p=KUEMpAOij`46^|yVLb5?yV_uLbbYPy<>RoQ z6_O=HYaB$M@kqmYeRSC-?$`#J-=Dv-u*G!D+>!a8@y+yeIBg)EtL=xwCaH&48XSY8 z%lJcEfo*YqWrOdMEyPHH6#hHRv2kORj5#PXK5_?ahke~E+rb<+e$7{X9@^HM*w)+; zc_pOlmoAG28C7|J$JKE~v!j1V?d7@P?M?m<;V%Kxih4(s6ipKvljZd#kg z0LK328p$P)tf={(;Y5uVVfM;}m1<`_sqJbY3zb9^B&W2GWvh0<=5qQ~F@Z}^$VDT>7I<=Ko$XATEv+XI}6c7MlS2f2`qh ziCK=GeW)fMp@hMg#@Y#WgOnK%5w?mMun*|F7#4_1|O zA)Rr)mgCFRb*cllX`Z^U2JAIjap8jRudT}wt3KJUOrXe#_6|tWE&aFKep+NJ z?5BT8tbzY-0wE#|nIF=2W31~c!#|_A*vv>SLq7cG2kH;-+*USus9Kkq;;_A0l-a~LT!=dSC8cb+)vGXdyo9b)RNk`$|PrQJ92F@|Y3o~wi zttSk*5l82jnp6gFy4%306TfGTQgsU)){9;Tb~5nl z_%93XO>p$V&P*~g=Q5M~X+r&9w>!bPbV5iHJM2oA*G8s2o6&+ZT7_>zG)@vx;nd-}=yeW2$1n^uIC%U&2M|0#kJ7~P0j|S(L4hx;9*e4L!ZCmLkAF}l z0`yY=fm1D6boYlq|5LE3SBE4jI9G?k%KcSII8nuzH|-G!>zWyH0&xUXps`|cTj{_6 zM6+w&!qF;CXBV4;zkBY(;fz97a;nvcwdDH%F}{-ydNuvwx9^I%TGP z5Iga>vj1NCJ2-F_0%xWgQIii$5;w+v-OAn=Gc-I0C)T5lVW#Ot0&l^P(dAJYhhr3b zeY-hPY2Sy;qx@(UOU9LB3}~?4Z{lJjIkpfb*`h6L9|L!Eq(+hsNBQT(>uY@HM(3S%bjR%tARI=uv zHD2n4|B%L=M?ml*nN*>h=?R?sUVQ`(y1td9pM*hvgaEAHqFGU<6tS;nJ0&PyEe z+Kx0S=cZ6{-HlKP)+0Hy7dR>>VMM!1m=-M zD64-aGJQGdH1feW67|1GFXwUs6qbh)T zOrUxy#y8GX8VfiO<0Gy&5=;Q6)u~qqY0I_mGN+}vC?j8<9O{L`>vKj)al7P~!vPL$lriPiEue0($Nz$=Q;IINYlkx#1x>z|N zO8t|pfSjAZff|w8iz+xhkuPJXeHk;O2#lRAhMh6q7>)h^2ZSOsa26WBtU0pMAm%L- zKt5qEIDxwbql+9ItXy*l`UR99s%Hx8rGAX%PfU8DvsO0N=VO`HP3EBzz8m0=e%nL% zy5+clGpy^|tV1S_TT4cYSuuycR4%TJ6vJ6O?9B1C92800Vynp{86QvCdSY6bF&lQ8 z`3|^O6a5k7Sx@sH=C=e;Z*y(mS~x{-4yHj7n*jV7()hY~w}dv0`IdziS0gg;y6doXdy15Lyyhg84W#2&mu>+Pf+0)Nf33JC5OJtq)aRIQ%gJju<}6 z-57Oh3sX~QcB>268!Hv+Yazk0gLCj;YM*@zVF^X;Zzv9mkPDijX~7;$WbPeexrW%x z-FH4lr=LXJ>>~`&b-1J<^0daDM*-6y=V5Sz+*uwN;#|0L?)MzOTdo zJpilHBGX+=vq^IWOQExiXG=2_mG=DkMnbGhSl*u!(C;+n@r{ILD{`L#J<#ti(a?g! zkByyubGpnp7U_EjYL?B@FS1D)NU|14;zkb)VWgl%!`Ig{x2FTNbm0EB(Y?12PO%-{8=d^g^dF*^lD9r%Obt`mnIeRIBqg!&v4 zz#6AmL-UWc_1YXv#Nu!5mGdzOgK}?*8gMJ>c&~_z>`V5{XasjSh2fqaH_ofaBK;~j`89IWfTjsX2x~Mm zLJyH({?|MXN;h)M_AS)xi-5q0rx$AvDs>z9*-v;Z2?ojsn!_AsQ9gZQuAHAaLdH>y zZ4c{b_YXjO5+5wy3|VB1bh)YvyK{~)+X$HSmylRGb7>2+a7QIVRT#8=BHIf^#dg_f zz}2B1u2f=-P0uR@SI4U*NwO%I$4n$>zw1)3Z)_uNQOXb81HD*Lm)5#eQ9g}zYvH)M!tox6q(ml z^8KLei}YjA-6$ICj&r_1P5#l$KJ0jPe!ti)<}8$K5RVBuUw`jD;tZ9(*H#O{(7kf3 zoHQKT(<0uv(P$~m5C>A&H+*6`aqmv5CV~GjLmsJMe z&ax_*!B0U(Q`D{-!-0;$HjpoG`hMp4G-oi2X&a!m5*(8+{UZsD!WMfC>MUH9G#tVL z9q%Xuyt|WmQe4Q43w9d+9EcvPJkhBB39O|N6g#BY{UEYk0!*Hutz?#8QBiQC55+98 zW?dm1SQjjD%+_;RvTVKgZwM+ip9md?{qm$&8(fira!6htvyzvWKlgk=CP2Vp=UVd% zCVzE!Q&>V!Xp5z$d9VhZatw^#aVqSgBTaTgvMyTkKPH{-m<_%tGns<~J$mKkkPJuA z3jERWgxLFgbr{xiEgHQItv@O`X&<1yB;#?8YcBMYVn;OptGaOEMj-sJbY-T}Jxk2S z2AP0>I?{+GNF7+$IZ;1G%dsX{xbg0034sn59y96#DWApR9nJ27D0~0f4{>EJQ_@d0 z{PsX*Vh!+)WyTO{2(?0};r)cA?rBBi;(0m02ofH8ZOTiEu!`UuF0CRi`VL(j*R9sG;twyFyW zZ)lBJ-?Vv&N8ho)UGp=@)6h8cUB~lKJuZbwi0qzKW6&u;^UET&KL2rU#YpL}fnX2K z9jP0FY$?2rp#$jq#5>;84EmyJ$-F`L zpam#u%vJ?GB3i*LH$FZ0FPuiv|42ldX(!%6G5j4wtur2L3d(Aj?71}H%jWA1Nrk_JyH4toT-7$J3yb(Xc>2)8AE=W|% zf2lMz5ZoXQ6_)Sof_I*QW)l~+)*@>Bb`EGQxzUDA6haTYx2KNP7A6g1``{7{V&6q| zUTCbM3vsO85}N5jFcvJ8jZMQMVN6_xAoWUTv`Y07hxv4@MO84#3yo5GOTm$7APP^p>TyIz~P1xK7z$I?QcvHod9tK7Ywx_l%aAkfQHrl4viF> za1g;8asLx@D@|fjUn@!71l@11dO};x#zbZuL=%RLs3ihUA`wqizc~g*awT{H(tukf zW;^kRq6-$7@(ij)K_uhL(D2pSUjeEQlpD>3^NCE6p(TMHXJgKS+!}&+$hEnFTzYNJ z9D?fD2}d9~e7NHU*gZ3j!KxYi5g~(TEHvYTu$t_F`8y!{>Qxs8brhl@<2&|2zUIdS zl>1Nu7-+^5AL_&qjJm?)H(0$z0KMS=-i}4}6PT#$Lrn01@n>TRD`YV~9J#X%S8sk$ z@2yw|-lRCWLO|@W$rSm`UbYNa(39Vb>~LRG5D?A!6N=XmH-xT1fBNpmS~mAoa_iJs zX)~#l(G5bW?|KERGCY7Tt5Nmx^+6!>4p2T~sUSTP0w1jLH03y`ZXg@qwnMCtcUK!o z=M^nU>RYfXY@F-dGG#{|uo%1^82nJo7LIO7hG43TwXBvNnq&Jdz)2-#vzCG*em(yC z6ndma{*c5wYHtGzOxFF*vau8uh&a5E7sk@iH2wj!>P90Mld$j~48odKV(uE+ZJ%ta zmv4fgTtD}J3^3)6Dv&2~Xc+72ZF3(P(>}|LM^FWT(C-(lc~t$DBbXAO?QNvi?N z4Qb9#J5E&l4`n6uqe2#EuYgVCRip?(_x9I+D!oB)1BJf_oSjo=&L7@|v8Qir{ZlL; z-Hd2x4ofmQ7u*N^<9UAp+#vW#WHC0%3Nbhofj?Dr_|N_k$Yc#I31o8Z?`4z9S{l%k zvf=vQ^~~BG`4*TOjVt95b=QF|&yeW?PR7?%FVNnxCBSc0fa@I=-Q+`g6MxA2Aq3li z5yh546llkF_<`-SOxYKhPlyEXhd;DN%ucemR#ISH->Zv_?}=_woR%7M1`-tR+~-?C zwAo9HXu{yNb^+POoR%^^rU*L0A{YW8DA(%v_3WM}GX1+!=+Bj3uIVK9vf!b}g`6e5bgSAv&-dWGIotXJse%3i9qm$Kd5*ic%mS`IP^d4Ki}6&&}>jnKS35y zD=?s90N0zOI_SUt)VvJk{atQEM&&$MzDCy>hUYr35szJUFailm8YCP{z}I1b@AkM^ z2iMu0?~m3;o|z|?aWV~W+9B*;Dnpml0r+7J(+Ndl+O_}h^XtT?5EyR`%xQ+kUaZxA zov)Y@v;Ek382mykZNqh_@4@~X{2^m8ibm5e4C>9WxAf_uvKtuGt5NAEGheSQ-XoF< zW-}EdU!I3R?h2UVvg`;l&3l0{3Vk2;*LyGq*!2@vM4xM$l8 zJ|WDsEdJ4q3p68^eK_-V*}_34Fn(CX39geC1s7mQ!YIy{bB;3Uc7;o5TOsbmA2bCE6VgBLeWC`i>0=3U4W#q0%LM=0Gi83@8enQ%v6n3txxvHG@|8UeUHN7?2B;(G#$6Zq2zzA@hfBfr;IDQYO-7sD*W%_RmfcQ~M0 z*z_wvpt0|Ff<-ix%rqDh5ox+7w*{Ev*t+}tDCEKTLwi~OsjUo@a%bzVC#PVF&#(*duRVmk z_>;y3_)}Jj;hEPPAVJm)r#~Jcz1sF+VbwHi4^E*`gzr1irLH0LzV`CHW=jb$bIkuC7~B`kZOp2ktWC zJ>%NJ!qE^C{>n6mpboOIUF@F^YJAV+**9Ewu(zlmC;t*=7Q%jb)MKnLCs0c^4#i z6Gu3p!t-{S7&VWle~MWY)UjNE#ar$fYqlNGeEAC4Gp{lxCJ@RoB0_2D^FJa@R~HH< z&0Jo1=?^ziSO|Y*wdHjCiIJ=kXaVuL>FVuln``lUF%yBc5DE`fL*6Xi=T`BlK9UN< zJcJ_~7AqFE^D^Ff2FoVju~;f&kUmsP=*m~Y0aGliXRQ5M z8{|&i!6NeA0LvW@0W?rO2?T_i4`!4B>Kdp%{l>ZGzHMUKnh3YKMT4lHIJ;;O8bLH* zl}{Ra<&Qzx=%P@6?`?l*brmlq#H`n%iFoA@I(<-%mKh-du<3V!g4CI>!b)U}Y0?6j z0`?uxQlU4f$lq^l6xtm0k7GQJu;=127OKZnc6oO}Y1UNT7WtdaLCb>5G~-zgVKePg z@?_ZciPt7@;afl_ynPMN02nXOwqpF<^$^U$q>EP5vLv`^@9;eD&V$%`?+J01FTxg; z&F820fEbK;a?_#8^-6mMa{k%k|6P{zCn3)eJctE-gufWDkUFA=q0-8DO~{4iCZM+|;q8PBxkq0=vAV;dvVYgZC4pnsY}3#oak>BqD}9c&H`Lhx48ZU8 zm>HTKsafv}`||zwEd;p~(`_~qD2G`}M_XA2Sd-MMmI;vyg96l$zPqr3y^ejMp>tli z;My^QaI8|AQd-2d=*gy&!D)OdLHmeL*56o7xV>bvKBaMGTo<<*&kpL6w)U?2dN0Yu zsA;Z_>C#OfUM%piS^pkm2GR|KcGF2!RuI-Y7O*$hAPrCHJPnT^3YF6S!Sos@-4*JZ zr}elI#oQRg=EN(Jlzq?VosWkKP{UhT9Fi|IX)q=til#=0Jd7aka|5ATJheW-EE}|o&-@8>qhqi4N(j>~s?X_s zoYY+d+a!cke=M*9@41hD-X|218=*(orvFK^V?LyCDtR`+0d{tcBZqM&XEgFR9+=#4xI9#Ae>jJ6-HZ9B8 zYj4U%&kCMjxWk0k*h;8;On9R!4l0E|#+{oql~yA)#%9mw*Rhve156T+u%XdkrX!iQ9KO%p-2>)c z@!J)-k|1~?2#9kiwwo2O8bOdoA8=l58CPJi# zRW#4TOgB1qcxCbm(AwM;=3=YB>+X8~_{(2b>`K{VO?qJ+{uMZBCiW=*>s!PL_MMe9 zz@D!3vrNl@8esjWj6cSYyDPM3$H&Q=ASrb`;=++2 z-WMMZdoD|Wj1`3jDtt_lfOXY&wl(V57 zJ93@=nj17<@ctM7M15fQcG$dw4}iYL%r3@mHf8fRRYlsM1h+m%avaugzT8q`oaCJ2qR$5OfG&Gy&~rfcELb?Htp2`b=)i z9Ky_yIv{f?Sx1c>fZZ`G|C}g=q-_1MP_T?KQJU4GiMnG*8lSBc+6)tA7C`cIn5EOE z*%&@xgiNm#pP7A3a`pxek>sCGcs(G@H6LM3gxfo8q8cG?x&{CeI$YKYbqTuI8fI9V zX2F~W7#&1%7KLFne2X~88pP)RdNM(gF%>F5w`Lg(b45f)Z9TwCoRu$tZ9mgx&zVwX znj#l`VT~uibK~}zS$(3K7`&xr#Ety@wu@PuiW#)TMAww>Q(IS^)^4P~xpK^B&3P#_ zw9W71i~-Y>$ruTh_sfDn%hcwdnT=4ST_~dS^!FOcAuJ(JQv;)TC>GuTbyw4ddGyWO zKAnbE`_hhEnD%PLN!aejIkSn7lnSb(ZRD+Evh`(u?7w~I-lwJ`P8YnKIRmq_Mq0jz@GuTPligiv3mG2_f^ zz#Am4=c2mRb@>wXfeY&fwWW<(BgLbdtxi!@8ugs6ZwrS&K{nytdEEza&bb+_~+{?Fb1& zcJbEp*vy8{_Lyv8w@TskEG*&TM!6R|nLe4?g1G~k4^ME}y8tu&Dz%ya*(;>;W>K5M zS@!kfM**4|sM4~wfS$2MiIJTS?ZL;2sXB`0C8;77B>pQDLw%oKQ7jaN<`! z?v0mD>ame|myJ-^X64JyNTEBR%_HNv@ZGM^1T!H^CkMRM!^1c|$-IZ!0|m}C!?r?} zW|Q2p00ZK`*PlCrBJ=1x>mi`R)HaRsH=7|dI>E){6Q{Fa^Mf;P^C)rvixup&UU7mY zRmV;jv}=L8ehrm@^~YX`=pS2fsUcuhIPu zP1hlhzTFNx8d>!ia#ot7>gXd9#Rdkt1m^yw zPMjgKdeK#CV6r@o<@kB+qXUHXAYfHzr!L3G$=rCvQt=zt%rX6?5V8w&iGSBaC&7q7fncf+&R8t)NxV$C~Qz znYVLl`S#dU83pAi+$m4-kgHCtl-Y|qCoQ#1Re%};ZNH;@(XmX!w$*vM#( z1QTML7opGPA-%i%eVZ3e2i|e61Ef&6Wea~A&GsZ@z8MaQf~)A=Myb>l?MK%cyKoVy zqN(N)i=do-J7@2ZiM?a&6hheV9CGwe>Jn7+g&@=PO_-SVaphSXu%XqmRAY>5= zn3$AwQJwVZ_~y&|(|G6T%vZecY#fisOE!e$4iMJY0fZkW#?I*p=5r{!PKc+iYC!*yiy=a z^Clb=9w3J=kmH5h1~vwyt&cP6BJ9;;fgG{h+~Vv|7gOhuK&48N);yM6=c)sP9}Eit z^SVpjthZ4)0B2!fvBz!Kql{6I7tJw${2dPEP2Pi}0~K?jm^B zmV{8j?B2xe+{O`_$|A{!L1!fL*Bc-xz<2^+R0MKan*RAM%lO*ikXc_f#x|rB1JW2Q z&Knp#dU5nNpu|xJAQn0b&_$X#hwv)`bh(yH>7sckA1@VaGtcrnjnF)Stu-KkUcgp| z*LP<5nMV|IMIN;KgD_3kPk6*5Ab=Y|0GBJUyi5MwjpO4OrI5cMUdAY3kqB56`>kYZ zlY@;}--Fh_hhYIj9)wf^7*+ucQyD9=W96L=7EFah{Ow?jOE5+%Hiir2U-nL7iTav>&wg&f$b{z4B;06QkY@Q z=HdosAWdbzrMM8k1-q$G8>lI*|4JmIe1#`n)0kO&LYITxpkNw6lgB^+UWZaDMZ7W> zLHK?4t*^FaY}?d50#c?1gJkT2K`LR8J>5HAv2NgK4L{(hy@DN?v)lJ!_&J&C4FuT% z1nJz(%CS|_SS0nY)j1R|!|6G;ON=h%$x_ANxGik}wtfY5l?J{`XC!uKf8#09mtv}t zMQTPJulXZ$j6ubgbTYcK?uA$tQ~1s{Vu}Jfi@}RA4{U?6>D*6~-bwi= z%XwTC- z0?d@F#Fj<;5vnFlMdg~AK*@WQf1K_0~gSrsrAdbbmi zTsnd=@BAi~7`Nf#!4P9H(D*qcAW3#&_eh)*E#O_x_)&4Dq4SDU#^c3W@xwkCSeIG zHXMpfcd^Y{I&ejyZ+td?pOj}AhoP;<|0?9K1-uNi!-_Te*~iukwg-v<`6LTi%>0D= zU`(psy0xITGKK65o4!wO%P*3o{wlr7Fa5-c^fCZ!VVfYq zIECf?#+F(a%>%2H5vo)~LiU<55~nA9gI>qMJad5TR&_ruoy-`JZZ2mha+QD_oYdRJ z>2>|0`k1;BKkZlti{l4B#r(P8P!06HdCzY49vSTixXM7 zYbqzeZUZ*r4A^1$ea>9M(&z?TJtqDE5F9W~I+7EWQD7TJ@B%3r%ThDmtZ3GL)X)E|~#9D9^er(NX%xBYoN1?+ObHD*CTF%AI*y6+)N@XA1|DQ1pb=v1NH zcMo9jRv7D3d?=znxF~r~L96XvL_CL0{$A15#{DEOc1};~alHn2nOIP+-A+XRc1SX+A>uKWhV^Z^D8*+KbIsi?_ZM@lpcG`&hhj z-^{DN`8e?FvtFDa%`qy=?avZo3TIL^~edadZN3LI50T zmnDZQ`VZUxvK0rqXlSCE;U`<8;t^^W+MjJgITC{|k(JiOm_ z76t-xW$h6@xGL|zT%EFwOdm_xh*`BsaMQUS83pWXF(GX`oV80q$7#`(o4vo;mII~a zv!2!;&Pfref*@4Suj(3YDhP<=n22TBA>$opeY-F=J6r_=S_P&f(RuY*apFL-9;jd< zLl(k8y%-EqvC9ZzbBMaAp6k)E0<1HyCXY3pT2NLEswd6_SGslDBGJ7yD zjj7t61kWH7f(pcCt?_Dg2l6(4*rDwu|C-~jtB*r*a!eb6dbYlpkcn7AaO}_=x`a9;+9Q!Jz#d+!?)XKPu>qap1xla0b=)y`Bkdq) zk7e^s2U!E!!3zzqYO=aAUNZ3x`UuVfvovk4(ri>AApJQA&GLu#a!^FiChbmV;!Jm& z%GZVfEWbN{?Wv`??tsm~%G)RT+l&Qrx5_C~B~``>&7~M?=EU?DnSNj|Ti1YVNd|A= z(S=1X)SkYM2tzcmP3IrgUuI%dS8e`};DO0TvD(~S*B7?aulmd-(5^P>|Gan)d$FJ< zmfe`(0nuG+C?-aUMr8%Uaz~jz$VjSC#7+|E5^`FUu0=Wsxcp!ltPHX?W^9^2uL6cdDHmzl~#nOq3 z7u6d>bR&0tq0NBH4L%+6kBvW!4>^|M2EO&-&>hEqJ7fmZvDOD_E4J9pN^o1Ko9sdf z-?#rm%M!z1J(?JEtCMFU7zXBw%UflKK2H#GA9}NdLebo`h!^!NhF9i^VriXJkYV*t zBG>2G_*(h_{mq)2|Gn0OzqSgj)>XoQAg5=VQ1=bDwU$!c7Gaiv*hJw!#IEfFElO7B zCp4-;G?g0P%`-Y>@nP7CL?N;NBPewk8?oOptoU&SG65prxYl~(ryfUovXH0*dX#MX z51q5=7&=q&t9LJ*0FpMUJ{=k3D1m%fXJL8nzj-||?X{WS|6zCsNkkC1dB3_#g13owPjAQuDA?>IK!etY1I7bLc6w$cawnV2#ld75W^abP;~62>HT zJ4>;7?UP~~h~SQ0oEo!O^XDx;3_m8Lx51lv%kvyEha?w0Y3_e?*}Ov6BHk!BM)6LA zWC3>l!0n)s!QENv4urB|LBd3k>ued*VoBpTV_pq#(Y*un?odtkmgb&*Y{-%8^LDt* zl%Znrd>GF6z(Rq92ZJ_}!_^$Dt00J(a~1FQBdBMz|IIq!TE>U_Xj&n7DIF(pY-9;A ztfwHb(|)iMd2z4(@1173^|)ErzwdJ^6XSKmxqSsU$*4GlDa3o~ZcrBc>%M`lRittzbcZZT{d?aG{~BNsB$&qN61 zhqPC?6lYG&!;yK!A;EPvaU(-|!clDcFD1CB>4K9b?H7J6*L>7iz_>km33WE6?du+f zJ)qJs=1c(^q~we0bPBQDJQT?{D)KwWBqI?g<7hD+Q#L|^L4JZ4ac*)5KD)9ks;m;R zoKH+|FZCZhy;-b#{Kvi(I1T0@Oupcd3emvJo28!fZBsckay-D%k)@vW>O=cgB!5?t zpFNL#$F9=ZJNOp^ISdao(FS@#Sp_yZ%{}Cak^yBqc(=CO?WHbE_oCUS^&djoVUDG& zXdZ|PF-JYgJc??U<0II9bXoheuWJ}a5fyb!6-J2Vs44ZpigrSXGFi0Oqm@l{xM1YL zH~)3`hDNmGJob+LLCjLn;=vZBH;Oc!h@whB#-mcZXExbDYAU2=!IXj4i^!Dx)k9j? zH^Ef?q5ULUD|}+_`dQ=)^#{23<@+7K7D0OS#+pgq+WkeE`H*Et5fRdB13bd2!es2) zNUn!8x4t8P&hN`)s!~jwk1p#Af0=?8s>_gaXI!zV;h?emLUk=~Ih-rrnqI2LPjCo! z^%>Q;*{*#RPqC>_F_Y%pw60N50kH?U2!DUr^GcAy%qBJq?cT{wL)-i96$=py_*OV{ z`Cv-MpDmaMi6^Xdeh{W&8~Ysr)? zfN~}ln!dV!wRnCVky;0tj{Zg-m$ykXJ2SA+-D|*k0r(oNb?6 zd9o*62A8gbm@yBUyp4qt1{dwqZ~ z(-fLD-fU~0Hw9}+b4Ms%65QTD_Hv9V_IezQF&z2Y{o9qnE9yX#PK3o&!fX#hk&c5*)D8h_BQ3{s zxgf8@@M4PVdp3Df$Nrm598*3Fi$kp`&NR<(osERLhjLR|eF7PY1I0Rbb;}X*%{Dwe9Sgwb8I_uBzgA zq$|TkbX9|Q?_2swp$kBPfr_Ip{B1%8jqFf`bCM}|x?Rk^gULj{=8m;xQ0oid5A}@e zOK47D9_QBaTD!!fQSuhG@ZWqn&vhA+`XZL-dNT4mOxx$ciOPMqzKyu!n*toHRnZ>HeBbYq2xYJNz5)ncyizWz)0`^A ze`!FOt2Gd`JIYmE-D73WD+toCr>!aG&%XSm_EIQxkRK~gjO`>L0pL-`N|g9>*`apU z>*3vVe(DG=Uaj{>9zfwOv^5QLCKPt5xxEKxbot65rP{=c}I{ecFVfK6av`S)%NC?W;8iZQ5L~ zw5?8i^0x%7Qv4Tv)4YXB71MPdqkFv=2mwV(-FEmNb|uJA@4S+L@cv2 z+r&@V`gS;+R&6o$^N1E7Cl1_(4ow%6U=H;31F)i7*}Q^))LGeyO*4E$I%~4tOURx2 zyNX?KR>R!!q;XTUjaJ3UKUNH4L==w-joOP~w2<-(nT{A7hK*E!mu$stB@rzX$yc|` zC8&mg*zHkmH0)FUr>*#eIaso;xx8UN7_-3y2m?zXVxq}bc0IlNt|!PMQ=Ol(5}<51 zFR9YB!@cJ2sdDWTwQq*Gz;eIAz}&*-#RZaGkWhK#N}#)S>lJh(2)D%Ep6C9}njZK! z@o5=#CiXAh#F_+fAQ`<1Eumzj6N>lrk#dk z+f=#cAU~3EmciUn$fd{hUC58^#?{0-A^#0ec2yU2Cl86+EQH|k7W&i3#6A4?jUq8v z=8O#%{w_es3Iu#+>+H5v(0t?^chcfPAY2B2mOuD5qKQ!eS>Lj}csh0qmo0)cPmqT| z6NaERGsdIWu`TJBXqeaXca=1YjRoap*~L?Mv`kU#K*MGo{u^UM;74TCn@byR4Z+#?{-Qw-RiZ(u`z!%#AoTK;?o_kVa? zRv!NIscDgj&E&lFp*fMlmAE94j8th79m`Z}*pS^?e>!=o^AOlrM5|>T%)8Le_sG?y zd_bn4{B2XuRi|#v^K!TwCtrs;>WSLGL!j(Bu8-Pza#t@MLC&rSX?fb(7e(c?)q_~F zpghrcZ-m0$2&Gl{L=%>nq<0*vC9Fb_uc==)6VZ2IPA#-U4jt?gDhUsERWGTX3N`7l zzlP(KV!*zsc$O~IgdqOTkY7T{Tt)Yl2=!g}*e&V82FX`qo4ehg@Uh!N?sPU1%2ORZ z%nt$Sb@{V(e$EK}+y-r9dpGuIJB=W6A1`dJ##YARo%hHTfn&ZzrIikCQG?e9rRE)-nn#T zSUW1*{Jmwu1KmOa?1H*YeO5z;@9!+j77Ufid}+;8 z$DffVqQgMM){ITFObOnx5TH^cuae244?$FjYMzakGsVWJ6=Urr+TO zE~*h27?sUD1z~maYJaq)3zc@i9nLk&{3%*8DjI=*lm&LFALCY_AcB-BfuE6ssXhx5 zu%*fr$M6exyPj?7zS!@iK%q6r_j(Mx`5I}F+K%6NgH8<*4eySQUcXB~=C(to!li&X z#6eNA?Yuy3`qhkg6CT7}MIzq4>soY676kME2cBtxLSiu}6REFBO6@bVO?`lb4X$dOgTV%I@E#J6{MfwAa?1T>^xNwgf@V zlicP-5f6xTcH8u#{S`hL|sfo~nEDg8RNH%Q#^NpBkVmDMVD zA6X*W0Sh^_5#{5KM=F%-db<0?6dK1H*28=u$_CwLQ})|1q~h;7^6lJ|FJ{1wcwN4b z#R~=FAJ{U~3-_okVW+|^rPyXbR`jL(T;imQQt^xl-3Zx^OuiGAb&Pp$02Q8xy(GE1 zE;4(|=8_ETCPsGZ*n$~5WnD2ln~`_`r*<@CDi<84KD+X9Ahfe}#iG23W-d@0HX~?s zoKlafkkPE-Y_zpY%S_dLJ52Ep16c^ij3BkHcss9Z!e|@xe+C}_nG^aKC z^hg_yP*iKbW5^q|E5G?h|Cr(SoT3jE@XARnO+z}Rf4hS$G|Gc4XqtX_T>i_eBNQ#& z=NMv8d}ZqI9@RIvMbf{$_Q#Esm>;D=$k;(xX_I4p!XMJx1}okDBo~Yg2vV)YaFePg{1A+1quFp@t`ESDK z3BJ!X5;|Of(2oJ%xtR**h^?Ml*%YBcg47 z`NH2o4n29sblnNXwkui8PvY>?vMvKx(nKBF#$?y~hsUY3Iq3=%`Q^!rhJ^tkLd+)ntup ze27H|EH&`jAp@!qRLu-Y#_ked9yd7rBgs{LY-wt9#88vuW}N1YLSW0PV?hU;IFT&f z?_Eh(2x$voCcI&Q%eueQAb5Rox1Fct)xIpJj-8t^;cHF6)I+mE8wW5wIy(aBhY`a< z_+j&kv9kuGDDIIT;V>1?X@M3$)zx@Pl%I|5ak+;t4We6%e|W1QBwET61l!;=s`s>R zPlQfcwq5z%xaR}reo$3>=^fX~-2Wcfo|hn^u$hUAd;rWS6bk{x& zyD?h!%#BN63aujl`xGcN#TR1$_y&o5C4&h=DK-@Y_%JWX;jpW=5KeppSi8#e3;Vqf z;_uibH@`Iq(Ggs0dB)h6wPA(3XDmrxIqbd*ui_FzFj5HK%*d~v{LfPB4=+NslnX1~ zDkDIZ1OrblbyKswavts~f)9Q8#XSi&BtU{IW0 zbOOSMqa{5?_HsB}gle4WX~R$EzQG-Cf-kVI$VIS@XYT{MT$Ur$u!$20wkwIsakV%Z zQ{^AvNm_si(<1^v0(Z2!~ak>SG+ zyX^*p$QI+XhvDPIT8pP6gG$(R;W`jEeSXq9ve6%*$yo`x`+#WtgxV0*s_C|$c<^J` zE;j_48Gc~q$+);ykH(!f@D-JQOY!R{QQjI`FQz%x*(Za)O9Fk?zngev#z4UGP$=pS zYXnvm28}Q7EVhq)!OzwbM4RbQmn*1T+zn|Zz=ub>Crrqx4HAJ;u)w80^Zy~l! zbYi6;$vq!;tBUsHes7>m`MIX$UI*=1B9d!AsGb4qo8g$xU%w(`dc5wy&vTJVXK%Oj z7&jh0aSL0fqpbs*xS~zBPfG=E!~KL2AFAUNo75-0>l0eD)cMD3fLj%Q*Bf5VT5vJu zh=Vx}-umJ%Hl!ng(5~Oxz@7?N&0$!9m4fwT$ zjYh5qT<4p99|aadx)crpX@8zyY=wS%)#Ye0{;`xPs9V!=9y(k&?<$U&dvw^ytnF#9 zr_oi2b5rk*Mr2nal;vP-!mWfy8kEecRC^yS@sjQJKu?Ms&%le%KQ29hcTKEi3)qdr zAl6sDyRk>~{WJdp3HlZ&RNBO3T(Yj{hk6#f3rv7G*i0Ru1AY+XdLI)VGA1F-m{6P^ zkalc0vO5P$KYzCnJI8DeIC6O9jAKhFPIe5M2fLZIi9w|YNlUE?ELX)iN$5?<$I^?g?=9P z0YSi<)|JJ9@)utT=&as$V+qoDKBapZY;K|*sH~(NKOykP^363NF#Qhiz<2#D^1+iB zG&SK14|KygzOP{0YUG%Y1mtMSdFw4ZKWN;jdb`R+;dcSqtLr7KRpzc8clht4J^mG( zh!{niK1|vJHQ4v9bf4W$LL@;Tj4iCl8dZ}90y*7ruje8G#JwU_-jsKk0gQ(w1MRBG zE$4IAAY`8Tm54uPWn4NgH7glp0Y+ZeUDb7*e6KfrbLv=Mh{X%iw=rv>`m^ELBy1!h z)nq#EUXS=fbc+a7w%^pK;`|786rUwjQ#t#0?YKBpW#QW-PlBM*#*$XL`)nj4{>Na` z-GU9J;#OE!#3ueRbr6J?v8Ck@q^*1pABJHHbG9Y1hMdkR2v;Fevq*t z&sf#3GkM80g!bt6-L@m0cXzpE%KKGf;Dv9;6(`JH5)2@^>hqIKHlz%)5W1~i2b7>r zMtL2SV)F-+L{U3^byjz(#t}Y3w_{;vi|*X`Q^zJeNt^A+3aJEZmZf?#dL1OdIBh?`u*1{4!XX@*v^5Gk<$ip$v8hp-X{)jAa$xzYisDiAOtOb&4kG8}Px? zx6kK;Dtwm#u-(v6}SMpwS!H(4f_4JWFZ(hOH_V*_98VyIje!0ZK zmQxaIeeaX)E5Ev)EUvjJ+ZjHx91!i*t*Sp{ZQhcO(jqK6$W=9ueAoSRBeqv`{ygXn zP4_bp`sD#VmC$N;a%Vc_(9a!12@&Wk|8}sBH?nk}9{y&CCDJrGb3K*oyNZQ0ZxWG4 z-9dw*%nOH|b`og1t=Iq+Vb|Q=yJ*qMW||&p8-ozHD_$QL_jfGpEFHuq9+Mmlb#bu? zIdOItHZtn)-rGM5MQ*ejlgQ3)y=nKWa*gsA-`EQ;iJSDn+tQMook%w1<+S$i8p)OpBsO3xzCg zie$-B*1Hf|bhiw$-l3?h(M{c!@9TB0Tkm(D|KR(0yssZ-bYJH>=e0av%Xyu1u1m@E zCzdcjauV^nnNP1D9#2s_l=Y!Kllczwe%#n2$WT-!`Ss z8QPG1huOdRf;yw+;Z=rX12nU1$W zPNtAE_gM9={q;hfiEz)>;U~#j3t%+Omu>5V9hy@sO2WBc42J|NJzO|Txe)V=9ztP?`za2)5m(4agY*<%o3G)Jpo9?!ACLIP2 zm0+2gwY%o9jQ^{1MWNG@wIHOh!#B40or();Jlr&da@A0>rt>BkSFVzEk5ji&rMllg`G0X#bgOh|0d$T~pbO zL!gn4mmZ7la?{sz^;ouuH|?o^5@rNeUAv^A-$*TxVu$6C9BjWJEXO$(CU(EFINSrC zK=hef{BtPT^qjt%e>)XGyl@EYt>!Km;)?2F0bZCg@ z!QYR$W}05FkMEo)eJwlJyhDzJ<*4{MBS3K?5rzDRRS+Vsk?a-Y$qmcxKj;pXI-(Vk zU4H!vpP~9y>7fq07&Cj5feW)iW?fYm3WbSQpYjai^qZoxv@+oIi~QHYaV*hoWvd5% z`wNEix|dY`C_yd@Ev`o|TKzf5He?)2?=Fm{XVCO--oX!XohHgAp5W(IR9e)fo7;*$ zWjYvRD%0cIJI^PxATlO9J1-V?(cd=(Ifj-_^dzDO^X9&@wry(Be&s2dn!on)&_$@l zTeZ(|ZoF54ez3`)l;@Rys&7$dcfn-B%8qKYDS&MoWf<-rnCtU2rKETCuisHN^{!>X z^2u|y^7SE6~si%(v= zvRcFX61QI43CQB+y0e=DLwwm*uNf7%^prc@1{ZZ!`W&cep>Ht`tW^KLZoEeml*eDSezWk zT7};KX40bQlEn_Db0t57In7q)_zAmsYFi))&JoUyzvY6r;>gzR_4PUXW+P0ROk6%{ z{X}Y8!N)Cl#mftP9yYjs@JTXf8aRcXUtg(L_x61V>Wm%*qB+|?OiqWG^Ik4}#YtfK zr0j!n9F0}*TKp;I;n&v(6>2^l{Z{RjkjdM~BYY zPXn!dn}lpG59=cPUsHvEGNfGKB# zJDs&lrEl;{t%2r_5_w#qwOu+NUKM@;0^Y=dFyU`am*3f>v^F+|L4lm8-|jzsyt{c< zO*@>YWT!*qI$c)hR3lpiN$KxCVq8__PZxfJ9wvB_9f|T8nH%~$3rBT&_daWTzzYyT zRrTI($v;#99&eZ0OU=?ZNx9SVw=!L_L8e!d~lJQ)>*~&GVhYwv$)gn$h zpW^9wfB%*}o-8P-_YWkwT+m78Yws9;ZP8E4GjhT}BAJiXD{Y2`hU7w@KL_qQ^z+}x zl-v*1*|-7@I9KBB<4O>Tg=1w#x-*)sc0pO;@vuVY@Q_~9ah9P`w>FtA(M^2+)8xIa=#4`L&i zyTutm*LY$a>kcgP6NfprQUf5TMs@FI$uT4WsTf5Q>Exdlce70k^FdPJ& z4D1=mXT^aV;H*UKOa|;Ic0Kp(ueUtrDCE&Ly8a(UD2 zH@`|%JFhbtbhy(zq5hI;*vJlf*wq!Ncmyq(Z6ya{&wS`@Q3Xr5@yRf`Za<$?zQ6aL z-$Tc@YtDy_&V$x$6$L?SR8r=nMarMEND?3+r`PLix#h^A{4+ZZdu>x;^^J%; zUrr6KKVt71qMLjhJn)iH!d=zK8TGCrW<*6D=x-j@L!tUSm)UijlgpT&c?J1B+$_xZUG>^kc4>&K(De&>@VK7C}22y23Rh8k3(F{xAo|mZS(Xks$gxNvmPGqUu(Xv#mn#1if@&HpAY4y z?KJ%OODZ~xw5h?k7g;z{F+1@z^~sryj8Eol#pfRJoNrZo4kerCr}-{PIFdgR931-& zlw{3g)C%{^0H2(bZStqT6q1{}Z+6>j7YVu%lVx!$xWnD{a7j}{c0jD*f$leu$P<3{ zBs!c)wqkY>gde}riktAkSQGiI(WH7&-6n`3oHgd*{*#_Px-=gqD3NF19KvE7d*Sue z&c?xcFm>YYfn+o2#%t}VsJh*#fzdS70F;DuyY1DUgM_Y4&G&L12r=VDo2YVNT#l2ilb|@YIG{jGPfKamY9IOzp9l9RKQl*Wcq+;5-xQhlKjBTW)#X&O)G3x9(cm-NF#p8ATK8Pe8bk zNa`H!)%gb@EPZW)aw^2gGq7Sn#IRhExl2ArxjFyI6CXwJmsXQ7fo}1(U6maMxaCZp zYLX&sbpq^(B@Bf(qOpHQo4-XBjbi?0v)`zz4%l0k%-!%4iC2s8-X0e$M7K_oE_CJX zX*Ra2axG5+yxWf?l|z9b3)3jhnq+msTxmLpnG8T_oAj4xR7R_BZNvrj+?(KLD$40FR`9(OL?MA!qM zFg6QZBk6dCQib2(i@XePobz?-e-c6eSdf8LJp4sO_e!?;#~e!mKY$afXKMlI zu*$`f{_`F0IxyCTEm_zybdiH=pkp$)em6_-C!F)O2Oea@OML2L^55Q@mih>}) zVf~FFvdwaj8Ay2Zv2YIfw*_&u)u!AgT*1sw?*v-&5VdPyDU@iTnmC3ZjKY)EyCHW!35G9qLq5ZKFeAkura#03 zcLn3RF7d&?tfs$=mg#O$$j6sK>VOd`NEHR4Lm+t(T`yvHY#c}OQ0ZD2IaL0%OXl-o z@`EYSX)xv9!NsZrhNwd;EP7d$fo9^@qh=;57lda_GJ4YxEk}rmF)GL2&ZLxg60EHT z7cFYw&>3l;41bZ4g#`(pwJQaN)P5Hu_Mnz3-cmom!lddaRs&4)SW+l_uI$VC3W4uo zb^zAozlox5k?M6GTCsZCecwAIO&WC0aYmhM>ejZ#KJMy$Dkf_( zgZs2AFJ!=<4v?9Amz+ad`4b<9ScZ^nEl@aX%(#LYMelqHEUR27IMKEn>bX4Iu-Gq4o= zUyoUiL8qN5e;Z&YbDK}?JIIc?PN;ozkiN5BAoO7VH`o`DNWK z;-roe6GDk)zSaODAsb085h84z2KlT+ht8Q`(VT^F+X;gV#|0a@m1z8yH=ayC51*`U zr};K)swcL~kXem`lIJ1oo2Y+F5?(BiQeKSui-1WRlsl?^ebC^9L2B&-(N9eCzBVTK!kHq#H! zu9&%unqtQd*;7|c`^lMFl0_qmf%pEWWNYVpQ@TIf?ofZXvcL)@Za+AsrPw9L`9Wc8I z)E@s10dV9z?7-#*Z2@pwzOyO#S(vE&gu%~rKO9AD18B3>fOF9mMmMt_Pa&`iZnr2D zM|O6hl)%K^sQSHmsT~et8p64=+fg)ti)R5snl;P)J_^xv74~#nahF$|t zLW8*^WgZ1e!m>rP0WON*AD?Wi$U7wA_eLvk~#LCmf&PS zSUrnDSjCZReKpE9gRBY=&Y{#DZH7}QtRuBYQ>S(x3Wri9#Na8G;;Yck%_!&}qot+{Z^ z=_h(?HQqACTffj-8$R{8zr}i$S5#8@y0?ax{C}PWa*xOJUh(MIt*j79@qI7X4cH8B z_Od+r6A8GDktLU-4Jib1ivV==(+GG5(a~SI+{&3it6l8JI1?Z1~ZRHAi(MuB!GFtyKJU8R(fX^f7 zYR99^(NkM$5pO@epvXEAb1i&%3mM#qTj=inFe$zIKIhm!hcIi-+#^ z7gj#;k(tAKyJ!}IWc z5d38^5Q02ddGyeb?`Q8~Ut5E2c%-R%uaIp{Qu&PpxwKJ}!HfhL-3sN$fUJ+rsMw$4Ypxfq@K0gEo5fD1|dpa{8)yyU<@uWxoQ|i5fZk&nvXI ziTe|zq7GZffEU0H+k}#!c+Q~fQ(6O0%HB_S@Xngx4mBNUW;-66&Bta1RLqiTqLz++ zCg-vX9sd~LzuSD@E3Z>)?;aa&N|4K_D+ja9#5CMV7A4h)x@LtMOc;nx=^mYb;8K7R zRBUWnO82u)Yt61Uy)>NDsS|b`B<}{hbV$!`9Oj4_lnPa424ELIM4oMqvKJagJ$>(> zgPf>-9Qq<{#3meQcFyoyk?=Fu;Q-Tlh>)G8r@W_X3byYbZ9F7+1IAGpHe#D0X@~{ zPkdn6fqMWYzaH^O2Jd`H*~AKp{rdag!|&s;E)s?OIwI-k=-uiwALiqHq6-Esk`_Ki$*cm~#44j^RhO z(l7hq-Lc@qFe{RR(mrgKa;O;A>$!Ote$C#|_^sE{bJ!22f6JWh>wHDjn*Pucwd`71 zuawd>;s~tUilg3NAm5jhf)xtVV^-bwy`~0d5(XOVgT&r`n$|ly&G^%OQ)`^ z=8fjxDf-klB3amYMO!qs17N4p$`?xPtqp+5A6E{ED4^#Bt%4$&zJBdL{&iHVx!S*v z-)d7}*SnI}A3OS?v$NgvYiD>7Admz=WuDZ`1qfO>A_VE>1BH1~b89993w}mJfakof zqGh;h3Ky#jt96m1E@LXv0jF|^lhc2G#LTaw0m`H-JxEZc!tVbn=t!eQ9;X zs#sigmYdAnV{O@KFMbKehRyE8$?SV)DLBG{*VvggKx{^s+lmrvge+dQ`oO8WI@BIn zTVRd>3p(nH?U-9+L4#jjnh4>>qfn~^hcH3inEr8X@y9KK!_VL%rE?5?-FkGWnGuLryx5KS zSc(g*7saQ1&KU7qEK|95?5Q=82AyEky2-HeO5C8n6xn-m1NeHptYi;A?!*eT0~(DR zb+Y(7+Zd&n;*K3Ig*kSawbGdM*6&2O9bak>Zw0}+;C4+V837!6ehKN^yNs}^G610M zG5NQ}6W+8QzKR{f#U92!FQY~{+i_jcS@HnvHlP=)JIYb~q!7u3!z+Yi_gyDCs9;*S z<#>)o!%iWth~ z$dbwn;0N(y3GEa{W{Qy+A7JK**VA7G%!q7+GVWpVqe8^(>@?`UyVXW3tn8QoU=U6L z-aZci-tjrGBvIM|Z!x8p?sLWL5H@7{&*?o}gInBClAgfaCGD`ah{SKD1E$JnY|y;Z za_X93$o_J_yuqnZD?-ft{vAj@XnO53XnHDw`WMdo>D7MIDCQNowkw2ugV+DSw$xUe z;jimVVy=&|D2meyA0)==%nx>`yZ+i{{s)hAaiZ%8i@8K(vqe>(*RDSW&}he;-bw`t zotW~2nloST&0|l6DKfpMsQgJ^v$DCyO~7F0Gb~rCs6z3Z9DN>LFWY-Bbj29NIpg{| zA1cAdk#s=FFMf@!g~4Ta)p4z!UhB`tYWt%Zid+DZlbkV)&KzqX9Dx=vVK-zdINx&$ zN-9@sSvK|-Ri|HWy^dX#gN{Yy5&((q32|?gl=2upJGRT`BtD~f*Mlk~=^6inW3Vz2 zq|zL$`LhQATqnx5FNhW>mbKd#v!kAs-~>n)6zPA^T?gAQ&uJ=Q?F8MCF7V)HhfQP# zPL63|y@5%XHn=D`IEa8}tqC{abB3{6lyS>0*yl|zucPiUxH8DvLbsJ<9>&(`Mz8Au?AgZQfG6&TN=AK25J9*wBP(M+9x{9 zZZiXva-EU5n)Iad#z!sJ1-4@4BycjC@G>OAmV#McNZI$BDgvSRsDcp_Uplpir3?Y- zEQ>YIpL~Ob4r1a77R(whfAX!bMOd#-VlBg4eBdf)-1kO2$^halt>j<@2dB~L2Vf5e zz1mO1S>*<$!g0iEJ*r0Z+7DKKdEl#EC0+I=X**$Ulc=BiL9iMQ@CpMV!h3|BkEcrt zV*eq|*!x`M7uXfaR6p1=X!*`2W=!kxUtVVO`oIldfyopWJy?rj@zcS{B)UFvjK3;_ zcRYas*XYnD$bn1Tn1|jWd!g3!zW-vtO)<=yi0$8W;pY5L40~hCG(;3$%SGa4K(mip z^kOj4!;5=UP@ZAGMb&2hNvnbNvSXtksVlkEW`p%$Rtth&*B8QnXqnvtwWop((qJVF z&hnGcCn$jkSk`gZv=}3fcX)1_0@QhbBP5iLUrJq}!}sy6lm**pbl1Q<4yx(q`_6g! zomc@oO6k?{8Wv8D0WfRf7!Z6k7TnwX`qY+`)a5D)l8E_Vwhwpx5y+ja< zyfV!W!VA@K{8~hIy!8*RxW@|CyrD=*Y^m@?p)&H{fty_r1H3~n%JP7TD||v0N)`9v z5b<~Ebk~-w7T7G0wt!6l|K)D{tY=5L@S#XU8k`WDhkr;xxJyIS6Cv)BhP@E+bWJEW z+`mf(K8l`y8!H?fEyTUnW~g)KB%b{q>_s?BDz`IB32x`WY;?uU^*!c>T@u)o#2Itj z$DM~Dvbj}5#zMZyDu43Ue1Db^tJN=a7_H@^-)Bi-rS-ou7LI9~_jXG--xx2ACgF4E z|9GqRa3kC7Y^-K-uxN<8BHz?`<&R-b3-UKu>zy%y=oX#fwgz&Kk-HzKUu+g^cF>T* z_aot@8?w6;a9e;Rz?)Tp1OV161lCxfQS1}UOC+#@LN-_UFNpe&a5Hsls{)|Fwi$F+ zjF}I+=?8DS;Dx;EJ09+tI*zbnrBatw_dVrv8UvPu682sQ^7&Oty7|;~hCH?qByvox zlAVU+IVIAy_$TVm$683GG(V-g8VJ1g5wdGo(~0c$f^Qwri^s+B5!QdAf(CEFT&H(% z`uB9nqm@~2Bu*$CGB5@nEZpOz`QW?wdQ4D*nxLZ8mR@+N7jysNazX$1U_&!3gg}8? zRrNmsgB#ljX~Yijxzut)*^ zxS~&B4MPpw9#+3E(y{ac>6#$ zFEh&@q9u`n7f2DDFm*U+T0jL9FRL>V3354(08q9yA~4O>JRFiUoA1^&vBk)u3Rd!< zrbX|=y5JAVV?jiAR$@x0A>gaW3c1X8+fu9HY&kf5F$lvyQvO07?6)+tYpbkaQB?)idYYQB& z?$T>(`7k9`MiDN=A#jt+>1OA-ZMf+Muh4_Dqf&6Gia6PhKCf(;uXc9dZeldkYTRpu zlbau_{B8XpUePvl!mo@Sb(h|B*=0-~_r=w?a@=ymsuG@4yg0 zQ%$5c@K9}6JW%)k5mKjLA=en;dX>>Fyvp9Na1i$Mvvj;!TNur;|I9!7Eo)dGWQ*T^BP9Q^oJG*LXZ`}=aQ``;+L?0@9*6IY*nNATw38gglJvNQax|=L&^0xQuU#6~cBMwkOJfy=F}KejL}E zEgtlNHS~eYV-Ey8P_dOhPECp>O{uW#2aB_qNxNg4pvZ~iJ|tZFjA{OqFVTDfR#`>8 zSx`7_gpVM>xW6QN?W{b#gg(XEN@-A5L8fN*3J9J=e+)vyVW0deKt;$q6pY^)WYF)Q z9}vOr_=;o8?!~d=eoMSJ6~-1oS;dfcHkooM+b0-de?>KvX$OBo%W+y#$h<0wZLfL0 zp7xYT#ebkxApVUzq(4^kj>v;Aaiv5}7?aky79=VLKHWyhu0M^PC33`?>Ql2ABft8u zFqNY{mE%K~;9|DFL@r;zPs?&Y`y%u(F=6=hDK^+I|7;3lBHU(+wZpX|R{-Ja%4i}I z;W5oizy<^`VZV_yl=6p@cdRGNP;}wm!|wx3GhQ*-am=%Y00(p(To)YM>Kb6IDRP&< zxoJP^$r=Xdfp}rRqk<`aH_K6P7GNA2=xBMGh#s;+QB{Ic| zc?aDb`7!-|mBp!vJ?t8Y$Rz5hyq;U3g#M ziCB}p(TVb$c8Kkc+G=&z#e0o2kvR5B@Cns`sC?bg1lj2g8!S$rrwjxy5K zGDDyQS&yxsIhhR@q}Xf}@*pT7DQ0)YX~ss>|JK70=Y!7&?nl$l-)`)@yVA5`-F)~7 z+~7Zjtq?*n2Ox!I*n?M2Otbsy0U3c6^{n%h5o5wMkmwfTvPt~GP41t%-)HnL}bHF>yK)<3DjiuB2*>}p#u4*7qw*WHJ9L= zcYcc78Edq1=E(q-5X>Y1zBdZ~$Yph@`)W0rolHbfc=+8|i1tG&2UXBCa3KKm#p~8L zi+PkpQ?v%6z1!G%{zr}=;IQ6!eTNd)4BY|iZn>Oj3orWYWk!%oI{*57-7XK|%DzQ; zIB>yqS7e`A#tUk%>}&lpO;OptTR_u2+i4FW<#LDDO6CeYoyx(ovt>6r52L~PtwOYN zueb!~xg0hSBy19w{RJL{RARbD?%ktkUe~pTVe-_&g|GL}jDY1zv9fOQe~XzF(yk|& ztpn*Axd-2ch1lul5*+Za;ePtEfhr?~!$i ziR!27g2MTNd`lMc!5ZpstrRGS8_?%{_XzBuei_TC>@_!m0*U#Qg3SyC0ws7wT2~0t z0`dsHZw+R9Ev7j;CVI@J1$?~InqZelNmz!_6BD|7@&~ql&f0f<2GC7dqv>K_4*8}| z0rsX)8Y+AxbDJXV8GGvq(gQVP5Ms$uB<}w~L{h}vmfkQF}Fqla#dboXnx z1&#$zudCVsbTl=hLw#0H2;xX5SCGmL=}IH1r12>VfqO&*cY8x+S3SaS1#I~xUNji6 zW_nBJ>Rb?b;*cLy$Mr|-5Mzi))4p8g@1VXgO#<~oWUWwbJsX%_t!B`nj4{C{+;ej& z(`_On8uR?jc*DX>(j=kPB4vky=EHkI!}9^ zBp`QtC)4w9Oyi48W4|PS6%?1mY{W5ZvWgr0gTQzFgo`2XV{M{9xHz2w$lw`4D;HC1n*Nif&6T1S<%pDM{};eUgL zee{crr_6KuWS=!^HcjO?B93e{vvjG>Y@jgf!xpavSI5ygd=xttlk*(KU2A5=-*RcIF%Vb zrds=T7!8^v0={|<-E{j%=2}M9pTE5l{10KvsvrNB-jIfObeQK&SC=|4O5(cxAg7){ z3TLmo=nfM2OlZf*ey8;f?Vy_)JGa3isH;feE7|k&&ugw?@+&f0LMgC}0B7{=#c-~` z+m5{d3q^#18{V&vkRs9%KVX?PotF?`AGiq&oODxv$KLR6R!%~II}_WjHif<|D&|=r z?uVwJs)@F~aj(XAr}cD)<>ze~j*!KH0tB&}Wpcr^{(PUWz=_=b{PV{g8oP-)g(|6B zlz2}R>^7UBlU3m(>dtr>l1Lo}dpn&?uX>%%XP|h_Nd#I{XYPjYIlwS9S0m!*OzR_H z7)D-KUq=olkGV%OXiqV_YnQR}Oh^-wr@>>aO?Dns)t|)|{3qgTMz^CZpe@m1Tz051C&wdhk|Az^fInX3jMTW}d z={k&Rj(U=LsT8dWPkR#lB{G{sKB)?;?g?zLkCpL*JpGQ1k|DdcJ(&X1ID-Nvknyo=;PRPI}Jzv6_|(ubN8>pmq ziO@fSz51@xCUi4BSf;}caY9mm#e9q9nT8v~$xx8S?JNK& zJ9;whbYrdgKv9&ZG=0SCHgj(cGvK(SjC)}V@|1T$donRq^bkf-r#a&gN%!KD6mC3g z>t=e@>sS1|ga23#Zg4e!LN=Ua%(vI;TeugwqJg!iqCT=;*L;p$#jv(Zhqfm=g+Xh%eWO+R zLD0+@o3+njnr$?nBj;q2!1wQ4LU~KgIJF>4`7*MZ%aEkCxt3r3 zUh%-E&dSyB?|42Hp$akIeobaz*8NcdYbtx)TeEnncfVwN(9y){Xd*D$tMUZjDUDQ+ z>`B+$d9$)~K#v#jYuca@Yh)&-ChR}W zY@D0?3jYX22*#&^5Kb(Bulm71Nbw>Hqwrgg7b)IvT5sVImj?@I@FueL7KcXV4tuS44%MWnG$eD%S=izzuv={U~QaU5a-P(IWp=Tj0JDhp=ukvzRIIX8TRWE_;zyC z04Lw^3Zyu2et;q@-+=1N>kVqS@t|~8;&omvN|j9KYoY(k+4snbbBKc>ezJIeMC7e5js-FGq1ZqGdMRXI&$H|708$Yw8 z3;uQaub`XA(7lCJ|D4r(0kW**B)!*&3b&(FFzuPEJSX<$f({XH zNQcmMG_zTDa>4x&9H>$0Sch2nRe^An737)O8g|ZZRH7Ez zzJlq?)_nWMXpt2?1>zsJV~5RQdkmhJ)qnv4(K%5Tt33d ze`jBiLVJ=}#PqZSsInf=@4JDP=l0hQyFjo##_vdI&?dT?rCojMxAUIwhPZSuJl8w< zz1Enu5bMPVbE3WpT*l`l-{6MsUDzrOK2B515OeAhC5Ee(lu@IcryQk*IOksWcyT9l zeQ2$xFy zhTr)EVY0(hv*+k&7Y zm48wbaGJ3GQOQar)W?dhD_Ll3Nez(wh2ocJ+>j5^2OisyNAay?eHVC_&mmRy$7u)g z#ejf6EZj{#b%ne)+70onQE@^?@_T>BMwjfyDk?6{Gzu1$6q?)fBLgRsfydWxxCGux(ODw6l8{g?qXDod3M|A=qrSj4k3wmkOHw1Y}Uscg*tv+-WmgFIpRT zG#*Wu#R!?Tbw>Vp(qrtdX$eP!F6TdeRm5@G)#cSou2q2v0iP+T#=57FvIb;(}Fq_cLroTV;1r zYeI+JW&aBS*_G(ObR!vDwO{sl=Pwo1?*A4NVP%75eEH8B{6`w-4#{o?@K)2o1ckff z772H|0%z-PgcfV~2EW-#b*9_I^doUQNnwujiN*mTO~Ri3I^3F<`Lr;2L1uRhrdF&% z8ncLsyhDF%qqa|6?i4ufCAVvIbh&Tq#R88xINWi@l-M^Ru$^3xtGu>fl+?#wqzc@` zJkLIPu6@{=-&bz~?moFr7PsM#X_r{s0uyQF_!spn`3Mt>`P2|Efi}?gl0$1&8c}M&mXWA zuLaNYXgku&ea@hfYkjNMw98YpK z4^ibqkR~xG5sTMVN~a2qVAw5neztylhq_@ z!4VgOsU7Zp0hv18M=4%Z7BDE5NJvuH%+(Il>llGAB>M};PyNWd&Kiz;pR!0fquLmK z7Aajk_9P>0(80*!(1XcU} zV?oRJ##K${o_Bh_)gW7YGjkgQMpusvph)OmZzn~jS$tpUxWQr1BBJ9UO^UJ)7B!iS zC|V(Wh=x9ci+&55;qkTq8RZ0=J2q8le2(SL)aZe`!#8rGQ*!3Fp*+dAKbY319=@0| zr2UVHc23i9;Q00^bG}i+H=S@jUZSmkcH^BG+O@aVt_}~hwl_&z3hPM%7FBs2kK8jOQAKy7C^c+p#>@%F*L=G| Date: Thu, 30 Jun 2016 09:35:10 -0400 Subject: [PATCH 196/274] fix links --- docs/api/plugins.md | 2 +- support/lib/helpers.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/api/plugins.md b/docs/api/plugins.md index aca72e4..0a8e47d 100644 --- a/docs/api/plugins.md +++ b/docs/api/plugins.md @@ -61,7 +61,7 @@ When plugins are [registered by name](docs/updaters.md), they are referred to as **Docs** -* [updaters](../docs/updaters.md) +* [updaters](../updaters.md) **API** diff --git a/support/lib/helpers.js b/support/lib/helpers.js index 9ded6f6..bcb787d 100644 --- a/support/lib/helpers.js +++ b/support/lib/helpers.js @@ -59,6 +59,7 @@ function createLink(dir, prop, link) { filename += '.md'; } if (dir !== key) { + if (key === 'docs') key = ''; filename = path.join('..', key, filename); } else if (key !== 'docs') { filename = path.join(key, filename); From 5dcaae980048e7a66634249a42c44d0c6c10accd Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Thu, 30 Jun 2016 09:41:31 -0400 Subject: [PATCH 197/274] improve updater docs --- docs/updaters.md | 85 ++++++++++++++++++++++++--------------- support/docs/updaters.md | 86 +++++++++++++++++++++++++--------------- 2 files changed, 107 insertions(+), 64 deletions(-) diff --git a/docs/updaters.md b/docs/updaters.md index 1681481..f6252d6 100644 --- a/docs/updaters.md +++ b/docs/updaters.md @@ -1,10 +1,11 @@ # Updaters -Updaters are [plugins](api/plugins.md) that are registered by name. This document describes how to create, register and run updaters. +This document describes how to create, register and run updaters. - [What is an updater?](#what-is-an-updater) - [Creating updaters](#creating-updaters) - [Registering updaters](#registering-updaters) +- [Nested updaters](#nested-updaters) - [Running updaters](#running-updaters) - [Resolving updaters](#resolving-updaters) * [Tasks and updaters](#tasks-and-updaters) @@ -16,11 +17,9 @@ Updaters are [plugins](api/plugins.md) that are registered by name. This documen ## What is an updater? -Updaters are just plugins. The only difference between "updaters" and "plugins" is how they're registered. +Updaters are [plugins](api/plugins.md) that are registered by name. If you're not familiar with plugins yet, it might be a good idea to review the [plugins docs](api/plugins.md) first. -**Comparison to plugins** - -Here is a comparison to illustrate the differences between the two in detail: +The primary difference between "updaters" and "plugins" is how they're registered, but there are a few other minor differences: | | **Plugin** | **Updater** | | --- | --- | --- | @@ -29,12 +28,6 @@ Here is a comparison to illustrate the differences between the two in detail: | Invoked | Immediately | `.register` deferred (lazy), `.updater` immediately | | Run using | [.run](plugins.md#run): all plugins are run at once | `.update`: only specified plugin(s) are run | -**Which method should I use?** - -In general, it's recommended that you use the `.register` method. In most cases update is smart enough to figure out when to invoke updater functions. - -However, there are always exceptions. If you create custom code and notice that update can't find the information it needs. Try using the `.updater` method to to invoke the function when the updater is registered. - ## Creating updaters An updater function takes an instance of `Update` as the first argument. @@ -42,11 +35,8 @@ An updater function takes an instance of `Update` as the first argument. **Example** ```js -var update = require('update'); -var app = update(); - function updater(app) { - console.log(app); + // do updater stuff } ``` @@ -60,32 +50,63 @@ Updaters may be registered using either of the following methods: **Example** ```js -var Update = require('update'); -var app = new Update(); - -function updater(msg) { - return function(app) { - // "app" is the instance of update we created - console.log(msg); - }; +var update = require('update'); +var app = update(); + +function updater(app) { + // do updater stuff } -app.register('foo', updater('One!!!')); -app.updater('bar', updater('Two!!!')); // <= invoked now +// register as an updater +app.register('foo', updater); -// `updater` foo won't be invoked until called by `.update` -app.update(['foo', 'bar'], function(err) { - if (err) return console.log(err); - // 'One!!!' - // 'Two!!!' -}); +// or register as a plugin +app.use(updater); ``` +**Should I use `.updater` or `.register`?** + +In general, it's recommended that you use the `.register` method. In most cases update is smart enough to figure out when to invoke updater functions. + +However, there are always exceptions. If you create custom code and notice that update can't find the information it needs. Try using the `.updater` method to to invoke the function when the updater is registered. + +## Nested updaters + +As with plugins, any updater can register other updaters, and any updater can be registered by other updaters. + ## Running updaters Updaters and their tasks can be run by command line or API. -todo +**Command line** + +To run globally or locally installed `updater-foo`, or an updater named `foo` in `updatefile.js`, run: + +```sh +$ update foo +``` + +**API** + +```js +var update = require('update'); +var app = update(); + +function fn() { + // do updater stuff +} + +// the `.register` method does not invoke the updater +app.register('foo', fn); + +// the `.updater` method invokes the updater immediately +app.updater('bar', fn); + +// run updaters foo and bar in series (both updaters will be invoked) +app.update(['foo', 'bar'], function(err) { + if (err) return console.log(err); +}); +``` ## Resolving updaters diff --git a/support/docs/updaters.md b/support/docs/updaters.md index a2fa296..e117551 100644 --- a/support/docs/updaters.md +++ b/support/docs/updaters.md @@ -4,17 +4,16 @@ related: doc: ['tasks', 'updatefile', 'installing-updaters', 'symlinking-updaters'] --- -Updaters are [plugins](api/plugins.md) that are registered by name. This document describes how to create, register and run updaters. +This document describes how to create, register and run updaters. + ## What is an updater? -Updaters are just plugins. The only difference between "updaters" and "plugins" is how they're registered. - -**Comparison to plugins** +Updaters are [plugins](api/plugins.md) that are registered by name. If you're not familiar with plugins yet, it might be a good idea to review the [plugins docs](api/plugins.md) first. -Here is a comparison to illustrate the differences between the two in detail: +The primary difference between "updaters" and "plugins" is how they're registered, but there are a few other minor differences: | | **Plugin** | **Updater** | | --- | --- | --- | @@ -23,12 +22,6 @@ Here is a comparison to illustrate the differences between the two in detail: | Invoked | Immediately | `.register` deferred (lazy), `.updater` immediately | | Run using | [.run](plugins.md#run): all plugins are run at once | `.update`: only specified plugin(s) are run | -**Which method should I use?** - -In general, it's recommended that you use the `.register` method. In most cases update is smart enough to figure out when to invoke updater functions. - -However, there are always exceptions. If you create custom code and notice that update can't find the information it needs. Try using the `.updater` method to to invoke the function when the updater is registered. - ## Creating updaters An updater function takes an instance of `Update` as the first argument. @@ -36,11 +29,8 @@ An updater function takes an instance of `Update` as the first argument. **Example** ```js -var update = require('update'); -var app = update(); - function updater(app) { - console.log(app); + // do updater stuff } ``` @@ -54,32 +44,64 @@ Updaters may be registered using either of the following methods: **Example** ```js -var Update = require('update'); -var app = new Update(); - -function updater(msg) { - return function(app) { - // "app" is the instance of update we created - console.log(msg); - }; +var update = require('update'); +var app = update(); + +function updater(app) { + // do updater stuff } -app.register('foo', updater('One!!!')); -app.updater('bar', updater('Two!!!')); // <= invoked now +// register as an updater +app.register('foo', updater); -// `updater` foo won't be invoked until called by `.update` -app.update(['foo', 'bar'], function(err) { - if (err) return console.log(err); - // 'One!!!' - // 'Two!!!' -}); +// or register as a plugin +app.use(updater); ``` +**Should I use `.updater` or `.register`?** + +In general, it's recommended that you use the `.register` method. In most cases update is smart enough to figure out when to invoke updater functions. + +However, there are always exceptions. If you create custom code and notice that update can't find the information it needs. Try using the `.updater` method to to invoke the function when the updater is registered. + +## Nested updaters + +As with plugins, any updater can register other updaters, and any updater can be registered by other updaters. + + ## Running updaters Updaters and their tasks can be run by command line or API. -todo +**Command line** + +To run globally or locally installed `updater-foo`, or an updater named `foo` in `updatefile.js`, run: + +```sh +$ update foo +``` + +**API** + +```js +var update = require('update'); +var app = update(); + +function fn() { + // do updater stuff +} + +// the `.register` method does not invoke the updater +app.register('foo', fn); + +// the `.updater` method invokes the updater immediately +app.updater('bar', fn); + +// run updaters foo and bar in series (both updaters will be invoked) +app.update(['foo', 'bar'], function(err) { + if (err) return console.log(err); +}); +``` ## Resolving updaters From 8150109f6d449821ac9e3a4736582f3d699fc372 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Thu, 30 Jun 2016 09:42:07 -0400 Subject: [PATCH 198/274] adds `symlinking-updaters.md` --- support/docs/symlinking-updaters.md | 71 +++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 support/docs/symlinking-updaters.md diff --git a/support/docs/symlinking-updaters.md b/support/docs/symlinking-updaters.md new file mode 100644 index 0000000..617cc9f --- /dev/null +++ b/support/docs/symlinking-updaters.md @@ -0,0 +1,71 @@ +--- +title: Symlinking updaters +related: + doc: ['tasks', 'updatefile', 'installing-updaters', 'updaters'] +--- + +While developing [updaters](updaters.md), you might find it useful to symlink them to global `node_modules` so that Update's CLI will find them and run them, as if they had been installed from npm using `npm install --global`. + +The following example shows you to do this. + +## Example + +**1. Create an updater project** + +Create a new project named `updater-aaa`. You can expedite this using [generate][] or Google's Yeoman or however you prefer. + +**2. Add `index.js`** + +In `index.js`, add the following code: + +```js +// -- index.js -- +module.exports = function(app) { + app.task('default', function(cb) { + console.log('updater', app.name, '> task', this.name); + cb(); + }); +}; +``` + +- `app.name` will display the name of the updater being run +- `this.name` will display the name of the task being run + +_(Also make sure the `index.js` is listed in the `main` property in package.json, so that node's `require()` system finds the file)_ + +**3. Symlink** + +Next, we need to symlink the module to global `node_modules`, so that `updater-aaa` is discoverable by Update's CLI. + +From the root of the `updater-aaa` project, run the following command: + +```sh +$ npm link +``` + +**4. Run** + +To test that `updater-aaa` was symlinked properly, run the following command: + +```sh +$ update aaa +``` + +You should see something like the following in the terminal + +```sh +updater aaa > task default +``` + +If not, review the steps and make sure you did everything described. If you still can't get it working please [create an issue](../../../issues) so we can look into it. + +**Next steps** + +If you'd like to see how multiple updaters can work together, repeat the same steps described above to create and symlnk `updater-bbb` and `updater-ccc`. + +Then run: + +```sh +update aaa bbb ccc +``` + From b55cf95c5ac6fae4edac51bca4b9b1ae1488e382 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Thu, 30 Jun 2016 09:42:41 -0400 Subject: [PATCH 199/274] generate docs --- .verb.md | 126 +++++++++++++++++++++++++------------------ README.md | 149 +++++++++++++++++++++++++++++++-------------------- package.json | 7 +-- 3 files changed, 167 insertions(+), 115 deletions(-) diff --git a/.verb.md b/.verb.md index b39982d..ea7389c 100644 --- a/.verb.md +++ b/.verb.md @@ -1,50 +1,21 @@ ---- -toc: false ---- +## Why use Update? -{%= include("logo") %} +You can install [updaters](docs/updaters.md) from npm, or create your own updaters to do things like: -## The basics - -- All of the actual _updating_ is accomplished by plugins called "updaters". -- [Updaters](docs/updaters.md) can be published to npm using the `updater-foo` naming convention, where `foo` is the updater's [alias](docs/faq.md#aliases). -- Run multiple updaters by passing a list of names after `update`. Example `$ update foo bar baz` will run updaters foo, bar and baz _in series_. The "next" updater always waits for the previous to finish. -- Updaters can have tasks, which are powered by [bach][] and use the same conventions as [gulp][] -- To run a task on an updater, do `$ update foo:abc`, where `abc` is the task name -- To run multiple tasks on an updater, do `$ update foo:abc,xyz`, where `abc` and `xyz` are task names -- Updaters that are published to npm can be [installed locally or globally](docs/installing-updaters.md) -- If you add an [updatefile.js](docs/updatefile.md) to the current working directory, Update's CLI will load it and register it as the ["default" updater](docs/updaters.md#default-updater). -- Update's CLI will look on the default updater for tasks and (other) updaters to run before looking elsewhere. - -More [features](#features) listed below. See the [docs](docs) for more detail. - -## TOC - -- [1 minute](#fast-track) -- [4 minutes](#getting-started) -- [???](docs) +* normalize configuration settings, verbiage, or preferences across all of your projects +* update files that are typically excluded from build cycles, and are often forgotten about after they're created. For example: + - fix dates in copyrights, [licenses](https://github.com/update/updater-license) and banners + - remove deprecated fields from [project manifests](https://github.com/update/updater-package) + - update settings in [runtime config](https://github.com/update/updater-eslint) files, preferences in [dotfiles](https://github.com/update/updater-editorconfig) +* after initializing a new project with a project generator, like [generate][] or Google's Yeoman, you can "normalize" all of the generated files to use your own preferences ## Fast track -Install `update` and the example "updater" with the following command: - -```sh -$ npm install --global update updater-license -``` - -Make sure your work is committed, then run: +The following instructions are intended to provide a basic demonstration of how Update works. Follow the links after this section for additional information. -```sh -$ update license -``` +**1. Install update** -## Getting started - -This section just covers the essentials, more detail is [provided below](#table-of-contents) and in the [docs/](docs) folder. - -**Installing Update** - -To use the CLI, update must first be installed globally with [npm](https://www.npmjs.com/): +To use Update's CLI, `update` must first be installed globally with [npm](https://www.npmjs.com/): ```sh $ npm install --global update @@ -52,7 +23,7 @@ $ npm install --global update This adds the `update` command to your system path, allowing it to be run from anywhere. -**Installing updaters** +**2. Install an "updater"** To see how updaters work, install `updater-example`: @@ -60,34 +31,79 @@ To see how updaters work, install `updater-example`: $ npm install --global updater-example ``` -**Running updaters** +**3. Create "example.txt"** + +In the current working directory, create an empty file named `example.txt`. -Add a file named `example.txt` to the current working directory, then run `updater-example` with the following command: +**4. Run** + +As a habit, when using `update` make sure your work is committed, then run: ```sh $ update example ``` -Next, try the following steps to get familiarized with how update works: +This simply appends the string `foo`. Visit the [updater-example][] project for additional steps and guidance. -- [ ] run `$ update example` to execute the default task, which will append the string `foo` to the file's contents. -- [ ] run `$ update example:foo` to execute the `foo` task, appending the string `foo` to the file's contents -- [ ] run `$ update example:bar` to execute the `bar` task, appending the string `bar` to the file's contents -- [ ] run `$ update example.abc` to execute the default task on the `abc` (sub-)updater, appending the string `abc:one` to the file's contents -- [ ] run `$ update example.abc:one` to execute the `one` task on the `abc` (sub-)updater, appending the string `abc:one` to the file's contents +**Next steps** +- Browse the [documentation](docs) +- Learn about [updaters](docs/updaters.md) +- Learn about the [built-in updaters](docs/cli/built-in-updaters.md) -**Init** +## Init -Now that you know how to run an updater directly, you can tell update to store a list of one or more updaters to run each time the `update` command is given: +Tell Update's CLI to automatically run certain updaters every time the `update` command is given: ```sh $ update init ``` -Whenever you install new updaters you can run `update init` to update your preferences. +You can run this command whenever you want to update your preferences, like after installing new updaters. + +## The basics + +**Usage** + +* Once installed globally, Update's CLI can be executed with the `$ update` command +* All of the actual _updating_ is accomplished by plugins called [updaters](docs/updaters.md) + +**Updaters** + +* You can create your own custom [updaters](docs/updaters.md), or install them from [npm](https://www.npmjs.com/) +* [Discover updaters on npm](https://www.npmjs.com/keywords/update-updater) using the `update-updater` keyword +* See the [updater API docs](docs/api/updater.md) to learn how to create updaters. +* From the command line, pass the names of one or more updaters to run after the `$ update` command. For example: + - To run `updater-foo`, you would do: `$ update foo` + - To run multiple updaters, like `foo`, `bar` and `baz`, you would do: `$ update foo bar baz` +* Using the API, pass the names of one or more updaters to run to the `.update` method. For example: + - To run `updater-foo`, you would do: `app.update('foo', function(err) {})` + - To run muliple updaters, you would do: `app.update(['foo', 'bar', 'baz'], function(err) {})` +* If using Update's API, updaters are registered with the `.register` or `.updater` methods (see the [updater docs](docs/updaters.md) for more details about these methods). +* Any updater can register other updaters, and any updater can be registered by other updaters +* Updaters always run _in series_, thus they will always wait for the previous updater to finish before executing. + +**Tasks** + +* Tasks can be defined inside updaters using the `app.task()` method +* Tasks are powered by [bach][] and use the same conventions as [gulp][] +* From the command line: + - To run task `abc` belonging to updater `foo`, you would run `$ update foo:abc` + - To run tasks `abc` and `xyz` belonging to updater `foo`, you would run `$ update foo:abc,xyz` +* Tasks always run _in series_, thus they will always wait for the previous task to finish before executing. -Visit the [documentation](docs) for details about authoring an publishing updaters. +**updatefile.js** + +- If an [updatefile.js](docs/updatefile.md) exists in the current working directory, Update's CLI will load it and register it as the ["default" updater](docs/updaters.md#default-updater) +- Using an `updatefile.js` provides a convenient way of testing and developing updaters. But you can also create your own ["private" updaters](docs/updaters.md#private-updaters) this way +- Update's CLI will look on the default updater for tasks and (other) updaters to run before looking elsewhere. + +**Publishing updaters** + +- Updaters can be published to npm using the `updater-foo` naming convention, where `foo` is the updater's [alias](docs/faq.md#aliases). +- Updaters that are published to npm can be [installed locally or globally](docs/installing-updaters.md) + +More [features](#features) listed below. See the [docs](docs) for more detail. ## Features @@ -99,3 +115,7 @@ Visit the [documentation](docs) for details about authoring an publishing update * **streams**: interact with the file system, with full support for [gulp][] and [assemble][] plugins * **smart plugins**: Update is built on [base][], so any "smart" plugin can be used * **stores**: persist configuration settings, global defaults, project-specific defaults, answers to prompts, and so on. +* much more! + + +[getting-started]: https://github.com/update/getting-started diff --git a/README.md b/README.md index c44a0c4..034d756 100644 --- a/README.md +++ b/README.md @@ -1,52 +1,46 @@ # update [![NPM version](https://img.shields.io/npm/v/update.svg?style=flat)](https://www.npmjs.com/package/update) [![NPM downloads](https://img.shields.io/npm/dm/update.svg?style=flat)](https://npmjs.org/package/update) [![Build Status](https://img.shields.io/travis/jonschlinkert/update.svg?style=flat)](https://travis-ci.org/jonschlinkert/update) -Update is a developer framework and CLI for automating updates of any kind in code projects. All updating is accomplished using plugins called _updaters_, which can be installed globally, locally, or in a local updatefile.js -

-## The basics - -* All of the actual _updating_ is accomplished by plugins called "updaters". -* [Updaters](docs/updaters.md) can be published to npm using the `updater-foo` naming convention, where `foo` is the updater's [alias](docs/faq.md#aliases). -* Run multiple updaters by passing a list of names after `update`. Example `$ update foo bar baz` will run updaters foo, bar and baz _in series_. The "next" updater always waits for the previous to finish. -* Updaters can have tasks, which are powered by [bach][] and use the same conventions as [gulp][] -* To run a task on an updater, do `$ update foo:abc`, where `abc` is the task name -* To run multiple tasks on an updater, do `$ update foo:abc,xyz`, where `abc` and `xyz` are task names -* Updaters that are published to npm can be [installed locally or globally](docs/installing-updaters.md) -* If you add an [updatefile.js](docs/updatefile.md) to the current working directory, Update's CLI will load it and register it as the ["default" updater](docs/updaters.md#default-updater). -* Update's CLI will look on the default updater for tasks and (other) updaters to run before looking elsewhere. - -More [features](#features) listed below. See the [docs](docs) for more detail. +Update is a developer framework and CLI for automating updates of any kind in code projects. All updating is accomplished using plugins called _updaters_, which can be installed globally, locally, or in a local `updatefile.js`. -* [1 minute](#fast-track) -* [4 minutes](#getting-started) -* [???](docs) +## TOC -## Fast track +- [Why use Update?](#why-use-update) +- [Fast track](#fast-track) +- [Init](#init) +- [The basics](#the-basics) +- [Features](#features) +- [Related projects](#related-projects) +- [Contributing](#contributing) +- [Running tests](#running-tests) +- [Author](#author) +- [License](#license) -Install `update` and the example "updater" with the following command: +_(TOC generated by [verb](https://github.com/verbose/verb) using [markdown-toc](https://github.com/jonschlinkert/markdown-toc))_ -```sh -$ npm install --global update updater-license -``` +## Why use Update? -Make sure your work is committed, then run: +You can install [updaters](docs/updaters.md) from npm, or create your own updaters to do things like: -```sh -$ update license -``` +* normalize configuration settings, verbiage, or preferences across all of your projects +* update files that are typically excluded from build cycles, and are often forgotten about after they're created. For example: + - fix dates in copyrights, [licenses](https://github.com/update/updater-license) and banners + - remove deprecated fields from [project manifests](https://github.com/update/updater-package) + - update settings in [runtime config](https://github.com/update/updater-eslint) files, preferences in [dotfiles](https://github.com/update/updater-editorconfig) +* after initializing a new project with a project generator, like [generate](https://github.com/generate/generate) or Google's Yeoman, you can "normalize" all of the generated files to use your own preferences -## Getting started +## Fast track -This section just covers the essentials, more detail is [provided below](#table-of-contents) and in the [docs/](docs) folder. +The following instructions are intended to provide a basic demonstration of how Update works. Follow the links after this section for additional information. -**Installing Update** +**1. Install update** -To use the CLI, update must first be installed globally with [npm](https://www.npmjs.com/): +To use Update's CLI, `update` must first be installed globally with [npm](https://www.npmjs.com/): ```sh $ npm install --global update @@ -54,7 +48,7 @@ $ npm install --global update This adds the `update` command to your system path, allowing it to be run from anywhere. -**Installing updaters** +**2. Install an "updater"** To see how updaters work, install `updater-example`: @@ -62,44 +56,91 @@ To see how updaters work, install `updater-example`: $ npm install --global updater-example ``` -**Running updaters** +**3. Create "example.txt"** + +In the current working directory, create an empty file named `example.txt`. -Add a file named `example.txt` to the current working directory, then run `updater-example` with the following command: +**4. Run** + +As a habit, when using `update` make sure your work is committed, then run: ```sh $ update example ``` -Next, try the following steps to get familiarized with how update works: +This simply appends the string `foo`. Visit the [updater-example](https://github.com/update/updater-example) project for additional steps and guidance. + +**Next steps** -* [ ] run `$ update example` to execute the default task, which will append the string `foo` to the file's contents. -* [ ] run `$ update example:foo` to execute the `foo` task, appending the string `foo` to the file's contents -* [ ] run `$ update example:bar` to execute the `bar` task, appending the string `bar` to the file's contents -* [ ] run `$ update example.abc` to execute the default task on the `abc` (sub-)updater, appending the string `abc:one` to the file's contents -* [ ] run `$ update example.abc:one` to execute the `one` task on the `abc` (sub-)updater, appending the string `abc:one` to the file's contents +* Browse the [documentation](docs) +* Learn about [updaters](docs/updaters.md) +* Learn about the [built-in updaters](docs/cli/built-in-updaters.md) -**Init** +## Init -Now that you know how to run an updater directly, you can tell update to store a list of one or more updaters to run each time the `update` command is given: +Tell Update's CLI to automatically run certain updaters every time the `update` command is given: ```sh $ update init ``` -Whenever you install new updaters you can run `update init` to update your preferences. +You can run this command whenever you want to update your preferences, like after installing new updaters. -Visit the [documentation](docs) for details about authoring an publishing updaters. +## The basics + +**Usage** + +* Once installed globally, Update's CLI can be executed with the `$ update` command +* All of the actual _updating_ is accomplished by plugins called [updaters](docs/updaters.md) + +**Updaters** + +* You can create your own custom [updaters](docs/updaters.md), or install them from [npm](https://www.npmjs.com/) +* [Discover updaters on npm](https://www.npmjs.com/keywords/update-updater) using the `update-updater` keyword +* See the [updater API docs](docs/api/updater.md) to learn how to create updaters. +* From the command line, pass the names of one or more updaters to run after the `$ update` command. For example: + - To run `updater-foo`, you would do: `$ update foo` + - To run multiple updaters, like `foo`, `bar` and `baz`, you would do: `$ update foo bar baz` +* Using the API, pass the names of one or more updaters to run to the `.update` method. For example: + - To run `updater-foo`, you would do: `app.update('foo', function(err) {})` + - To run muliple updaters, you would do: `app.update(['foo', 'bar', 'baz'], function(err) {})` +* If using Update's API, updaters are registered with the `.register` or `.updater` methods (see the [updater docs](docs/updaters.md) for more details about these methods). +* Any updater can register other updaters, and any updater can be registered by other updaters +* Updaters always run _in series_, thus they will always wait for the previous updater to finish before executing. + +**Tasks** + +* Tasks can be defined inside updaters using the `app.task()` method +* Tasks are powered by [bach](https://github.com/gulpjs/bach) and use the same conventions as [gulp](http://gulpjs.com) +* From the command line: + - To run task `abc` belonging to updater `foo`, you would run `$ update foo:abc` + - To run tasks `abc` and `xyz` belonging to updater `foo`, you would run `$ update foo:abc,xyz` +* Tasks always run _in series_, thus they will always wait for the previous task to finish before executing. + +**updatefile.js** + +* If an [updatefile.js](docs/updatefile.md) exists in the current working directory, Update's CLI will load it and register it as the ["default" updater](docs/updaters.md#default-updater) +* Using an `updatefile.js` provides a convenient way of testing and developing updaters. But you can also create your own ["private" updaters](docs/updaters.md#private-updaters) this way +* Update's CLI will look on the default updater for tasks and (other) updaters to run before looking elsewhere. + +**Publishing updaters** + +* Updaters can be published to npm using the `updater-foo` naming convention, where `foo` is the updater's [alias](docs/faq.md#aliases). +* Updaters that are published to npm can be [installed locally or globally](docs/installing-updaters.md) + +More [features](#features) listed below. See the [docs](docs) for more detail. ## Features -* **unparalleled flow control**: through the use of [updaters][getting-started], [sub-updaters][getting-started] and [tasks][getting-started] +* **unparalleled flow control**: through the use of [updaters](https://github.com/update/getting-started), [sub-updaters](https://github.com/update/getting-started) and [tasks](https://github.com/update/getting-started) * **templates, scaffolds and boilerplates**: update a single file, initialize an entire project, or provide ad-hoc "components" throughout the duration of a project using any combination of [templates, scaffolds and boilerplates](#templates-scaffolds-and-boilerplates). -* **any engine**: use any template engine to render templates, including [handlebars][], [lodash][], [swig][] and [pug][] +* **any engine**: use any template engine to render templates, including [handlebars](http://www.handlebarsjs.com/), [lodash](https://lodash.com/), [swig](https://github.com/paularmstrong/swig) and [pug](http://jade-lang.com) * **prompts**: asks you for data when it can't find what it needs, and it's easy to customize prompts for any data you want. * **data**: gathers data from the user's environment to populate "hints" in user prompts and render templates -* **streams**: interact with the file system, with full support for [gulp][] and [assemble][] plugins -* **smart plugins**: Update is built on [base][], so any "smart" plugin can be used +* **streams**: interact with the file system, with full support for [gulp](http://gulpjs.com) and [assemble](https://github.com/assemble/assemble) plugins +* **smart plugins**: Update is built on [base](https://github.com/node-base/base), so any "smart" plugin can be used * **stores**: persist configuration settings, global defaults, project-specific defaults, answers to prompts, and so on. +* much more! ## Related projects @@ -138,14 +179,4 @@ Released under the [MIT license](https://github.com/jonschlinkert/update/blob/ma *** -_This file was generated by [verb](https://github.com/verbose/verb), v0.9.0, on June 28, 2016._ - -[bach]: https://github.com/gulpjs/bach -[gulp]: http://gulpjs.com -[getting-started]: https://github.com/taunus/getting-started -[handlebars]: http://www.handlebarsjs.com/ -[lodash]: https://lodash.com/ -[swig]: https://github.com/paularmstrong/swig -[pug]: http://jade-lang.com -[assemble]: https://github.com/assemble/assemble -[base]: https://github.com/node-base/base \ No newline at end of file +_This file was generated by [verb](https://github.com/verbose/verb), v0.9.0, on June 30, 2016._ \ No newline at end of file diff --git a/package.json b/package.json index bfbbe48..7abf9e0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "update", - "description": "Update is a developer framework and CLI for automating updates of any kind in code projects. All updating is accomplished using plugins called _updaters_, which can be installed globally, locally, or in a local updatefile.js", + "description": "Update is a developer framework and CLI for automating updates of any kind in code projects. All updating is accomplished using plugins called _updaters_, which can be installed globally, locally, or in a local `updatefile.js`.", "version": "0.5.0", "homepage": "https://github.com/jonschlinkert/update", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", @@ -97,12 +97,13 @@ "assemble", "bach", "base", - "getting-started", + "generate", "gulp", "handlebars", "lodash", "pug", - "swig" + "swig", + "updater-example" ], "lint": { "reflinks": true From 5b127d0a65adb4a70951006fdcb5437ddf33d269 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Thu, 30 Jun 2016 09:44:20 -0400 Subject: [PATCH 200/274] 0.5.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7abf9e0..9b3e08a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "update", "description": "Update is a developer framework and CLI for automating updates of any kind in code projects. All updating is accomplished using plugins called _updaters_, which can be installed globally, locally, or in a local `updatefile.js`.", - "version": "0.5.0", + "version": "0.5.1", "homepage": "https://github.com/jonschlinkert/update", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", "repository": "jonschlinkert/update", From 1d73a7dde8bb566bf481abe88734156c3641e611 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 1 Jul 2016 05:43:01 -0400 Subject: [PATCH 201/274] externalize helpers --- support/lib/helpers.js | 15 --------------- support/lib/utils.js | 1 - 2 files changed, 16 deletions(-) diff --git a/support/lib/helpers.js b/support/lib/helpers.js index bcb787d..1e10bf6 100644 --- a/support/lib/helpers.js +++ b/support/lib/helpers.js @@ -7,21 +7,6 @@ module.exports = function(options) { return function(app) { if (!utils.isValid(app, 'update-support-helpers')) return; app.helpers(utils.helpers()); - app.helper('hasValue', function(val, str) { - return utils.hasValue(val) ? str : ''; - }); - app.helper('hasAny', function(arr, str) { - arr = utils.arrayify(arr); - var len = arr.length; - var idx = -1; - while (++idx < len) { - var ele = arr[idx] || []; - if (ele.length) { - return str; - } - } - return ''; - }); app.helper('links', function(related, prop) { var arr = related[prop] || (related[prop] = []); arr = utils.arrayify(arr); diff --git a/support/lib/utils.js b/support/lib/utils.js index 67c9684..a69c8db 100644 --- a/support/lib/utils.js +++ b/support/lib/utils.js @@ -5,7 +5,6 @@ var rules = require('pretty-remarkable/lib/rules'); var fn = require; require = utils; -require('has-value'); require('is-valid-app', 'isValid'); require('pretty-remarkable', 'prettify'); require('remarkable', 'Remarkable'); From 39dbe15669e0bed95e0581b0d24932756001dd33 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 1 Jul 2016 05:43:24 -0400 Subject: [PATCH 202/274] adds custom commands --- bin/update.js | 2 +- lib/cli.js | 10 ++++------ lib/commands/silent.js | 10 ++++++++++ lib/commands/version.js | 10 ++++++++++ lib/configfiles.js | 2 -- lib/utils.js | 19 ++++++++++++++++++- 6 files changed, 43 insertions(+), 10 deletions(-) create mode 100644 lib/commands/silent.js create mode 100644 lib/commands/version.js delete mode 100644 lib/configfiles.js diff --git a/bin/update.js b/bin/update.js index 1d18a4a..011ee62 100755 --- a/bin/update.js +++ b/bin/update.js @@ -5,7 +5,7 @@ require('set-blocking')(true); var Update = require('..'); var commands = require('../lib/commands'); var utils = require('../lib/utils'); -var argv = require('yargs-parser')(process.argv.slice(2), utils.opts); +var argv = utils.parseArgs(process.argv.slice(2)); /** * Listen for errors diff --git a/lib/cli.js b/lib/cli.js index 31e10c8..a017a44 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -71,12 +71,8 @@ module.exports = function(Update, config, cb) { logger(log.timestamp, 'using file', log.green('~' + updatefile)); var val = require(updatefile); if (typeof val === 'function') { - base.register('default', function fn() { - if (!utils.isValid(app, 'default-updater')) return; - this.use(fn); - this.use(val); - return fn; - }); + base.use(fn); + base.use(val); } else if (val && val.isApp) { base = val; } @@ -108,11 +104,13 @@ module.exports = function(Update, config, cb) { */ base.on('build', function(event, build) { + if (!build) return; var prefix = event === 'finished' ? log.success + ' ' : ''; logger(log.timestamp, event, build.key, prefix + log.red(build.time)); }); base.on('task', function(event, task) { + if (!task) return; logger(log.timestamp, event, task.key, log.red(task.time)); }); diff --git a/lib/commands/silent.js b/lib/commands/silent.js new file mode 100644 index 0000000..7ec1dec --- /dev/null +++ b/lib/commands/silent.js @@ -0,0 +1,10 @@ +'use strict'; + +var pkg = require('../../package'); + +module.exports = function(app) { + return function(val, key, config, next) { + app.enable('silent'); + next(); + }; +}; diff --git a/lib/commands/version.js b/lib/commands/version.js new file mode 100644 index 0000000..abc843d --- /dev/null +++ b/lib/commands/version.js @@ -0,0 +1,10 @@ +'use strict'; + +var pkg = require('../../package'); + +module.exports = function(app) { + return function(val, key, config, next) { + console.log(app.log.cyan('Update v' + pkg.version)); + process.exit(); + }; +}; diff --git a/lib/configfiles.js b/lib/configfiles.js deleted file mode 100644 index eb109ab..0000000 --- a/lib/configfiles.js +++ /dev/null @@ -1,2 +0,0 @@ -'use strict'; - diff --git a/lib/utils.js b/lib/utils.js index da8f22e..5646e7e 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -20,14 +20,27 @@ require('is-valid-app', 'isValid'); require('global-modules', 'gm'); require('log-utils', 'log'); require('os-homedir', 'home'); +require('yargs-parser', 'parse'); require = fn; // eslint-disable-line - utils.stripPrefixes = function(file) { file.basename = file.basename.replace(/^_/, '.'); file.basename = file.basename.replace(/^\$/, ''); }; +utils.parseArgs = function(argv) { + var obj = utils.parse(argv, utils.opts); + if (obj.init) { + obj._.push('init'); + delete obj.init; + } + if (obj.help) { + obj._.push('help'); + delete obj.help; + } + return obj; +}; + /** * argv options */ @@ -38,6 +51,10 @@ utils.opts = { config: 'c', configfile: 'f', global: 'g', + help: 'h', + init: 'i', + silent: 'S', + version: 'V', remove: 'r' } }; From 7b1bf326209568d4bbceb1b8f89a9e4ada77d1a8 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 1 Jul 2016 05:44:57 -0400 Subject: [PATCH 203/274] make `updater` non-enumerable --- index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/index.js b/index.js index 1cf82af..7705f99 100644 --- a/index.js +++ b/index.js @@ -52,6 +52,7 @@ Update.prototype.initUpdate = function() { Update.prototype.initDefaults = function() { this.define('generators', this.generators); + this.define('updater', this.generator); this.updaters = this.generators; this.option('help', { From bff72741d441a144f334fd5fa3591257ba0550d0 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 1 Jul 2016 05:45:12 -0400 Subject: [PATCH 204/274] rebuild docs --- support/docs/symlinking-updaters.md | 2 +- support/docs/tasks.md | 8 +++++++- support/docs/updaters.md | 6 ------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/support/docs/symlinking-updaters.md b/support/docs/symlinking-updaters.md index 617cc9f..d0cfedd 100644 --- a/support/docs/symlinking-updaters.md +++ b/support/docs/symlinking-updaters.md @@ -61,7 +61,7 @@ If not, review the steps and make sure you did everything described. If you stil **Next steps** -If you'd like to see how multiple updaters can work together, repeat the same steps described above to create and symlnk `updater-bbb` and `updater-ccc`. +If you'd like to see how multiple updaters can work together, repeat the same steps described above to create and symlink `updater-bbb` and `updater-ccc`. Then run: diff --git a/support/docs/tasks.md b/support/docs/tasks.md index 1685e7d..6895c54 100644 --- a/support/docs/tasks.md +++ b/support/docs/tasks.md @@ -20,7 +20,13 @@ Tasks can be run by command line or API. Pass the names of the tasks to run after the `update` command. -**Example** +**Examples** + +Run task `foo`: + +```sh +update foo +``` Run tasks `foo`, `bar` and `baz`: diff --git a/support/docs/updaters.md b/support/docs/updaters.md index e117551..26178ac 100644 --- a/support/docs/updaters.md +++ b/support/docs/updaters.md @@ -6,7 +6,6 @@ related: This document describes how to create, register and run updaters. - ## What is an updater? @@ -64,11 +63,6 @@ In general, it's recommended that you use the `.register` method. In most cases However, there are always exceptions. If you create custom code and notice that update can't find the information it needs. Try using the `.updater` method to to invoke the function when the updater is registered. -## Nested updaters - -As with plugins, any updater can register other updaters, and any updater can be registered by other updaters. - - ## Running updaters Updaters and their tasks can be run by command line or API. From d2f4e6c97dfe167df3fe56f3e7ae69060335d530 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 1 Jul 2016 05:46:20 -0400 Subject: [PATCH 205/274] temporarily re-add helpers --- support/lib/helpers.js | 17 +++++++++++++++++ support/lib/utils.js | 1 + 2 files changed, 18 insertions(+) diff --git a/support/lib/helpers.js b/support/lib/helpers.js index 1e10bf6..d5e256c 100644 --- a/support/lib/helpers.js +++ b/support/lib/helpers.js @@ -7,6 +7,23 @@ module.exports = function(options) { return function(app) { if (!utils.isValid(app, 'update-support-helpers')) return; app.helpers(utils.helpers()); + app.helper('hasValue', function(val, str) { + return utils.hasValue(val) ? str : ''; + }); + + app.helper('hasAny', function(arr, str) { + arr = utils.arrayify(arr); + var len = arr.length; + var idx = -1; + while (++idx < len) { + var ele = arr[idx] || []; + if (ele.length) { + return str; + } + } + return ''; + }); + app.helper('links', function(related, prop) { var arr = related[prop] || (related[prop] = []); arr = utils.arrayify(arr); diff --git a/support/lib/utils.js b/support/lib/utils.js index a69c8db..47d415d 100644 --- a/support/lib/utils.js +++ b/support/lib/utils.js @@ -6,6 +6,7 @@ var fn = require; require = utils; require('is-valid-app', 'isValid'); +require('has-value'); require('pretty-remarkable', 'prettify'); require('remarkable', 'Remarkable'); require('strip-color'); From 41659d98182f485cd4ecd93ebff871e0f8d3ff8e Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 1 Jul 2016 05:46:34 -0400 Subject: [PATCH 206/274] generate docs --- .verb.md | 145 ++++++++++++++++++++++++--------- README.md | 157 ++++++++++++++++++++++++++---------- docs/symlinking-updaters.md | 2 +- docs/tasks.md | 8 +- docs/updaters.md | 5 -- 5 files changed, 229 insertions(+), 88 deletions(-) diff --git a/.verb.md b/.verb.md index ea7389c..9291205 100644 --- a/.verb.md +++ b/.verb.md @@ -1,17 +1,46 @@ -## Why use Update? +Please read our [contributing guide](.github/contributing.md) if you'd like to learn more about contributing to this project. -You can install [updaters](docs/updaters.md) from npm, or create your own updaters to do things like: +## What does it do? -* normalize configuration settings, verbiage, or preferences across all of your projects +You can create your own [updaters](docs/updaters.md) using Update's API, or install updaters using [npm](https://www.npmjs.com/), to do things like: + +* enforce conventions across all of your projects +* instantly update an old or inherited project to your latest personal preferences (convert tabs to spaces, convert from `jshint` to `eslint` or the other way around, or any other detail) +* reformat code to meet your standards +* convert a config file to a different format (json to yaml, yaml to json, etc) * update files that are typically excluded from build cycles, and are often forgotten about after they're created. For example: - fix dates in copyrights, [licenses](https://github.com/update/updater-license) and banners - remove deprecated fields from [project manifests](https://github.com/update/updater-package) - update settings in [runtime config](https://github.com/update/updater-eslint) files, preferences in [dotfiles](https://github.com/update/updater-editorconfig) * after initializing a new project with a project generator, like [generate][] or Google's Yeoman, you can "normalize" all of the generated files to use your own preferences -## Fast track +## Why update? + +**Shared plugin ecosystem** + +Update is built on [base][], a developer framework and API for rapidly building high quality node.js applications, using plugins like building blocks. This means that, in addition to plugins created just for Update, plugins from any [other Base application](#in-the-wild) can be used to add functionality to your updaters as well. + +**Learn one, learn them all** + +Moreover, once you learn how to use Update, or any other Base application, you will have at least a firm understanding of how to use the others, if not a good head start. + +**Plays well with others** -The following instructions are intended to provide a basic demonstration of how Update works. Follow the links after this section for additional information. +Update is valueable by itself and can be used completely standalone. But it's even better when used with complementary frameworks that address other parts of the software development lifecycle, such as: + +* [generate][]: create projects +* [assemble][]: build projects +* [verb][]: document projects + +## Who should use Update? + +* anyone who cares about having consistency across all of their projects +* developers or organizations with many projects under their stewardship +* agencies or consultants who maintain and/or create client projects and would like to reduce time spent on maintainance + +## Getting started + +The following instructions are intended to provide a basic demonstration of how Update works. Visit the links after this section for more information. **1. Install update** @@ -43,7 +72,7 @@ As a habit, when using `update` make sure your work is committed, then run: $ update example ``` -This simply appends the string `foo`. Visit the [updater-example][] project for additional steps and guidance. +This appends the string `foo` to the contents of `example.txt`. Visit the [updater-example][] project for additional steps and guidance. **Next steps** @@ -61,49 +90,51 @@ $ update init You can run this command whenever you want to update your preferences, like after installing new updaters. -## The basics +```console +$ update help -**Usage** + Usage: update [options] -* Once installed globally, Update's CLI can be executed with the `$ update` command -* All of the actual _updating_ is accomplished by plugins called [updaters](docs/updaters.md) + Command: updater or tasks to run -**Updaters** + Options: -* You can create your own custom [updaters](docs/updaters.md), or install them from [npm](https://www.npmjs.com/) -* [Discover updaters on npm](https://www.npmjs.com/keywords/update-updater) using the `update-updater` keyword -* See the [updater API docs](docs/api/updater.md) to learn how to create updaters. -* From the command line, pass the names of one or more updaters to run after the `$ update` command. For example: - - To run `updater-foo`, you would do: `$ update foo` - - To run multiple updaters, like `foo`, `bar` and `baz`, you would do: `$ update foo bar baz` -* Using the API, pass the names of one or more updaters to run to the `.update` method. For example: - - To run `updater-foo`, you would do: `app.update('foo', function(err) {})` - - To run muliple updaters, you would do: `app.update(['foo', 'bar', 'baz'], function(err) {})` -* If using Update's API, updaters are registered with the `.register` or `.updater` methods (see the [updater docs](docs/updaters.md) for more details about these methods). -* Any updater can register other updaters, and any updater can be registered by other updaters -* Updaters always run _in series_, thus they will always wait for the previous updater to finish before executing. + --config, -c Save a configuration value to the `update` object in package.json + --cwd Set or display the current working directory + --data, -d Define data. API equivalent of `app.data()` + --disable Disable an option. API equivalent of "app.disable('foo')" + --enable Enable an option. API equivalent of "app.enable('foo')" + --global, -g Save a global configuration value to use as a default + --help, -h Display this help menu + --init, -i Prompts for configuration values and stores the answers + --option, -o Define options. API equivalent of `app.option()` + --run Force tasks to run regardless of command line flags used + --silent, -S Silence all tasks and updaters in the terminal + --show Display the value of + --version, -V Display the current version of update + --verbose, -v Display all verbose logging messages -**Tasks** + Examples: -* Tasks can be defined inside updaters using the `app.task()` method -* Tasks are powered by [bach][] and use the same conventions as [gulp][] -* From the command line: - - To run task `abc` belonging to updater `foo`, you would run `$ update foo:abc` - - To run tasks `abc` and `xyz` belonging to updater `foo`, you would run `$ update foo:abc,xyz` -* Tasks always run _in series_, thus they will always wait for the previous task to finish before executing. + # run updater "foo" + $ update foo -**updatefile.js** + # run task "bar" from updater "foo" + $ update foo:bar -- If an [updatefile.js](docs/updatefile.md) exists in the current working directory, Update's CLI will load it and register it as the ["default" updater](docs/updaters.md#default-updater) -- Using an `updatefile.js` provides a convenient way of testing and developing updaters. But you can also create your own ["private" updaters](docs/updaters.md#private-updaters) this way -- Update's CLI will look on the default updater for tasks and (other) updaters to run before looking elsewhere. + # run multiple tasks from updater "foo" + $ update foo:bar,baz,qux -**Publishing updaters** + # run a sub-generator from updater "foo" + $ update foo.abc -- Updaters can be published to npm using the `updater-foo` naming convention, where `foo` is the updater's [alias](docs/faq.md#aliases). -- Updaters that are published to npm can be [installed locally or globally](docs/installing-updaters.md) + # run task "xyz" from sub-generator "foo.abc" + $ update foo.abc:xyz -More [features](#features) listed below. See the [docs](docs) for more detail. + Update attempts to automatically determine if "foo" is a task or updater. + If there is a conflict, you can force update to run updater "foo" + by specifying its default task. Example: `$ update foo:default` +``` ## Features @@ -117,5 +148,43 @@ More [features](#features) listed below. See the [docs](docs) for more detail. * **stores**: persist configuration settings, global defaults, project-specific defaults, answers to prompts, and so on. * much more! +## Discovering updaters + +* Find updaters to install by [searching npm](https://www.npmjs.com/browse/keyword/update-updater) for packages with the keyword `update-updater` +* Visit [Update's GitHub org](https://github.com/update) to see the updaters maintained by the core team + +## Discovering plugins + +Plugins from any applications built on [base][] should work with Update (and can be used in your updater): + +* [base][base-plugin]: find base plugins on npm using the `baseplugin` keyword +* [assemble][assemble-plugin]: find assemble plugins on npm using the `assembleplugin` keyword +* [generate][generate-plugin]: find generate plugins on npm using the `generateplugin` keyword +* [templates][templates-plugin]: find templates plugins on npm using the `templatesplugin` keyword +* [verb][verb-plugin]: find verb plugins on npm using the `verbplugin` keyword + +## Authoring updaters + +Visit the [updater documentation](docs/updaters.md) guide to learn how to use, author and publish updaters. + +## More information + +* [Documentation](docs) +* [API documentation](docs/api) +* [Updaters maintained by the core team](https://github.com/update) + +## Community + +Are you using Update in your project? Have you published an [updater](docs/updaters.md)? Want to share your Update project with the world? Here are some suggestions: + +* If you get like Update and want to tweet about it, please use the hashtag `#updatejs` +* Get implementation help on [StackOverflow](http://stackoverflow.com/questions/tagged/update) (pluse use the `update` tag in questions) +* **Gitter** Discuss Update with us on [Gitter](https://gitter.im/update) +* If you publish an updater, thank you! To make your project as discoverable as possible, please add the keyword `update-updater` to package.json. [getting-started]: https://github.com/update/getting-started +[base-plugin]: https://www.npmjs.com/browse/keyword/baseplugin +[assemble-plugin]: https://www.npmjs.com/browse/keyword/assembleplugin +[generate-plugin]: https://www.npmjs.com/browse/keyword/generateplugin +[templates-plugin]: https://www.npmjs.com/browse/keyword/templatesplugin +[verb-plugin]: https://www.npmjs.com/browse/keyword/verbplugin \ No newline at end of file diff --git a/README.md b/README.md index 034d756..bdd7ad3 100644 --- a/README.md +++ b/README.md @@ -6,15 +6,21 @@

-Update is a developer framework and CLI for automating updates of any kind in code projects. All updating is accomplished using plugins called _updaters_, which can be installed globally, locally, or in a local `updatefile.js`. +Update is a developer framework and CLI for automating updates of any kind in code projects. All updating is accomplished using plugins called _updaters_, which are run by command line or API, and can be installed globally, locally, or in a local `updatefile.js`. ## TOC -- [Why use Update?](#why-use-update) -- [Fast track](#fast-track) +- [What does it do?](#what-does-it-do) +- [Why update?](#why-update) +- [Who should use Update?](#who-should-use-update) +- [Getting started](#getting-started) - [Init](#init) -- [The basics](#the-basics) - [Features](#features) +- [Discovering updaters](#discovering-updaters) +- [Discovering plugins](#discovering-plugins) +- [Authoring updaters](#authoring-updaters) +- [More information](#more-information) +- [Community](#community) - [Related projects](#related-projects) - [Contributing](#contributing) - [Running tests](#running-tests) @@ -23,20 +29,49 @@ Update is a developer framework and CLI for automating updates of any kind in co _(TOC generated by [verb](https://github.com/verbose/verb) using [markdown-toc](https://github.com/jonschlinkert/markdown-toc))_ -## Why use Update? +Please read our [contributing guide](.github/contributing.md) if you'd like to learn more about contributing to this project. -You can install [updaters](docs/updaters.md) from npm, or create your own updaters to do things like: +## What does it do? -* normalize configuration settings, verbiage, or preferences across all of your projects +You can create your own [updaters](docs/updaters.md) using Update's API, or install updaters using [npm](https://www.npmjs.com/), to do things like: + +* enforce conventions across all of your projects +* instantly update an old or inherited project to your latest personal preferences (convert tabs to spaces, convert from `jshint` to `eslint` or the other way around, or any other detail) +* reformat code to meet your standards +* convert a config file to a different format (json to yaml, yaml to json, etc) * update files that are typically excluded from build cycles, and are often forgotten about after they're created. For example: - fix dates in copyrights, [licenses](https://github.com/update/updater-license) and banners - remove deprecated fields from [project manifests](https://github.com/update/updater-package) - update settings in [runtime config](https://github.com/update/updater-eslint) files, preferences in [dotfiles](https://github.com/update/updater-editorconfig) * after initializing a new project with a project generator, like [generate](https://github.com/generate/generate) or Google's Yeoman, you can "normalize" all of the generated files to use your own preferences -## Fast track +## Why update? + +**Shared plugin ecosystem** + +Update is built on [base](https://github.com/node-base/base), a developer framework and API for rapidly building high quality node.js applications, using plugins like building blocks. This means that, in addition to plugins created just for Update, plugins from any [other Base application](#in-the-wild) can be used to add functionality to your updaters as well. + +**Learn one, learn them all** + +Moreover, once you learn how to use Update, or any other Base application, you will have at least a firm understanding of how to use the others, if not a good head start. + +**Plays well with others** -The following instructions are intended to provide a basic demonstration of how Update works. Follow the links after this section for additional information. +Update is valueable by itself and can be used completely standalone. But it's even better when used with complementary frameworks that address other parts of the software development lifecycle, such as: + +* [generate](https://github.com/generate/generate): create projects +* [assemble](https://github.com/assemble/assemble): build projects +* [verb](https://github.com/verbose/verb): document projects + +## Who should use Update? + +* anyone who cares about having consistency across all of their projects +* developers or organizations with many projects under their stewardship +* agencies or consultants who maintain and/or create client projects and would like to reduce time spent on maintainance + +## Getting started + +The following instructions are intended to provide a basic demonstration of how Update works. Visit the links after this section for more information. **1. Install update** @@ -68,7 +103,7 @@ As a habit, when using `update` make sure your work is committed, then run: $ update example ``` -This simply appends the string `foo`. Visit the [updater-example](https://github.com/update/updater-example) project for additional steps and guidance. +This appends the string `foo` to the contents of `example.txt`. Visit the [updater-example](https://github.com/update/updater-example) project for additional steps and guidance. **Next steps** @@ -86,49 +121,51 @@ $ update init You can run this command whenever you want to update your preferences, like after installing new updaters. -## The basics +```console +$ update help -**Usage** + Usage: update [options] -* Once installed globally, Update's CLI can be executed with the `$ update` command -* All of the actual _updating_ is accomplished by plugins called [updaters](docs/updaters.md) + Command: updater or tasks to run -**Updaters** + Options: -* You can create your own custom [updaters](docs/updaters.md), or install them from [npm](https://www.npmjs.com/) -* [Discover updaters on npm](https://www.npmjs.com/keywords/update-updater) using the `update-updater` keyword -* See the [updater API docs](docs/api/updater.md) to learn how to create updaters. -* From the command line, pass the names of one or more updaters to run after the `$ update` command. For example: - - To run `updater-foo`, you would do: `$ update foo` - - To run multiple updaters, like `foo`, `bar` and `baz`, you would do: `$ update foo bar baz` -* Using the API, pass the names of one or more updaters to run to the `.update` method. For example: - - To run `updater-foo`, you would do: `app.update('foo', function(err) {})` - - To run muliple updaters, you would do: `app.update(['foo', 'bar', 'baz'], function(err) {})` -* If using Update's API, updaters are registered with the `.register` or `.updater` methods (see the [updater docs](docs/updaters.md) for more details about these methods). -* Any updater can register other updaters, and any updater can be registered by other updaters -* Updaters always run _in series_, thus they will always wait for the previous updater to finish before executing. + --config, -c Save a configuration value to the `update` object in package.json + --cwd Set or display the current working directory + --data, -d Define data. API equivalent of `app.data()` + --disable Disable an option. API equivalent of "app.disable('foo')" + --enable Enable an option. API equivalent of "app.enable('foo')" + --global, -g Save a global configuration value to use as a default + --help, -h Display this help menu + --init, -i Prompts for configuration values and stores the answers + --option, -o Define options. API equivalent of `app.option()` + --run Force tasks to run regardless of command line flags used + --silent, -S Silence all tasks and updaters in the terminal + --show Display the value of + --version, -V Display the current version of update + --verbose, -v Display all verbose logging messages -**Tasks** + Examples: -* Tasks can be defined inside updaters using the `app.task()` method -* Tasks are powered by [bach](https://github.com/gulpjs/bach) and use the same conventions as [gulp](http://gulpjs.com) -* From the command line: - - To run task `abc` belonging to updater `foo`, you would run `$ update foo:abc` - - To run tasks `abc` and `xyz` belonging to updater `foo`, you would run `$ update foo:abc,xyz` -* Tasks always run _in series_, thus they will always wait for the previous task to finish before executing. + # run updater "foo" + $ update foo -**updatefile.js** + # run task "bar" from updater "foo" + $ update foo:bar -* If an [updatefile.js](docs/updatefile.md) exists in the current working directory, Update's CLI will load it and register it as the ["default" updater](docs/updaters.md#default-updater) -* Using an `updatefile.js` provides a convenient way of testing and developing updaters. But you can also create your own ["private" updaters](docs/updaters.md#private-updaters) this way -* Update's CLI will look on the default updater for tasks and (other) updaters to run before looking elsewhere. + # run multiple tasks from updater "foo" + $ update foo:bar,baz,qux -**Publishing updaters** + # run a sub-generator from updater "foo" + $ update foo.abc -* Updaters can be published to npm using the `updater-foo` naming convention, where `foo` is the updater's [alias](docs/faq.md#aliases). -* Updaters that are published to npm can be [installed locally or globally](docs/installing-updaters.md) + # run task "xyz" from sub-generator "foo.abc" + $ update foo.abc:xyz -More [features](#features) listed below. See the [docs](docs) for more detail. + Update attempts to automatically determine if "foo" is a task or updater. + If there is a conflict, you can force update to run updater "foo" + by specifying its default task. Example: `$ update foo:default` +``` ## Features @@ -142,6 +179,40 @@ More [features](#features) listed below. See the [docs](docs) for more detail. * **stores**: persist configuration settings, global defaults, project-specific defaults, answers to prompts, and so on. * much more! +## Discovering updaters + +* Find updaters to install by [searching npm](https://www.npmjs.com/browse/keyword/update-updater) for packages with the keyword `update-updater` +* Visit [Update's GitHub org](https://github.com/update) to see the updaters maintained by the core team + +## Discovering plugins + +Plugins from any applications built on [base](https://github.com/node-base/base) should work with Update (and can be used in your updater): + +* [base](https://www.npmjs.com/browse/keyword/baseplugin): find base plugins on npm using the `baseplugin` keyword +* [assemble](https://www.npmjs.com/browse/keyword/assembleplugin): find assemble plugins on npm using the `assembleplugin` keyword +* [generate](https://www.npmjs.com/browse/keyword/generateplugin): find generate plugins on npm using the `generateplugin` keyword +* [templates](https://www.npmjs.com/browse/keyword/templatesplugin): find templates plugins on npm using the `templatesplugin` keyword +* [verb](https://www.npmjs.com/browse/keyword/verbplugin): find verb plugins on npm using the `verbplugin` keyword + +## Authoring updaters + +Visit the [updater documentation](docs/updaters.md) guide to learn how to use, author and publish updaters. + +## More information + +* [Documentation](docs) +* [API documentation](docs/api) +* [Updaters maintained by the core team](https://github.com/update) + +## Community + +Are you using Update in your project? Have you published an [updater](docs/updaters.md)? Want to share your Update project with the world? Here are some suggestions: + +* If you get like Update and want to tweet about it, please use the hashtag `#updatejs` +* Get implementation help on [StackOverflow](http://stackoverflow.com/questions/tagged/update) (pluse use the `update` tag in questions) +* **Gitter** Discuss Update with us on [Gitter](https://gitter.im/update) +* If you publish an updater, thank you! To make your project as discoverable as possible, please add the keyword `update-updater` to package.json. + ## Related projects You might also be interested in these projects: @@ -179,4 +250,4 @@ Released under the [MIT license](https://github.com/jonschlinkert/update/blob/ma *** -_This file was generated by [verb](https://github.com/verbose/verb), v0.9.0, on June 30, 2016._ \ No newline at end of file +_This file was generated by [verb](https://github.com/verbose/verb), v0.9.0, on July 01, 2016._ \ No newline at end of file diff --git a/docs/symlinking-updaters.md b/docs/symlinking-updaters.md index 15ec070..d6dd416 100644 --- a/docs/symlinking-updaters.md +++ b/docs/symlinking-updaters.md @@ -57,7 +57,7 @@ If not, review the steps and make sure you did everything described. If you stil **Next steps** -If you'd like to see how multiple updaters can work together, repeat the same steps described above to create and symlnk `updater-bbb` and `updater-ccc`. +If you'd like to see how multiple updaters can work together, repeat the same steps described above to create and symlink `updater-bbb` and `updater-ccc`. Then run: diff --git a/docs/tasks.md b/docs/tasks.md index 15f492c..3a2d003 100644 --- a/docs/tasks.md +++ b/docs/tasks.md @@ -27,7 +27,13 @@ Tasks can be run by command line or API. Pass the names of the tasks to run after the `update` command. -**Example** +**Examples** + +Run task `foo`: + +```sh +update foo +``` Run tasks `foo`, `bar` and `baz`: diff --git a/docs/updaters.md b/docs/updaters.md index f6252d6..c4dd8c3 100644 --- a/docs/updaters.md +++ b/docs/updaters.md @@ -5,7 +5,6 @@ This document describes how to create, register and run updaters. - [What is an updater?](#what-is-an-updater) - [Creating updaters](#creating-updaters) - [Registering updaters](#registering-updaters) -- [Nested updaters](#nested-updaters) - [Running updaters](#running-updaters) - [Resolving updaters](#resolving-updaters) * [Tasks and updaters](#tasks-and-updaters) @@ -70,10 +69,6 @@ In general, it's recommended that you use the `.register` method. In most cases However, there are always exceptions. If you create custom code and notice that update can't find the information it needs. Try using the `.updater` method to to invoke the function when the updater is registered. -## Nested updaters - -As with plugins, any updater can register other updaters, and any updater can be registered by other updaters. - ## Running updaters Updaters and their tasks can be run by command line or API. From d6dbc220b66ba0f0037e8dcaf85a74e374796fe4 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 1 Jul 2016 05:46:46 -0400 Subject: [PATCH 207/274] update metadata --- package.json | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 9b3e08a..6fac33f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "update", - "description": "Update is a developer framework and CLI for automating updates of any kind in code projects. All updating is accomplished using plugins called _updaters_, which can be installed globally, locally, or in a local `updatefile.js`.", + "description": "Update is a developer framework and CLI for automating updates of any kind in code projects. All updating is accomplished using plugins called _updaters_, which are run by command line or API, and can be installed globally, locally, or in a local `updatefile.js`.", "version": "0.5.1", "homepage": "https://github.com/jonschlinkert/update", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", @@ -12,7 +12,9 @@ "files": [ "bin", "index.js", - "lib" + "lib", + "LICENSE", + "README.md" ], "main": "index.js", "preferGlobal": true, @@ -103,10 +105,11 @@ "lodash", "pug", "swig", - "updater-example" + "updater-example", + "verb" ], "lint": { "reflinks": true } } -} +} \ No newline at end of file From 149e8dd0461e72a8dc35083717dae9052fb3b14e Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 1 Jul 2016 05:47:06 -0400 Subject: [PATCH 208/274] 0.5.2 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 6fac33f..ed5cff3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "update", "description": "Update is a developer framework and CLI for automating updates of any kind in code projects. All updating is accomplished using plugins called _updaters_, which are run by command line or API, and can be installed globally, locally, or in a local `updatefile.js`.", - "version": "0.5.1", + "version": "0.5.2", "homepage": "https://github.com/jonschlinkert/update", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", "repository": "jonschlinkert/update", @@ -112,4 +112,4 @@ "reflinks": true } } -} \ No newline at end of file +} From e5d8e7602df76c4b10e9c5eb70a5ff8ee0145e28 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 1 Jul 2016 05:47:58 -0400 Subject: [PATCH 209/274] fix urls --- .verb.md | 2 +- package.json | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.verb.md b/.verb.md index 9291205..0bc7152 100644 --- a/.verb.md +++ b/.verb.md @@ -187,4 +187,4 @@ Are you using Update in your project? Have you published an [updater](docs/updat [assemble-plugin]: https://www.npmjs.com/browse/keyword/assembleplugin [generate-plugin]: https://www.npmjs.com/browse/keyword/generateplugin [templates-plugin]: https://www.npmjs.com/browse/keyword/templatesplugin -[verb-plugin]: https://www.npmjs.com/browse/keyword/verbplugin \ No newline at end of file +[verb-plugin]: https://www.npmjs.com/browse/keyword/verbplugin diff --git a/package.json b/package.json index ed5cff3..a8ce073 100644 --- a/package.json +++ b/package.json @@ -2,11 +2,11 @@ "name": "update", "description": "Update is a developer framework and CLI for automating updates of any kind in code projects. All updating is accomplished using plugins called _updaters_, which are run by command line or API, and can be installed globally, locally, or in a local `updatefile.js`.", "version": "0.5.2", - "homepage": "https://github.com/jonschlinkert/update", + "homepage": "https://github.com/update/update", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", - "repository": "jonschlinkert/update", + "repository": "update/update", "bugs": { - "url": "https://github.com/jonschlinkert/update/issues" + "url": "https://github.com/update/update/issues" }, "license": "MIT", "files": [ From 081888a68ea54f9558510bd04a8fc997f630742b Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 1 Jul 2016 05:48:38 -0400 Subject: [PATCH 210/274] 0.5.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a8ce073..6a98718 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "update", "description": "Update is a developer framework and CLI for automating updates of any kind in code projects. All updating is accomplished using plugins called _updaters_, which are run by command line or API, and can be installed globally, locally, or in a local `updatefile.js`.", - "version": "0.5.2", + "version": "0.5.3", "homepage": "https://github.com/update/update", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", "repository": "update/update", From bc428d3681995224e3067af1bee9e5fa9dfd3e73 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 1 Jul 2016 06:21:36 -0400 Subject: [PATCH 211/274] improve list output --- lib/list.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/list.js b/lib/list.js index 3ed456e..9a04a65 100644 --- a/lib/list.js +++ b/lib/list.js @@ -2,16 +2,18 @@ var through = require('through2'); -module.exports = function() { +module.exports = function(app) { var paths = []; return through.obj(function(file, enc, next) { paths.push(file.basename); next(); }, function(cb) { + console.log(); console.log('- ' + paths.join('\n- ')); console.log(); - console.log(paths.length + ' updaters installed'); + console.log(app.log.magenta(paths.length + ' updaters installed')); + console.log(); cb(); }); }; From d1fdfb46a0667fb7b1894f75f2b64d9da74aef1a Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 1 Jul 2016 06:42:41 -0400 Subject: [PATCH 212/274] adds 1200 unit tests --- test/_suite.js | 21 + test/app.cli.js | 20 + test/app.doc.js | 23 + test/app.docs.js | 25 + test/app.extendWith.js | 599 ++++++++++++++ test/app.getUpdater.js | 103 +++ test/app.include.js | 26 + test/app.includes.js | 28 + test/app.layout.js | 24 + test/app.layouts.js | 26 + test/app.lookupGenerator.js | 61 ++ test/app.matchGenerator.js | 57 ++ test/app.page.js | 31 + test/app.pages.js | 35 + test/app.partial.js | 24 + test/app.partials.js | 27 + test/app.questions.js | 293 +++++++ test/app.register.js | 234 ++++++ test/app.store.js | 335 ++++++++ test/app.task.js | 191 +++++ test/app.toAlias.js | 41 + test/app.update-array.js | 931 ++++++++++++++++++++++ test/app.update.js | 961 +++++++++++++++++++++++ test/app.updater.js | 291 +++++++ test/fixtures/def-gen.js | 8 + test/fixtures/not-exposed.js | 8 + test/fixtures/one/package.json | 9 + test/fixtures/one/templates/a.txt | 1 + test/fixtures/one/templates/x.txt | 1 + test/fixtures/one/templates/y.txt | 1 + test/fixtures/one/templates/z.txt | 1 + test/fixtures/one/updatefile.js | 18 + test/fixtures/one/verbfile.js | 19 + test/fixtures/package.json | 9 + test/fixtures/pages/a.hbs | 2 + test/fixtures/pages/b.hbs | 2 + test/fixtures/pages/c.hbs | 2 + test/fixtures/updater-foo/updatefile.js | 7 + test/fixtures/updater.js | 49 ++ test/fixtures/updaters-array.json | 3 - test/fixtures/updaters-object.json | 6 - test/fixtures/updaters/a/.gitignore | 15 + test/fixtures/updaters/a/package.json | 7 + test/fixtures/updaters/a/post.hbs | 5 + test/fixtures/updaters/a/updatefile.js | 8 + test/fixtures/updaters/a/verbfile.js | 10 + test/fixtures/updaters/qux/package.json | 7 + test/fixtures/updaters/qux/updatefile.js | 7 + test/fixtures/updaters/qux/verbfile.js | 11 + test/runner.js | 111 +++ test/support/ignore.js | 6 + test/support/index.js | 64 ++ test/support/spy.js | 31 + test/update.js | 35 + test/updaters.env.js | 127 +++ test/updaters.events.js | 157 ++++ test/updaters.js | 10 - 57 files changed, 5145 insertions(+), 19 deletions(-) create mode 100644 test/_suite.js create mode 100644 test/app.cli.js create mode 100644 test/app.doc.js create mode 100644 test/app.docs.js create mode 100644 test/app.extendWith.js create mode 100644 test/app.getUpdater.js create mode 100644 test/app.include.js create mode 100644 test/app.includes.js create mode 100644 test/app.layout.js create mode 100644 test/app.layouts.js create mode 100644 test/app.lookupGenerator.js create mode 100644 test/app.matchGenerator.js create mode 100644 test/app.page.js create mode 100644 test/app.pages.js create mode 100644 test/app.partial.js create mode 100644 test/app.partials.js create mode 100644 test/app.questions.js create mode 100644 test/app.register.js create mode 100644 test/app.store.js create mode 100644 test/app.task.js create mode 100644 test/app.toAlias.js create mode 100644 test/app.update-array.js create mode 100644 test/app.update.js create mode 100644 test/app.updater.js create mode 100644 test/fixtures/def-gen.js create mode 100644 test/fixtures/not-exposed.js create mode 100644 test/fixtures/one/package.json create mode 100644 test/fixtures/one/templates/a.txt create mode 100644 test/fixtures/one/templates/x.txt create mode 100644 test/fixtures/one/templates/y.txt create mode 100644 test/fixtures/one/templates/z.txt create mode 100644 test/fixtures/one/updatefile.js create mode 100644 test/fixtures/one/verbfile.js create mode 100644 test/fixtures/package.json create mode 100644 test/fixtures/pages/a.hbs create mode 100644 test/fixtures/pages/b.hbs create mode 100644 test/fixtures/pages/c.hbs create mode 100644 test/fixtures/updater-foo/updatefile.js create mode 100644 test/fixtures/updater.js delete mode 100644 test/fixtures/updaters-array.json delete mode 100644 test/fixtures/updaters-object.json create mode 100644 test/fixtures/updaters/a/.gitignore create mode 100644 test/fixtures/updaters/a/package.json create mode 100644 test/fixtures/updaters/a/post.hbs create mode 100644 test/fixtures/updaters/a/updatefile.js create mode 100644 test/fixtures/updaters/a/verbfile.js create mode 100644 test/fixtures/updaters/qux/package.json create mode 100644 test/fixtures/updaters/qux/updatefile.js create mode 100644 test/fixtures/updaters/qux/verbfile.js create mode 100644 test/runner.js create mode 100644 test/support/ignore.js create mode 100644 test/support/index.js create mode 100644 test/support/spy.js create mode 100644 test/update.js create mode 100644 test/updaters.env.js create mode 100644 test/updaters.events.js delete mode 100644 test/updaters.js diff --git a/test/_suite.js b/test/_suite.js new file mode 100644 index 0000000..8a136aa --- /dev/null +++ b/test/_suite.js @@ -0,0 +1,21 @@ +'use strict'; + +var generate = require('..'); +var runner = require('base-test-runner')(); +var suite = require('base-test-suite'); + +/** + * Run the tests in `base-test-suite` + */ + +runner.on('templates', function(file) { + var fn = require(file.path); + if (typeof fn === 'function') { + fn(generate); + } else { + throw new Error('expected ' + file.path + ' to export a function'); + } +}); + +runner.addFiles('templates', suite.test.templates); +runner.addFiles('templates', suite.test['assemble-core']); diff --git a/test/app.cli.js b/test/app.cli.js new file mode 100644 index 0000000..9b2227a --- /dev/null +++ b/test/app.cli.js @@ -0,0 +1,20 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var update = require('..'); +var app; + +describe('app.cli', function() { + beforeEach(function() { + app = update({cli: true}); + }); + + describe('app.cli.map', function() { + it('should add a property to app.cli', function() { + app.cli.map('abc', function() {}); + assert.equal(app.cli.keys.pop(), 'abc'); + }); + }); +}); + diff --git a/test/app.doc.js b/test/app.doc.js new file mode 100644 index 0000000..abf0879 --- /dev/null +++ b/test/app.doc.js @@ -0,0 +1,23 @@ +'use strict'; + +require('mocha'); +require('should'); +var assert = require('assert'); +var generate = require('..'); +var app; + +describe('app', function() { + beforeEach(function() { + app = generate(); + app.create('docs'); + }); + + describe('add doc', function() { + it('should add docs to `app.views.docs`:', function() { + app.doc('a.hbs', {path: 'a.hbs', content: 'a'}); + app.doc('b.hbs', {path: 'b.hbs', content: 'b'}); + app.doc('c.hbs', {path: 'c.hbs', content: 'c'}); + assert(Object.keys(app.views.docs).length === 3); + }); + }); +}); diff --git a/test/app.docs.js b/test/app.docs.js new file mode 100644 index 0000000..cdf8955 --- /dev/null +++ b/test/app.docs.js @@ -0,0 +1,25 @@ +'use strict'; + +require('mocha'); +require('should'); +var assert = require('assert'); +var generate = require('..'); +var app; + +describe('app', function() { + beforeEach(function() { + app = generate(); + app.create('docs'); + }); + + describe('add docs', function() { + it('should add docs to `app.views.docs`:', function() { + app.docs({ + 'a.hbs': {path: 'a.hbs', content: 'a'}, + 'b.hbs': {path: 'b.hbs', content: 'b'}, + 'c.hbs': {path: 'c.hbs', content: 'c'}, + }); + assert.equal(Object.keys(app.views.docs).length, 3); + }); + }); +}); diff --git a/test/app.extendWith.js b/test/app.extendWith.js new file mode 100644 index 0000000..7228e75 --- /dev/null +++ b/test/app.extendWith.js @@ -0,0 +1,599 @@ +'use strict'; + +require('mocha'); +var path = require('path'); +var assert = require('assert'); +var gm = require('global-modules'); +var npm = require('npm-install-global'); +var isAbsolute = require('is-absolute'); +var exists = require('fs-exists-sync'); +var resolve = require('resolve'); +var Base = require('..'); +var base; + +var fixture = path.resolve.bind(path, __dirname, 'fixtures/updaters'); +function resolver(search, app) { + try { + if (isAbsolute(search.name)) { + search.name = require.resolve(search.name); + } else { + search.name = resolve.sync(search.name, {basedir: gm}); + } + search.app = app.register(search.name, search.name); + } catch (err) {} +} + +describe('.extendWith', function() { + before(function(cb) { + if (!exists(path.resolve(gm, 'generate-bar'))) { + npm.install('generate-bar', cb); + } else { + cb(); + } + }); + + beforeEach(function() { + base = new Base(); + base.option('toAlias', function(name) { + return name.replace(/^generate-/, ''); + }); + + base.on('unresolved', resolver); + }); + + it.skip('should throw an error when an updater is not found', function(cb) { + try { + base.register('foo', function(app) { + app.extendWith('fofoofofofofof'); + }); + + base.getUpdater('foo'); + cb(new Error('expected an error')); + } catch (err) { + assert.equal(err.message, 'cannot find updater: "fofoofofofofof"'); + cb(); + } + }); + + it('should extend an updater with settings in the default updater', function(cb) { + var count = 0; + + base.register('foo', function(app) { + app.task('default', function(next) { + assert.equal(app.options.foo, 'bar'); + assert.equal(app.cache.data.foo, 'bar'); + count++; + next(); + }); + }); + + base.register('default', function(app) { + app.data({foo: 'bar'}); + app.option({foo: 'bar'}); + }); + + base.update('foo', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should not extend tasks by default', function(cb) { + var count = 0; + + base.register('foo', function(app) { + app.task('default', function(next) { + assert(app.tasks.hasOwnProperty('default')); + assert(!app.tasks.hasOwnProperty('a')); + assert(!app.tasks.hasOwnProperty('b')); + assert(!app.tasks.hasOwnProperty('c')); + count++; + next(); + }); + }); + + base.register('default', function(app) { + app.task('a', function(next) { + next(); + }); + app.task('b', function(next) { + next(); + }); + app.task('c', function(next) { + next(); + }); + }); + + base.update('foo', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should get a named updater', function(cb) { + var count = 0; + + base.register('foo', function(app) { + app.extendWith('bar'); + count++; + }); + + base.register('bar', function(app) { + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); + }); + + base.getUpdater('foo'); + assert.equal(count, 1); + cb(); + }); + + it('should extend an updater with a named updater', function(cb) { + base.register('foo', function(app) { + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + + app.extendWith('bar'); + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + cb(); + }); + + base.register('bar', function(app) { + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); + }); + + base.getUpdater('foo'); + }); + + it('should extend an updater with an array of updaters', function(cb) { + base.register('foo', function(app) { + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + + app.extendWith(['bar', 'baz', 'qux']); + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + cb(); + }); + + base.register('bar', function(app) { + app.task('a', function() {}); + }); + + base.register('baz', function(app) { + app.task('b', function() {}); + }); + + base.register('qux', function(app) { + app.task('c', function() {}); + }); + + base.getUpdater('foo'); + }); + + describe('invoke updaters', function(cb) { + it('should extend with an updater instance', function(cb) { + base.register('foo', function(app) { + var bar = app.getUpdater('bar'); + app.extendWith(bar); + + assert(app.tasks.hasOwnProperty('a')); + assert(app.tasks.hasOwnProperty('b')); + assert(app.tasks.hasOwnProperty('c')); + cb(); + }); + + base.register('bar', function(app) { + app.isBar = true; + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); + }); + + base.getUpdater('foo'); + }); + + it('should invoke a named updater', function(cb) { + base.register('foo', function(app) { + app.extendWith('bar'); + + assert(app.tasks.hasOwnProperty('a')); + assert(app.tasks.hasOwnProperty('b')); + assert(app.tasks.hasOwnProperty('c')); + cb(); + }); + + base.register('bar', function(app) { + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); + }); + + base.getUpdater('foo'); + }); + }); + + describe('extend updaters', function(cb) { + it('should extend an updater with an updater invoked by name', function(cb) { + base.register('foo', function(app) { + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + + app.extendWith('bar'); + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + cb(); + }); + + base.register('bar', function(app) { + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); + }); + + base.getUpdater('foo'); + }); + + it('should extend an updater with an updater invoked by alias', function(cb) { + base.register('foo', function(app) { + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + + app.extendWith('qux'); + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + cb(); + }); + + base.register('generate-qux', function(app) { + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); + }); + + base.getUpdater('qux'); + base.getUpdater('foo'); + }); + + it('should extend with an updater invoked by filepath', function(cb) { + base.register('foo', function(app) { + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + + app.extendWith(fixture('qux')); + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + cb(); + }); + + base.getUpdater('foo'); + }); + + it('should extend with an updater invoked from node_modules by name', function(cb) { + base.register('abc', function(app) { + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + + app.extendWith('generate-foo'); + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + cb(); + }); + + base.getUpdater('abc'); + }); + + it('should extend with an updater invoked from node_modules by name on a default instance', function() { + var app = new Base(); + + app.on('unresolved', resolver); + app.option('toAlias', function(name) { + return name.replace(/^generate-/, ''); + }); + + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + + app.extendWith('generate-foo'); + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + }); + + it('should use an updater from node_modules as a plugin', function() { + var app = new Base(); + + app.option('toAlias', function(name) { + return name.replace(/^generate-/, ''); + }); + + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + + app.use(require('generate-foo')); + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + }); + + it('should extend with an updater invoked from global modules by name', function(cb) { + base.register('zzz', function(app) { + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + app.extendWith('generate-bar'); + + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + cb(); + }); + + base.getUpdater('zzz'); + }); + + it('should extend with an updater invoked from global modules by alias', function(cb) { + base.register('generate-bar'); + + base.register('zzz', function(app) { + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + + app.extendWith('bar'); + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + cb(); + }); + + base.getUpdater('zzz'); + }); + }); + + describe('sub-updaters', function(cb) { + it('should invoke sub-updaters', function(cb) { + base.register('foo', function(app) { + app.register('one', function(app) { + app.task('a', function() {}); + }); + app.register('two', function(app) { + app.task('b', function() {}); + }); + + app.extendWith('one'); + app.extendWith('two'); + + assert(app.tasks.hasOwnProperty('a')); + assert(app.tasks.hasOwnProperty('b')); + cb(); + }); + + base.getUpdater('foo'); + }); + + it('should invoke a sub-updater on the base instance', function(cb) { + base.register('foo', function(app) { + app.extendWith('bar.sub'); + assert(app.tasks.hasOwnProperty('a')); + assert(app.tasks.hasOwnProperty('b')); + assert(app.tasks.hasOwnProperty('c')); + cb(); + }); + + base.register('bar', function(app) { + app.register('sub', function(sub) { + sub.task('a', function() {}); + sub.task('b', function() {}); + sub.task('c', function() {}); + }); + }); + + base.getUpdater('foo'); + }); + + it('should invoke a sub-updater from node_modules by name', function(cb) { + base.register('abc', function(app) { + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + + app.extendWith('xyz'); + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + cb(); + }); + + base.register('xyz', function(app) { + app.extendWith('generate-foo'); + }); + + base.getUpdater('abc'); + }); + + it('should invoke a sub-updater from node_modules by alias', function(cb) { + base.register('generate-foo'); + + base.register('abc', function(app) { + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + + app.extendWith('xyz'); + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + cb(); + }); + + base.register('xyz', function(app) { + app.extendWith('foo'); + }); + + base.getUpdater('abc'); + }); + + it('should invoke an array of sub-updaters', function(cb) { + base.register('foo', function(app) { + app.register('one', function(app) { + app.task('a', function() {}); + }); + app.register('two', function(app) { + app.task('b', function() {}); + }); + + app.extendWith(['one', 'two']); + + assert(app.tasks.hasOwnProperty('a')); + assert(app.tasks.hasOwnProperty('b')); + cb(); + }); + + base.getUpdater('foo'); + }); + + it('should invoke sub-updaters from sub-updaters', function(cb) { + base.register('foo', function(app) { + app.register('one', function(sub) { + sub.register('a', function(a) { + a.task('a', function() {}); + }); + }); + + app.register('two', function(sub) { + sub.register('a', function(a) { + a.task('b', function() {}); + }); + }); + + app.extendWith('one.a'); + app.extendWith('two.a'); + + assert(app.tasks.hasOwnProperty('a')); + assert(app.tasks.hasOwnProperty('b')); + cb(); + }); + + base.getUpdater('foo'); + }); + + it('should invoke an array of sub-updaters from sub-updaters', function(cb) { + base.register('foo', function(app) { + app.register('one', function(sub) { + sub.register('a', function(a) { + a.task('a', function() {}); + }); + }); + + app.register('two', function(sub) { + sub.register('a', function(a) { + a.task('b', function() {}); + }); + }); + + app.extendWith(['one.a', 'two.a']); + + assert(app.tasks.hasOwnProperty('a')); + assert(app.tasks.hasOwnProperty('b')); + cb(); + }); + + base.getUpdater('foo'); + }); + + it('should invoke sub-updater that invokes another updater', function(cb) { + base.register('foo', function(app) { + app.extendWith('bar'); + assert(app.tasks.hasOwnProperty('a')); + assert(app.tasks.hasOwnProperty('b')); + assert(app.tasks.hasOwnProperty('c')); + cb(); + }); + + base.register('bar', function(app) { + app.extendWith('baz'); + }); + + base.register('baz', function(app) { + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); + }); + + base.getUpdater('foo'); + }); + + it('should invoke sub-updater that invokes another sub-updater', function(cb) { + base.register('foo', function(app) { + app.extendWith('bar.sub'); + assert(app.tasks.hasOwnProperty('a')); + assert(app.tasks.hasOwnProperty('b')); + assert(app.tasks.hasOwnProperty('c')); + cb(); + }); + + base.register('bar', function(app) { + app.register('sub', function(sub) { + sub.extendWith('baz.sub'); + }); + }); + + base.register('baz', function(app) { + app.register('sub', function(sub) { + sub.task('a', function() {}); + sub.task('b', function() {}); + sub.task('c', function() {}); + }); + }); + + base.getUpdater('foo'); + }); + + it('should invoke sub-updater that invokes another sub-updater', function(cb) { + base.register('foo', function(app) { + app.extendWith('bar.sub'); + assert(app.tasks.hasOwnProperty('a')); + assert(app.tasks.hasOwnProperty('b')); + assert(app.tasks.hasOwnProperty('c')); + cb(); + }); + + base.register('bar', function(app) { + app.register('sub', function(sub) { + sub.extendWith('baz.sub'); + }); + }); + + base.register('baz', function(app) { + app.register('sub', function(sub) { + sub.task('a', function() {}); + sub.task('b', function() {}); + sub.task('c', function() {}); + }); + }); + + base.getUpdater('foo'); + }); + }); +}); diff --git a/test/app.getUpdater.js b/test/app.getUpdater.js new file mode 100644 index 0000000..0b57e42 --- /dev/null +++ b/test/app.getUpdater.js @@ -0,0 +1,103 @@ +'use strict'; + +var path = require('path'); +var assert = require('assert'); +var Base = require('..'); +var base; + +var fixtures = path.resolve.bind(path, __dirname + '/fixtures'); + +describe('.updater', function() { + beforeEach(function() { + base = new Base(); + }); + + it('should get a updater from the base instance', function() { + base.register('abc', function() {}); + var updater = base.getUpdater('abc'); + assert(updater); + assert.equal(typeof updater, 'object'); + assert.equal(updater.name, 'abc'); + }); + + it('should fail when a updater is not found', function() { + var updater = base.getUpdater('whatever'); + assert(!updater); + }); + + it('should get a updater from the base instance from a nested updater', function() { + base.register('abc', function() {}); + base.register('xyz', function(app) { + app.register('sub', function(sub) { + var updater = base.getUpdater('abc'); + assert(updater); + assert.equal(typeof updater, 'object'); + assert.equal(updater.name, 'abc'); + }); + }); + base.getUpdater('xyz'); + }); + + it('should get a updater from the base instance using `this`', function() { + base.register('abc', function() {}); + base.register('xyz', function(app) { + app.register('sub', function(sub) { + var updater = this.getUpdater('abc'); + assert(updater); + assert.equal(typeof updater, 'object'); + assert.equal(updater.name, 'abc'); + }); + }); + base.getUpdater('xyz'); + }); + + it('should get a base updater from "app" from a nested updater', function() { + base.register('abc', function() {}); + base.register('xyz', function(app) { + app.register('sub', function(sub) { + var updater = app.getUpdater('abc'); + assert(updater); + assert.equal(typeof updater, 'object'); + assert.equal(updater.name, 'abc'); + }); + }); + base.getUpdater('xyz'); + }); + + it('should get a nested updater', function() { + base.register('abc', function(abc) { + abc.register('def', function(def) {}); + }); + + var updater = base.getUpdater('abc.def'); + assert(updater); + assert.equal(typeof updater, 'object'); + assert.equal(updater.name, 'def'); + }); + + it('should get a deeply nested updater', function() { + base.register('abc', function(abc) { + abc.register('def', function(def) { + def.register('ghi', function(ghi) { + ghi.register('jkl', function(jkl) { + jkl.register('mno', function() {}); + }); + }); + }); + }); + + var updater = base.getUpdater('abc.def.ghi.jkl.mno'); + assert(updater); + assert.equal(typeof updater, 'object'); + assert.equal(updater.name, 'mno'); + }); + + it('should get a updater that was registered by path', function() { + base.register('a', fixtures('updaters/a')); + var updater = base.getUpdater('a'); + + assert(updater); + assert(updater.tasks); + assert(updater.tasks.hasOwnProperty('default')); + }); +}); diff --git a/test/app.include.js b/test/app.include.js new file mode 100644 index 0000000..df7a101 --- /dev/null +++ b/test/app.include.js @@ -0,0 +1,26 @@ +'use strict'; + +require('mocha'); +require('should'); +var assert = require('assert'); +var generate = require('..'); +var app, len; + +describe('app', function() { + beforeEach(function() { + app = generate(); + if (typeof app.include === 'undefined') { + app.create('include'); + } + len = Object.keys(app.views.includes).length; + }); + + describe('add include', function() { + it('should add includes to `app.views.includes`:', function() { + app.include('a.hbs', {path: 'a.hbs', content: 'a'}); + app.include('b.hbs', {path: 'b.hbs', content: 'b'}); + app.include('c.hbs', {path: 'c.hbs', content: 'c'}); + assert((Object.keys(app.views.includes).length - len) === 3); + }); + }); +}); diff --git a/test/app.includes.js b/test/app.includes.js new file mode 100644 index 0000000..390660d --- /dev/null +++ b/test/app.includes.js @@ -0,0 +1,28 @@ +'use strict'; + +require('mocha'); +require('should'); +var assert = require('assert'); +var generate = require('..'); +var app, len; + +describe('app', function() { + beforeEach(function() { + app = generate(); + if (typeof app.include === 'undefined') { + app.create('include'); + } + len = Object.keys(app.views.includes).length; + }) + + describe('add includes', function() { + it('should add includes to `app.views.includes`:', function() { + app.includes({ + 'a.hbs': {path: 'a.hbs', content: 'a'}, + 'b.hbs': {path: 'b.hbs', content: 'b'}, + 'c.hbs': {path: 'c.hbs', content: 'c'}, + }); + assert((Object.keys(app.views.includes).length - len) === 3); + }); + }); +}); diff --git a/test/app.layout.js b/test/app.layout.js new file mode 100644 index 0000000..f763157 --- /dev/null +++ b/test/app.layout.js @@ -0,0 +1,24 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var assemble = require('..'); +var app; + +describe('.layout()', function() { + beforeEach(function() { + app = assemble(); + if (!app.layout) { + app.create('layout', {viewType: 'layout'}); + } + }); + + describe('add layout', function() { + it('should add layouts to `app.views.layouts`:', function() { + app.layout('a.hbs', {path: 'a.hbs', contents: new Buffer('a')}); + app.layout('b.hbs', {path: 'b.hbs', contents: new Buffer('b')}); + app.layout('c.hbs', {path: 'c.hbs', contents: new Buffer('c')}); + assert(Object.keys(app.views.layouts).length === 3); + }); + }); +}); diff --git a/test/app.layouts.js b/test/app.layouts.js new file mode 100644 index 0000000..d731a0b --- /dev/null +++ b/test/app.layouts.js @@ -0,0 +1,26 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var assemble = require('..'); +var app; + +describe('.layouts()', function() { + beforeEach(function() { + app = assemble(); + if (!app.layout) { + app.create('layout', {viewType: 'layout'}); + } + }); + + describe('add layouts', function() { + it('should add layouts to `app.views.layouts`:', function() { + app.layouts({ + 'a.hbs': {path: 'a.hbs', contents: new Buffer('a')}, + 'b.hbs': {path: 'b.hbs', contents: new Buffer('b')}, + 'c.hbs': {path: 'c.hbs', contents: new Buffer('c')}, + }); + assert(Object.keys(app.views.layouts).length === 3); + }); + }); +}); diff --git a/test/app.lookupGenerator.js b/test/app.lookupGenerator.js new file mode 100644 index 0000000..7b4d159 --- /dev/null +++ b/test/app.lookupGenerator.js @@ -0,0 +1,61 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var Base = require('..'); +var base; + +describe('.lookupGenerator', function() { + beforeEach(function() { + base = new Base(); + + base.option('toAlias', function(key) { + return key.replace(/^generate-(.*)/, '$1'); + }); + }); + + it('should get a generator using a custom lookup function', function() { + base.register('generate-foo', function() {}); + base.register('generate-bar', function() {}); + var gen = base.lookupGenerator('foo', function(key) { + return ['generate-' + key, 'verb-' + key + '-generator', key]; + }); + + assert(gen); + assert.equal(gen.env.name, 'generate-foo'); + assert.equal(gen.env.alias, 'foo'); + }); + + it('should get a generator using a custom lookup function passed on options', function() { + base.register('generate-foo', function() {}); + base.register('generate-bar', function() {}); + + var gen = base.getGenerator('foo', { + lookup: function(key) { + return ['generate-' + key, 'verb-' + key + '-generator', key]; + } + }); + + assert(gen); + assert.equal(gen.env.name, 'generate-foo'); + assert.equal(gen.env.alias, 'foo'); + }); + + it('should return undefined when nothing is found', function() { + var gen = base.lookupGenerator('fofofofofo', function(key) { + return ['generate-' + key, 'verb-' + key + '-generator', key]; + }); + + assert(!gen); + }); + + it('should throw an error when a function is not passed', function(cb) { + try { + base.lookupGenerator('foo'); + cb(new Error('expected an error')); + } catch (err) { + assert.equal(err.message, 'expected `fn` to be a lookup function'); + cb(); + } + }); +}); diff --git a/test/app.matchGenerator.js b/test/app.matchGenerator.js new file mode 100644 index 0000000..922d5de --- /dev/null +++ b/test/app.matchGenerator.js @@ -0,0 +1,57 @@ +'use strict'; + +require('mocha'); +var path = require('path'); +var assert = require('assert'); +var spawn = require('cross-spawn'); +var exists = require('fs-exists-sync'); +var Base = require('..'); +var base; + +describe('.matchGenerator', function() { + before(function(cb) { + if (!exists(path.resolve(__dirname, '../node_modules/generate-foo'))) { + spawn('npm', ['install', '--global', 'generate-foo'], {stdio: 'inherit'}) + .on('error', cb) + .on('close', function(code, err) { + cb(err, code); + }); + } else { + cb(); + } + }); + + beforeEach(function() { + base = new Base(); + + base.option('toAlias', function(key) { + return key.replace(/^generate-(.*)/, '$1'); + }); + }); + + it('should match a generator by name', function() { + base.register('generate-foo'); + + var gen = base.matchGenerator('generate-foo'); + assert(gen); + assert.equal(gen.env.name, 'generate-foo'); + assert.equal(gen.env.alias, 'foo'); + }); + + it('should match a generator by alias', function() { + base.register('generate-foo'); + + var gen = base.matchGenerator('foo'); + assert(gen); + assert.equal(gen.env.name, 'generate-foo'); + assert.equal(gen.env.alias, 'foo'); + }); + + it('should match a generator by path', function() { + base.register('generate-foo'); + var gen = base.matchGenerator(require.resolve('generate-foo')); + assert(gen); + assert.equal(gen.env.name, 'generate-foo'); + assert.equal(gen.env.alias, 'foo'); + }); +}); diff --git a/test/app.page.js b/test/app.page.js new file mode 100644 index 0000000..0f314f1 --- /dev/null +++ b/test/app.page.js @@ -0,0 +1,31 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var assemble = require('..'); +var app; + +describe('.page()', function() { + beforeEach(function() { + app = assemble(); + if (!app.pages) { + app.create('pages'); + } + }); + + describe('add page', function() { + it('should add pages to `app.views.pages`:', function() { + app.page('a.hbs', {path: 'a.hbs', contents: new Buffer('a')}); + app.page('b.hbs', {path: 'b.hbs', contents: new Buffer('b')}); + app.page('c.hbs', {path: 'c.hbs', contents: new Buffer('c')}); + assert(Object.keys(app.views.pages).length === 3); + }); + }); + + describe('load', function() { + it('should load a page from a non-glob filepath', function() { + app.page('test/fixtures/pages/a.hbs'); + assert(Object.keys(app.views.pages).length === 1); + }); + }); +}); diff --git a/test/app.pages.js b/test/app.pages.js new file mode 100644 index 0000000..1f773a2 --- /dev/null +++ b/test/app.pages.js @@ -0,0 +1,35 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var loader = require('assemble-loader'); +var update = require('..'); +var app; + +describe('.pages()', function() { + beforeEach(function() { + app = update({cli: true}); + app.use(loader()); + if (!app.pages) { + app.create('pages'); + } + }); + + describe('add pages', function() { + it('should add pages to `app.views.pages`:', function() { + app.pages({ + 'a.hbs': {path: 'a.hbs', contents: new Buffer('a')}, + 'b.hbs': {path: 'b.hbs', contents: new Buffer('b')}, + 'c.hbs': {path: 'c.hbs', contents: new Buffer('c')}, + }); + assert.equal(Object.keys(app.views.pages).length, 3); + }); + }); + + describe('load pages', function() { + it('should load pages onto `app.views.pages`:', function() { + app.pages('test/fixtures/pages/*.hbs'); + assert.equal(Object.keys(app.views.pages).length, 3); + }); + }); +}); diff --git a/test/app.partial.js b/test/app.partial.js new file mode 100644 index 0000000..ba6ca57 --- /dev/null +++ b/test/app.partial.js @@ -0,0 +1,24 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var assemble = require('..'); +var app; + +describe('.partial()', function() { + beforeEach(function() { + app = assemble(); + if (!app.partials) { + app.create('partials', {viewType: 'partial'}); + } + }); + + describe('add partial', function() { + it('should add partials to `app.views.partials`:', function() { + app.partial('a.hbs', {path: 'a.hbs', contents: new Buffer('a')}); + app.partial('b.hbs', {path: 'b.hbs', contents: new Buffer('b')}); + app.partial('c.hbs', {path: 'c.hbs', contents: new Buffer('c')}); + assert(Object.keys(app.views.partials).length === 3); + }); + }); +}); diff --git a/test/app.partials.js b/test/app.partials.js new file mode 100644 index 0000000..9622fcf --- /dev/null +++ b/test/app.partials.js @@ -0,0 +1,27 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var assemble = require('..'); +var app; + +describe('.partials()', function() { + beforeEach(function() { + app = assemble(); + if (!app.partials) { + app.create('partials', {viewType: 'partial'}); + } + }); + + describe('add partials', function() { + it('should add partials to `app.views.partials`:', function() { + app.partials({ + 'a.hbs': {path: 'a.hbs', contents: new Buffer('a')}, + 'b.hbs': {path: 'b.hbs', contents: new Buffer('b')}, + 'c.hbs': {path: 'c.hbs', contents: new Buffer('c')}, + }); + + assert(Object.keys(app.views.partials).length === 3); + }); + }); +}); diff --git a/test/app.questions.js b/test/app.questions.js new file mode 100644 index 0000000..eb9d670 --- /dev/null +++ b/test/app.questions.js @@ -0,0 +1,293 @@ +'use strict'; + +process.env.NODE_ENV = 'test'; + +require('mocha'); +var fs = require('fs'); +var assert = require('assert'); +var questions = require('base-questions'); +var config = require('base-config-process'); +var store = require('base-store'); +var App = require('..'); +var app, base, site; + +describe('app.questions', function() { + describe('plugin', function() { + beforeEach(function() { + base = new App(); + base.use(config()); + base.use(questions()); + base.use(store('base-questions-tests/base')); + + app = new App(); + app.use(config()); + app.use(questions()); + app.use(store('base-questions-tests/app')); + }); + + it('should expose a `questions` object on "app"', function() { + assert.equal(typeof app.questions, 'object'); + }); + + it('should expose a `set` method on "app.questions"', function() { + assert.equal(typeof app.questions.set, 'function'); + }); + it('should expose a `get` method on "app.questions"', function() { + assert.equal(typeof app.questions.get, 'function'); + }); + it('should expose an `ask` method on "app.questions"', function() { + assert.equal(typeof app.questions.ask, 'function'); + }); + + it('should expose an `ask` method on "app"', function() { + assert.equal(typeof app.ask, 'function'); + }); + it('should expose a `question` method on "app"', function() { + assert.equal(typeof app.question, 'function'); + }); + }); + + if (process.env.TRAVIS) { + return; + + describe('app.ask', function() { + beforeEach(function() { + app = new App(); + app.use(config()); + app.use(questions()); + app.use(store('base-questions-tests/ask')); + }); + + afterEach(function() { + app.store.del({force: true}); + app.questions.clear(); + app.cache.data = {}; + }); + + it.skip('should force all questions to be asked', function(cb) { + app.questions.option('init', 'author'); + app.ask({force: true}, function(err, answers) { + console.log(answers) + cb(); + }); + }); + + it('should store a question:', function() { + app.question('a', 'b'); + assert(app.questions); + assert(app.questions.cache); + assert(app.questions.cache.a); + assert.equal(app.questions.cache.a.name, 'a'); + assert.equal(app.questions.cache.a.message, 'b'); + }); + + it.skip('should re-init a specific question:', function(cb) { + this.timeout(20000); + app.question('a', 'b'); + app.question('c', 'd'); + app.question('e', 'f'); + app.data({a: 'b'}); + + app.questions.get('e') + .force() + + app.ask(function(err, answers) { + console.log(answers); + cb(); + }); + }); + + it('should ask a question defined on `ask`', function(cb) { + app.data('name', 'Brian Woodward'); + + app.ask('name', function(err, answers) { + if(err) return cb(err) + assert.equal(answers.name, 'Brian Woodward'); + cb(); + }); + }); + + it('should ask a question and use a `cache.data` value to answer:', function(cb) { + app.question('a', 'this is a question'); + app.data('a', 'b'); + + app.ask('a', function(err, answers) { + if(err) return cb(err) + assert.equal(answers.a, 'b'); + + app.data('a', 'zzz'); + app.ask('a', function(err, answers) { + if(err) return cb(err) + assert.equal(answers.a, 'zzz'); + cb(); + }) + }); + }); + + it('should ask a question and use a `store.data` value to answer:', function(cb) { + app.question('a', 'this is another question'); + app.store.set('a', 'c'); + app.ask('a', function(err, answers) { + if (err) return cb(err); + assert(answers); + assert.equal(answers.a, 'c'); + cb(); + }) + }); + + it('should ask a question and use a config value to answer:', function(cb) { + app.question('a', 'b'); + app.config.process({data: {a: 'foo'}}, function(err) { + if (err) return cb(err); + + app.store.set('a', 'c'); + + app.ask('a', function(err, answer) { + if (err) return cb(err); + assert(answer); + assert.equal(answer.a, 'foo'); + cb(); + }); + }); + }); + + it('should prefer `cache.data` to `store.data`', function(cb) { + app.question('a', 'b'); + app.data('a', 'b'); + app.store.set('a', 'c'); + + app.ask('a', function(err, answer) { + if (err) return cb(err); + assert(answer); + assert.equal(answer.a, 'b'); + cb(); + }) + }); + + it('should update data with data loaded by config', function(cb) { + app.question('a', 'this is a question'); + app.data('a', 'b'); + + app.config.process({data: {a: 'foo'}}, function(err) { + if (err) return cb(err); + + app.ask('a', function(err, answer) { + if (err) return cb(err); + + assert(answer); + assert.equal(answer.a, 'foo'); + cb(); + }); + }); + }); + }); + + describe('session data', function() { + before(function() { + site = new App(); + site.use(config()); + site.use(questions()); + site.use(store('base-questions-tests/site')); + + app = new App(); + app.use(config()); + app.use(questions()); + app.use(store('base-questions-tests/ask')); + }); + + after(function() { + site.store.del({force: true}); + site.questions.clear(); + + app.store.del({force: true}); + app.questions.clear(); + }); + + it('[app] should ask a question and use a `cache.data` value to answer:', function(cb) { + app.question('package.name', 'this is a question'); + app.data('package.name', 'base-questions'); + + app.ask('package.name', function(err, answers) { + if(err) return cb(err) + assert.equal(answers.package.name, 'base-questions'); + + app.data('package.name', 'foo-bar-baz'); + app.ask('package.name', function(err, answers) { + if(err) return cb(err) + assert.equal(answers.package.name, 'foo-bar-baz'); + cb(); + }) + }); + }); + + it('[site] should ask a question and use a `cache.data` value to answer:', function(cb) { + site.question('package.name', 'this is a question'); + site.data('package.name', 'base-questions'); + + site.ask('package.name', function(err, answers) { + if(err) return cb(err) + assert.equal(answers.package.name, 'base-questions'); + + site.data('package.name', 'foo-bar-baz'); + site.ask('package.name', function(err, answers) { + if(err) return cb(err) + assert.equal(answers.package.name, 'foo-bar-baz'); + cb(); + }) + }); + }); + + it('[app] should ask a question and use a `store.data` value to answer:', function(cb) { + app.question('author.name', 'this is another question'); + app.store.set('author.name', 'Brian Woodward'); + app.ask('author.name', function(err, answers) { + if (err) return cb(err); + assert(answers); + assert.equal(answers.author.name, 'Brian Woodward'); + cb(); + }) + }); + + it('[site] should ask a question and use a `store.data` value to answer:', function(cb) { + site.question('author.name', 'this is another question'); + site.store.set('author.name', 'Jon Schlinkert'); + site.ask('author.name', function(err, answers) { + if (err) return cb(err); + assert(answers); + assert.equal(answers.author.name, 'Brian Woodward'); + cb(); + }) + }); + + it('[app] should ask a question and use a config value to answer:', function(cb) { + app.question('foo', 'Username?'); + app.config.process({data: {foo: 'jonschlinkert'}}, function(err) { + if (err) return cb(err); + + app.store.set('foo', 'doowb'); + + app.ask('foo', function(err, answer) { + if (err) return cb(err); + assert(answer); + assert.equal(answer.foo, 'jonschlinkert'); + cb(); + }); + }); + }); + + it('[site] should ask a question and use a config value to answer:', function(cb) { + site.question('foo', 'Username?'); + site.config.process({data: {foo: 'doowb'}}, function(err) { + if (err) return cb(err); + + site.ask('foo', function(err, answer) { + if (err) return cb(err); + assert(answer); + assert.equal(answer.foo, 'doowb'); + cb(); + }); + }); + }); + }); + } +}); diff --git a/test/app.register.js b/test/app.register.js new file mode 100644 index 0000000..db12253 --- /dev/null +++ b/test/app.register.js @@ -0,0 +1,234 @@ +'use strict'; + +require('mocha'); +var path = require('path'); +var assert = require('assert'); +var generators = require('base-generators'); +var Base = require('..'); +var base; + +var fixtures = path.resolve.bind(path, __dirname + '/fixtures'); + +describe('.register', function() { + beforeEach(function() { + base = new Base(); + }); + + describe('function', function() { + it('should register an updater a function', function() { + base.register('foo', function() {}); + var foo = base.getGenerator('foo'); + assert(foo); + assert.equal(foo.env.alias, 'foo'); + }); + + it('should get a task from an updater registered as a function', function() { + base.register('foo', function(foo) { + foo.task('default', function() {}); + }); + var updater = base.getGenerator('foo'); + assert(updater); + assert(updater.tasks); + assert(updater.tasks.hasOwnProperty('default')); + }); + + it('should get an updater from an updater registered as a function', function() { + base.register('foo', function(foo) { + foo.register('bar', function(bar) {}); + }); + var bar = base.getGenerator('foo.bar'); + assert(bar); + assert.equal(bar.env.alias, 'bar'); + }); + + it('should get a sub-updater from an updater registered as a function', function() { + base.register('foo', function(foo) { + foo.register('bar', function(bar) { + bar.task('something', function() {}); + }); + }); + var bar = base.getGenerator('foo.bar'); + assert(bar); + assert(bar.tasks); + assert(bar.tasks.hasOwnProperty('something')); + }); + + it('should get a deeply-nested sub-updater registered as a function', function() { + base.register('foo', function(foo) { + foo.register('bar', function(bar) { + bar.register('baz', function(baz) { + baz.register('qux', function(qux) { + qux.task('qux-one', function() {}); + }); + }); + }); + }); + + var qux = base.getGenerator('foo.bar.baz.qux'); + assert(qux); + assert(qux.tasks); + assert(qux.tasks.hasOwnProperty('qux-one')); + }); + + it('should expose the instance from each updater', function() { + base.register('foo', function(foo) { + foo.register('bar', function(bar) { + bar.register('baz', function(baz) { + baz.register('qux', function(qux) { + qux.task('qux-one', function() {}); + }); + }); + }); + }); + + var qux = base + .getGenerator('foo') + .getGenerator('bar') + .getGenerator('baz') + .getGenerator('qux'); + + assert(qux); + assert(qux.tasks); + assert(qux.tasks.hasOwnProperty('qux-one')); + }); + + it('should fail when an updater that does not exist is defined', function() { + base.register('foo', function(foo) { + foo.register('bar', function(bar) { + bar.register('baz', function(baz) { + baz.register('qux', function(qux) { + }); + }); + }); + }); + var fez = base.getGenerator('foo.bar.fez'); + assert.equal(typeof fez, 'undefined'); + }); + + it('should expose the `base` instance as the second param', function(cb) { + base.register('foo', function(foo, base) { + assert(base.updaters.hasOwnProperty('foo')); + cb(); + }); + base.getGenerator('foo'); + }); + + it('should expose sibling updaters on the `base` instance', function(cb) { + base.register('foo', function(foo, base) { + foo.task('abc', function() {}); + }); + base.register('bar', function(bar, base) { + assert(base.updaters.hasOwnProperty('foo')); + assert(base.updaters.hasOwnProperty('bar')); + cb(); + }); + + base.getGenerator('foo'); + base.getGenerator('bar'); + }); + }); + + describe('alias', function() { + it('should use a custom function to create the alias', function() { + base.option('toAlias', function(name) { + return name.slice(name.lastIndexOf('-') + 1); + }); + + base.register('base-abc-xyz', function() {}); + var xyz = base.getGenerator('xyz'); + assert(xyz); + assert.equal(xyz.env.alias, 'xyz'); + }); + }); + + describe('path', function() { + it('should register an updater function by name', function() { + base.register('foo', function() {}); + assert(base.updaters.hasOwnProperty('foo')); + }); + + it('should register an updater function by alias', function() { + base.register('abc', function() {}); + assert(base.updaters.hasOwnProperty('abc')); + }); + + it('should register an updater by dirname', function() { + base.register('a', fixtures('updaters/a')); + assert(base.updaters.hasOwnProperty('a')); + }); + + it('should register an updater from a configfile filepath', function() { + base.register('base-abc', fixtures('updaters/a/updatefile.js')); + assert(base.updaters.hasOwnProperty('base-abc')); + }); + + it('should throw when an updater does not expose the instance', function(cb) { + try { + base.register('not-exposed', require(fixtures('not-exposed.js'))); + cb(new Error('expected an error')); + } catch (err) { + assert.equal(err.message, `cannot resolve: 'not-exposed'`); + cb(); + } + }); + }); + + describe('instance', function() { + it('should register an instance', function() { + base.register('base-inst', new Base()); + assert(base.updaters.hasOwnProperty('base-inst')); + }); + + it('should get an updater that was registered as an instance', function() { + var foo = new Base(); + foo.task('default', function() {}); + base.register('foo', foo); + assert(base.getGenerator('foo')); + }); + + it('should register multiple instances', function() { + var foo = new Base(); + var bar = new Base(); + var baz = new Base(); + base.register('foo', foo); + base.register('bar', bar); + base.register('baz', baz); + assert(base.getGenerator('foo')); + assert(base.getGenerator('bar')); + assert(base.getGenerator('baz')); + }); + + it('should get tasks from an updater that was registered as an instance', function() { + var foo = new Base(); + foo.task('default', function() {}); + base.register('foo', foo); + var updater = base.getGenerator('foo'); + assert(updater); + assert(updater.tasks.hasOwnProperty('default')); + }); + + it('should get sub-updaters from an updater registered as an instance', function() { + var foo = new Base(); + foo.use(generators()); + foo.register('bar', function() {}); + base.register('foo', foo); + var updater = base.getGenerator('foo.bar'); + assert(updater); + }); + + it('should get tasks from sub-updaters registered as an instance', function() { + var foo = new Base(); + foo.use(generators()); + + foo.options.isFoo = true; + foo.register('bar', function(bar) { + bar.task('whatever', function() {}); + }); + + base.register('foo', foo); + var updater = base.getGenerator('foo.bar'); + assert(updater.tasks); + assert(updater.tasks.hasOwnProperty('whatever')); + }); + }); +}); diff --git a/test/app.store.js b/test/app.store.js new file mode 100644 index 0000000..7c44e4e --- /dev/null +++ b/test/app.store.js @@ -0,0 +1,335 @@ +'use strict'; + +require('mocha'); +var fs = require('fs'); +var path = require('path'); +var assert = require('assert'); +var store = require('base-store'); +var App = require('..'); +var app; + +describe('store', function() { + describe('methods', function() { + beforeEach(function() { + app = new App(); + app.use(store()); + app.store.create('app-data-tests'); + }); + + afterEach(function() { + app.store.data = {}; + app.store.del({force: true}); + app.options.cli = false; + }); + + it('should `.set()` a value on the store', function() { + app.store.set('one', 'two'); + assert.equal(app.store.data.one, 'two'); + }); + + it('should `.set()` an object', function() { + app.store.set({four: 'five', six: 'seven'}); + assert.equal(app.store.data.four, 'five'); + assert.equal(app.store.data.six, 'seven'); + }); + + it('should `.set()` a nested value', function() { + app.store.set('a.b.c.d', {e: 'f'}); + assert.equal(app.store.data.a.b.c.d.e, 'f'); + }); + + it('should `.union()` a value on the store', function() { + app.store.union('one', 'two'); + assert.deepEqual(app.store.data.one, ['two']); + }); + + it('should not union duplicate values', function() { + app.store.union('one', 'two'); + assert.deepEqual(app.store.data.one, ['two']); + + app.store.union('one', ['two']); + assert.deepEqual(app.store.data.one, ['two']); + }); + + it('should concat an existing array:', function() { + app.store.union('one', 'a'); + assert.deepEqual(app.store.data.one, ['a']); + + app.store.union('one', ['b']); + assert.deepEqual(app.store.data.one, ['a', 'b']); + + app.store.union('one', ['c', 'd']); + assert.deepEqual(app.store.data.one, ['a', 'b', 'c', 'd']); + }); + + it('should return true if a key `.has()` on the store', function() { + app.store.set('foo', 'bar'); + app.store.set('baz', null); + app.store.set('qux', undefined); + + assert(app.store.has('foo')); + assert(app.store.has('baz')); + assert(!app.store.has('bar')); + assert(!app.store.has('qux')); + }); + + it('should return true if a nested key `.has()` on the store', function() { + app.store.set('a.b.c.d', {x: 'zzz'}); + app.store.set('a.b.c.e', {f: null}); + app.store.set('a.b.g.j', {k: undefined}); + + assert(!app.store.has('a.b.bar')); + assert(app.store.has('a.b.c.d')); + assert(app.store.has('a.b.c.d.x')); + assert(!app.store.has('a.b.c.d.z')); + assert(app.store.has('a.b.c.e')); + assert(app.store.has('a.b.c.e.f')); + assert(!app.store.has('a.b.c.e.z')); + assert(app.store.has('a.b.g.j')); + assert(!app.store.has('a.b.g.j.k')); + assert(!app.store.has('a.b.g.j.z')); + }); + + it('should return true if a key exists `.hasOwn()` on the store', function() { + app.store.set('foo', 'bar'); + app.store.set('baz', null); + app.store.set('qux', undefined); + + assert(app.store.hasOwn('foo')); + assert(!app.store.hasOwn('bar')); + assert(app.store.hasOwn('baz')); + assert(app.store.hasOwn('qux')); + }); + + it('should return true if a nested key exists `.hasOwn()` on the store', function() { + app.store.set('a.b.c.d', {x: 'zzz'}); + app.store.set('a.b.c.e', {f: null}); + app.store.set('a.b.g.j', {k: undefined}); + + assert(!app.store.hasOwn('a.b.bar')); + assert(app.store.hasOwn('a.b.c.d')); + assert(app.store.hasOwn('a.b.c.d.x')); + assert(!app.store.hasOwn('a.b.c.d.z')); + assert(app.store.has('a.b.c.e.f')); + assert(app.store.hasOwn('a.b.c.e.f')); + assert(!app.store.hasOwn('a.b.c.e.bar')); + assert(!app.store.has('a.b.g.j.k')); + assert(app.store.hasOwn('a.b.g.j.k')); + assert(!app.store.hasOwn('a.b.g.j.foo')); + }); + + it('should `.get()` a stored value', function() { + app.store.set('three', 'four'); + assert.equal(app.store.get('three'), 'four'); + }); + + it('should `.get()` a nested value', function() { + app.store.set({a: {b: {c: 'd'}}}); + assert.equal(app.store.get('a.b.c'), 'd'); + }); + + it('should `.del()` a stored value', function() { + app.store.set('a', 'b'); + app.store.set('c', 'd'); + assert(app.store.data.hasOwnProperty('a')); + assert(app.store.data.hasOwnProperty('c')); + + app.store.del('a'); + app.store.del('c'); + assert(!app.store.data.hasOwnProperty('a')); + assert(!app.store.data.hasOwnProperty('c')); + }); + + it('should `.del()` multiple stored values', function() { + app.store.set('a', 'b'); + app.store.set('c', 'd'); + app.store.set('e', 'f'); + app.store.del(['a', 'c', 'e']); + assert.deepEqual(app.store.data, {}); + }); + }); +}); + +describe('create', function() { + beforeEach(function() { + app = new App({cli: true}); + app.use(store()); + app.store.create('abc'); + + // init the actual store json file + app.store.set('a', 'b'); + }); + + afterEach(function() { + app.store.data = {}; + app.store.del({force: true}); + app.options.cli = false; + }); + + it('should expose a `create` method', function() { + assert.equal(typeof app.store.create, 'function'); + }); + + it('should create a "sub-store" with the given name', function() { + var store = app.store.create('created'); + assert.equal(store.name, 'created'); + }); + + it('should create a "sub-store" with the project name when no name is passed', function() { + var store = app.store.create('app-store'); + assert.equal(store.name, 'app-store'); + }); + + it('should throw an error when a conflicting store name is used', function(cb) { + try { + app.store.create('create'); + cb(new Error('expected an error')); + } catch (err) { + assert.equal(err.message, 'Cannot create store: "create", since "create" is a reserved property key. Please choose a different store name.'); + cb(); + } + }); + + it('should add a store object to store[name]', function() { + app.store.create('foo'); + assert.equal(typeof app.store.foo, 'object'); + assert.equal(typeof app.store.foo.set, 'function'); + app.store.foo.del({force: true}); + }); + + it('should save the store in a namespaced directory under the parent', function() { + app.store.create('foo'); + var dir = path.dirname(app.store.path); + + assert.equal(app.store.foo.path, path.join(dir, 'update/foo.json')); + app.store.foo.set('a', 'b'); + app.store.foo.del({force: true}); + }); + + it('should set values on the custom store', function() { + app.store.create('foo'); + app.store.foo.set('a', 'b'); + assert.equal(app.store.foo.data.a, 'b'); + app.store.foo.del({force: true}); + }); + + it('should get values from the custom store', function() { + app.store.create('foo'); + app.store.foo.set('a', 'b'); + assert.equal(app.store.foo.get('a'), 'b'); + app.store.foo.del({force: true}); + }); +}); + +describe('events', function() { + beforeEach(function() { + app = new App({cli: true}); + app.use(store()); + app.store.create('abc'); + }); + + afterEach(function() { + app.store.data = {}; + app.store.del({force: true}); + app.options.cli = false; + }); + + it('should emit `set` when an object is set:', function() { + var keys = []; + app.store.on('set', function(key) { + keys.push(key); + }); + + app.store.set({a: {b: {c: 'd'}}}); + assert.deepEqual(keys, ['a']); + }); + + it('should emit `set` when a key/value pair is set:', function() { + var keys = []; + + app.store.on('set', function(key) { + keys.push(key); + }); + + app.store.set('a', 'b'); + assert.deepEqual(keys, ['a']); + }); + + it('should emit `set` when an object value is set:', function() { + var keys = []; + + app.store.on('set', function(key) { + keys.push(key); + }); + + app.store.set('a', {b: 'c'}); + assert.deepEqual(keys, ['a']); + }); + + it('should emit `set` when an array of objects is passed:', function(cb) { + var keys = []; + + app.store.on('set', function(key) { + keys.push(key); + }); + + app.store.set([{a: 'b'}, {c: 'd'}]); + assert.deepEqual(keys, ['a', 'c']); + cb(); + }); + + it('should emit `has`:', function(cb) { + var keys = []; + + app.store.on('has', function(val) { + assert(val); + cb(); + }); + + app.store.set('a', 'b'); + app.store.has('a'); + }); + + it('should emit `del` when a value is delted:', function(cb) { + app.store.on('del', function(keys) { + assert.deepEqual(keys, 'a'); + assert.equal(typeof app.store.get('a'), 'undefined'); + cb(); + }); + + app.store.set('a', {b: 'c'}); + assert.deepEqual(app.store.get('a'), {b: 'c'}); + app.store.del('a'); + }); + + it('should emit deleted keys on `del`:', function(cb) { + var arr = []; + + app.store.on('del', function(key) { + arr.push(key); + assert.equal(Object.keys(app.store.data).length, 0); + }); + + app.store.set('a', 'b'); + app.store.set('c', 'd'); + app.store.set('e', 'f'); + + app.store.del({force: true}); + assert.deepEqual(arr, ['a', 'c', 'e']); + cb(); + }); + + it('should throw an error if force is not passed', function(cb) { + app.store.set('a', 'b'); + app.store.set('c', 'd'); + app.store.set('e', 'f'); + + try { + app.store.del(); + cb(new Error('expected an error')); + } catch (err) { + assert.equal(err.message, 'options.force is required to delete the entire cache.'); + cb(); + } + }); +}); diff --git a/test/app.task.js b/test/app.task.js new file mode 100644 index 0000000..68ad212 --- /dev/null +++ b/test/app.task.js @@ -0,0 +1,191 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var Base = require('..'); +var base; + +describe('.generate', function() { + beforeEach(function() { + base = new Base(); + }); + + it('should register a task', function() { + var fn = function(cb) { + cb(); + }; + base.task('default', fn); + assert.equal(typeof base.tasks.default, 'object'); + assert.equal(base.tasks.default.fn, fn); + }); + + it('should register a task with an array of dependencies', function(cb) { + var count = 0; + base.task('foo', function(next) { + count++; + next(); + }); + base.task('bar', function(next) { + count++; + next(); + }); + base.task('default', ['foo', 'bar'], function(next) { + count++; + next(); + }); + assert.equal(typeof base.tasks.default, 'object'); + assert.deepEqual(base.tasks.default.deps, ['foo', 'bar']); + base.build('default', function(err) { + if (err) return cb(err); + assert.equal(count, 3); + cb(); + }); + }); + + it('should run a glob of tasks', function(cb) { + var count = 0; + base.task('foo', function(next) { + count++; + next(); + }); + base.task('bar', function(next) { + count++; + next(); + }); + base.task('baz', function(next) { + count++; + next(); + }); + base.task('qux', function(next) { + count++; + next(); + }); + base.task('default', ['b*']); + assert.equal(typeof base.tasks.default, 'object'); + base.build('default', function(err) { + if (err) return cb(err); + assert.equal(count, 2); + cb(); + }); + }); + + it('should register a task with a list of strings as dependencies', function() { + base.task('default', 'foo', 'bar', function(cb) { + cb(); + }); + assert.equal(typeof base.tasks.default, 'object'); + assert.deepEqual(base.tasks.default.deps, ['foo', 'bar']); + }); + + it('should run a task', function(cb) { + var count = 0; + base.task('default', function(cb) { + count++; + cb(); + }); + + base.build('default', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should throw an error when a task with unregistered dependencies is run', function(cb) { + base.task('default', ['foo', 'bar']); + base.build('default', function(err) { + assert(err); + cb(); + }); + }); + + it('should throw an error when `.build` is called without a callback function.', function() { + try { + base.build('default'); + throw new Error('Expected an error to be thrown.'); + } catch (err) { + } + }); + + it('should emit task events', function(cb) { + var events = []; + base.on('task:starting', function(task) { + events.push('starting.' + task.name); + }); + base.on('task:finished', function(task) { + events.push('finished.' + task.name); + }); + base.on('task:error', function(e, task) { + events.push('error.' + task.name); + }); + base.task('foo', function(cb) { + cb(); + }); + base.task('bar', ['foo'], function(cb) { + cb(); + }); + base.task('default', ['bar']); + base.build('default', function(err) { + if (err) return cb(err); + assert.deepEqual(events, [ + 'starting.default', + 'starting.bar', + 'starting.foo', + 'finished.foo', + 'finished.bar', + 'finished.default' + ]); + cb(); + }); + }); + + it('should emit an error event when an error is passed back in a task', function(cb) { + base.on('error', function(err) { + assert(err); + assert.equal(err.message, 'This is an error'); + }); + base.task('default', function(cb) { + return cb(new Error('This is an error')); + }); + base.build('default', function(err) { + if (err) return cb(); + cb(new Error('Expected an error')); + }); + }); + + it('should emit an error event when an error is thrown in a task', function(cb) { + base.on('error', function(err) { + assert(err); + assert.equal(err.message, 'This is an error'); + }); + base.task('default', function(cb) { + cb(new Error('This is an error')); + }); + base.build('default', function(err) { + assert(err); + cb(); + }); + }); + + it('should run dependencies before running the dependent task.', function(cb) { + var seq = []; + base.task('foo', function(cb) { + seq.push('foo'); + cb(); + }); + base.task('bar', function(cb) { + seq.push('bar'); + cb(); + }); + base.task('default', ['foo', 'bar'], function(cb) { + seq.push('default'); + cb(); + }); + + base.build('default', function(err) { + if (err) return cb(err); + assert.deepEqual(seq, ['foo', 'bar', 'default']); + cb(); + }); + }); +}); diff --git a/test/app.toAlias.js b/test/app.toAlias.js new file mode 100644 index 0000000..e5f8bf4 --- /dev/null +++ b/test/app.toAlias.js @@ -0,0 +1,41 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var Base = require('..'); +var base; + +describe('.toAlias', function() { + beforeEach(function() { + base = new Base(); + }); + + it('should not create an alias when no prefix is given', function() { + assert.equal(base.toAlias('foo-bar'), 'foo-bar'); + }); + + it('should create an alias using the `options.toAlias` function', function() { + var alias = base.toAlias('one-two-three', { + toAlias: function(name) { + return name.slice(name.lastIndexOf('-') + 1); + } + }); + assert.equal(alias, 'three'); + }); + + it('should create an alias using the given function', function() { + var alias = base.toAlias('one-two-three', function(name) { + return name.slice(name.lastIndexOf('-') + 1); + }); + assert.equal(alias, 'three'); + }); + + it('should create an alias using base.options.toAlias function', function() { + base.options.toAlias = function(name) { + return name.slice(name.lastIndexOf('-') + 1); + }; + + var alias = base.toAlias('one-two-three'); + assert.equal(alias, 'three'); + }); +}); diff --git a/test/app.update-array.js b/test/app.update-array.js new file mode 100644 index 0000000..1964250 --- /dev/null +++ b/test/app.update-array.js @@ -0,0 +1,931 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var config = require('base-config-process'); +var Base = require('..'); +var base; + +describe('.generate', function() { + beforeEach(function() { + base = new Base(); + }); + + describe('config.process', function(cb) { + it('should run tasks when the base-config plugin is used', function(cb) { + base.use(config()); + var count = 0; + base.task('default', function(next) { + count++; + next(); + }); + + base.generate('default', function(err) { + assert(!err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run handle errors when the base-config plugin is used', function(cb) { + base.use(config()); + var count = 0; + base.task('default', function(next) { + count++; + next(new Error('fooo')); + }); + + base.generate('default', function(err) { + assert.equal(err.message, 'fooo'); + assert.equal(count, 1); + cb(); + }); + }); + + it('should handle config errors when the base-config plugin is used', function(cb) { + base.use(config()); + var count = 0; + base.config.map('foo', function(val, key, config, next) { + count++; + next(new Error('fooo')); + }); + + base.set('cache.config', {foo: true}); + + base.task('default', function(next) { + count--; + next(); + }); + + base.generate('default', function(err) { + assert(err); + assert.equal(err.message, 'fooo'); + assert.equal(count, 1); + cb(); + }); + }); + }); + + describe('generators', function(cb) { + it('should throw an error when a generator is not found', function(cb) { + base.generate('fdsslsllsfjssl', function(err) { + assert(err); + assert.equal('Cannot find generator: "fdsslsllsfjssl"', err.message); + cb(); + }); + }); + + it('should *not* throw an error when the default task is not found', function(cb) { + base.register('foo', function() {}); + base.generate('foo:default', function(err) { + assert(!err); + cb(); + }); + }); + + it('should not throw an error when a default generator is not found', function(cb) { + base.generate('default', function(err) { + assert(!err); + cb(); + }); + }); + + it('should not throw an error when a default task and default generator is not found', function(cb) { + base.generate('default:default', function(err) { + assert(!err); + cb(); + }); + }); + + // special case + it('should throw an error when a generator is not found in argv.cwd', function(cb) { + base.option('cwd', 'foo/bar/baz'); + base.generate('sflsjljskksl', function(err) { + assert(err); + assert.equal('Cannot find generator: "sflsjljskksl" in "foo/bar/baz"', err.message); + cb(); + }); + }); + + it('should not reformat error messages that are not about invalid tasks', function(cb) { + base.task('default', function(cb) { + cb(new Error('whatever')); + }); + + base.generate('default', function(err) { + assert(err); + assert.equal(err.message, 'whatever'); + cb(); + }); + }); + + it('should not throw an error when the default task is not defined', function(cb) { + base.register('foo', function() {}); + base.register('bar', function() {}); + base.generate('foo', ['default'], function(err) { + if (err) return cb(err); + + base.generate('bar', function(err) { + if (err) return cb(err); + + cb(); + }); + }); + }); + + it('should run a task on the instance', function(cb) { + base.task('abc123', function(next) { + next(); + }); + + base.generate('abc123', function(err) { + assert(!err); + cb(); + }); + }); + + it('should run same-named task instead of a generator', function(cb) { + base.register('123xyz', function(app) { + cb(new Error('expected the task to run first')); + }); + + base.task('123xyz', function() { + cb(); + }); + + base.generate('123xyz', function(err) { + assert(!err); + }); + }); + + it('should run a task instead of a generator with a default task', function(cb) { + base.register('123xyz', function(app) { + app.task('default', function() { + cb(new Error('expected the task to run first')); + }); + }); + base.task('123xyz', function() { + cb(); + }); + base.generate('123xyz', function(err) { + assert(!err); + }); + }); + + it('should run a task on a same-named generator when the task is specified', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.task('foo', function() { + cb(new Error('expected the generator to run')); + }); + + base.generate('foo:default', function(err) { + assert(!err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run an array of tasks that includes a same-named generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.register('bar', function(app) { + app.task('baz', function(next) { + count++; + next(); + }); + }); + + base.task('foo', function() { + cb(new Error('expected the generator to run')); + }); + + base.generate(['foo:default', 'bar:baz'], function(err) { + assert(!err); + assert.equal(count, 2); + cb(); + }); + }); + + it('should run a generator from a task with the same name', function(cb) { + base.register('foo', function(app) { + app.task('default', function() { + cb(); + }); + }); + + base.task('foo', function(cb) { + base.generate('foo', cb); + }); + + base.build('foo', function(err) { + if (err) cb(err); + }); + }); + + it('should run the default task on a generator', function(cb) { + base.register('foo', function(app) { + app.task('default', function(next) { + next(); + }); + }); + + base.generate('foo', function(err) { + assert(!err); + cb(); + }); + }); + + it('should run a stringified array of tasks on the instance', function(cb) { + var count = 0; + base.task('a', function(next) { + count++; + next(); + }); + base.task('b', function(next) { + count++; + next(); + }); + base.task('c', function(next) { + count++; + next(); + }); + + base.generate('a,b,c', function(err) { + assert.equal(count, 3); + assert(!err); + cb(); + }); + }); + + it('should run an array of tasks on the instance', function(cb) { + var count = 0; + base.task('a', function(next) { + count++; + next(); + }); + base.task('b', function(next) { + count++; + next(); + }); + base.task('c', function(next) { + count++; + next(); + }); + + base.generate(['a', 'b', 'c'], function(err) { + if (err) return cb(err); + assert.equal(count, 3); + assert(!err); + cb(); + }); + }); + + it('should run the default task on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.generate('foo', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run an array of generators', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.register('bar', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.generate(['foo', 'bar'], function(err) { + if (err) return cb(err); + assert.equal(count, 2); + cb(); + }); + }); + + it('should run the default task on the default generator', function(cb) { + var count = 0; + base.register('default', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.generate(function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run the specified task on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + + app.task('abc', function(next) { + count++; + next(); + }); + }); + + base.generate('foo:abc', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run an array of tasks on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + + app.task('a', function(next) { + count++; + next(); + }); + + app.task('b', function(next) { + count++; + next(); + }); + + app.task('c', function(next) { + count++; + next(); + }); + }); + + base.generate('foo:a,b,c', function(err) { + if (err) return cb(err); + assert.equal(count, 3); + cb(); + }); + }); + + it('should run the default tasks on an array of generators', function(cb) { + var count = 0; + base.register('a', function(app) { + this.task('default', function(cb) { + count++; + cb(); + }); + }); + + base.register('b', function(app) { + this.task('default', function(cb) { + count++; + cb(); + }); + }); + + base.register('c', function(app) { + this.task('default', function(cb) { + count++; + cb(); + }); + }); + + base.generate(['a', 'b', 'c'], function(err) { + if (err) return cb(err); + assert.equal(count, 3); + assert(!err); + cb(); + }); + }); + }); + + describe('options', function(cb) { + it('should pass options to generator.options', function(cb) { + var count = 0; + base.register('default', function(app, base, env, options) { + app.task('default', function(next) { + count++; + assert.equal(app.options.foo, 'bar'); + next(); + }); + }); + + base.generate({foo: 'bar'}, function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should expose options on generator options', function(cb) { + var count = 0; + base.register('default', function(app, base, env, options) { + app.task('default', function(next) { + count++; + assert.equal(options.foo, 'bar'); + next(); + }); + }); + + base.generate({foo: 'bar'}, function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should not mutate options on parent instance', function(cb) { + var count = 0; + base.register('default', function(app, base, env, options) { + app.task('default', function(next) { + count++; + assert.equal(options.foo, 'bar'); + assert(!base.options.foo); + next(); + }); + }); + + base.generate({foo: 'bar'}, function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + }); + + describe('default tasks', function(cb) { + it('should run the default task on the default generator', function(cb) { + var count = 0; + base.register('default', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.generate(function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run the default task on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.generate('foo', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + }); + + describe('specified tasks', function(cb) { + it('should run the specified task on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + + app.task('abc', function(next) { + count++; + next(); + }); + }); + + base.generate('foo', ['abc'], function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run an array of tasks on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + + app.task('a', function(next) { + count++; + next(); + }); + + app.task('b', function(next) { + count++; + next(); + }); + + app.task('c', function(next) { + count++; + next(); + }); + }); + + base.generate('foo', 'a,b,c', function(err) { + if (err) return cb(err); + assert.equal(count, 3); + cb(); + }); + }); + }); + + describe('sub-generators', function(cb) { + it('should run the default task on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + count++; + next(); + }); + + sub.task('abc', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.sub', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run the specified task string on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + count++; + next(); + }); + + sub.task('abc', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.sub:abc', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run the specified task array on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + count++; + next(); + }); + + sub.task('abc', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.sub', ['abc'], function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run an of stringified-tasks on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('bar', function(bar) { + bar.task('default', function(next) { + count++; + next(); + }); + + bar.task('a', function(next) { + count++; + next(); + }); + + bar.task('b', function(next) { + count++; + next(); + }); + + bar.task('c', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.bar:a,b,c', function(err) { + if (err) return cb(err); + assert.equal(count, 3); + cb(); + }); + }); + + it('should run an array of tasks on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('bar', function(bar) { + bar.task('default', function(next) { + count++; + next(); + }); + + bar.task('a', function(next) { + count++; + next(); + }); + + bar.task('b', function(next) { + count++; + next(); + }); + + bar.task('c', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.bar', ['a', 'b', 'c'], function(err) { + if (err) return cb(err); + assert.equal(count, 3); + cb(); + }); + }); + + it('should run an multiple tasks on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('bar', function(bar) { + bar.task('default', function(next) { + count++; + next(); + }); + + bar.task('a', function(next) { + count++; + next(); + }); + + bar.task('b', function(next) { + count++; + next(); + }); + + bar.task('c', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.bar', 'a,b,c', function(err) { + if (err) return cb(err); + assert.equal(count, 3); + cb(); + }); + }); + + it('should run an multiple tasks on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('bar', function(bar) { + bar.task('default', function(next) { + count++; + next(); + }); + + bar.task('a', function(next) { + count++; + next(); + }); + + bar.task('b', function(next) { + count++; + next(); + }); + + bar.task('c', function(next) { + count++; + next(); + }); + }); + }); + + base.register('qux', function(app) { + app.register('fez', function(fez) { + fez.task('default', function(next) { + count++; + next(); + }); + + fez.task('a', function(next) { + count++; + next(); + }); + + fez.task('b', function(next) { + count++; + next(); + }); + + fez.task('c', function(next) { + count++; + next(); + }); + }); + }); + + base.generate(['foo.bar:a,b,c', 'qux.fez:a,b,c'], function(err) { + if (err) return cb(err); + assert.equal(count, 6); + cb(); + }); + }); + }); + + describe('cross-generator', function(cb) { + it('should run a generator from another generator', function(cb) { + var res = ''; + + base.register('foo', function(app, two) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + res += 'foo > sub > default '; + base.generate('bar.sub', next); + }); + }); + }); + + base.register('bar', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + res += 'bar > sub > default '; + next(); + }); + }); + }); + + base.generate('foo.sub', function(err) { + if (err) return cb(err); + assert.equal(res, 'foo > sub > default bar > sub > default '); + cb(); + }); + }); + + it('should run the specified task on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + count++; + next(); + }); + + sub.task('abc', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.sub:abc', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + }); + + describe('events', function(cb) { + it('should emit generate', function(cb) { + var count = 0; + + base.on('generate', function() { + count++; + }); + + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + next(); + }); + + sub.task('abc', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.sub', ['abc'], function(err) { + if (err) return cb(err); + assert.equal(count, 2); + cb(); + }); + }); + + it('should expose the generator alias as the first parameter', function(cb) { + base.on('generate', function(name) { + assert.equal(name, 'sub'); + cb(); + }); + + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + next(); + }); + + sub.task('abc', function(next) { + next(); + }); + }); + }); + + base.generate('foo.sub:abc', function(err) { + if (err) return cb(err); + }); + }); + + it('should expose the tasks array as the second parameter', function(cb) { + base.on('generate', function(name, tasks) { + assert.deepEqual(tasks, ['abc']); + cb(); + }); + + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + next(); + }); + + sub.task('abc', function(next) { + next(); + }); + }); + }); + + base.generate('foo.sub:abc', function(err) { + if (err) return cb(err); + }); + }); + }); +}); diff --git a/test/app.update.js b/test/app.update.js new file mode 100644 index 0000000..b1403d9 --- /dev/null +++ b/test/app.update.js @@ -0,0 +1,961 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var config = require('base-config-process'); +var Base = require('..'); +var base; + +describe('.generate', function() { + beforeEach(function() { + base = new Base(); + }); + + describe('config.process', function(cb) { + it('should run tasks when the base-config plugin is used', function(cb) { + base.use(config()); + var count = 0; + base.task('default', function(next) { + count++; + next(); + }); + + base.generate('default', function(err) { + assert(!err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should handle errors when the base-config plugin is used', function(cb) { + base.use(config()); + var count = 0; + base.task('default', function(next) { + count++; + next(new Error('fooo')); + }); + + base.generate('default', function(err) { + assert.equal(err.message, 'fooo'); + assert.equal(count, 1); + cb(); + }); + }); + + it('should handle config errors when the base-config plugin is used', function(cb) { + base.use(config()); + var count = 0; + + base.config.map('foo', function(val, key, config, next) { + count++; + next(new Error('fooo')); + }); + + base.set('cache.config', {foo: true}); + base.task('default', function(next) { + count--; + next(); + }); + + base.generate('default', function(err) { + assert(err); + assert.equal(err.message, 'fooo'); + assert.equal(count, 1); + cb(); + }); + }); + }); + + describe('generators', function(cb) { + it('should throw an error when a generator is not found', function(cb) { + base.generate('fdsslsllsfjssl', function(err) { + assert(err); + assert.equal('Cannot find generator: "fdsslsllsfjssl"', err.message); + cb(); + }); + }); + + it('should *not* throw an error when the default task is not found', function(cb) { + base.register('foo', function() {}); + base.generate('foo:default', function(err) { + assert(!err); + cb(); + }); + }); + + it('should not throw an error when a default generator is not found', function(cb) { + base.generate('default', function(err) { + assert(!err); + cb(); + }); + }); + + it('should not throw an error when a default task and default generator is not found', function(cb) { + base.generate('default:default', function(err) { + assert(!err); + cb(); + }); + }); + + // special case + it('should throw an error when a generator is not found in argv.cwd', function(cb) { + base.option('cwd', 'foo/bar/baz'); + base.generate('sflsjljskksl', function(err) { + assert(err); + assert.equal('Cannot find generator: "sflsjljskksl" in "foo/bar/baz"', err.message); + cb(); + }); + }); + + it('should throw an error when a task is not found (task array)', function(cb) { + var fn = console.error; + var res = []; + console.error = function(msg) { + res.push(msg); + }; + base.register('fdsslsllsfjssl', function() {}); + base.generate('fdsslsllsfjssl', ['foo'], function(err) { + console.error = fn; + if (err) return cb(err); + assert.equal(res[0], 'Cannot find task: "foo" in generator: "fdsslsllsfjssl"'); + cb(); + }); + }); + + it('should throw an error when a task is not found (task string)', function(cb) { + var fn = console.error; + var res = []; + console.error = function(msg) { + res.push(msg); + }; + base.register('fdsslsllsfjssl', function() {}); + base.generate('fdsslsllsfjssl:foo', function(err) { + console.error = fn; + if (err) return cb(err); + assert.equal(res[0], 'Cannot find task: "foo" in generator: "fdsslsllsfjssl"'); + cb(); + }); + }); + + it('should not reformat error messages that are not about invalid tasks', function(cb) { + base.task('default', function(cb) { + cb(new Error('whatever')); + }); + + base.generate('default', function(err) { + assert(err); + assert.equal(err.message, 'whatever'); + cb(); + }); + }); + + it('should not throw an error when the default task is not defined', function(cb) { + base.register('foo', function() {}); + base.register('bar', function() {}); + base.generate('foo', ['default'], function(err) { + if (err) return cb(err); + + base.generate('bar', function(err) { + if (err) return cb(err); + + cb(); + }); + }); + }); + + it('should run a task on the instance', function(cb) { + base.task('abc123', function(next) { + next(); + }); + + base.generate('abc123', function(err) { + assert(!err); + cb(); + }); + }); + + it('should run same-named task instead of a generator', function(cb) { + base.register('123xyz', function(app) { + cb(new Error('expected the task to run first')); + }); + + base.task('123xyz', function() { + cb(); + }); + + base.generate('123xyz', function(err) { + assert(!err); + }); + }); + + it('should run a task instead of a generator with a default task', function(cb) { + base.register('123xyz', function(app) { + app.task('default', function() { + cb(new Error('expected the task to run first')); + }); + }); + base.task('123xyz', function() { + cb(); + }); + base.generate('123xyz', function(err) { + assert(!err); + }); + }); + + it('should run a task on a same-named generator when the task is specified', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.task('foo', function() { + cb(new Error('expected the generator to run')); + }); + + base.generate('foo:default', function(err) { + assert(!err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run an array of tasks that includes a same-named generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.register('bar', function(app) { + app.task('baz', function(next) { + count++; + next(); + }); + }); + + base.task('foo', function() { + cb(new Error('expected the generator to run')); + }); + + base.generate(['foo:default', 'bar:baz'], function(err) { + assert(!err); + assert.equal(count, 2); + cb(); + }); + }); + + it('should run a generator from a task with the same name', function(cb) { + base.register('foo', function(app) { + app.task('default', function() { + cb(); + }); + }); + + base.task('foo', function(cb) { + base.generate('foo', cb); + }); + + base.build('foo', function(err) { + if (err) cb(err); + }); + }); + + it('should run the default task on a generator', function(cb) { + base.register('foo', function(app) { + app.task('default', function(next) { + next(); + }); + }); + + base.generate('foo', function(err) { + assert(!err); + cb(); + }); + }); + + it('should run a stringified array of tasks on the instance', function(cb) { + var count = 0; + base.task('a', function(next) { + count++; + next(); + }); + base.task('b', function(next) { + count++; + next(); + }); + base.task('c', function(next) { + count++; + next(); + }); + + base.generate('a,b,c', function(err) { + assert.equal(count, 3); + assert(!err); + cb(); + }); + }); + + it('should run an array of tasks on the instance', function(cb) { + var count = 0; + base.task('a', function(next) { + count++; + next(); + }); + base.task('b', function(next) { + count++; + next(); + }); + base.task('c', function(next) { + count++; + next(); + }); + + base.generate(['a', 'b', 'c'], function(err) { + if (err) return cb(err); + assert.equal(count, 3); + assert(!err); + cb(); + }); + }); + + it('should run the default task on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.generate('foo', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run an array of generators', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.register('bar', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.generate(['foo', 'bar'], function(err) { + if (err) return cb(err); + assert.equal(count, 2); + cb(); + }); + }); + + it('should run the default task on the default generator', function(cb) { + var count = 0; + base.register('default', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.generate(function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run the specified task on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + + app.task('abc', function(next) { + count++; + next(); + }); + }); + + base.generate('foo:abc', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run an array of tasks on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + + app.task('a', function(next) { + count++; + next(); + }); + + app.task('b', function(next) { + count++; + next(); + }); + + app.task('c', function(next) { + count++; + next(); + }); + }); + + base.generate('foo:a,b,c', function(err) { + if (err) return cb(err); + assert.equal(count, 3); + cb(); + }); + }); + + it('should run the default tasks on an array of generators', function(cb) { + var count = 0; + base.register('a', function(app) { + this.task('default', function(cb) { + count++; + cb(); + }); + }); + + base.register('b', function(app) { + this.task('default', function(cb) { + count++; + cb(); + }); + }); + + base.register('c', function(app) { + this.task('default', function(cb) { + count++; + cb(); + }); + }); + + base.generate(['a', 'b', 'c'], function(err) { + if (err) return cb(err); + assert.equal(count, 3); + assert(!err); + cb(); + }); + }); + }); + + describe('options', function(cb) { + it('should pass options to generator.options', function(cb) { + var count = 0; + base.register('default', function(app, base, env, options) { + app.task('default', function(next) { + count++; + assert.equal(app.options.foo, 'bar'); + next(); + }); + }); + + base.generate({foo: 'bar'}, function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should expose options on generator options', function(cb) { + var count = 0; + base.register('default', function(app, base, env, options) { + app.task('default', function(next) { + count++; + assert.equal(options.foo, 'bar'); + next(); + }); + }); + + base.generate({foo: 'bar'}, function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should not mutate options on parent instance', function(cb) { + var count = 0; + base.register('default', function(app, base, env, options) { + app.task('default', function(next) { + count++; + assert.equal(options.foo, 'bar'); + assert(!base.options.foo); + next(); + }); + }); + + base.generate({foo: 'bar'}, function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + }); + + describe('default tasks', function(cb) { + it('should run the default task on the default generator', function(cb) { + var count = 0; + base.register('default', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.generate(function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run the default task on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.generate('foo', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + }); + + describe('specified tasks', function(cb) { + it('should run the specified task on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + + app.task('abc', function(next) { + count++; + next(); + }); + }); + + base.generate('foo', ['abc'], function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run an array of tasks on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + + app.task('a', function(next) { + count++; + next(); + }); + + app.task('b', function(next) { + count++; + next(); + }); + + app.task('c', function(next) { + count++; + next(); + }); + }); + + base.generate('foo', 'a,b,c', function(err) { + if (err) return cb(err); + assert.equal(count, 3); + cb(); + }); + }); + }); + + describe('sub-generators', function(cb) { + it('should run the default task on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + count++; + next(); + }); + + sub.task('abc', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.sub', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run the specified task string on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + count++; + next(); + }); + + sub.task('abc', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.sub:abc', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run the specified task array on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + count++; + next(); + }); + + sub.task('abc', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.sub', ['abc'], function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run an of stringified-tasks on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('bar', function(bar) { + bar.task('default', function(next) { + count++; + next(); + }); + + bar.task('a', function(next) { + count++; + next(); + }); + + bar.task('b', function(next) { + count++; + next(); + }); + + bar.task('c', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.bar:a,b,c', function(err) { + if (err) return cb(err); + assert.equal(count, 3); + cb(); + }); + }); + + it('should run an array of tasks on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('bar', function(bar) { + bar.task('default', function(next) { + count++; + next(); + }); + + bar.task('a', function(next) { + count++; + next(); + }); + + bar.task('b', function(next) { + count++; + next(); + }); + + bar.task('c', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.bar', ['a', 'b', 'c'], function(err) { + if (err) return cb(err); + assert.equal(count, 3); + cb(); + }); + }); + + it('should run an multiple tasks on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('bar', function(bar) { + bar.task('default', function(next) { + count++; + next(); + }); + + bar.task('a', function(next) { + count++; + next(); + }); + + bar.task('b', function(next) { + count++; + next(); + }); + + bar.task('c', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.bar', 'a,b,c', function(err) { + if (err) return cb(err); + assert.equal(count, 3); + cb(); + }); + }); + + it('should run an multiple tasks on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('bar', function(bar) { + bar.task('default', function(next) { + count++; + next(); + }); + + bar.task('a', function(next) { + count++; + next(); + }); + + bar.task('b', function(next) { + count++; + next(); + }); + + bar.task('c', function(next) { + count++; + next(); + }); + }); + }); + + base.register('qux', function(app) { + app.register('fez', function(fez) { + fez.task('default', function(next) { + count++; + next(); + }); + + fez.task('a', function(next) { + count++; + next(); + }); + + fez.task('b', function(next) { + count++; + next(); + }); + + fez.task('c', function(next) { + count++; + next(); + }); + }); + }); + + base.generate(['foo.bar:a,b,c', 'qux.fez:a,b,c'], function(err) { + if (err) return cb(err); + assert.equal(count, 6); + cb(); + }); + }); + }); + + describe('cross-generator', function(cb) { + it('should run a generator from another generator', function(cb) { + var res = ''; + + base.register('foo', function(app, two) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + res += 'foo > sub > default '; + base.generate('bar.sub', next); + }); + }); + }); + + base.register('bar', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + res += 'bar > sub > default '; + next(); + }); + }); + }); + + base.generate('foo.sub', function(err) { + if (err) return cb(err); + assert.equal(res, 'foo > sub > default bar > sub > default '); + cb(); + }); + }); + + it('should run the specified task on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + count++; + next(); + }); + + sub.task('abc', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.sub', ['abc'], function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + }); + + describe('events', function(cb) { + it('should emit generate', function(cb) { + var count = 0; + + base.on('generate', function() { + count++; + }); + + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + next(); + }); + + sub.task('abc', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.sub', ['abc'], function(err) { + if (err) return cb(err); + assert.equal(count, 2); + cb(); + }); + }); + + it('should expose the generator alias as the first parameter', function(cb) { + base.on('generate', function(name) { + assert.equal(name, 'sub'); + cb(); + }); + + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + next(); + }); + + sub.task('abc', function(next) { + next(); + }); + }); + }); + + base.generate('foo.sub', ['abc'], function(err) { + if (err) return cb(err); + }); + }); + + it('should expose the tasks array as the second parameter', function(cb) { + base.on('generate', function(name, tasks) { + assert.deepEqual(tasks, ['abc']); + cb(); + }); + + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + next(); + }); + + sub.task('abc', function(next) { + next(); + }); + }); + }); + + base.generate('foo.sub', ['abc'], function(err) { + if (err) return cb(err); + }); + }); + }); +}); diff --git a/test/app.updater.js b/test/app.updater.js new file mode 100644 index 0000000..3126482 --- /dev/null +++ b/test/app.updater.js @@ -0,0 +1,291 @@ +'use strict'; + +require('mocha'); +var path = require('path'); +var assert = require('assert'); +var Base = require('..'); +var base; + +var fixtures = path.resolve.bind(path, __dirname, 'fixtures'); + +describe('.updater', function() { + beforeEach(function() { + base = new Base(); + + base.option('toAlias', function(key) { + return key.replace(/^updater-(.*)/, '$1'); + }); + }); + + describe('updater', function() { + it('should get an updater by alias', function() { + base.register('updater-example', require('updater-example')); + var gen = base.getUpdater('example'); + assert(gen); + assert.equal(gen.env.name, 'updater-example'); + assert.equal(gen.env.alias, 'example'); + }); + + it('should get an updater using a custom lookup function', function() { + base.register('updater-foo', function() {}); + base.register('updater-bar', function() {}); + + var gen = base.getUpdater('foo', { + lookup: function(key) { + return ['updater-' + key, 'verb-' + key + '-updater', key]; + } + }); + + assert(gen); + assert.equal(gen.env.name, 'updater-foo'); + assert.equal(gen.env.alias, 'foo'); + }); + }); + + describe('register > function', function() { + it('should register an updater function by name', function() { + base.updater('foo', function() {}); + assert(base.updaters.hasOwnProperty('foo')); + }); + + it('should register an updater function by alias', function() { + base.updater('updater-abc', function() {}); + assert(base.updaters.hasOwnProperty('updater-abc')); + }); + }); + + describe('get > alias', function() { + it('should get an updater by alias', function() { + base.updater('updater-abc', function() {}); + var abc = base.updater('abc'); + assert(abc); + assert.equal(typeof abc, 'object'); + }); + }); + + describe('get > name', function() { + it('should get an updater by name', function() { + base.updater('updater-abc', function() {}); + var abc = base.updater('updater-abc'); + assert(abc); + assert.equal(typeof abc, 'object'); + }); + }); + + describe('updaters', function() { + it('should invoke a registered updater when `getGenerator` is called', function(cb) { + base.register('foo', function(app) { + app.task('default', function() {}); + cb(); + }); + base.getGenerator('foo'); + }); + + it('should expose the updater instance on `app`', function(cb) { + base.register('foo', function(app) { + app.task('default', function(next) { + assert.equal(app.get('a'), 'b'); + next(); + }); + }); + + var foo = base.getGenerator('foo'); + foo.set('a', 'b'); + foo.build('default', function(err) { + if (err) return cb(err); + cb(); + }); + }); + + it('should expose the "base" instance on `base`', function(cb) { + base.set('x', 'z'); + base.register('foo', function(app, base) { + app.task('default', function(next) { + assert.equal(base.get('x'), 'z'); + next(); + }); + }); + + var foo = base.getGenerator('foo'); + foo.set('a', 'b'); + foo.build('default', function(err) { + if (err) return cb(err); + cb(); + }); + }); + + it('should expose the "env" object on `env`', function(cb) { + base.register('foo', function(app, base, env) { + app.task('default', function(next) { + assert.equal(env.alias, 'foo'); + next(); + }); + }); + + base.getGenerator('foo').build('default', function(err) { + if (err) return cb(err); + cb(); + }); + }); + + it('should expose an app\'s updaters on app.updaters', function(cb) { + base.register('foo', function(app) { + app.register('a', function() {}); + app.register('b', function() {}); + + app.updaters.hasOwnProperty('a'); + app.updaters.hasOwnProperty('b'); + cb(); + }); + + base.getGenerator('foo'); + }); + + it('should expose all root updaters on base.updaters', function(cb) { + base.register('foo', function(app, b) { + b.updaters.hasOwnProperty('foo'); + b.updaters.hasOwnProperty('bar'); + b.updaters.hasOwnProperty('baz'); + cb(); + }); + + base.register('bar', function(app, base) {}); + base.register('baz', function(app, base) {}); + base.getGenerator('foo'); + }); + }); + + describe('cross-updaters', function() { + it('should get an updater from another updater', function(cb) { + base.register('foo', function(app, b) { + var bar = b.getGenerator('bar'); + assert(bar); + cb(); + }); + + base.register('bar', function(app, base) {}); + base.register('baz', function(app, base) {}); + base.getGenerator('foo'); + }); + + it('should set options on another updater instance', function(cb) { + base.updater('foo', function(app) { + app.task('default', function(next) { + assert.equal(app.option('abc'), 'xyz'); + next(); + }); + }); + + base.updater('bar', function(app, b) { + var foo = b.getGenerator('foo'); + foo.option('abc', 'xyz'); + foo.build(function(err) { + if (err) return cb(err); + cb(); + }); + }); + }); + }); + + describe('updaters > filepath', function() { + it('should register an updater function from a file path', function() { + var one = base.updater('one', fixtures('one/updatefile.js')); + assert(base.updaters.hasOwnProperty('one')); + assert(typeof base.updaters.one === 'object'); + assert.deepEqual(base.updaters.one, one); + }); + + it('should get a registered updater by name', function() { + var one = base.updater('one', fixtures('one/updatefile.js')); + assert.deepEqual(base.updater('one'), one); + }); + }); + + describe('tasks', function() { + it('should expose an updater\'s tasks on app.tasks', function(cb) { + base.register('foo', function(app) { + app.task('a', function() {}); + app.task('b', function() {}); + assert(app.tasks.a); + assert(app.tasks.b); + cb(); + }); + + base.getGenerator('foo'); + }); + + it('should get tasks from another updater', function(cb) { + base.register('foo', function(app, b) { + var baz = b.getGenerator('baz'); + var task = baz.tasks.aaa; + assert(task); + cb(); + }); + + base.register('bar', function(app, base) {}); + base.register('baz', function(app, base) { + app.task('aaa', function() {}); + }); + base.getGenerator('foo'); + }); + }); + + describe('namespace', function() { + it('should expose `app.namespace`', function(cb) { + base.updater('foo', function(app) { + assert(typeof app.namespace, 'string'); + cb(); + }); + }); + + it('should create namespace from updater alias', function(cb) { + base.updater('updater-foo', function(app) { + assert.equal(app.namespace, base._name + '.foo'); + cb(); + }); + }); + + it('should create sub-updater namespace from parent namespace and alias', function(cb) { + var name = base._name; + base.updater('updater-foo', function(app) { + assert.equal(app.namespace, name + '.foo'); + + app.updater('updater-bar', function(bar) { + assert.equal(bar.namespace, name + '.foo.bar'); + + bar.updater('updater-baz', function(baz) { + assert.equal(baz.namespace, name + '.foo.bar.baz'); + + baz.updater('updater-qux', function(qux) { + assert.equal(qux.namespace, name + '.foo.bar.baz.qux'); + cb(); + }); + }); + }); + }); + }); + + it('should expose namespace on `this`', function(cb) { + var name = base._name; + + base.updater('updater-foo', function(app, first) { + assert.equal(this.namespace, base._name + '.foo'); + + this.updater('updater-bar', function() { + assert.equal(this.namespace, base._name + '.foo.bar'); + + this.updater('updater-baz', function() { + assert.equal(this.namespace, base._name + '.foo.bar.baz'); + + this.updater('updater-qux', function() { + assert.equal(this.namespace, base._name + '.foo.bar.baz.qux'); + assert.equal(app.namespace, base._name + '.foo'); + assert.equal(first.namespace, base._name); + cb(); + }); + }); + }); + }); + }); + }); +}); diff --git a/test/fixtures/def-gen.js b/test/fixtures/def-gen.js new file mode 100644 index 0000000..9955ea4 --- /dev/null +++ b/test/fixtures/def-gen.js @@ -0,0 +1,8 @@ +'use strict'; + +module.exports = function(app) { + app.task('default', function(cb) { + console.log('default'); + cb(); + }); +}; diff --git a/test/fixtures/not-exposed.js b/test/fixtures/not-exposed.js new file mode 100644 index 0000000..5cee793 --- /dev/null +++ b/test/fixtures/not-exposed.js @@ -0,0 +1,8 @@ +'use strict'; + +var Base = require('../..'); +var base = new Base({isApp: true}); + +base.register('not-exposed', function(app) { + +}); diff --git a/test/fixtures/one/package.json b/test/fixtures/one/package.json new file mode 100644 index 0000000..42509ea --- /dev/null +++ b/test/fixtures/one/package.json @@ -0,0 +1,9 @@ +{ + "name": "update-one", + "version": "0.0.0", + "private": true, + "description": "", + "main": "updatefile.js", + "license": "MIT", + "base": {} +} diff --git a/test/fixtures/one/templates/a.txt b/test/fixtures/one/templates/a.txt new file mode 100644 index 0000000..2ff8250 --- /dev/null +++ b/test/fixtures/one/templates/a.txt @@ -0,0 +1 @@ +one: aaa \ No newline at end of file diff --git a/test/fixtures/one/templates/x.txt b/test/fixtures/one/templates/x.txt new file mode 100644 index 0000000..139e1d8 --- /dev/null +++ b/test/fixtures/one/templates/x.txt @@ -0,0 +1 @@ +one: xxx \ No newline at end of file diff --git a/test/fixtures/one/templates/y.txt b/test/fixtures/one/templates/y.txt new file mode 100644 index 0000000..7308ea9 --- /dev/null +++ b/test/fixtures/one/templates/y.txt @@ -0,0 +1 @@ +one: yyy \ No newline at end of file diff --git a/test/fixtures/one/templates/z.txt b/test/fixtures/one/templates/z.txt new file mode 100644 index 0000000..04c378a --- /dev/null +++ b/test/fixtures/one/templates/z.txt @@ -0,0 +1 @@ +one: zzz \ No newline at end of file diff --git a/test/fixtures/one/updatefile.js b/test/fixtures/one/updatefile.js new file mode 100644 index 0000000..eb51b6d --- /dev/null +++ b/test/fixtures/one/updatefile.js @@ -0,0 +1,18 @@ +'use strict'; + +module.exports = function(app, base, env) { + app.task('default', function(cb) { + console.log('one > default'); + cb(); + }); + + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); + + app.register('foo', function(app) { + app.task('x', function() {}); + app.task('y', function() {}); + app.task('z', function() {}); + }); +}; diff --git a/test/fixtures/one/verbfile.js b/test/fixtures/one/verbfile.js new file mode 100644 index 0000000..546d004 --- /dev/null +++ b/test/fixtures/one/verbfile.js @@ -0,0 +1,19 @@ +'use strict'; + + +module.exports = function(app, base, env) { + app.task('default', function(cb) { + console.log('one > default'); + cb(); + }); + + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); + + app.register('foo', function(app) { + app.task('x', function() {}); + app.task('y', function() {}); + app.task('z', function() {}); + }); +}; \ No newline at end of file diff --git a/test/fixtures/package.json b/test/fixtures/package.json new file mode 100644 index 0000000..543ca49 --- /dev/null +++ b/test/fixtures/package.json @@ -0,0 +1,9 @@ +{ + "name": "update-tests", + "version": "0.0.0", + "private": true, + "description": "", + "main": "index.js", + "license": "MIT", + "base": {} +} diff --git a/test/fixtures/pages/a.hbs b/test/fixtures/pages/a.hbs new file mode 100644 index 0000000..51320bd --- /dev/null +++ b/test/fixtures/pages/a.hbs @@ -0,0 +1,2 @@ +

{{title}}

+

<%= title() %>

\ No newline at end of file diff --git a/test/fixtures/pages/b.hbs b/test/fixtures/pages/b.hbs new file mode 100644 index 0000000..51320bd --- /dev/null +++ b/test/fixtures/pages/b.hbs @@ -0,0 +1,2 @@ +

{{title}}

+

<%= title() %>

\ No newline at end of file diff --git a/test/fixtures/pages/c.hbs b/test/fixtures/pages/c.hbs new file mode 100644 index 0000000..51320bd --- /dev/null +++ b/test/fixtures/pages/c.hbs @@ -0,0 +1,2 @@ +

{{title}}

+

<%= title() %>

\ No newline at end of file diff --git a/test/fixtures/updater-foo/updatefile.js b/test/fixtures/updater-foo/updatefile.js new file mode 100644 index 0000000..887c058 --- /dev/null +++ b/test/fixtures/updater-foo/updatefile.js @@ -0,0 +1,7 @@ +'use strict'; + +module.exports = function(app, base, env) { + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); +}; diff --git a/test/fixtures/updater.js b/test/fixtures/updater.js new file mode 100644 index 0000000..de1c362 --- /dev/null +++ b/test/fixtures/updater.js @@ -0,0 +1,49 @@ +'use strict'; + +module.exports = function(app) { + app.register('updater-aaa', function(app) { + app.task('default', function(cb) { + console.log('update > default'); + cb(); + }); + + app.register('sub', function(sub) { + sub.task('default', function(cb) { + console.log('aaa > sub > default'); + cb(); + }); + + sub.register('bbb', function(bbb) { + bbb.task('default', function(cb) { + console.log('aaa > sub > bbb > default'); + cb(); + }); + }); + }); + }); + + app.register('updater-abc', 'test/fixtures/generators/a/generator.js'); + + app.register('updater-bbb', function(app) { + app.task('default', function(cb) { + app.update('aaa.sub.bbb', 'default', cb); + }); + }); + + app.register('updater-ccc', function(app) { + app.task('default', function(cb) { + app.update('abc', 'default', cb); + }); + }); + + app.register('updater-ddd', function(app) { + app.task('default', function(cb) { + app.update('abc.docs', 'x', cb); + }); + }); + + app.update('aaa.sub', ['default'], function(err) { + if (err) throw err; + console.log('done'); + }); +}; diff --git a/test/fixtures/updaters-array.json b/test/fixtures/updaters-array.json deleted file mode 100644 index 003f59c..0000000 --- a/test/fixtures/updaters-array.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "updaters": ["foo", "bar"] -} \ No newline at end of file diff --git a/test/fixtures/updaters-object.json b/test/fixtures/updaters-object.json deleted file mode 100644 index 03e075f..0000000 --- a/test/fixtures/updaters-object.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "updaters": { - "foo": {}, - "bar": {} - } -} \ No newline at end of file diff --git a/test/fixtures/updaters/a/.gitignore b/test/fixtures/updaters/a/.gitignore new file mode 100644 index 0000000..80a228c --- /dev/null +++ b/test/fixtures/updaters/a/.gitignore @@ -0,0 +1,15 @@ +*.DS_Store +*.sublime-* +_gh_pages +bower_components +node_modules +npm-debug.log +actual +test/actual +temp +tmp +TODO.md +vendor +.idea +benchmark +coverage diff --git a/test/fixtures/updaters/a/package.json b/test/fixtures/updaters/a/package.json new file mode 100644 index 0000000..b9bab76 --- /dev/null +++ b/test/fixtures/updaters/a/package.json @@ -0,0 +1,7 @@ +{ + "name": "updater-a", + "private": true, + "version": "0.1.0", + "files": ["index.js"], + "main": "updatefile.js" +} diff --git a/test/fixtures/updaters/a/post.hbs b/test/fixtures/updaters/a/post.hbs new file mode 100644 index 0000000..b2cb52b --- /dev/null +++ b/test/fixtures/updaters/a/post.hbs @@ -0,0 +1,5 @@ +--- +title: Post +--- + +This is SOME POST \ No newline at end of file diff --git a/test/fixtures/updaters/a/updatefile.js b/test/fixtures/updaters/a/updatefile.js new file mode 100644 index 0000000..0198ef6 --- /dev/null +++ b/test/fixtures/updaters/a/updatefile.js @@ -0,0 +1,8 @@ +'use strict'; + +module.exports = function(app) { + app.task('default', function(cb) { + console.log('fixtures/a > default'); + cb(); + }); +}; diff --git a/test/fixtures/updaters/a/verbfile.js b/test/fixtures/updaters/a/verbfile.js new file mode 100644 index 0000000..c3f4d75 --- /dev/null +++ b/test/fixtures/updaters/a/verbfile.js @@ -0,0 +1,10 @@ +'use strict'; + +var path = require('path'); + +module.exports = function(app) { + app.task('default', function(cb) { + console.log('fixtures/a > default'); + cb(); + }); +}; diff --git a/test/fixtures/updaters/qux/package.json b/test/fixtures/updaters/qux/package.json new file mode 100644 index 0000000..f160d17 --- /dev/null +++ b/test/fixtures/updaters/qux/package.json @@ -0,0 +1,7 @@ +{ + "name": "updater-qux", + "private": true, + "version": "0.1.0", + "files": ["updatefile.js"], + "main": "updatefile.js" +} diff --git a/test/fixtures/updaters/qux/updatefile.js b/test/fixtures/updaters/qux/updatefile.js new file mode 100644 index 0000000..887c058 --- /dev/null +++ b/test/fixtures/updaters/qux/updatefile.js @@ -0,0 +1,7 @@ +'use strict'; + +module.exports = function(app, base, env) { + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); +}; diff --git a/test/fixtures/updaters/qux/verbfile.js b/test/fixtures/updaters/qux/verbfile.js new file mode 100644 index 0000000..28979ee --- /dev/null +++ b/test/fixtures/updaters/qux/verbfile.js @@ -0,0 +1,11 @@ +'use strict'; + +/** + * testing... + */ + +module.exports = function(app, base, env) { + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); +}; diff --git a/test/runner.js b/test/runner.js new file mode 100644 index 0000000..e680047 --- /dev/null +++ b/test/runner.js @@ -0,0 +1,111 @@ +'use strict'; + +require('mocha'); +var path = require('path'); +var assert = require('assert'); +var argv = require('yargs-parser')(process.argv.slice(2)); +var runner = require('base-runner'); +var Generate = require('..'); +var base; + +var fixtures = path.resolve.bind(path, __dirname, 'fixtures'); +var config = { + name: 'foo', + runner: require(fixtures('package.json')), + configName: 'updater', + extensions: { + '.js': null + } +}; + +describe('.runner', function() { + var error = console.error; + + beforeEach(function() { + console.error = function() {}; + }); + + afterEach(function() { + console.error = error; + }); + + describe('errors', function() { + it('should throw an error when a callback is not passed', function(cb) { + try { + runner(); + cb(new Error('expected an error')); + } catch (err) { + assert.equal(err.message, 'expected a callback function'); + cb(); + } + }); + + it('should error when an options object is not passed', function(cb) { + runner(Generate, {}, null, function(err, app, runnerContext) { + assert(err); + assert.equal(err.message, 'expected the third argument to be an options object'); + cb(); + }); + }); + + it('should error when a liftoff config object is not passed', function(cb) { + runner(Generate, null, {}, function(err, app, runnerContext) { + assert(err); + assert.equal(err.message, 'expected the second argument to be a liftoff config object'); + cb(); + }); + }); + + it('should error when a Generate constructor is not passed', function(cb) { + runner(null, {}, {}, function(err, app, runnerContext) { + assert(err); + assert.equal(err.message, 'expected the first argument to be a Base constructor'); + cb(); + }); + }); + }); + + describe('runner', function() { + it('should set "env" on app.cache.runnerContext', function(cb) { + runner(Generate, config, argv, function(err, app, runnerContext) { + if (err) return cb(err); + assert(app.cache.runnerContext.env); + assert.equal(typeof app.cache.runnerContext.env, 'object'); + cb(); + }); + }); + + it('should set "config" on app.cache.runnerContext', function(cb) { + runner(Generate, config, argv, function(err, app, runnerContext) { + if (err) return cb(err); + assert(app.cache.runnerContext.config); + assert.equal(typeof app.cache.runnerContext.config, 'object'); + cb(); + }); + }); + + it('should set the configFile on app.cache.runnerContext.env', function(cb) { + runner(Generate, config, argv, function(err, app, runnerContext) { + if (err) return cb(err); + assert.equal(app.cache.runnerContext.env.configFile, 'updater.js'); + cb(); + }); + }); + + it('should set cwd on the instance', function(cb) { + runner(Generate, config, {cwd: fixtures()}, function(err, app, runnerContext) { + if (err) return cb(err); + assert.equal(app.cwd, fixtures()); + cb(); + }); + }); + + it('should resolve configpath from app.cwd and app.configFile', function(cb) { + runner(Generate, config, {cwd: fixtures()}, function(err, app, runnerContext) { + if (err) return cb(err); + assert.equal(app.cache.runnerContext.env.configPath, path.resolve(__dirname, 'fixtures/updater.js')); + cb(); + }); + }); + }); +}); diff --git a/test/support/ignore.js b/test/support/ignore.js new file mode 100644 index 0000000..7dcbb75 --- /dev/null +++ b/test/support/ignore.js @@ -0,0 +1,6 @@ +module.exports = [ + 'addEventListener', + 'removeEventListener', + 'removeAllListeners', + 'removeListener' +]; diff --git a/test/support/index.js b/test/support/index.js new file mode 100644 index 0000000..e2194a5 --- /dev/null +++ b/test/support/index.js @@ -0,0 +1,64 @@ +'use strict'; + +var path = require('path'); +var loadpkg = require('load-pkg'); +var assert = require('assert'); +var ignore = require('./ignore'); +var cache = {}; + +exports.containEql = function containEql(actual, expected) { + if (Array.isArray(expected)) { + var len = expected.length; + while (len--) { + exports.containEql(actual[len], expected[len]); + } + } else { + for (var key in expected) { + assert.deepEqual(actual[key], expected[key]); + } + } +}; + +exports.keys = function keys(obj) { + var arr = []; + for (var key in obj) { + if (ignore.indexOf(key) === -1) { + arr.push(key); + } + } + return arr; +}; + +exports.resolve = function(filepath) { + filepath = filepath || ''; + var key = 'app:' + filepath; + if (cache.hasOwnProperty(key)) { + return cache[key]; + } + + var pkg = loadpkg.sync(process.cwd()); + var prefix = pkg.name !== 'templates' + ? 'templates' + : ''; + + var base = filepath + ? path.join(prefix, filepath) + : process.cwd(); + + var fp = tryResolve(base); + + if (typeof fp === 'undefined') { + throw new Error('cannot resolve: ' + fp); + } + return (cache[key] = require(fp)); +}; + +function tryResolve(name) { + try { + return require.resolve(name); + } catch (err) {} + + try { + return require.resolve(path.resolve(name)); + } catch (err) {} +} diff --git a/test/support/spy.js b/test/support/spy.js new file mode 100644 index 0000000..b473c6e --- /dev/null +++ b/test/support/spy.js @@ -0,0 +1,31 @@ +'use strict'; + +var fs = require('graceful-fs'); +var sinon = require('sinon'); + +var errorfn = false; + +function maybeCallAsync(module, func) { + var original = module[func]; + return sinon.stub(module, func, function() { + var args = Array.prototype.slice.call(arguments); + args.unshift(module, func); + var err = typeof errorfn === 'function' && errorfn.apply(this, args); + if (!err) { + original.apply(this, arguments); + } else { + arguments[arguments.length - 1](err); + } + }); +} + +module.exports = { + setError: function(fn) { + errorfn = fn; + }, + chmodSpy: maybeCallAsync(fs, 'chmod'), + fchmodSpy: maybeCallAsync(fs, 'fchmod'), + futimesSpy: maybeCallAsync(fs, 'futimes'), + statSpy: maybeCallAsync(fs, 'stat'), + fstatSpy: maybeCallAsync(fs, 'fstat') +}; diff --git a/test/update.js b/test/update.js new file mode 100644 index 0000000..17378a4 --- /dev/null +++ b/test/update.js @@ -0,0 +1,35 @@ +'use strict'; + +require('mocha'); +var path = require('path'); +var assert = require('assert'); +var Update = require('..'); +var update; + +describe('update', function() { + describe('cwd', function() { + beforeEach(function() { + update = new Update(); + }); + + it('should get the current working directory', function() { + assert.equal(update.cwd, process.cwd()); + }); + + it('should set the current working directory', function() { + update.cwd = 'test/fixtures'; + assert.equal(update.cwd, path.join(process.cwd(), 'test/fixtures')); + }); + }); + + describe('generator', function() { + beforeEach(function() { + update = new Update(); + }); + + it('should register the default generator', function() { + update.register('default', require('./fixtures/def-gen')); + assert(update.getGenerator('default')); + }); + }); +}); diff --git a/test/updaters.env.js b/test/updaters.env.js new file mode 100644 index 0000000..f2d2e97 --- /dev/null +++ b/test/updaters.env.js @@ -0,0 +1,127 @@ +'use strict'; + +require('mocha'); +var path = require('path'); +var assert = require('assert'); +var Base = require('..'); +var base; +var env; + +var fixtures = path.resolve.bind(path, __dirname + '/fixtures'); + +describe('env', function() { + describe('plugin', function() { + it('should work as a plugin', function() { + base = new Base(); + assert.equal(typeof base.createEnv, 'function'); + }); + }); + + describe('createEnv paths', function() { + beforeEach(function() { + base = new Base(); + }); + + describe('alias and function', function() { + it('should make the alias the exact name when the second arg is a function', function() { + var fn = function() {}; + var env = base.createEnv('foo-bar-baz', fn); + assert(env); + assert(env.alias); + assert.equal(env.alias, 'foo-bar-baz'); + }); + + it('should not change the name when the second arg is a function', function() { + var fn = function() {}; + var env = base.createEnv('foo-bar-baz', fn); + assert(env); + assert(env.name); + assert.equal(env.name, 'foo-bar-baz'); + }); + }); + + describe('alias and path', function() { + it('should set the env.name using the given name', function() { + var env = base.createEnv('foo', fixtures('updater-foo/updatefile.js')); + assert.equal(env.name, 'foo'); + }); + + it('should not change the name when the second arg is a function', function() { + var fn = function() {}; + var env = base.createEnv('foo-bar-baz', fn); + assert(env); + assert(env.name); + assert.equal(env.name, 'foo-bar-baz'); + }); + }); + }); + + describe('createEnv', function() { + beforeEach(function() { + base = new Base(); + }); + + it('should add an env object to the instance', function() { + var fn = function() {}; + env = base.createEnv('foo', fn); + assert(env); + }); + + it('should take options as the second arg', function() { + var fn = function() {}; + env = base.createEnv('foo', {}, fn); + assert(env); + }); + + it('should prime `env` if it doesn\'t exist', function() { + var fn = function() {}; + env = base.createEnv('foo', {}, fn); + assert(env); + }); + + it('should add an alias to the env object', function() { + var fn = function() {}; + env = base.createEnv('foo', {}, fn); + assert.equal(env.alias, 'foo'); + }); + + it('should not prefix the alias when a function is passed', function() { + var fn = function() {}; + delete base.prefix; + env = base.createEnv('foo', {}, fn); + assert.equal(env.name, 'foo'); + }); + + it('should not prefix a custom alias when a function is passed', function() { + var fn = function() {}; + base.prefix = 'whatever'; + env = base.createEnv('foo', {}, fn); + assert.equal(env.name, 'foo'); + }); + + it('should try to resolve an absolute path passed as the second arg', function() { + env = base.createEnv('foo', fixtures('updater.js')); + assert.equal(env.alias, 'foo'); + assert.equal(env.name, 'foo'); + }); + + it('should try to resolve a relative path passed as the second arg', function() { + env = base.createEnv('foo', fixtures('updater-foo/updatefile.js')); + assert.equal(env.key, 'foo'); + assert.equal(env.alias, 'foo'); + assert.equal(env.name, 'foo'); + }); + + it('should throw an error when the path is not resolved', function(cb) { + try { + var env = base.createEnv('foo', fixtures('whatever.js')); + env.invoke(); + env.path; + cb(new Error('expected an error')); + } catch (err) { + assert.equal(err.message, 'cannot resolve: \'' + fixtures('whatever.js') + '\''); + cb(); + } + }); + }); +}); diff --git a/test/updaters.events.js b/test/updaters.events.js new file mode 100644 index 0000000..00db2ae --- /dev/null +++ b/test/updaters.events.js @@ -0,0 +1,157 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var Base = require('..'); +var base; + +var updaters = require('..'); + +describe('updaters events', function() { + describe('generator', function() { + beforeEach(function() { + base = new Base(); + base.enable('silent'); + }); + + it('should emit generator when a generator is registered', function(cb) { + base.on('generator', function(generator) { + assert.equal(generator.alias, 'foo'); + cb(); + }); + + base.register('foo', function() {}); + }); + + it('should emit generator when base.updaters.get is called', function(cb) { + + base.on('generator', function(generator) { + assert.equal(generator.alias, 'foo'); + cb(); + }); + + base.register('foo', function() {}); + base.getGenerator('foo'); + }); + + it('should emit generator.get when base.updaters.get is called', function(cb) { + base.on('generator', function(generator) { + assert.equal(generator.alias, 'foo'); + cb(); + }); + + base.register('foo', function() {}); + base.getGenerator('foo'); + }); + + it('should emit error on base when a base generator emits an error', function(cb) { + var called = 0; + + base.on('error', function(err) { + assert.equal(err.message, 'whatever'); + called++; + }); + + base.register('foo', function(app) { + app.emit('error', new Error('whatever')); + }); + + base.getGenerator('foo'); + assert.equal(called, 1); + cb(); + }); + + it('should emit error on base when a base generator throws an error', function(cb) { + var called = 0; + + base.on('error', function(err) { + assert.equal(err.message, 'whatever'); + called++; + }); + + base.register('foo', function(app) { + app.task('default', function(cb) { + cb(new Error('whatever')); + }); + }); + + base.getGenerator('foo') + .build(function(err) { + assert(err); + assert.equal(called, 1); + cb(); + }); + + }); + + it('should emit errors on base from deeply nested updaters', function(cb) { + var called = 0; + + base.on('error', function(err) { + assert.equal(err.message, 'whatever'); + called++; + }); + + base.register('a', function() { + this.register('b', function() { + this.register('c', function() { + this.register('d', function() { + this.task('default', function(cb) { + cb(new Error('whatever')); + }); + }); + }); + }); + }); + + base.getGenerator('a.b.c.d') + .build(function(err) { + assert(err); + assert.equal(called, 1); + cb(); + }); + + }); + + it('should bubble up errors to all parent updaters', function(cb) { + var called = 0; + + function count() { + called++; + } + + base.on('error', function(err) { + assert.equal(err.message, 'whatever'); + called++; + }); + + base.register('a', function() { + this.on('error', count); + + this.register('b', function() { + this.on('error', count); + + this.register('c', function() { + this.on('error', count); + + this.register('d', function() { + this.on('error', count); + + this.task('default', function(cb) { + cb(new Error('whatever')); + }); + }); + }); + }); + }); + + base.getGenerator('a.b.c.d') + .build(function(err) { + assert(err); + assert.equal(called, 6); + assert.equal(err.message, 'whatever'); + cb(); + }); + }); + }); +}); diff --git a/test/updaters.js b/test/updaters.js deleted file mode 100644 index 5d1367c..0000000 --- a/test/updaters.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; - -require('mocha'); -var assert = require('assert'); -var Update = require('..'); -var app; - -describe('updaters', function() { - -}); From 857c2133d456dc7477e7c9b26b0a5cf8c1ef2995 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 1 Jul 2016 06:43:00 -0400 Subject: [PATCH 213/274] update editorconfig --- .editorconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.editorconfig b/.editorconfig index 991900b..408d870 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,10 +13,10 @@ insert_final_newline = true trim_trailing_whitespace = false insert_final_newline = false -[test/**] +[**/{actual,fixtures,expected}/**] trim_trailing_whitespace = false insert_final_newline = false -[templates/**] +[**/templates/**] trim_trailing_whitespace = false insert_final_newline = false From e511d5b17a95ef575aa9dbb65425ebdf6b82bdf7 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 1 Jul 2016 06:43:47 -0400 Subject: [PATCH 214/274] use assemble-core, minor edits --- bin/update.js | 1 + index.js | 10 +++++++++- lib/updatefile.js | 2 +- lib/utils.js | 1 + package.json | 22 +++++++++++++++++++--- 5 files changed, 31 insertions(+), 5 deletions(-) diff --git a/bin/update.js b/bin/update.js index 011ee62..a009f13 100755 --- a/bin/update.js +++ b/bin/update.js @@ -6,6 +6,7 @@ var Update = require('..'); var commands = require('../lib/commands'); var utils = require('../lib/utils'); var argv = utils.parseArgs(process.argv.slice(2)); +var util = require('util'); /** * Listen for errors diff --git a/index.js b/index.js index 7705f99..c8affb5 100644 --- a/index.js +++ b/index.js @@ -7,7 +7,7 @@ 'use strict'; -var Base = require('base-app'); +var Base = require('assemble-core'); var resolve = require('resolve-file'); var utils = require('./lib/utils'); var cli = require('./lib/cli'); @@ -24,6 +24,9 @@ var cli = require('./lib/cli'); */ function Update(options) { + if (!(this instanceof Update)) { + return new Update(options); + } Base.call(this, options); this.is('update'); this.initUpdate(this); @@ -64,6 +67,10 @@ Update.prototype.initDefaults = function() { return this.generate.apply(this, arguments); }); + this.define('getUpdater', function() { + return this.getGenerator.apply(this, arguments); + }); + this.option('toAlias', function(name) { return name.replace(/^updater?-(.*)$/, '$1'); }); @@ -143,6 +150,7 @@ Update.prototype.addUpdaters = function(names, options) { */ Update.plugins = function(app) { + app.use(utils.generators()); app.use(utils.store('update')); app.use(utils.runtimes()); app.use(utils.questions()); diff --git a/lib/updatefile.js b/lib/updatefile.js index e476c18..5aa4905 100644 --- a/lib/updatefile.js +++ b/lib/updatefile.js @@ -48,7 +48,7 @@ module.exports = function(app, base) { file.basename = app.toAlias(file.basename); next(null, file); })) - .pipe(list()); + .pipe(list(app)); }); /** diff --git a/lib/utils.js b/lib/utils.js index 5646e7e..6ab8469 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -11,6 +11,7 @@ require = utils; // eslint-disable-line require('arr-union', 'union'); require('base-cli-process', 'cli'); require('base-config-process', 'config'); +require('base-generators', 'generators'); require('base-questions', 'questions'); require('base-runtimes', 'runtimes'); require('base-store', 'store'); diff --git a/package.json b/package.json index 6a98718..64e4f03 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "update", - "description": "Update is a developer framework and CLI for automating updates of any kind in code projects. All updating is accomplished using plugins called _updaters_, which are run by command line or API, and can be installed globally, locally, or in a local `updatefile.js`.", + "description": "Update is a new developer framework and CLI for automating updates of any kind in code projects. All updating is accomplished using plugins called _updaters_, which are run by command line or API, and can be installed globally, locally, or in a local `updatefile.js`.", "version": "0.5.3", "homepage": "https://github.com/update/update", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", @@ -29,9 +29,10 @@ }, "dependencies": { "arr-union": "^3.1.0", - "base-app": "^0.2.6", + "assemble-core": "^0.23.0", "base-cli-process": "^0.1.13", "base-config-process": "^0.1.7", + "base-generators": "^0.4.1", "base-questions": "^0.6.6", "base-runtimes": "^0.1.11", "base-store": "^0.4.4", @@ -51,13 +52,28 @@ "yargs-parser": "^2.4.0" }, "devDependencies": { + "assemble-loader": "^0.6.1", + "base-runner": "^0.8.2", + "base-test-runner": "^0.2.0", + "base-test-suite": "^0.1.11", + "cross-spawn": "^4.0.0", + "generate-foo": "^0.1.5", + "generator-util": "^0.2.9", + "graceful-fs": "^4.1.4", "gulp": "^3.9.1", "gulp-eslint": "^2.0.0", "gulp-format-md": "^0.1.9", "gulp-istanbul": "^0.10.4", "gulp-mocha": "^2.2.0", "gulp-unused": "^0.1.2", - "mocha": "^2.5.3" + "is-absolute": "^0.2.5", + "load-pkg": "^3.0.1", + "mocha": "^2.5.3", + "npm-install-global": "^0.1.2", + "resolve": "^1.1.7", + "should": "^9.0.2", + "sinon": "^1.17.4", + "updater-example": "^0.1.1" }, "keywords": [ "convention", From 625c327a5112f0ce078b468ae71dd43bd2bb247a Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 1 Jul 2016 06:44:19 -0400 Subject: [PATCH 215/274] 0.5.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 64e4f03..3e9af8a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "update", "description": "Update is a new developer framework and CLI for automating updates of any kind in code projects. All updating is accomplished using plugins called _updaters_, which are run by command line or API, and can be installed globally, locally, or in a local `updatefile.js`.", - "version": "0.5.3", + "version": "0.5.4", "homepage": "https://github.com/update/update", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", "repository": "update/update", From cb878ec49953c87918074911101393ecc9801398 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 1 Jul 2016 06:47:05 -0400 Subject: [PATCH 216/274] fix links --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index bdd7ad3..d877577 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ -# update [![NPM version](https://img.shields.io/npm/v/update.svg?style=flat)](https://www.npmjs.com/package/update) [![NPM downloads](https://img.shields.io/npm/dm/update.svg?style=flat)](https://npmjs.org/package/update) [![Build Status](https://img.shields.io/travis/jonschlinkert/update.svg?style=flat)](https://travis-ci.org/jonschlinkert/update) +# update [![NPM version](https://img.shields.io/npm/v/update.svg?style=flat)](https://www.npmjs.com/package/update) [![NPM downloads](https://img.shields.io/npm/dm/update.svg?style=flat)](https://npmjs.org/package/update) [![Build Status](https://img.shields.io/travis/update/update.svg?style=flat)](https://travis-ci.org/update/update)

- - + +

-Update is a developer framework and CLI for automating updates of any kind in code projects. All updating is accomplished using plugins called _updaters_, which are run by command line or API, and can be installed globally, locally, or in a local `updatefile.js`. +Update is a new developer framework and CLI for automating updates of any kind in code projects. All updating is accomplished using plugins called _updaters_, which are run by command line or API, and can be installed globally, locally, or in a local `updatefile.js`. ## TOC @@ -246,7 +246,7 @@ $ npm install -d && npm test ## License Copyright © 2016, [Jon Schlinkert](https://github.com/jonschlinkert). -Released under the [MIT license](https://github.com/jonschlinkert/update/blob/master/LICENSE). +Released under the [MIT license](https://github.com/update/update/blob/master/LICENSE). *** From ceb09f01086730afe30c9ed28e20fd1f9df67707 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 1 Jul 2016 06:47:17 -0400 Subject: [PATCH 217/274] 0.5.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3e9af8a..e5c1d58 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "update", "description": "Update is a new developer framework and CLI for automating updates of any kind in code projects. All updating is accomplished using plugins called _updaters_, which are run by command line or API, and can be installed globally, locally, or in a local `updatefile.js`.", - "version": "0.5.4", + "version": "0.5.5", "homepage": "https://github.com/update/update", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", "repository": "update/update", From 2f6be35d797cda73038c6b07b255038dc833f461 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 1 Jul 2016 06:54:40 -0400 Subject: [PATCH 218/274] drafts --- docs/summary.md | 119 --------------------------------------- docs/tutorial.md | 111 ------------------------------------ support/docs/summary.md | 1 + support/docs/tutorial.md | 1 + 4 files changed, 2 insertions(+), 230 deletions(-) delete mode 100644 docs/summary.md delete mode 100644 docs/tutorial.md diff --git a/docs/summary.md b/docs/summary.md deleted file mode 100644 index 74f1913..0000000 --- a/docs/summary.md +++ /dev/null @@ -1,119 +0,0 @@ -# Summary - -
-
-
-![separator](sep.png) -
-
-
- -## Introduction - -### What is update? - -{%= doc("what-is-update.md") %} -todo - -### What are updaters? - -{%= doc("what-are-updaters.md") %} -todo - -### Why use update? - -{%= doc("why-use-update.md") %} -todo - -
-
-
-![separator](sep.png) -
-
-
- -## Getting started - -### Installing the CLI - -{%= doc("intro.installing-the-cli.md") %} -todo - -### Installing updaters - -{%= doc("intro.installing-updaters.md") %} -todo - -
-
-
-![separator](sep.png) -
-
-
- -## Usage - -Update can be used by command line or API. We recommend you start out with the CLI to become familiarized with how update works before diving into the API. - -### Using tasks - -{%= doc("getting-started.using-tasks.md") %} -todo - -### Using updaters - -{%= doc("getting-started.using-updaters.md") %} -todo - -### Using plugins - -{%= doc("getting-started.using-plugins.md") %} -todo - -
-
-
-![separator](sep.png) -
-
-
- -## Docs - -### Authoring updaters - -todo - -### Publishing updaters - -todo - -### Feature highlights - -{%= doc("features.md") %} - -### FAQ - -{%= doc("faq.md") %} - -
-
-
-![separator](sep.png) -
-
-
- -## Developers - -Hacking on update. - -### Base - -todo - -### Architecture - -todo diff --git a/docs/tutorial.md b/docs/tutorial.md deleted file mode 100644 index c4a8b4e..0000000 --- a/docs/tutorial.md +++ /dev/null @@ -1,111 +0,0 @@ -# Tutorial - -The following intro only skims the surface of what update has to offer. For a more in-depth introduction, we highly recommend visiting the [getting started guide](https://github.com/taunus/getting-started). - -**Create an updater** - -Add a `updatefile.js` to the current working directory with the following code: - -```js -module.exports = function(app) { - console.log('success!'); -}; -``` - -**Run an updater** - -Enter the following command: - -```sh -update -``` - -If successful, you should see `success!` in the terminal. - -**Create a task** - -Now, add a task to your updater. - -```js -module.exports = function(app) { - app.task('default', function(cb) { - console.log('success!'); - cb(); - }); -}; -``` - -Now, in the command line, run: - -```sh -$ update -# then try -$ update default -``` - -When a local `updatefile.js` exists, the `update` command is aliased to automatically run the `default` task if one exists. But you can also run the task with `update default`. - -**Run a task** - -Let's try adding more tasks to your updater: - -```js -module.exports = function(app) { - app.task('default', function(cb) { - console.log('default > success!'); - cb(); - }); - - app.task('foo', function(cb) { - console.log('foo > success!'); - cb(); - }); - - app.task('bar', function(cb) { - console.log('bar > success!'); - cb(); - }); -}; -``` - -Now, in the command line, run: - -```sh -$ update -# then try -$ update foo -# then try -$ update foo bar -``` - -**Run task dependencies** - -Now update your code to the following: - -```js -module.exports = function(app) { - app.task('default', ['foo', 'bar']); - - app.task('foo', function(cb) { - console.log('foo > success!'); - cb(); - }); - - app.task('bar', function(cb) { - console.log('bar > success!'); - cb(); - }); -}; -``` - -And run: - -```sh -$ update -``` - -You're now a master at running tasks with update! You can do anything with update tasks that you can do with [gulp](http://gulpjs.com) tasks (we use and support gulp libraries after all!). - -**Next steps** - -Update does much more than this. For a more in-depth introduction, we highly recommend visiting the [getting started guide](https://github.com/update/getting-started). diff --git a/support/docs/summary.md b/support/docs/summary.md index f2ed123..bf082b2 100644 --- a/support/docs/summary.md +++ b/support/docs/summary.md @@ -1,5 +1,6 @@ --- title: Summary +draft: true ---

diff --git a/support/docs/tutorial.md b/support/docs/tutorial.md index 0836181..e3cdcd9 100644 --- a/support/docs/tutorial.md +++ b/support/docs/tutorial.md @@ -1,5 +1,6 @@ --- title: Tutorial +draft: true related: doc: [] --- From 2f26d6268ed0b59bb0b335038a7b1038135e94fe Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 1 Jul 2016 07:23:54 -0400 Subject: [PATCH 219/274] build docs --- .verb.md | 5 +-- README.md | 5 +-- docs/api/plugins.md | 4 --- docs/faq.md | 11 ++++-- docs/features.md | 1 - docs/installing-updaters.md | 20 ++--------- docs/nested-updaters.md | 49 ++++++++++++++++++++++++++ support/docs/api.plugins.md | 2 +- support/docs/faq.md | 12 +++++-- support/docs/features.md | 2 +- support/docs/installing-updaters.md | 20 ++--------- support/docs/nested-updaters.md | 54 +++++++++++++++++++++++++++++ 12 files changed, 132 insertions(+), 53 deletions(-) create mode 100644 docs/nested-updaters.md create mode 100644 support/docs/nested-updaters.md diff --git a/.verb.md b/.verb.md index 0bc7152..6fe8fd9 100644 --- a/.verb.md +++ b/.verb.md @@ -150,7 +150,7 @@ $ update help ## Discovering updaters -* Find updaters to install by [searching npm](https://www.npmjs.com/browse/keyword/update-updater) for packages with the keyword `update-updater` +* Find updaters to install by [searching npm](https://www.npmjs.com/browse/keyword/updateupdater) for packages with the keyword `updateupdater` * Visit [Update's GitHub org](https://github.com/update) to see the updaters maintained by the core team ## Discovering plugins @@ -161,6 +161,7 @@ Plugins from any applications built on [base][] should work with Update (and can * [assemble][assemble-plugin]: find assemble plugins on npm using the `assembleplugin` keyword * [generate][generate-plugin]: find generate plugins on npm using the `generateplugin` keyword * [templates][templates-plugin]: find templates plugins on npm using the `templatesplugin` keyword +* [update][update-plugin]: find update plugins on npm using the `updateplugin` keyword * [verb][verb-plugin]: find verb plugins on npm using the `verbplugin` keyword ## Authoring updaters @@ -180,7 +181,7 @@ Are you using Update in your project? Have you published an [updater](docs/updat * If you get like Update and want to tweet about it, please use the hashtag `#updatejs` * Get implementation help on [StackOverflow](http://stackoverflow.com/questions/tagged/update) (pluse use the `update` tag in questions) * **Gitter** Discuss Update with us on [Gitter](https://gitter.im/update) -* If you publish an updater, thank you! To make your project as discoverable as possible, please add the keyword `update-updater` to package.json. +* If you publish an updater, thank you! To make your project as discoverable as possible, please add the keyword `updateupdater` to package.json. [getting-started]: https://github.com/update/getting-started [base-plugin]: https://www.npmjs.com/browse/keyword/baseplugin diff --git a/README.md b/README.md index d877577..9fc3415 100644 --- a/README.md +++ b/README.md @@ -181,7 +181,7 @@ $ update help ## Discovering updaters -* Find updaters to install by [searching npm](https://www.npmjs.com/browse/keyword/update-updater) for packages with the keyword `update-updater` +* Find updaters to install by [searching npm](https://www.npmjs.com/browse/keyword/updateupdater) for packages with the keyword `updateupdater` * Visit [Update's GitHub org](https://github.com/update) to see the updaters maintained by the core team ## Discovering plugins @@ -192,6 +192,7 @@ Plugins from any applications built on [base](https://github.com/node-base/base) * [assemble](https://www.npmjs.com/browse/keyword/assembleplugin): find assemble plugins on npm using the `assembleplugin` keyword * [generate](https://www.npmjs.com/browse/keyword/generateplugin): find generate plugins on npm using the `generateplugin` keyword * [templates](https://www.npmjs.com/browse/keyword/templatesplugin): find templates plugins on npm using the `templatesplugin` keyword +* [update][update-plugin]: find update plugins on npm using the `updateplugin` keyword * [verb](https://www.npmjs.com/browse/keyword/verbplugin): find verb plugins on npm using the `verbplugin` keyword ## Authoring updaters @@ -211,7 +212,7 @@ Are you using Update in your project? Have you published an [updater](docs/updat * If you get like Update and want to tweet about it, please use the hashtag `#updatejs` * Get implementation help on [StackOverflow](http://stackoverflow.com/questions/tagged/update) (pluse use the `update` tag in questions) * **Gitter** Discuss Update with us on [Gitter](https://gitter.im/update) -* If you publish an updater, thank you! To make your project as discoverable as possible, please add the keyword `update-updater` to package.json. +* If you publish an updater, thank you! To make your project as discoverable as possible, please add the keyword `updateupdater` to package.json. ## Related projects diff --git a/docs/api/plugins.md b/docs/api/plugins.md index 0a8e47d..32e22f5 100644 --- a/docs/api/plugins.md +++ b/docs/api/plugins.md @@ -59,10 +59,6 @@ When plugins are [registered by name](docs/updaters.md), they are referred to as ## Related -**Docs** - -* [updaters](../updaters.md) - **API** * [updater](api/updater.md) diff --git a/docs/faq.md b/docs/faq.md index dd6b30f..64354ac 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -1,12 +1,18 @@ # Faq +## How is the name written? + +Update, with a capital "U", the rest lowercase. `updatefile.js` is all lowercase, no capital letters. + -**What's an alias, and what do they do?** +## Aliases + +**What's an updater's alias, and what do they do?** Update tries to find globally installed updaters using an "alias" first, falling back on the updater's full name if not found by its alias. -A updater's alias is created by stripping the substring `update-` from the _full name_ of updater. Thus, when publishing a updater the naming convention `update-foo` should be used (where `foo` is the alias, and `update-foo` is the full name). +A updater's alias is created by stripping the substring `updater-` from the _full name_ of updater. Thus, when publishing a updater the naming convention `updater-foo` should be used (where `foo` is the alias, and `updater-foo` is the full name). Note that **no dots may be used in published updater names**. Aside from that, any characters considered valid by npm are fine. @@ -15,4 +21,3 @@ Note that **no dots may be used in published updater names**. Aside from that, a **Docs** * [features](features.md) -* [introduction](introduction.md) diff --git a/docs/features.md b/docs/features.md index f8424cb..a3d4623 100644 --- a/docs/features.md +++ b/docs/features.md @@ -18,4 +18,3 @@ Visit the [getting started guide](https://github.com/taunus/getting-started) to **Docs** * [faq](faq.md) -* [introduction](introduction.md) diff --git a/docs/installing-updaters.md b/docs/installing-updaters.md index e97f148..bd631bd 100644 --- a/docs/installing-updaters.md +++ b/docs/installing-updaters.md @@ -1,24 +1,8 @@ # Installing updaters -Updaters are responsible for all of the "updating" that happens in update. You can find [updaters to install](#updaters to install) on npm, or create your own. +Updaters are responsible for all of the "updating" that happens in update. You can find updaters to install by [searching npm](https://www.npmjs.com/browse/keyword/updateupdater) for packages that have the keyword `updateupdater`. -If you author and publish a updater, please name your updater using the pattern `updater-foo`, where `foo` is the `alias` to be passed to Update's CLI for executing your updater (example: `update foo` would run `updater-foo`). - -_(Before proceding, please keep in mind that update's job is to make changes to files that are often neglected. You will get used to this quickly, but to prevent surprises always make sure your work is committed before running `update`)_ - -Let's install an updater so you can see how this works. - -```sh -$ npm install --global updater-license -``` - -_(The `license` updater will update the copyright statement your `LICENSE` or `LICENSE-MIT` file. If you don't use MIT, that's okay. This is just an example intended to give you the gist of running update. Just be sure to `git commit` first so you can revert any changes afterwards.)_ - -If `updater-license` installed successfully, you should now be able to run it with the following command: - -```sh -$ update license -``` +TODO ## Related diff --git a/docs/nested-updaters.md b/docs/nested-updaters.md new file mode 100644 index 0000000..a90a5f2 --- /dev/null +++ b/docs/nested-updaters.md @@ -0,0 +1,49 @@ +# Nested updaters + +Updaters provide a convenient way of wrapping code that should be executed on-demand, whilst also "namespacing" the code being wrapped, and making it available to be executed using a consistent and intuitive syntax by either CLI or API. + +**Example** + +The following updater: + +```js + +``` + +## Pre-requisites + +* [plugins](api/plugins.md) +* [updaters](updaters.md) + +## Sub-updaters + +As with [plugins](api/plugins.md), updaters may be nested: _any updater can register other updaters, and any updater can be registered by other updaters._ We refer to nested updaters as **sub-updaters**. + +**Example** + +```js +app.register('foo', function(foo) { + // do udpater stuff + this.register('bar', function(bar) { + // do udpater stuff + this.register('baz', function(baz) { + // do udpater stuff + this.task('default', function(cb) { + console.log(baz.namespace); + cb(); + }); + }); + }); +}); +``` + +## Run nested updaters + +Use dot-notation to get the updater you wish to run: + +```js +app.update('foo.bar.baz', function(err) { + if (err) return console.log(err); + +}); +``` diff --git a/support/docs/api.plugins.md b/support/docs/api.plugins.md index 3ac6b67..07357d1 100644 --- a/support/docs/api.plugins.md +++ b/support/docs/api.plugins.md @@ -2,7 +2,7 @@ title: Plugins related: api: ['updater', 'register'] - doc: ['updaters'] + doc: [] --- A plugin is function that takes an instance of `Update` and is registered with the `.use` method. See the [base-plugins][] documentation for additional details. diff --git a/support/docs/faq.md b/support/docs/faq.md index b7e8eb4..d1777d1 100644 --- a/support/docs/faq.md +++ b/support/docs/faq.md @@ -1,15 +1,21 @@ --- title: Faq related: - doc: ['features', 'introduction'] + doc: ['features'] --- +## How is the name written? + +Update, with a capital "U", the rest lowercase. `updatefile.js` is all lowercase, no capital letters. + + +## Aliases -**What's an alias, and what do they do?** +**What's an updater's alias, and what do they do?** Update tries to find globally installed updaters using an "alias" first, falling back on the updater's full name if not found by its alias. -A updater's alias is created by stripping the substring `update-` from the _full name_ of updater. Thus, when publishing a updater the naming convention `update-foo` should be used (where `foo` is the alias, and `update-foo` is the full name). +A updater's alias is created by stripping the substring `updater-` from the _full name_ of updater. Thus, when publishing a updater the naming convention `updater-foo` should be used (where `foo` is the alias, and `updater-foo` is the full name). Note that **no dots may be used in published updater names**. Aside from that, any characters considered valid by npm are fine. \ No newline at end of file diff --git a/support/docs/features.md b/support/docs/features.md index 422e4b3..d61268b 100644 --- a/support/docs/features.md +++ b/support/docs/features.md @@ -1,7 +1,7 @@ --- title: Features related: - doc: ['faq', 'introduction'] + doc: ['faq'] --- Update offers an elegant and robust suite of methods, carefully organized to help you accomplish common activities in less time, including: diff --git a/support/docs/installing-updaters.md b/support/docs/installing-updaters.md index f3051f2..e8604d5 100644 --- a/support/docs/installing-updaters.md +++ b/support/docs/installing-updaters.md @@ -4,22 +4,6 @@ related: doc: ['installing-the-cli'] --- -Updaters are responsible for all of the "updating" that happens in update. You can find [updaters to install](#updaters to install) on npm, or create your own. +Updaters are responsible for all of the "updating" that happens in update. You can find updaters to install by [searching npm](https://www.npmjs.com/browse/keyword/updateupdater) for packages that have the keyword `updateupdater`. -If you author and publish a updater, please name your updater using the pattern `updater-foo`, where `foo` is the `alias` to be passed to Update's CLI for executing your updater (example: `update foo` would run `updater-foo`). - -_(Before proceding, please keep in mind that update's job is to make changes to files that are often neglected. You will get used to this quickly, but to prevent surprises always make sure your work is committed before running `update`)_ - -Let's install an updater so you can see how this works. - -```sh -$ npm install --global updater-license -``` - -_(The `license` updater will update the copyright statement your `LICENSE` or `LICENSE-MIT` file. If you don't use MIT, that's okay. This is just an example intended to give you the gist of running update. Just be sure to `git commit` first so you can revert any changes afterwards.)_ - -If `updater-license` installed successfully, you should now be able to run it with the following command: - -```sh -$ update license -``` \ No newline at end of file +TODO \ No newline at end of file diff --git a/support/docs/nested-updaters.md b/support/docs/nested-updaters.md new file mode 100644 index 0000000..c4b0ce5 --- /dev/null +++ b/support/docs/nested-updaters.md @@ -0,0 +1,54 @@ +--- +title: Nested updaters +--- + +Updaters provide a convenient way of wrapping code that should be executed on-demand, whilst also "namespacing" the code being wrapped, and making it available to be executed using a consistent and intuitive syntax by either CLI or API. + +**Example** + +The following updater: + +```js + + +``` + + + +## Pre-requisites + +- [plugins](api/plugins.md) +- [updaters](updaters.md) + +## Sub-updaters + +As with [plugins](api/plugins.md), updaters may be nested: _any updater can register other updaters, and any updater can be registered by other updaters._ We refer to nested updaters as **sub-updaters**. + +**Example** + +```js +app.register('foo', function(foo) { + // do udpater stuff + this.register('bar', function(bar) { + // do udpater stuff + this.register('baz', function(baz) { + // do udpater stuff + this.task('default', function(cb) { + console.log(baz.namespace); + cb(); + }); + }); + }); +}); +``` + +## Run nested updaters + +Use dot-notation to get the updater you wish to run: + +```js +app.update('foo.bar.baz', function(err) { + if (err) return console.log(err); + +}); +``` From 04e8d07bea5a999333011a106e6c2a811eb5b0c1 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 1 Jul 2016 07:24:06 -0400 Subject: [PATCH 220/274] ignore `issues` links in linter --- support/lib/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/support/lib/utils.js b/support/lib/utils.js index 47d415d..a9b8fa2 100644 --- a/support/lib/utils.js +++ b/support/lib/utils.js @@ -63,7 +63,7 @@ utils.links = function(rules) { if (typeof group === 'undefined') { throw new Error('directory group: ' + seg + ' is not defined'); } - if (group.indexOf(rest) === -1) { + if (group.indexOf(rest) === -1 && !/issues/.test(rest)) { throw new Error(`cannot find filepath: ${rest} in "${seg}" (${file.path})`); } } From 39d8bd8430ecc4df8596ce423dbe591420b574d1 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 1 Jul 2016 07:25:39 -0400 Subject: [PATCH 221/274] update gitignore --- .gitignore | 2 +- support/docs/summary.md | 103 ---------------------------------------- 2 files changed, 1 insertion(+), 104 deletions(-) delete mode 100644 support/docs/summary.md diff --git a/.gitignore b/.gitignore index 8dc5b44..752f934 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,4 @@ vendor benchmark coverage _notes.md -_draft +_drafts diff --git a/support/docs/summary.md b/support/docs/summary.md deleted file mode 100644 index bf082b2..0000000 --- a/support/docs/summary.md +++ /dev/null @@ -1,103 +0,0 @@ ---- -title: Summary -draft: true ---- -
-
-
-![separator](sep.png) -
-
-
- -## Introduction -### What is update? -{%%= doc("what-is-update.md") %} -todo - -### What are updaters? -{%%= doc("what-are-updaters.md") %} -todo - -### Why use update? -{%%= doc("why-use-update.md") %} -todo - -
-
-
-![separator](sep.png) -
-
-
- -## Getting started -### Installing the CLI -{%%= doc("intro.installing-the-cli.md") %} -todo - -### Installing updaters -{%%= doc("intro.installing-updaters.md") %} -todo - -
-
-
-![separator](sep.png) -
-
-
- -## Usage -Update can be used by command line or API. We recommend you start out with the CLI to become familiarized with how update works before diving into the API. - -### Using tasks -{%%= doc("getting-started.using-tasks.md") %} -todo - -### Using updaters -{%%= doc("getting-started.using-updaters.md") %} -todo - -### Using plugins -{%%= doc("getting-started.using-plugins.md") %} -todo - -
-
-
-![separator](sep.png) -
-
-
- -## Docs - -### Authoring updaters -todo - -### Publishing updaters -todo - -### Feature highlights -{%%= doc("features.md") %} - -### FAQ -{%%= doc("faq.md") %} - -
-
-
-![separator](sep.png) -
-
-
- -## Developers -Hacking on update. - -### Base -todo - -### Architecture -todo From d70092efd8e415f1ecf8f6b437458383c11ea594 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 1 Jul 2016 07:33:15 -0400 Subject: [PATCH 222/274] todos --- docs/nested-updaters.md | 8 -------- support/docs/nested-updaters.md | 11 +++++------ support/docs/updaters.md | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/docs/nested-updaters.md b/docs/nested-updaters.md index a90a5f2..1f689b2 100644 --- a/docs/nested-updaters.md +++ b/docs/nested-updaters.md @@ -2,14 +2,6 @@ Updaters provide a convenient way of wrapping code that should be executed on-demand, whilst also "namespacing" the code being wrapped, and making it available to be executed using a consistent and intuitive syntax by either CLI or API. -**Example** - -The following updater: - -```js - -``` - ## Pre-requisites * [plugins](api/plugins.md) diff --git a/support/docs/nested-updaters.md b/support/docs/nested-updaters.md index c4b0ce5..f140e85 100644 --- a/support/docs/nested-updaters.md +++ b/support/docs/nested-updaters.md @@ -4,16 +4,15 @@ title: Nested updaters Updaters provide a convenient way of wrapping code that should be executed on-demand, whilst also "namespacing" the code being wrapped, and making it available to be executed using a consistent and intuitive syntax by either CLI or API. -**Example** - -The following updater: -```js +TBC... -``` - +## TODO +- [ ] explain how nested updaters work +- [ ] command line syntax +- [ ] API syntax ## Pre-requisites diff --git a/support/docs/updaters.md b/support/docs/updaters.md index 26178ac..64865e0 100644 --- a/support/docs/updaters.md +++ b/support/docs/updaters.md @@ -8,6 +8,13 @@ This document describes how to create, register and run updaters. +## TODO + +- [ ] document updater args +- [ ] explain how the `base` instance works +- [ ] document `env` + + ## What is an updater? Updaters are [plugins](api/plugins.md) that are registered by name. If you're not familiar with plugins yet, it might be a good idea to review the [plugins docs](api/plugins.md) first. @@ -33,8 +40,16 @@ function updater(app) { } ``` + + ## Registering updaters +Register an updater function with the given `name`. + +```js +app.register(name, fn); +``` + Updaters may be registered using either of the following methods: * `.register`: if the plugin should not be invoked until it's called by `.update` (stays lazy while it's cached, this is preferred) From 8d0995b018838d066f05ad4145a6f507d1718168 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 1 Jul 2016 08:02:40 -0400 Subject: [PATCH 223/274] tweak descriptions --- .verb.md | 20 +++++--------------- README.md | 22 ++++++---------------- package.json | 2 +- 3 files changed, 12 insertions(+), 32 deletions(-) diff --git a/.verb.md b/.verb.md index 6fe8fd9..16dbfd1 100644 --- a/.verb.md +++ b/.verb.md @@ -2,6 +2,8 @@ Please read our [contributing guide](.github/contributing.md) if you'd like to l ## What does it do? +All updating is accomplished using plugins called _updaters_, which are run by command line or API, and can be installed globally, locally, or created in a local `updatefile.js`. + You can create your own [updaters](docs/updaters.md) using Update's API, or install updaters using [npm](https://www.npmjs.com/), to do things like: * enforce conventions across all of your projects @@ -16,21 +18,9 @@ You can create your own [updaters](docs/updaters.md) using Update's API, or inst ## Why update? -**Shared plugin ecosystem** - -Update is built on [base][], a developer framework and API for rapidly building high quality node.js applications, using plugins like building blocks. This means that, in addition to plugins created just for Update, plugins from any [other Base application](#in-the-wild) can be used to add functionality to your updaters as well. - -**Learn one, learn them all** - -Moreover, once you learn how to use Update, or any other Base application, you will have at least a firm understanding of how to use the others, if not a good head start. - -**Plays well with others** - -Update is valueable by itself and can be used completely standalone. But it's even better when used with complementary frameworks that address other parts of the software development lifecycle, such as: - -* [generate][]: create projects -* [assemble][]: build projects -* [verb][]: document projects +- **be more productive**: Update makes you more productive by eliminating time spent on things that _can be automated_, but typically aren't since they don't need to be done often, don't fit into the build cycle or a project's deliverables, or they're usually updated by hand. As code projects mature, time spent on these things tend to stay linear or increase as the size of a community grows. And this only compounds which each new project under your stewardship. +- **your way, instantly**: updaters can be published to and installed from npm, but you can also easily create your own [personal updaters](docs/symlinking-updaters.md). Once you're updaters are setup, projects under your maintenance will convert to the the conventions you prefer in milliseconds. +- **plugin ecosystem**: any plugins that work with [Base applications](#discovering-plugins) will work with Update. Which means you can use [assemble][], [verb][], and [generate][] plugins, to name a few. ## Who should use Update? diff --git a/README.md b/README.md index 9fc3415..2e4ef3d 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@

-Update is a new developer framework and CLI for automating updates of any kind in code projects. All updating is accomplished using plugins called _updaters_, which are run by command line or API, and can be installed globally, locally, or in a local `updatefile.js`. +Be scalable. Update is a new developer framework and CLI for automating updates of any kind in code projects. ## TOC @@ -33,6 +33,8 @@ Please read our [contributing guide](.github/contributing.md) if you'd like to l ## What does it do? +All updating is accomplished using plugins called _updaters_, which are run by command line or API, and can be installed globally, locally, or created in a local `updatefile.js`. + You can create your own [updaters](docs/updaters.md) using Update's API, or install updaters using [npm](https://www.npmjs.com/), to do things like: * enforce conventions across all of your projects @@ -47,21 +49,9 @@ You can create your own [updaters](docs/updaters.md) using Update's API, or inst ## Why update? -**Shared plugin ecosystem** - -Update is built on [base](https://github.com/node-base/base), a developer framework and API for rapidly building high quality node.js applications, using plugins like building blocks. This means that, in addition to plugins created just for Update, plugins from any [other Base application](#in-the-wild) can be used to add functionality to your updaters as well. - -**Learn one, learn them all** - -Moreover, once you learn how to use Update, or any other Base application, you will have at least a firm understanding of how to use the others, if not a good head start. - -**Plays well with others** - -Update is valueable by itself and can be used completely standalone. But it's even better when used with complementary frameworks that address other parts of the software development lifecycle, such as: - -* [generate](https://github.com/generate/generate): create projects -* [assemble](https://github.com/assemble/assemble): build projects -* [verb](https://github.com/verbose/verb): document projects +* **be more productive**: Update makes you more productive by eliminating time spent on things that _can be automated_, but typically aren't since they don't need to be done often, don't fit into the build cycle or a project's deliverables, or they're usually updated by hand. As code projects mature, time spent on these things tend to stay linear or increase as the size of a community grows. And this only compounds which each new project under your stewardship. +* **your way, instantly**: updaters can be published to and installed from npm, but you can also easily create your own [personal updaters](docs/symlinking-updaters.md). Once you're updaters are setup, projects under your maintenance will convert to the the conventions you prefer in milliseconds. +* **plugin ecosystem**: any plugins that work with [Base applications](#discovering-plugins) will work with Update. Which means you can use [assemble](https://github.com/assemble/assemble), [verb](https://github.com/verbose/verb), and [generate](https://github.com/generate/generate) plugins, to name a few. ## Who should use Update? diff --git a/package.json b/package.json index e5c1d58..6892578 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "update", - "description": "Update is a new developer framework and CLI for automating updates of any kind in code projects. All updating is accomplished using plugins called _updaters_, which are run by command line or API, and can be installed globally, locally, or in a local `updatefile.js`.", + "description": "Be scalable. Update is a new developer framework and CLI for automating updates of any kind in code projects.", "version": "0.5.5", "homepage": "https://github.com/update/update", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", From ac0e1d9683e43f4ccc0f536d7d04b7a7b45ec729 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 1 Jul 2016 10:44:22 -0400 Subject: [PATCH 224/274] remove notes --- support/docs/_drafts/notes.md | 54 ----------------------------------- 1 file changed, 54 deletions(-) delete mode 100644 support/docs/_drafts/notes.md diff --git a/support/docs/_drafts/notes.md b/support/docs/_drafts/notes.md deleted file mode 100644 index a2eca69..0000000 --- a/support/docs/_drafts/notes.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -title: Notes -related: - doc: [] ---- - -Visit the [getting started guide][getting-started] to learn more. - -Not all generators are created for rendering templates and writing files to the file system. Some generators are created to handle a specific part of the build workflow. For example, [generate-dest][] does one specific thing: when `gen dest` is run in the command line, it will prompt you for the destination directory to use for any generated files. So you can run `gen dest foo` to set the destination directory for files written by `generator-foo`. You can run a generator more than once in the same command, so it's also possible to do: `gen dest foo dest bar`, if both `foo` and `bar` require different destinations. - -Generators can be stacked, chained, and nested, making it possible and easy to - -build-system features - -Powered by some of the same libraries used in [gulp][] and [assemble][], Generate was built from the ground up to kick ass at generating projects. - -maintain a separation of concerns between configuration and application logic. - -use of [generators](#generators) and [tasks](#tasks) - -## tldr - -Install `update` globally with the following command: - -```js -$ npm install --global update -``` - -Update is installed globally and run with the `update` command. - -- download -- git commit! -- run `update` - -```js -var updater = require('{%= name %}'); -``` - -**Example** - -This example shows two updaters working together seamlessly. - -![Example of how to update a gulpfile.js](demo.gif) - -**Want to know more?** - -You can use update from the command line, or as a node.js library as a part of your own application. - -- Jump to [feature highlights](#feature-highlights) -- Visit the [getting started guide][getting-started] - -Continue on, and start updating! - -Using a combination of through the use of [updaters]() and [tasks](). intuitive CLI, and a powerful and expressive API, Update is easy to learn, and enjoyable to use. \ No newline at end of file From 49c8115357e9d5a9008891f3d10d3c86c335600c Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 1 Jul 2016 10:44:29 -0400 Subject: [PATCH 225/274] run update --- .editorconfig | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/.editorconfig b/.editorconfig index 408d870..818e072 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,4 +1,3 @@ -# http://editorconfig.org root = true [*] @@ -9,14 +8,6 @@ indent_size = 2 trim_trailing_whitespace = true insert_final_newline = true -[*.md] +[{**/{actual,fixtures,expected,templates}/**,*.md}] trim_trailing_whitespace = false -insert_final_newline = false - -[**/{actual,fixtures,expected}/**] -trim_trailing_whitespace = false -insert_final_newline = false - -[**/templates/**] -trim_trailing_whitespace = false -insert_final_newline = false +insert_final_newline = false \ No newline at end of file From 276715152298d70389ebc83772e2cc74231a6bf2 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 1 Jul 2016 10:45:30 -0400 Subject: [PATCH 226/274] ensure package.json exists, load defaults in order --- lib/cli.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/cli.js b/lib/cli.js index a017a44..20b41a8 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -28,7 +28,7 @@ module.exports = function(Update, config, cb) { */ var pkgPath = find.sync(process.cwd()); - var cwd = path.dirname(pkgPath); + var cwd = pkgPath ? path.dirname(pkgPath) : process.cwd(); /** * Set `cwd` @@ -71,8 +71,12 @@ module.exports = function(Update, config, cb) { logger(log.timestamp, 'using file', log.green('~' + updatefile)); var val = require(updatefile); if (typeof val === 'function') { - base.use(fn); - base.use(val); + function updater() { + if (!utils.isValid(this, 'default-updater')) return; + this.use(fn); + this.use(val); + } + base.register('default', updater); } else if (val && val.isApp) { base = val; } From 9c2fe61bf795524581105482314724df5d38e8c1 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Fri, 1 Jul 2016 10:46:08 -0400 Subject: [PATCH 227/274] rebuild docs, fix typos --- .verb.md | 4 ++-- README.md | 6 +++--- package.json | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.verb.md b/.verb.md index 16dbfd1..4ab394b 100644 --- a/.verb.md +++ b/.verb.md @@ -166,10 +166,10 @@ Visit the [updater documentation](docs/updaters.md) guide to learn how to use, a ## Community -Are you using Update in your project? Have you published an [updater](docs/updaters.md)? Want to share your Update project with the world? Here are some suggestions: +Are you using Update in your project? Have you published an [updater](docs/updaters.md) and want to share your Update project with the world? Here are some suggestions: * If you get like Update and want to tweet about it, please use the hashtag `#updatejs` -* Get implementation help on [StackOverflow](http://stackoverflow.com/questions/tagged/update) (pluse use the `update` tag in questions) +* Get implementation help on [StackOverflow](http://stackoverflow.com/questions/tagged/update) (please use the `update` tag in questions) * **Gitter** Discuss Update with us on [Gitter](https://gitter.im/update) * If you publish an updater, thank you! To make your project as discoverable as possible, please add the keyword `updateupdater` to package.json. diff --git a/README.md b/README.md index 2e4ef3d..9d8e9de 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@

-Be scalable. Update is a new developer framework and CLI for automating updates of any kind in code projects. +Be scalable! Update is a new, open source developer framework and CLI for automating updates of any kind in code projects. ## TOC @@ -197,10 +197,10 @@ Visit the [updater documentation](docs/updaters.md) guide to learn how to use, a ## Community -Are you using Update in your project? Have you published an [updater](docs/updaters.md)? Want to share your Update project with the world? Here are some suggestions: +Are you using Update in your project? Have you published an [updater](docs/updaters.md) and want to share your Update project with the world? Here are some suggestions: * If you get like Update and want to tweet about it, please use the hashtag `#updatejs` -* Get implementation help on [StackOverflow](http://stackoverflow.com/questions/tagged/update) (pluse use the `update` tag in questions) +* Get implementation help on [StackOverflow](http://stackoverflow.com/questions/tagged/update) (please use the `update` tag in questions) * **Gitter** Discuss Update with us on [Gitter](https://gitter.im/update) * If you publish an updater, thank you! To make your project as discoverable as possible, please add the keyword `updateupdater` to package.json. diff --git a/package.json b/package.json index 6892578..7959f21 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "update", - "description": "Be scalable. Update is a new developer framework and CLI for automating updates of any kind in code projects.", + "description": "Be scalable! Update is a new, open source developer framework and CLI for automating updates of any kind in code projects.", "version": "0.5.5", "homepage": "https://github.com/update/update", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", From 521a6ccb418b5e3e1fbe6956c0f6a18cbe322634 Mon Sep 17 00:00:00 2001 From: Brian Woodward Date: Fri, 1 Jul 2016 17:10:56 -0400 Subject: [PATCH 228/274] lint --- support/lib/helpers.js | 1 - 1 file changed, 1 deletion(-) diff --git a/support/lib/helpers.js b/support/lib/helpers.js index d5e256c..615096b 100644 --- a/support/lib/helpers.js +++ b/support/lib/helpers.js @@ -48,7 +48,6 @@ module.exports = function(options) { function createLink(dir, prop, link) { var key = (prop === 'doc') ? 'docs' : prop; - var filepath = name; var name = link; var anchor = ''; var segs = link.split('#'); From 2e47cd86fe13c87e01a21910bfc6225e782968f0 Mon Sep 17 00:00:00 2001 From: Brian Woodward Date: Fri, 1 Jul 2016 17:11:49 -0400 Subject: [PATCH 229/274] typos, links, content updates fixing some small typos and fixing some links adding some content --- docs/api/plugins.md | 2 +- docs/faq.md | 2 +- docs/features.md | 12 +++--- docs/installing-updaters.md | 2 +- docs/nested-updaters.md | 14 +++++-- docs/symlinking-updaters.md | 6 +-- docs/tasks.md | 9 +++-- docs/updatefile.md | 4 +- docs/updaters.md | 57 +++++++++++++++++++++----- support/docs/api.plugins.md | 2 +- support/docs/cli.built-in-updaters.md | 2 +- support/docs/cli.commands.md | 2 +- support/docs/faq.md | 4 +- support/docs/features.md | 14 ++++--- support/docs/installing-updaters.md | 4 +- support/docs/nested-updaters.md | 6 +-- support/docs/symlinking-updaters.md | 10 ++--- support/docs/tasks.md | 9 +++-- support/docs/updatefile.md | 12 +++--- support/docs/updaters.md | 58 ++++++++++++++++++--------- 20 files changed, 152 insertions(+), 79 deletions(-) diff --git a/docs/api/plugins.md b/docs/api/plugins.md index 32e22f5..e3f5270 100644 --- a/docs/api/plugins.md +++ b/docs/api/plugins.md @@ -49,7 +49,7 @@ app.run(obj); Additionally: * If `obj` has a `.use` method, it will be used on each plugin (e.g. `obj.use(fn)`). Otherwise `fn(obj)`. -* If the plugin returns a function again and `obj` has a `.run` method, the plugin will be pushed onto the `obj.fns` array +* If the plugin returns a function again and `obj` has a `.run` method, the plugin will be pushed onto the `obj.fns` array. This can continue indefinitely as long as the plugin returns a function and the receiving object has `.use`/`.run` functions. diff --git a/docs/faq.md b/docs/faq.md index 64354ac..edb8ea8 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -12,7 +12,7 @@ Update, with a capital "U", the rest lowercase. `updatefile.js` is all lowercase Update tries to find globally installed updaters using an "alias" first, falling back on the updater's full name if not found by its alias. -A updater's alias is created by stripping the substring `updater-` from the _full name_ of updater. Thus, when publishing a updater the naming convention `updater-foo` should be used (where `foo` is the alias, and `updater-foo` is the full name). +An updater's alias is created by stripping the substring `updater-` from the _full name_ of updater. Thus, when publishing an updater the naming convention `updater-foo` should be used (where `foo` is the alias, and `updater-foo` is the full name). Note that **no dots may be used in published updater names**. Aside from that, any characters considered valid by npm are fine. diff --git a/docs/features.md b/docs/features.md index a3d4623..60e9e14 100644 --- a/docs/features.md +++ b/docs/features.md @@ -2,16 +2,16 @@ Update offers an elegant and robust suite of methods, carefully organized to help you accomplish common activities in less time, including: -* **unparalleled flow control**: through the use of [updaters](https://github.com/taunus/getting-started), [sub-updaters](https://github.com/taunus/getting-started) and [tasks](https://github.com/taunus/getting-started) -* **templates, scaffolds and boilerplates**: update a single file, initialize an entire project, or provide ad-hoc "components" throughout the duration of a project using any combination of [templates, scaffolds and boilerplates](#templates-scaffolds-and-boilerplates). +* **unparalleled flow control**: through the use of [updaters](https://github.com/update/getting-started), [sub-updaters](https://github.com/update/getting-started) and [tasks](https://github.com/update/getting-started) +* **templates, scaffolds and boilerplates**: update a single file, initialize an entire project, or provide ad-hoc "components" throughout the duration of a project using any combination of [templates, scaffolds and boilerplates](#templates-scaffolds-and-boilerplates) * **any engine**: use any template engine to render templates, including [handlebars](http://www.handlebarsjs.com/), [lodash](https://lodash.com/), [swig](https://github.com/paularmstrong/swig) and [pug](http://jade-lang.com) -* **prompts**: asks you for data when it can't find what it needs, and it's easy to customize prompts for any data you want. -* **data**: gathers data from the user's environment to populate "hints" in user prompts and render templates +* **prompts**: asks you for data when it can't find what it needs, and it's easy to customize prompts for any data you want +* **data**: gathers data from the user's environment to populate "hints" in user prompts and to use when rendering templates * **streams**: interact with the file system, with full support for [gulp](http://gulpjs.com) and [assemble](https://github.com/assemble/assemble) plugins * **smart plugins**: Update is built on [base](https://github.com/node-base/base), so any "smart" plugin can be used -* **stores**: persist configuration settings, global defaults, project-specific defaults, answers to prompts, and so on. +* **stores**: persist configuration settings, global defaults, project-specific defaults, answers to prompts, and so on -Visit the [getting started guide](https://github.com/taunus/getting-started) to learn more. +Visit the [getting started guide](https://github.com/update/getting-started) to learn more. ## Related diff --git a/docs/installing-updaters.md b/docs/installing-updaters.md index bd631bd..450bb6a 100644 --- a/docs/installing-updaters.md +++ b/docs/installing-updaters.md @@ -1,6 +1,6 @@ # Installing updaters -Updaters are responsible for all of the "updating" that happens in update. You can find updaters to install by [searching npm](https://www.npmjs.com/browse/keyword/updateupdater) for packages that have the keyword `updateupdater`. +Updaters are responsible for all of the "updating" that happens in update. You can find updaters to install by [searching npm](https://www.npmjs.com/browse/keyword/update-updater) for packages that have the keyword `update-updater`. TODO diff --git a/docs/nested-updaters.md b/docs/nested-updaters.md index 1f689b2..ff87484 100644 --- a/docs/nested-updaters.md +++ b/docs/nested-updaters.md @@ -2,6 +2,14 @@ Updaters provide a convenient way of wrapping code that should be executed on-demand, whilst also "namespacing" the code being wrapped, and making it available to be executed using a consistent and intuitive syntax by either CLI or API. +TBC... + +## TODO + +* [ ] explain how nested updaters work +* [ ] command line syntax +* [ ] API syntax + ## Pre-requisites * [plugins](api/plugins.md) @@ -15,11 +23,11 @@ As with [plugins](api/plugins.md), updaters may be nested: _any updater can regi ```js app.register('foo', function(foo) { - // do udpater stuff + // do updater stuff this.register('bar', function(bar) { - // do udpater stuff + // do updater stuff this.register('baz', function(baz) { - // do udpater stuff + // do updater stuff this.task('default', function(cb) { console.log(baz.namespace); cb(); diff --git a/docs/symlinking-updaters.md b/docs/symlinking-updaters.md index d6dd416..3e4347c 100644 --- a/docs/symlinking-updaters.md +++ b/docs/symlinking-updaters.md @@ -44,13 +44,13 @@ $ npm link To test that `updater-aaa` was symlinked properly, run the following command: ```sh -$ update aaa +$ update aaa ``` You should see something like the following in the terminal ```sh -updater aaa > task default +updater updater-aaa > task default ``` If not, review the steps and make sure you did everything described. If you still can't get it working please [create an issue](../../../issues) so we can look into it. @@ -62,7 +62,7 @@ If you'd like to see how multiple updaters can work together, repeat the same st Then run: ```sh -update aaa bbb ccc +update aaa bbb ccc ``` ## Related diff --git a/docs/tasks.md b/docs/tasks.md index 3a2d003..8cfc923 100644 --- a/docs/tasks.md +++ b/docs/tasks.md @@ -75,7 +75,7 @@ When using update's file system API (`.src`/`.dest` etc), you can optionally ret #### .build -Run one more tasks. +Run one or more tasks. **Params** @@ -89,7 +89,8 @@ app.task('foo', function(cb) { // do task stuff cb(); }); -app.task('foo', function(cb) { + +app.task('bar', function(cb) { // do task stuff cb(); }); @@ -102,11 +103,11 @@ app.build(['foo', 'bar'], function(err) { #### .update -The `.update` may also be used to run tasks. However, `.update` can be used to run _tasks and updaters_, thus it will also look for updaters to run when a task is not found. +The `.update` method may also be used to run tasks. However, `.update` can be used to run _tasks and updaters_, thus it will also look for updaters to run when a task is not found. _To ensure that only tasks are run, use the `.build` method._ -See the [updaters documentation](#updaters) for more details. +See the [updaters documentation](updaters.md) for more details. ### Task composition diff --git a/docs/updatefile.md b/docs/updatefile.md index 3c45262..6e878ee 100644 --- a/docs/updatefile.md +++ b/docs/updatefile.md @@ -10,13 +10,13 @@ Update's CLI attempts to: * Load the configuration from `updatefile.js` using node.js `require()` system * Register it as the ["default" updater](updaters.md#default-updater) * Execute any tasks or updaters you've specified for it to run. -* If multiple task or updater names are specified on the command line, Update's CLI will attempt to run +* If multiple task or updater names are specified on the command line, Update's CLI will attempt to run all of the specified tasks and updaters. **If `updatefile.js` does not exist** Update's CLI attempts to: -* Find any updaters you've specified for it to run by using node's `require()` system to search for locally installed modules with the name `updater-*`, +* Find any updaters you've specified for it to run by using node's `require()` system to search for locally and globally installed modules with the name `updater-*`. ## Creating an updatefile.js diff --git a/docs/updaters.md b/docs/updaters.md index c4dd8c3..a7b27ef 100644 --- a/docs/updaters.md +++ b/docs/updaters.md @@ -2,9 +2,12 @@ This document describes how to create, register and run updaters. +- [TODO](#todo) - [What is an updater?](#what-is-an-updater) - [Creating updaters](#creating-updaters) - [Registering updaters](#registering-updaters) + * [.register](#register) + * [.updater](#updater) - [Running updaters](#running-updaters) - [Resolving updaters](#resolving-updaters) * [Tasks and updaters](#tasks-and-updaters) @@ -14,6 +17,12 @@ This document describes how to create, register and run updaters. - [Default updater](#default-updater) - [Related](#related) +## TODO + +* [ ] document updater args +* [ ] explain how the `base` instance works +* [ ] document `env` + ## What is an updater? Updaters are [plugins](api/plugins.md) that are registered by name. If you're not familiar with plugins yet, it might be a good idea to review the [plugins docs](api/plugins.md) first. @@ -22,10 +31,10 @@ The primary difference between "updaters" and "plugins" is how they're registere | | **Plugin** | **Updater** | | --- | --- | --- | -| Registered with | [.use](plugins.md#use) method | [.register](#register) method or [.updater](#updater) method | +| Registered with | [.use](api/plugins.md#use) method | [.register](#register) method or [.updater](#updater) method | | Instance | Loaded onto "current" `Update` instance | A `new Update()` instance is created for every updater registered | | Invoked | Immediately | `.register` deferred (lazy), `.updater` immediately | -| Run using | [.run](plugins.md#run): all plugins are run at once | `.update`: only specified plugin(s) are run | +| Run using | [.run](api/plugins.md#run): all plugins are run at once | `.update`: only specified plugin(s) are run | ## Creating updaters @@ -46,6 +55,10 @@ Updaters may be registered using either of the following methods: * `.register`: if the plugin should not be invoked until it's called by `.update` (stays lazy while it's cached, this is preferred) * `.updater`: if the plugin needs to be invoked immediately when registered +### .register + +Register an updater function with the given `name` using the `.register` method. + **Example** ```js @@ -53,21 +66,45 @@ var update = require('update'); var app = update(); function updater(app) { - // do updater stuff + // do updater stuff when the updater is run with the `.update` method. + console.log('foo is being run'); } -// register as an updater +// register as an updater with the `.register` method app.register('foo', updater); -// or register as a plugin -app.use(updater); +// run the `foo` updater with the `.update` method +app.update('foo', function(err) { + if (err) return console.log(err); +}); +//=> "foo is being run" +``` + +### .updater + +Register an updater function with the given `name` using the `.updater` method. + +**Example** + +```js +var update = require('update'); +var app = update(); + +function updater(app) { + // do updater stuff when the updater is registered + console.log('foo is being registered'); +} + +// register as an updater using `.updater` +app.updater('foo', updater); +//=> "foo is being registered" ``` **Should I use `.updater` or `.register`?** In general, it's recommended that you use the `.register` method. In most cases update is smart enough to figure out when to invoke updater functions. -However, there are always exceptions. If you create custom code and notice that update can't find the information it needs. Try using the `.updater` method to to invoke the function when the updater is registered. +However, there are always exceptions. If you create custom code and notice that update can't find the information it needs. Try using the `.updater` method to invoke the function when the updater is registered. ## Running updaters @@ -105,7 +142,7 @@ app.update(['foo', 'bar'], function(err) { ## Resolving updaters -Updaters can be published to npm and installed globally or locally. But you there is no requirement that updaters must be published. You can also create custom updaters and register them using the [.register](#register) or [.updater](#updater) methods. +Updaters can be published to npm and installed globally or locally. But there is no requirement that updaters must be published. You can also create custom updaters and register using the [.register](#register) or [.updater](#updater) methods. This provides a great deal of flexibility, but it also means that we need a strategy for _finding updaters_ when `update` is run from the command line. @@ -146,7 +183,7 @@ module.exports = function(app) { }); }); - // `.build` doesn't run updaters + // `.update` will run updater `foo` app.update('foo', function(err) { if (err) return console.log(err); }); @@ -168,7 +205,7 @@ module.exports = function(app) { app.update('foo', cb); }); - // `.build` will run task `foo`, which runs the updater + // `.build` will run task `foo`, which runs updater `foo` app.build('foo', function(err) { if (err) return console.log(err); }); diff --git a/support/docs/api.plugins.md b/support/docs/api.plugins.md index 07357d1..d6558c4 100644 --- a/support/docs/api.plugins.md +++ b/support/docs/api.plugins.md @@ -54,7 +54,7 @@ app.run(obj); Additionally: * If `obj` has a `.use` method, it will be used on each plugin (e.g. `obj.use(fn)`). Otherwise `fn(obj)`. -* If the plugin returns a function again and `obj` has a `.run` method, the plugin will be pushed onto the `obj.fns` array +* If the plugin returns a function again and `obj` has a `.run` method, the plugin will be pushed onto the `obj.fns` array. This can continue indefinitely as long as the plugin returns a function and the receiving object has `.use`/`.run` functions. diff --git a/support/docs/cli.built-in-updaters.md b/support/docs/cli.built-in-updaters.md index 05352e2..84101a9 100644 --- a/support/docs/cli.built-in-updaters.md +++ b/support/docs/cli.built-in-updaters.md @@ -52,4 +52,4 @@ Display a help menu with all available commands: ```sh $ update help -``` \ No newline at end of file +``` diff --git a/support/docs/cli.commands.md b/support/docs/cli.commands.md index e14de4e..d37c210 100644 --- a/support/docs/cli.commands.md +++ b/support/docs/cli.commands.md @@ -8,7 +8,7 @@ Supported command line flags. ## --run -Force stored tasks to run. Stored tasks are "skipped" if you have an `updatefile.js` in the current working directory. +Force stored tasks to run. Stored tasks are "skipped" if you have an `updatefile.js` in the current working directory. ```sh $ update --run diff --git a/support/docs/faq.md b/support/docs/faq.md index d1777d1..8eab822 100644 --- a/support/docs/faq.md +++ b/support/docs/faq.md @@ -16,6 +16,6 @@ Update, with a capital "U", the rest lowercase. `updatefile.js` is all lowercase Update tries to find globally installed updaters using an "alias" first, falling back on the updater's full name if not found by its alias. -A updater's alias is created by stripping the substring `updater-` from the _full name_ of updater. Thus, when publishing a updater the naming convention `updater-foo` should be used (where `foo` is the alias, and `updater-foo` is the full name). +An updater's alias is created by stripping the substring `updater-` from the _full name_ of updater. Thus, when publishing an updater the naming convention `updater-foo` should be used (where `foo` is the alias, and `updater-foo` is the full name). -Note that **no dots may be used in published updater names**. Aside from that, any characters considered valid by npm are fine. \ No newline at end of file +Note that **no dots may be used in published updater names**. Aside from that, any characters considered valid by npm are fine. diff --git a/support/docs/features.md b/support/docs/features.md index d61268b..86780be 100644 --- a/support/docs/features.md +++ b/support/docs/features.md @@ -7,12 +7,16 @@ related: Update offers an elegant and robust suite of methods, carefully organized to help you accomplish common activities in less time, including: * **unparalleled flow control**: through the use of [updaters][getting-started], [sub-updaters][getting-started] and [tasks][getting-started] -* **templates, scaffolds and boilerplates**: update a single file, initialize an entire project, or provide ad-hoc "components" throughout the duration of a project using any combination of [templates, scaffolds and boilerplates](#templates-scaffolds-and-boilerplates). +* **templates, scaffolds and boilerplates**: update a single file, initialize an entire project, or provide ad-hoc "components" throughout the duration of a project using any combination of [templates, scaffolds and boilerplates](#templates-scaffolds-and-boilerplates) * **any engine**: use any template engine to render templates, including [handlebars][], [lodash][], [swig][] and [pug][] -* **prompts**: asks you for data when it can't find what it needs, and it's easy to customize prompts for any data you want. -* **data**: gathers data from the user's environment to populate "hints" in user prompts and render templates +* **prompts**: asks you for data when it can't find what it needs, and it's easy to customize prompts for any data you want +* **data**: gathers data from the user's environment to populate "hints" in user prompts and to use when rendering templates * **streams**: interact with the file system, with full support for [gulp][] and [assemble][] plugins * **smart plugins**: Update is built on [base][], so any "smart" plugin can be used -* **stores**: persist configuration settings, global defaults, project-specific defaults, answers to prompts, and so on. +* **stores**: persist configuration settings, global defaults, project-specific defaults, answers to prompts, and so on -Visit the [getting started guide][getting-started] to learn more. \ No newline at end of file +Visit the [getting started guide][getting-started] to learn more. + + + +[getting-started]: https://github.com/update/getting-started diff --git a/support/docs/installing-updaters.md b/support/docs/installing-updaters.md index e8604d5..cd053ba 100644 --- a/support/docs/installing-updaters.md +++ b/support/docs/installing-updaters.md @@ -4,6 +4,6 @@ related: doc: ['installing-the-cli'] --- -Updaters are responsible for all of the "updating" that happens in update. You can find updaters to install by [searching npm](https://www.npmjs.com/browse/keyword/updateupdater) for packages that have the keyword `updateupdater`. +Updaters are responsible for all of the "updating" that happens in update. You can find updaters to install by [searching npm](https://www.npmjs.com/browse/keyword/update-updater) for packages that have the keyword `update-updater`. -TODO \ No newline at end of file +TODO diff --git a/support/docs/nested-updaters.md b/support/docs/nested-updaters.md index f140e85..1006132 100644 --- a/support/docs/nested-updaters.md +++ b/support/docs/nested-updaters.md @@ -27,11 +27,11 @@ As with [plugins](api/plugins.md), updaters may be nested: _any updater can regi ```js app.register('foo', function(foo) { - // do udpater stuff + // do updater stuff this.register('bar', function(bar) { - // do udpater stuff + // do updater stuff this.register('baz', function(baz) { - // do udpater stuff + // do updater stuff this.task('default', function(cb) { console.log(baz.namespace); cb(); diff --git a/support/docs/symlinking-updaters.md b/support/docs/symlinking-updaters.md index d0cfedd..aca2717 100644 --- a/support/docs/symlinking-updaters.md +++ b/support/docs/symlinking-updaters.md @@ -12,7 +12,7 @@ The following example shows you to do this. **1. Create an updater project** -Create a new project named `updater-aaa`. You can expedite this using [generate][] or Google's Yeoman or however you prefer. +Create a new project named `updater-aaa`. You can expedite this using [generate][] or Google's Yeoman or however you prefer. **2. Add `index.js`** @@ -48,24 +48,24 @@ $ npm link To test that `updater-aaa` was symlinked properly, run the following command: ```sh -$ update aaa +$ update aaa ``` You should see something like the following in the terminal ```sh -updater aaa > task default +updater updater-aaa > task default ``` If not, review the steps and make sure you did everything described. If you still can't get it working please [create an issue](../../../issues) so we can look into it. **Next steps** -If you'd like to see how multiple updaters can work together, repeat the same steps described above to create and symlink `updater-bbb` and `updater-ccc`. +If you'd like to see how multiple updaters can work together, repeat the same steps described above to create and symlink `updater-bbb` and `updater-ccc`. Then run: ```sh -update aaa bbb ccc +update aaa bbb ccc ``` diff --git a/support/docs/tasks.md b/support/docs/tasks.md index 6895c54..d912bc3 100644 --- a/support/docs/tasks.md +++ b/support/docs/tasks.md @@ -68,7 +68,7 @@ When using update's file system API (`.src`/`.dest` etc), you can optionally ret #### .build -Run one more tasks. +Run one or more tasks. **Params** @@ -82,7 +82,8 @@ app.task('foo', function(cb) { // do task stuff cb(); }); -app.task('foo', function(cb) { + +app.task('bar', function(cb) { // do task stuff cb(); }); @@ -95,11 +96,11 @@ app.build(['foo', 'bar'], function(err) { #### .update -The `.update` may also be used to run tasks. However, `.update` can be used to run _tasks and updaters_, thus it will also look for updaters to run when a task is not found. +The `.update` method may also be used to run tasks. However, `.update` can be used to run _tasks and updaters_, thus it will also look for updaters to run when a task is not found. _To ensure that only tasks are run, use the `.build` method._ -See the [updaters documentation](#updaters) for more details. +See the [updaters documentation](updaters.md) for more details. ### Task composition diff --git a/support/docs/updatefile.md b/support/docs/updatefile.md index 09dc3df..6537e51 100644 --- a/support/docs/updatefile.md +++ b/support/docs/updatefile.md @@ -4,23 +4,23 @@ related: doc: ['installing-updaters', 'updaters', 'tasks'] --- -Each time `update` is run, Update's CLI looks for an `updatefile.js` in the current working directory. +Each time `update` is run, Update's CLI looks for an `updatefile.js` in the current working directory. **If `updatefile.js` exists** Update's CLI attempts to: -* Load a local installation of the Update library using node's `require()` system, falling back to global installation if not found. -* Load the configuration from `updatefile.js` using node.js `require()` system -* Register it as the ["default" updater](updaters.md#default-updater) +* Load a local installation of the Update library using node's `require()` system, falling back to global installation if not found. +* Load the configuration from `updatefile.js` using node.js `require()` system +* Register it as the ["default" updater](updaters.md#default-updater) * Execute any tasks or updaters you've specified for it to run. -* If multiple task or updater names are specified on the command line, Update's CLI will attempt to run +* If multiple task or updater names are specified on the command line, Update's CLI will attempt to run all of the specified tasks and updaters. **If `updatefile.js` does not exist** Update's CLI attempts to: -* Find any updaters you've specified for it to run by using node's `require()` system to search for locally installed modules with the name `updater-*`, +* Find any updaters you've specified for it to run by using node's `require()` system to search for locally and globally installed modules with the name `updater-*`. ## Creating an updatefile.js diff --git a/support/docs/updaters.md b/support/docs/updaters.md index 64865e0..f2a8a80 100644 --- a/support/docs/updaters.md +++ b/support/docs/updaters.md @@ -4,7 +4,7 @@ related: doc: ['tasks', 'updatefile', 'installing-updaters', 'symlinking-updaters'] --- -This document describes how to create, register and run updaters. +This document describes how to create, register and run updaters. @@ -21,12 +21,12 @@ Updaters are [plugins](api/plugins.md) that are registered by name. If you're no The primary difference between "updaters" and "plugins" is how they're registered, but there are a few other minor differences: -| | **Plugin** | **Updater** | +| | **Plugin** | **Updater** | | --- | --- | --- | -| Registered with | [.use](plugins.md#use) method | [.register](#register) method or [.updater](#updater) method | +| Registered with | [.use](api/plugins.md#use) method | [.register](#register) method or [.updater](#updater) method | | Instance | Loaded onto "current" `Update` instance | A `new Update()` instance is created for every updater registered | | Invoked | Immediately | `.register` deferred (lazy), `.updater` immediately | -| Run using | [.run](plugins.md#run): all plugins are run at once | `.update`: only specified plugin(s) are run | +| Run using | [.run](api/plugins.md#run): all plugins are run at once | `.update`: only specified plugin(s) are run | ## Creating updaters @@ -44,17 +44,15 @@ function updater(app) { ## Registering updaters -Register an updater function with the given `name`. - -```js -app.register(name, fn); -``` - Updaters may be registered using either of the following methods: * `.register`: if the plugin should not be invoked until it's called by `.update` (stays lazy while it's cached, this is preferred) * `.updater`: if the plugin needs to be invoked immediately when registered +### .register + +Register an updater function with the given `name` using the `.register` method. + **Example** ```js @@ -62,21 +60,45 @@ var update = require('update'); var app = update(); function updater(app) { - // do updater stuff + // do updater stuff when the updater is run with the `.update` method. + console.log('foo is being run'); } -// register as an updater +// register as an updater with the `.register` method app.register('foo', updater); -// or register as a plugin -app.use(updater); +// run the `foo` updater with the `.update` method +app.update('foo', function(err) { + if (err) return console.log(err); +}); +//=> "foo is being run" +``` + +### .updater + +Register an updater function with the given `name` using the `.updater` method. + +**Example** + +```js +var update = require('update'); +var app = update(); + +function updater(app) { + // do updater stuff when the updater is registered + console.log('foo is being registered'); +} + +// register as an updater using `.updater` +app.updater('foo', updater); +//=> "foo is being registered" ``` **Should I use `.updater` or `.register`?** In general, it's recommended that you use the `.register` method. In most cases update is smart enough to figure out when to invoke updater functions. -However, there are always exceptions. If you create custom code and notice that update can't find the information it needs. Try using the `.updater` method to to invoke the function when the updater is registered. +However, there are always exceptions. If you create custom code and notice that update can't find the information it needs. Try using the `.updater` method to invoke the function when the updater is registered. ## Running updaters @@ -114,7 +136,7 @@ app.update(['foo', 'bar'], function(err) { ## Resolving updaters -Updaters can be published to npm and installed globally or locally. But you there is no requirement that updaters must be published. You can also create custom updaters and register them using the [.register](#register) or [.updater](#updater) methods. +Updaters can be published to npm and installed globally or locally. But there is no requirement that updaters must be published. You can also create custom updaters and register using the [.register](#register) or [.updater](#updater) methods. This provides a great deal of flexibility, but it also means that we need a strategy for _finding updaters_ when `update` is run from the command line. @@ -155,7 +177,7 @@ module.exports = function(app) { }); }); - // `.build` doesn't run updaters + // `.update` will run updater `foo` app.update('foo', function(err) { if (err) return console.log(err); }); @@ -177,7 +199,7 @@ module.exports = function(app) { app.update('foo', cb); }); - // `.build` will run task `foo`, which runs the updater + // `.build` will run task `foo`, which runs updater `foo` app.build('foo', function(err) { if (err) return console.log(err); }); From 83cab7a42bd6e442e19aeb0a220f771a7f558e66 Mon Sep 17 00:00:00 2001 From: Brian Woodward Date: Fri, 1 Jul 2016 17:21:27 -0400 Subject: [PATCH 230/274] don't prefix filename with key if file is in the same directory --- support/lib/helpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/support/lib/helpers.js b/support/lib/helpers.js index 615096b..14d0e75 100644 --- a/support/lib/helpers.js +++ b/support/lib/helpers.js @@ -62,7 +62,7 @@ function createLink(dir, prop, link) { if (dir !== key) { if (key === 'docs') key = ''; filename = path.join('..', key, filename); - } else if (key !== 'docs') { + } else if (key !== 'docs' && dir !== key) { filename = path.join(key, filename); } return `- [${name}](${filename}${anchor})`; From 3b2ef15dbfebab12f8dfa26101a5ef24c6a31a5a Mon Sep 17 00:00:00 2001 From: Brian Woodward Date: Fri, 1 Jul 2016 17:21:33 -0400 Subject: [PATCH 231/274] generate docs --- docs/api/plugins.md | 4 ++-- docs/api/register.md | 4 ++-- docs/api/updater.md | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/api/plugins.md b/docs/api/plugins.md index e3f5270..8834950 100644 --- a/docs/api/plugins.md +++ b/docs/api/plugins.md @@ -61,5 +61,5 @@ When plugins are [registered by name](docs/updaters.md), they are referred to as **API** -* [updater](api/updater.md) -* [register](api/register.md) +* [updater](updater.md) +* [register](register.md) diff --git a/docs/api/register.md b/docs/api/register.md index eb9a6a7..1c507ba 100644 --- a/docs/api/register.md +++ b/docs/api/register.md @@ -31,5 +31,5 @@ app.update('foo', function(err) { **API** -* [updater](api/updater.md) -* [plugins](api/plugins.md) +* [updater](updater.md) +* [plugins](plugins.md) diff --git a/docs/api/updater.md b/docs/api/updater.md index 3ed39de..d9ae2ce 100644 --- a/docs/api/updater.md +++ b/docs/api/updater.md @@ -31,5 +31,5 @@ app.update('bar', function(err) { **API** -* [register](api/register.md) -* [plugins](api/plugins.md) +* [register](register.md) +* [plugins](plugins.md) From 76830d34b872e09833f186fafcc4a9a2f31945f7 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 2 Jul 2016 04:38:21 -0400 Subject: [PATCH 232/274] lint, use assemble-loader --- bin/update.js | 1 - index.js | 4 ++-- lib/cli.js | 1 - lib/commands/silent.js | 2 -- lib/utils.js | 1 + package.json | 5 ++--- 6 files changed, 5 insertions(+), 9 deletions(-) diff --git a/bin/update.js b/bin/update.js index a009f13..011ee62 100755 --- a/bin/update.js +++ b/bin/update.js @@ -6,7 +6,6 @@ var Update = require('..'); var commands = require('../lib/commands'); var utils = require('../lib/utils'); var argv = utils.parseArgs(process.argv.slice(2)); -var util = require('util'); /** * Listen for errors diff --git a/index.js b/index.js index c8affb5..503e635 100644 --- a/index.js +++ b/index.js @@ -102,7 +102,7 @@ Update.prototype.initDefaults = function() { */ Update.prototype.configfile = function(cwd) { - return utils.configfile(cwd) + return utils.configfile(cwd); }; /** @@ -154,6 +154,7 @@ Update.plugins = function(app) { app.use(utils.store('update')); app.use(utils.runtimes()); app.use(utils.questions()); + app.use(utils.loader()); app.use(utils.config()); app.use(utils.cli()); }; @@ -210,7 +211,6 @@ Object.defineProperty(Update.prototype, 'log', { } }); - /** * Expose static `cli` method */ diff --git a/lib/cli.js b/lib/cli.js index 20b41a8..1474c82 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -60,7 +60,6 @@ module.exports = function(Update, config, cb) { var defaults = path.resolve(__dirname, 'updatefile.js'); var updatefile = path.resolve(cwd, 'updatefile.js'); - var app = new Update(opts); var fn = require(defaults); /** diff --git a/lib/commands/silent.js b/lib/commands/silent.js index 7ec1dec..3bd0d44 100644 --- a/lib/commands/silent.js +++ b/lib/commands/silent.js @@ -1,7 +1,5 @@ 'use strict'; -var pkg = require('../../package'); - module.exports = function(app) { return function(val, key, config, next) { app.enable('silent'); diff --git a/lib/utils.js b/lib/utils.js index 6ab8469..1d61d16 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -8,6 +8,7 @@ require = utils; // eslint-disable-line * Utils */ +require('assemble-loader', 'loader'); require('arr-union', 'union'); require('base-cli-process', 'cli'); require('base-config-process', 'config'); diff --git a/package.json b/package.json index 7959f21..b8fb46e 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "dependencies": { "arr-union": "^3.1.0", "assemble-core": "^0.23.0", + "assemble-loader": "^0.6.1", "base-cli-process": "^0.1.13", "base-config-process": "^0.1.7", "base-generators": "^0.4.1", @@ -52,13 +53,11 @@ "yargs-parser": "^2.4.0" }, "devDependencies": { - "assemble-loader": "^0.6.1", "base-runner": "^0.8.2", "base-test-runner": "^0.2.0", "base-test-suite": "^0.1.11", "cross-spawn": "^4.0.0", "generate-foo": "^0.1.5", - "generator-util": "^0.2.9", "graceful-fs": "^4.1.4", "gulp": "^3.9.1", "gulp-eslint": "^2.0.0", @@ -128,4 +127,4 @@ "reflinks": true } } -} +} \ No newline at end of file From 777117a860fa2d24e3056611485d1cffae047e07 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 2 Jul 2016 04:39:33 -0400 Subject: [PATCH 233/274] typo --- .verb.md | 2 +- README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.verb.md b/.verb.md index 4ab394b..2dda003 100644 --- a/.verb.md +++ b/.verb.md @@ -18,7 +18,7 @@ You can create your own [updaters](docs/updaters.md) using Update's API, or inst ## Why update? -- **be more productive**: Update makes you more productive by eliminating time spent on things that _can be automated_, but typically aren't since they don't need to be done often, don't fit into the build cycle or a project's deliverables, or they're usually updated by hand. As code projects mature, time spent on these things tend to stay linear or increase as the size of a community grows. And this only compounds which each new project under your stewardship. +- **be more productive**: Update makes you more productive by eliminating time spent on things that _can be automated_, but typically aren't since they don't need to be done often, don't fit into the build cycle or a project's deliverables, or they're usually updated by hand. As code projects mature, time spent on these things tend to stay linear or increase as the size of a community grows. And this only compounds with each new project under your stewardship. - **your way, instantly**: updaters can be published to and installed from npm, but you can also easily create your own [personal updaters](docs/symlinking-updaters.md). Once you're updaters are setup, projects under your maintenance will convert to the the conventions you prefer in milliseconds. - **plugin ecosystem**: any plugins that work with [Base applications](#discovering-plugins) will work with Update. Which means you can use [assemble][], [verb][], and [generate][] plugins, to name a few. diff --git a/README.md b/README.md index 9d8e9de..3a5a2bb 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ You can create your own [updaters](docs/updaters.md) using Update's API, or inst ## Why update? -* **be more productive**: Update makes you more productive by eliminating time spent on things that _can be automated_, but typically aren't since they don't need to be done often, don't fit into the build cycle or a project's deliverables, or they're usually updated by hand. As code projects mature, time spent on these things tend to stay linear or increase as the size of a community grows. And this only compounds which each new project under your stewardship. +* **be more productive**: Update makes you more productive by eliminating time spent on things that _can be automated_, but typically aren't since they don't need to be done often, don't fit into the build cycle or a project's deliverables, or they're usually updated by hand. As code projects mature, time spent on these things tend to stay linear or increase as the size of a community grows. And this only compounds with each new project under your stewardship. * **your way, instantly**: updaters can be published to and installed from npm, but you can also easily create your own [personal updaters](docs/symlinking-updaters.md). Once you're updaters are setup, projects under your maintenance will convert to the the conventions you prefer in milliseconds. * **plugin ecosystem**: any plugins that work with [Base applications](#discovering-plugins) will work with Update. Which means you can use [assemble](https://github.com/assemble/assemble), [verb](https://github.com/verbose/verb), and [generate](https://github.com/generate/generate) plugins, to name a few. @@ -241,4 +241,4 @@ Released under the [MIT license](https://github.com/update/update/blob/master/LI *** -_This file was generated by [verb](https://github.com/verbose/verb), v0.9.0, on July 01, 2016._ \ No newline at end of file +_This file was generated by [verb](https://github.com/verbose/verb), v0.9.0, on July 02, 2016._ \ No newline at end of file From a00a2b2b4b61cfb2c719aa58a3eed61845b1aec6 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 2 Jul 2016 04:43:04 -0400 Subject: [PATCH 234/274] 0.6.0 --- .verb.md | 3 +++ CHANGELOG.md | 8 ++++++++ README.md | 18 +++++++++++++++++- package.json | 7 +++++-- 4 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 CHANGELOG.md diff --git a/.verb.md b/.verb.md index 2dda003..d76c1fe 100644 --- a/.verb.md +++ b/.verb.md @@ -173,6 +173,9 @@ Are you using Update in your project? Have you published an [updater](docs/updat * **Gitter** Discuss Update with us on [Gitter](https://gitter.im/update) * If you publish an updater, thank you! To make your project as discoverable as possible, please add the keyword `updateupdater` to package.json. +## History +{%= doc('CHANGELOG.md') %} + [getting-started]: https://github.com/update/getting-started [base-plugin]: https://www.npmjs.com/browse/keyword/baseplugin [assemble-plugin]: https://www.npmjs.com/browse/keyword/assembleplugin diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8ac7b5e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +**v0.6.0** + +- Swap out [base][] for [assemble-core][] (which uses Base via [templates][]). This allows updaters to seamlessly run generators from [generate][], [assemble][], or [verb][] (when a file needs to be created, or re-created for example) +- Adds [assemble-loader][] to support glob patterns in collection methods + +**v0.5.0** + +First stable release! \ No newline at end of file diff --git a/README.md b/README.md index 3a5a2bb..9d1d63a 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Be scalable! Update is a new, open source developer framework and CLI for automa - [Authoring updaters](#authoring-updaters) - [More information](#more-information) - [Community](#community) +- [History](#history) - [Related projects](#related-projects) - [Contributing](#contributing) - [Running tests](#running-tests) @@ -204,6 +205,17 @@ Are you using Update in your project? Have you published an [updater](docs/updat * **Gitter** Discuss Update with us on [Gitter](https://gitter.im/update) * If you publish an updater, thank you! To make your project as discoverable as possible, please add the keyword `updateupdater` to package.json. +## History + +**v0.6.0** + +* Swap out [base](https://github.com/node-base/base) for [assemble-core][] (which uses Base via [templates][]). This allows updaters to seamlessly run generators from [generate](https://github.com/generate/generate), [assemble](https://github.com/assemble/assemble), or [verb](https://github.com/verbose/verb) (when a file needs to be created, or re-created for example) +* Adds [assemble-loader][] to support glob patterns in collection methods + +**v0.5.0** + +First stable release! + ## Related projects You might also be interested in these projects: @@ -241,4 +253,8 @@ Released under the [MIT license](https://github.com/update/update/blob/master/LI *** -_This file was generated by [verb](https://github.com/verbose/verb), v0.9.0, on July 02, 2016._ \ No newline at end of file +_This file was generated by [verb](https://github.com/verbose/verb), v0.9.0, on July 02, 2016._ + +[assemble-core]: https://github.com/assemble/assemble-core +[templates]: https://github.com/jonschlinkert/templates +[assemble-loader]: https://github.com/assemble/assemble-loader \ No newline at end of file diff --git a/package.json b/package.json index b8fb46e..cf08305 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "update", "description": "Be scalable! Update is a new, open source developer framework and CLI for automating updates of any kind in code projects.", - "version": "0.5.5", + "version": "0.6.0", "homepage": "https://github.com/update/update", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", "repository": "update/update", @@ -121,7 +121,10 @@ "pug", "swig", "updater-example", - "verb" + "verb", + "assemble-core", + "templates", + "assemble-loader" ], "lint": { "reflinks": true From c1dbbbb39fd6cb0bae660e6ded0303895e2d5d76 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sun, 3 Jul 2016 01:14:50 -0400 Subject: [PATCH 235/274] ensure options exists --- index.js | 10 ++++++---- package.json | 14 ++++++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/index.js b/index.js index 503e635..6866c31 100644 --- a/index.js +++ b/index.js @@ -63,9 +63,7 @@ Update.prototype.initDefaults = function() { appname: 'update' }); - this.define('update', function() { - return this.generate.apply(this, arguments); - }); + this.define('update', this.generate); this.define('getUpdater', function() { return this.getGenerator.apply(this, arguments); @@ -110,6 +108,11 @@ Update.prototype.configfile = function(cwd) { */ Update.prototype.getUpdaters = function(names, options) { + if (utils.isObject(names)) { + options = names; + names = []; + } + options = options || {}; var updaters = this.option('updaters'); this.addUpdaters(names, options); if (utils.isEmpty(updaters)) { @@ -194,7 +197,6 @@ Object.defineProperty(Update.prototype, 'log', { log.warn = function(msg) { return utils.logger('warning', 'yellow').apply(null, arguments); }; - log.success = function() { return utils.logger('success', 'green').apply(null, arguments); }; diff --git a/package.json b/package.json index cf08305..23996ed 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "global-modules": "^0.2.2", "gulp-choose-files": "^0.1.2", "is-valid-app": "^0.2.0", + "isobject": "^2.1.0", "lazy-cache": "^2.0.1", "log-utils": "^0.1.4", "os-homedir": "^1.0.1", @@ -88,6 +89,7 @@ "update" ], "update": { + "run": true, "add": [ "keywords" ] @@ -103,6 +105,8 @@ "gulp-format-md" ], "related": { + "highlight": "generate", + "description": "Update shares a common architecture and plugin ecosystem with the following libraries:", "list": [ "assemble", "base", @@ -112,22 +116,24 @@ }, "reflinks": [ "assemble", + "assemble-core", + "assemble-loader", "bach", "base", + "consolidate", "generate", "gulp", "handlebars", "lodash", "pug", "swig", + "templates", "updater-example", "verb", - "assemble-core", - "templates", - "assemble-loader" + "vinyl" ], "lint": { "reflinks": true } } -} \ No newline at end of file +} From 16eeee4365916d5366b149530fec308b80bac487 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sun, 3 Jul 2016 01:15:30 -0400 Subject: [PATCH 236/274] ensure listeners respect `silent` --- lib/cli.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/cli.js b/lib/cli.js index 1474c82..962d911 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -106,21 +106,21 @@ module.exports = function(Update, config, cb) { * Setup listeners */ + base.on('error', function(err) { + logger(err.stack); + }); + base.on('build', function(event, build) { - if (!build) return; + if (!build || build.isSilent) return; var prefix = event === 'finished' ? log.success + ' ' : ''; logger(log.timestamp, event, build.key, prefix + log.red(build.time)); }); base.on('task', function(event, task) { - if (!task) return; + if (!task || task.isSilent) return; logger(log.timestamp, event, task.key, log.red(task.time)); }); - base.on('error', function(err) { - logger(err.stack); - }); - /** * Resolve the "app" to use */ From a4f094e4df884a70015ed55fb8c5f38041af1e94 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sun, 3 Jul 2016 01:15:58 -0400 Subject: [PATCH 237/274] allow default and stored tasks to run --- bin/update.js | 10 +++++++++- lib/utils.js | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/bin/update.js b/bin/update.js index 011ee62..053ea0c 100755 --- a/bin/update.js +++ b/bin/update.js @@ -35,9 +35,17 @@ Update.cli(Update, argv, function(err, app) { var tasks = argv._.length ? argv._ : ['default']; if (app.updatefile !== true || argv.run) { tasks = Update.resolveTasks(app, argv); + + } else if (app.updatefile === true && app.pkg.get('update.run')) { + tasks = Update.resolveTasks(app, argv).concat(tasks); } - app.log.success('running:', tasks); + app.once('task', function() { + if (!app.base.enabled('silent')) { + app.log.success('running:', tasks); + } + }); + app.update(tasks, function(err) { if (err) return console.log(err); app.emit('done'); diff --git a/lib/utils.js b/lib/utils.js index 1d61d16..d223382 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -18,6 +18,7 @@ require('base-runtimes', 'runtimes'); require('base-store', 'store'); require('extend-shallow', 'extend'); require('fs-exists-sync', 'exists'); +require('isobject', 'isObject'); require('is-valid-app', 'isValid'); require('global-modules', 'gm'); require('log-utils', 'log'); From 953b3fd014a63ebe30eae73ac6249930c2bb68ae Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sun, 3 Jul 2016 01:24:55 -0400 Subject: [PATCH 238/274] update docs --- .github/contributing.md | 4 +++- .verb.md | 27 +++++++++++++++--------- README.md | 40 ++++++++++++++++++++---------------- docs/cli/commands.md | 8 +++++++- support/docs/cli.commands.md | 8 +++++++- 5 files changed, 56 insertions(+), 31 deletions(-) diff --git a/.github/contributing.md b/.github/contributing.md index 31109e8..3957f52 100644 --- a/.github/contributing.md +++ b/.github/contributing.md @@ -10,6 +10,8 @@ Creating an issue is the simplest form of contributing to a project. But there a - Feature requests - Bug reports +The [Guide to Idiomatic Contributing](https://github.com/jonschlinkert/idiomatic-contributing) also has good advice. + ## Issues **Before creating an issue** @@ -40,4 +42,4 @@ Here are some tips for creating idiomatic issues. Taking just a little bit extra - use syntax highlighting by adding the correct language name after the first "code fence" [node-glob]: https://github.com/isaacs/node-glob -[micromatch]: https://github.com/jonschlinkert/micromatch \ No newline at end of file +[micromatch]: https://github.com/jonschlinkert/micromatch diff --git a/.verb.md b/.verb.md index d76c1fe..a353fad 100644 --- a/.verb.md +++ b/.verb.md @@ -19,8 +19,9 @@ You can create your own [updaters](docs/updaters.md) using Update's API, or inst ## Why update? - **be more productive**: Update makes you more productive by eliminating time spent on things that _can be automated_, but typically aren't since they don't need to be done often, don't fit into the build cycle or a project's deliverables, or they're usually updated by hand. As code projects mature, time spent on these things tend to stay linear or increase as the size of a community grows. And this only compounds with each new project under your stewardship. -- **your way, instantly**: updaters can be published to and installed from npm, but you can also easily create your own [personal updaters](docs/symlinking-updaters.md). Once you're updaters are setup, projects under your maintenance will convert to the the conventions you prefer in milliseconds. -- **plugin ecosystem**: any plugins that work with [Base applications](#discovering-plugins) will work with Update. Which means you can use [assemble][], [verb][], and [generate][] plugins, to name a few. +- **your way, instantly**: updaters can be published to and installed from npm, but you can also easily create your own [personal updaters](docs/symlinking-updaters.md). Once your updaters are setup, just run `update init`, then projects under your maintenance will convert to the the conventions you prefer within milliseconds after running `update`. +- **plugin ecosystem**: any plugins that work with [Base applications](#discovering-plugins) will work also with Update. Which means you can use plugins (or generators) from [assemble][], [verb][], and [generate][], to name a few. +- **well tested**: with more than 1,200 unit tests ## Who should use Update? @@ -69,6 +70,8 @@ This appends the string `foo` to the contents of `example.txt`. Visit the [updat - Browse the [documentation](docs) - Learn about [updaters](docs/updaters.md) - Learn about the [built-in updaters](docs/cli/built-in-updaters.md) +- Learn more about [base][] +- Get [Sublime Text Snippets][snippets] for creating tasks and updaters ## Init @@ -128,19 +131,22 @@ $ update help ## Features -* **unparalleled flow control**: through the use of [updaters][getting-started], [sub-updaters][getting-started] and [tasks][getting-started] -* **templates, scaffolds and boilerplates**: update a single file, initialize an entire project, or provide ad-hoc "components" throughout the duration of a project using any combination of [templates, scaffolds and boilerplates](#templates-scaffolds-and-boilerplates). -* **any engine**: use any template engine to render templates, including [handlebars][], [lodash][], [swig][] and [pug][] -* **prompts**: asks you for data when it can't find what it needs, and it's easy to customize prompts for any data you want. -* **data**: gathers data from the user's environment to populate "hints" in user prompts and render templates -* **streams**: interact with the file system, with full support for [gulp][] and [assemble][] plugins -* **smart plugins**: Update is built on [base][], so any "smart" plugin can be used +* **unparalleled flow control**: through the use of [updaters](docs/updaters.md), [sub-updaters][getting-started] and [tasks](docs/tasks.md) +* **generators**: support for [generate][] generators. If your updater needs to create new files, there might be a [generator for that](https://www.npmjs.com/browse/keyword/generate-generator). Just use the generator the same way you would use an [updater](docs/updaters.md). +* **render templates**: use templates to create new files, or replace existing files +* **prompts**: It's easy to create custom prompts. Answers to prompts can be used as context for rendering templates, for settings options, determining file names, directory structure, and anything else that requires use feedback. +* **any engine**: use any template engine to render templates, including [handlebars][], [lodash][], [swig][] and [pug][], or anything supported by [consolidate][]. +* **data**: gather data from the user's environment to populate "hints" in user prompts or for rendering templates +* **fs**: in the spirit of [gulp][], use `.src` and `.dest` to read and write globs of files. +* **vinyl**: files and templates are [vinyl][] files +* **streams**: full support for [gulp][] and [assemble][] plugins +* **smart plugins**: Update is built on [base][], so any "smart" plugin from the Base ecosystem can be used * **stores**: persist configuration settings, global defaults, project-specific defaults, answers to prompts, and so on. * much more! ## Discovering updaters -* Find updaters to install by [searching npm](https://www.npmjs.com/browse/keyword/updateupdater) for packages with the keyword `updateupdater` +* Find updaters to install by [searching npm](https://www.npmjs.com/browse/keyword/update-updater) for packages with the keyword `updateupdater` * Visit [Update's GitHub org](https://github.com/update) to see the updaters maintained by the core team ## Discovering plugins @@ -182,3 +188,4 @@ Are you using Update in your project? Have you published an [updater](docs/updat [generate-plugin]: https://www.npmjs.com/browse/keyword/generateplugin [templates-plugin]: https://www.npmjs.com/browse/keyword/templatesplugin [verb-plugin]: https://www.npmjs.com/browse/keyword/verbplugin +[snippets]: https://github.com/node-base/sublime-text-base-snippets diff --git a/README.md b/README.md index 9d1d63a..8e262b2 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ Be scalable! Update is a new, open source developer framework and CLI for automating updates of any kind in code projects. +You might also be interested in [generate](https://github.com/generate/generate). + ## TOC - [What does it do?](#what-does-it-do) @@ -51,8 +53,9 @@ You can create your own [updaters](docs/updaters.md) using Update's API, or inst ## Why update? * **be more productive**: Update makes you more productive by eliminating time spent on things that _can be automated_, but typically aren't since they don't need to be done often, don't fit into the build cycle or a project's deliverables, or they're usually updated by hand. As code projects mature, time spent on these things tend to stay linear or increase as the size of a community grows. And this only compounds with each new project under your stewardship. -* **your way, instantly**: updaters can be published to and installed from npm, but you can also easily create your own [personal updaters](docs/symlinking-updaters.md). Once you're updaters are setup, projects under your maintenance will convert to the the conventions you prefer in milliseconds. -* **plugin ecosystem**: any plugins that work with [Base applications](#discovering-plugins) will work with Update. Which means you can use [assemble](https://github.com/assemble/assemble), [verb](https://github.com/verbose/verb), and [generate](https://github.com/generate/generate) plugins, to name a few. +* **your way, instantly**: updaters can be published to and installed from npm, but you can also easily create your own [personal updaters](docs/symlinking-updaters.md). Once your updaters are setup, just run `update init`, then projects under your maintenance will convert to the the conventions you prefer within milliseconds after running `update`. +* **plugin ecosystem**: any plugins that work with [Base applications](#discovering-plugins) will work also with Update. Which means you can use plugins (or generators) from [assemble](https://github.com/assemble/assemble), [verb](https://github.com/verbose/verb), and [generate](https://github.com/generate/generate), to name a few. +* **well tested**: with more than 1,200 unit tests ## Who should use Update? @@ -101,6 +104,8 @@ This appends the string `foo` to the contents of `example.txt`. Visit the [updat * Browse the [documentation](docs) * Learn about [updaters](docs/updaters.md) * Learn about the [built-in updaters](docs/cli/built-in-updaters.md) +* Learn more about [base](https://github.com/node-base/base) +* Get [Sublime Text Snippets](https://github.com/node-base/sublime-text-base-snippets) for creating tasks and updaters ## Init @@ -160,19 +165,22 @@ $ update help ## Features -* **unparalleled flow control**: through the use of [updaters](https://github.com/update/getting-started), [sub-updaters](https://github.com/update/getting-started) and [tasks](https://github.com/update/getting-started) -* **templates, scaffolds and boilerplates**: update a single file, initialize an entire project, or provide ad-hoc "components" throughout the duration of a project using any combination of [templates, scaffolds and boilerplates](#templates-scaffolds-and-boilerplates). -* **any engine**: use any template engine to render templates, including [handlebars](http://www.handlebarsjs.com/), [lodash](https://lodash.com/), [swig](https://github.com/paularmstrong/swig) and [pug](http://jade-lang.com) -* **prompts**: asks you for data when it can't find what it needs, and it's easy to customize prompts for any data you want. -* **data**: gathers data from the user's environment to populate "hints" in user prompts and render templates -* **streams**: interact with the file system, with full support for [gulp](http://gulpjs.com) and [assemble](https://github.com/assemble/assemble) plugins -* **smart plugins**: Update is built on [base](https://github.com/node-base/base), so any "smart" plugin can be used +* **unparalleled flow control**: through the use of [updaters](docs/updaters.md), [sub-updaters](https://github.com/update/getting-started) and [tasks](docs/tasks.md) +* **generators**: support for [generate](https://github.com/generate/generate) generators. If your updater needs to create new files, there might be a [generator for that](https://www.npmjs.com/browse/keyword/generate-generator). Just use the generator the same way you would use an [updater](docs/updaters.md). +* **render templates**: use templates to create new files, or replace existing files +* **prompts**: It's easy to create custom prompts. Answers to prompts can be used as context for rendering templates, for settings options, determining file names, directory structure, and anything else that requires use feedback. +* **any engine**: use any template engine to render templates, including [handlebars](http://www.handlebarsjs.com/), [lodash](https://lodash.com/), [swig](https://github.com/paularmstrong/swig) and [pug](http://jade-lang.com), or anything supported by [consolidate](https://github.com/visionmedia/consolidate.js). +* **data**: gather data from the user's environment to populate "hints" in user prompts or for rendering templates +* **fs**: in the spirit of [gulp](http://gulpjs.com), use `.src` and `.dest` to read and write globs of files. +* **vinyl**: files and templates are [vinyl](http://github.com/wearefractal/vinyl) files +* **streams**: full support for [gulp](http://gulpjs.com) and [assemble](https://github.com/assemble/assemble) plugins +* **smart plugins**: Update is built on [base](https://github.com/node-base/base), so any "smart" plugin from the Base ecosystem can be used * **stores**: persist configuration settings, global defaults, project-specific defaults, answers to prompts, and so on. * much more! ## Discovering updaters -* Find updaters to install by [searching npm](https://www.npmjs.com/browse/keyword/updateupdater) for packages with the keyword `updateupdater` +* Find updaters to install by [searching npm](https://www.npmjs.com/browse/keyword/update-updater) for packages with the keyword `updateupdater` * Visit [Update's GitHub org](https://github.com/update) to see the updaters maintained by the core team ## Discovering plugins @@ -209,8 +217,8 @@ Are you using Update in your project? Have you published an [updater](docs/updat **v0.6.0** -* Swap out [base](https://github.com/node-base/base) for [assemble-core][] (which uses Base via [templates][]). This allows updaters to seamlessly run generators from [generate](https://github.com/generate/generate), [assemble](https://github.com/assemble/assemble), or [verb](https://github.com/verbose/verb) (when a file needs to be created, or re-created for example) -* Adds [assemble-loader][] to support glob patterns in collection methods +* Swap out [base](https://github.com/node-base/base) for [assemble-core](https://github.com/assemble/assemble-core) (which uses Base via [templates](https://github.com/jonschlinkert/templates)). This allows updaters to seamlessly run generators from [generate](https://github.com/generate/generate), [assemble](https://github.com/assemble/assemble), or [verb](https://github.com/verbose/verb) (when a file needs to be created, or re-created for example) +* Adds [assemble-loader](https://github.com/assemble/assemble-loader) to support glob patterns in collection methods **v0.5.0** @@ -218,7 +226,7 @@ First stable release! ## Related projects -You might also be interested in these projects: +Update shares a common architecture and plugin ecosystem with the following libraries: * [assemble](https://www.npmjs.com/package/assemble): Assemble is a powerful, extendable and easy to use static site generator for node.js. Used… [more](https://github.com/assemble/assemble) | [homepage](https://github.com/assemble/assemble "Assemble is a powerful, extendable and easy to use static site generator for node.js. Used by thousands of projects for much more than building websites, Assemble is also used for creating themes, scaffolds, boilerplates, e-books, UI components, API docum") * [base](https://www.npmjs.com/package/base): base is the foundation for creating modular, unit testable and highly pluggable node.js applications, starting… [more](https://github.com/node-base/base) | [homepage](https://github.com/node-base/base "base is the foundation for creating modular, unit testable and highly pluggable node.js applications, starting with a handful of common methods, like `set`, `get`, `del` and `use`.") @@ -253,8 +261,4 @@ Released under the [MIT license](https://github.com/update/update/blob/master/LI *** -_This file was generated by [verb](https://github.com/verbose/verb), v0.9.0, on July 02, 2016._ - -[assemble-core]: https://github.com/assemble/assemble-core -[templates]: https://github.com/jonschlinkert/templates -[assemble-loader]: https://github.com/assemble/assemble-loader \ No newline at end of file +_This file was generated by [verb](https://github.com/verbose/verb), v0.9.0, on July 03, 2016._ \ No newline at end of file diff --git a/docs/cli/commands.md b/docs/cli/commands.md index 3c79081..41df6d6 100644 --- a/docs/cli/commands.md +++ b/docs/cli/commands.md @@ -4,7 +4,13 @@ Supported command line flags. ## --run -Force stored tasks to run. Stored tasks are "skipped" if you have an `updatefile.js` in the current working directory. +By default, when an `updatefile.js` exists in the current working directory, the `update` command will only run explicitly specified tasks or, if no tasks are explicitly defined, the `default` task in `updatefile.js`. + +The `--run` flag forces `update` to run stored tasks and the `default` task or explicitly specified tasks in `updatefile.js`. Stored tasks are executed first, in the order defined, then the `default` task or explicitly defined tasks. + +**Default**: `undefined` + +**Example** ```sh $ update --run diff --git a/support/docs/cli.commands.md b/support/docs/cli.commands.md index d37c210..e3fc621 100644 --- a/support/docs/cli.commands.md +++ b/support/docs/cli.commands.md @@ -8,7 +8,13 @@ Supported command line flags. ## --run -Force stored tasks to run. Stored tasks are "skipped" if you have an `updatefile.js` in the current working directory. +By default, when an `updatefile.js` exists in the current working directory, the `update` command will only run explicitly specified tasks or, if no tasks are explicitly defined, the `default` task in `updatefile.js`. + +The `--run` flag forces `update` to run stored tasks and the `default` task or explicitly specified tasks in `updatefile.js`. Stored tasks are executed first, in the order defined, then the `default` task or explicitly defined tasks. + +**Default**: `undefined` + +**Example** ```sh $ update --run From 0f6bc0831c564daf38024a5f444012f95b969a32 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sun, 3 Jul 2016 01:26:31 -0400 Subject: [PATCH 239/274] 0.6.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 23996ed..b49c13e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "update", "description": "Be scalable! Update is a new, open source developer framework and CLI for automating updates of any kind in code projects.", - "version": "0.6.0", + "version": "0.6.1", "homepage": "https://github.com/update/update", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", "repository": "update/update", From fb8e422c79ad69398a7cc7f109c0121ce03b5b10 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 5 Jul 2016 13:59:36 -0400 Subject: [PATCH 240/274] list updaters --- lib/list.js | 22 +++++++++++++++++---- lib/utils.js | 55 +++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 64 insertions(+), 13 deletions(-) diff --git a/lib/list.js b/lib/list.js index 9a04a65..ac2d96b 100644 --- a/lib/list.js +++ b/lib/list.js @@ -1,19 +1,33 @@ 'use strict'; +var path = require('path'); +var strip = require('strip-color'); var through = require('through2'); +var table = require('text-table'); module.exports = function(app) { - var paths = []; + function bold(str) { + return app.log.underline(app.log.bold(str)); + } + var list = [[bold('version'), bold('name'), bold('alias')]]; return through.obj(function(file, enc, next) { - paths.push(file.basename); + var pkgPath = path.resolve(file.path, 'package.json'); + var pkg = require(pkgPath); + list.push([app.log.gray(pkg.version), file.basename, app.log.cyan(file.alias)]); next(); }, function(cb) { console.log(); - console.log('- ' + paths.join('\n- ')); + console.log(table(list, { + stringLength: function(str) { + return strip(str).length; + } + })); + console.log(); - console.log(app.log.magenta(paths.length + ' updaters installed')); + console.log(app.log.magenta(list.length + ' updaters installed')); console.log(); cb(); }); }; + diff --git a/lib/utils.js b/lib/utils.js index d223382..53908ea 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -16,6 +16,7 @@ require('base-generators', 'generators'); require('base-questions', 'questions'); require('base-runtimes', 'runtimes'); require('base-store', 'store'); +require('data-store', 'Store'); require('extend-shallow', 'extend'); require('fs-exists-sync', 'exists'); require('isobject', 'isObject'); @@ -23,6 +24,7 @@ require('is-valid-app', 'isValid'); require('global-modules', 'gm'); require('log-utils', 'log'); require('os-homedir', 'home'); +require('resolve-dir'); require('yargs-parser', 'parse'); require = fn; // eslint-disable-line @@ -103,19 +105,54 @@ utils.arrayify = function(val) { }; /** - * Placeholder logger for updaters + * Add logging methods */ -utils.logger = function(prop, color) { - color = color || 'dim'; - return function(msg) { - var rest = [].slice.call(arguments, 1); - return console.log - .bind(console, utils.log.timestamp, utils.log[prop]) - .apply(console, [utils.log[color](msg), ...rest]); +utils.logger = function(options) { + return function() { + + function logger(prop, color) { + color = color || 'dim'; + return function(msg) { + var rest = [].slice.call(arguments, 1); + return console.log + .bind(console, utils.log.timestamp + (prop ? (' ' + utils.log[prop]) : '')) + .apply(console, [utils.log[color](msg)].concat(rest)); + }; + }; + + Object.defineProperty(this, 'log', { + configurable: true, + get: function() { + function log() { + return console.log.apply(console, arguments); + } + log.path = function(msg) { + return logger(null, 'dim').apply(null, arguments); + }; + log.time = function(msg) { + return logger(null, 'dim').apply(null, arguments); + }; + log.warn = function(msg) { + return logger('warning', 'yellow').apply(null, arguments); + }; + log.success = function() { + return logger('success', 'green').apply(null, arguments); + }; + + log.info = function() { + return logger('info', 'cyan').apply(null, arguments); + }; + + log.error = function() { + return logger('error', 'red').apply(null, arguments); + }; + log.__proto__ = utils.log; + return log; + } + }); }; }; - /** * Expose utils */ From e0dfdf7e094d3265719ef4c85d534edacb8bf253 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 5 Jul 2016 14:01:31 -0400 Subject: [PATCH 241/274] update cli --- index.js | 51 ++++++++++++------------------------------ lib/commands/remove.js | 6 ++--- lib/updatefile.js | 9 +++++--- 3 files changed, 23 insertions(+), 43 deletions(-) diff --git a/index.js b/index.js index 6866c31..19090c3 100644 --- a/index.js +++ b/index.js @@ -58,13 +58,8 @@ Update.prototype.initDefaults = function() { this.define('updater', this.generator); this.updaters = this.generators; - this.option('help', { - configname: 'updater', - appname: 'update' - }); - + this.option('help', {configname: 'updater', appname: 'update'}); this.define('update', this.generate); - this.define('getUpdater', function() { return this.getGenerator.apply(this, arguments); }); @@ -77,6 +72,16 @@ Update.prototype.initDefaults = function() { return /^(updater|generate)?-/.test(name); } + // create `app.globals` store + Object.defineProperty(this, 'globals', { + configurable: true, + get: function() { + return new utils.Store('generate-globals', { + cwd: utils.resolveDir('~/') + }); + } + }); + this.option('lookup', function(name) { var patterns = []; if (!isUpdater(name)) { @@ -119,7 +124,7 @@ Update.prototype.getUpdaters = function(names, options) { updaters = this.pkg.get('update.updaters'); } if (utils.isEmpty(updaters)) { - updaters = this.store.get('updaters'); + updaters = this.globals.get('updaters'); } if (options.remove) { updaters = utils.remove(updaters, utils.toArray(options.remove)); @@ -143,7 +148,7 @@ Update.prototype.addUpdaters = function(names, options) { this.pkg.union('update.updaters', names); } if (options.global) { - this.store.union('updaters', names); + this.globals.union('updaters', names); } }; @@ -153,6 +158,7 @@ Update.prototype.addUpdaters = function(names, options) { */ Update.plugins = function(app) { + app.use(utils.logger()); app.use(utils.generators()); app.use(utils.store('update')); app.use(utils.runtimes()); @@ -184,35 +190,6 @@ Update.resolveTasks = function(app, argv) { return tasks; }; -/** - * Expose logging methods - */ - -Object.defineProperty(Update.prototype, 'log', { - configurable: true, - get: function() { - function log() { - return console.log.apply(console, arguments); - } - log.warn = function(msg) { - return utils.logger('warning', 'yellow').apply(null, arguments); - }; - log.success = function() { - return utils.logger('success', 'green').apply(null, arguments); - }; - - log.info = function() { - return utils.logger('info', 'cyan').apply(null, arguments); - }; - - log.error = function() { - return utils.logger('error', 'red').apply(null, arguments); - }; - log.__proto__ = utils.log; - return log; - } -}); - /** * Expose static `cli` method */ diff --git a/lib/commands/remove.js b/lib/commands/remove.js index b86ad5c..f93cdd2 100644 --- a/lib/commands/remove.js +++ b/lib/commands/remove.js @@ -31,9 +31,9 @@ module.exports = function(app, options) { } if (config.global) { - updaters = app.store.get('updaters') || []; - app.store.del('updaters'); - app.store.set('updaters', utils.remove(updaters, names)); + updaters = app.globals.get('updaters') || []; + app.globals.del('updaters'); + app.globals.set('updaters', utils.remove(updaters, names)); } if (config.config || !config.global) { diff --git a/lib/updatefile.js b/lib/updatefile.js index 5aa4905..e10b3fd 100644 --- a/lib/updatefile.js +++ b/lib/updatefile.js @@ -45,7 +45,7 @@ module.exports = function(app, base) { app.task('list', { silent: true }, function() { return app.src([gm('updater-*'), cwd('node_modules/updater-*')]) .pipe(through.obj(function(file, enc, next) { - file.basename = app.toAlias(file.basename); + file.alias = app.toAlias(file.basename); next(null, file); })) .pipe(list(app)); @@ -81,7 +81,10 @@ module.exports = function(app, base) { app.task('show', { silent: true }, function(cb) { argv._ = []; - app.log.success('queued tasks:', Update.resolveTasks(app, argv)); + var list = Update.resolveTasks(app, argv); + console.log(); + console.log(' queued updaters:', `\n · ` + list.join('\n · ')); + console.log(); cb(); }); @@ -111,7 +114,7 @@ function save(app, list) { if (app.options.c || app.options.config) { app.pkg.set('update.updaters', list); } else { - app.store.set('updaters', list); + app.globals.set('updaters', list); } var suffix = list.length > 1 ? 'updaters are' : 'updater is'; From b631aa2399f6e9b1827b12e4733419e4db3355ca Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 5 Jul 2016 14:01:43 -0400 Subject: [PATCH 242/274] features --- docs/features.md | 1 - support/docs/tasks.md | 7 +++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/features.md b/docs/features.md index 60e9e14..304b15e 100644 --- a/docs/features.md +++ b/docs/features.md @@ -3,7 +3,6 @@ Update offers an elegant and robust suite of methods, carefully organized to help you accomplish common activities in less time, including: * **unparalleled flow control**: through the use of [updaters](https://github.com/update/getting-started), [sub-updaters](https://github.com/update/getting-started) and [tasks](https://github.com/update/getting-started) -* **templates, scaffolds and boilerplates**: update a single file, initialize an entire project, or provide ad-hoc "components" throughout the duration of a project using any combination of [templates, scaffolds and boilerplates](#templates-scaffolds-and-boilerplates) * **any engine**: use any template engine to render templates, including [handlebars](http://www.handlebarsjs.com/), [lodash](https://lodash.com/), [swig](https://github.com/paularmstrong/swig) and [pug](http://jade-lang.com) * **prompts**: asks you for data when it can't find what it needs, and it's easy to customize prompts for any data you want * **data**: gathers data from the user's environment to populate "hints" in user prompts and to use when rendering templates diff --git a/support/docs/tasks.md b/support/docs/tasks.md index d912bc3..09e3149 100644 --- a/support/docs/tasks.md +++ b/support/docs/tasks.md @@ -12,6 +12,13 @@ Tasks are used for wrapping code that should be executed at a later point, eithe Tasks are asynchronous functions that are registered by name using the `.task` method, and can be run using the `.build` method. +```js +app.task('foo', function(cb) { + // since tasks are asynchronous, you must call the callback when the task is complete + cb(); +}); +``` + ## Running tasks Tasks can be run by command line or API. From 1228c4086a33535e38d0edb2a93a74386aca6085 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 5 Jul 2016 16:20:24 -0400 Subject: [PATCH 243/274] fix link linting --- .verb.md | 11 ++++-- docs/api/updater.md | 8 ++++ docs/features.md | 1 + docs/tasks.md | 9 ++++- package.json | 4 ++ support/assemblefile.js | 5 ++- support/docs/api.updater.md | 2 + support/docs/layouts/default.md | 7 +++- support/lib/helpers.js | 14 ++++--- support/lib/middleware.js | 1 + support/lib/plugins.js | 2 +- support/lib/utils.js | 65 ++++++++++++++++++++------------- 12 files changed, 90 insertions(+), 39 deletions(-) diff --git a/.verb.md b/.verb.md index a353fad..f0c6fa2 100644 --- a/.verb.md +++ b/.verb.md @@ -1,11 +1,14 @@ Please read our [contributing guide](.github/contributing.md) if you'd like to learn more about contributing to this project. -## What does it do? +## About + +### What does it do? All updating is accomplished using plugins called _updaters_, which are run by command line or API, and can be installed globally, locally, or created in a local `updatefile.js`. You can create your own [updaters](docs/updaters.md) using Update's API, or install updaters using [npm](https://www.npmjs.com/), to do things like: +* create continuity and consistency across projects * enforce conventions across all of your projects * instantly update an old or inherited project to your latest personal preferences (convert tabs to spaces, convert from `jshint` to `eslint` or the other way around, or any other detail) * reformat code to meet your standards @@ -16,14 +19,14 @@ You can create your own [updaters](docs/updaters.md) using Update's API, or inst - update settings in [runtime config](https://github.com/update/updater-eslint) files, preferences in [dotfiles](https://github.com/update/updater-editorconfig) * after initializing a new project with a project generator, like [generate][] or Google's Yeoman, you can "normalize" all of the generated files to use your own preferences -## Why update? +### Why update? - **be more productive**: Update makes you more productive by eliminating time spent on things that _can be automated_, but typically aren't since they don't need to be done often, don't fit into the build cycle or a project's deliverables, or they're usually updated by hand. As code projects mature, time spent on these things tend to stay linear or increase as the size of a community grows. And this only compounds with each new project under your stewardship. - **your way, instantly**: updaters can be published to and installed from npm, but you can also easily create your own [personal updaters](docs/symlinking-updaters.md). Once your updaters are setup, just run `update init`, then projects under your maintenance will convert to the the conventions you prefer within milliseconds after running `update`. - **plugin ecosystem**: any plugins that work with [Base applications](#discovering-plugins) will work also with Update. Which means you can use plugins (or generators) from [assemble][], [verb][], and [generate][], to name a few. - **well tested**: with more than 1,200 unit tests -## Who should use Update? +### Who should use Update? * anyone who cares about having consistency across all of their projects * developers or organizations with many projects under their stewardship @@ -134,7 +137,7 @@ $ update help * **unparalleled flow control**: through the use of [updaters](docs/updaters.md), [sub-updaters][getting-started] and [tasks](docs/tasks.md) * **generators**: support for [generate][] generators. If your updater needs to create new files, there might be a [generator for that](https://www.npmjs.com/browse/keyword/generate-generator). Just use the generator the same way you would use an [updater](docs/updaters.md). * **render templates**: use templates to create new files, or replace existing files -* **prompts**: It's easy to create custom prompts. Answers to prompts can be used as context for rendering templates, for settings options, determining file names, directory structure, and anything else that requires use feedback. +* **prompts**: It's easy to create custom prompts. Answers to prompts can be used as context for rendering templates, for settings options, determining file names, directory structure, and anything else that requires user feedback. * **any engine**: use any template engine to render templates, including [handlebars][], [lodash][], [swig][] and [pug][], or anything supported by [consolidate][]. * **data**: gather data from the user's environment to populate "hints" in user prompts or for rendering templates * **fs**: in the spirit of [gulp][], use `.src` and `.dest` to read and write globs of files. diff --git a/docs/api/updater.md b/docs/api/updater.md index d9ae2ce..5bd5a88 100644 --- a/docs/api/updater.md +++ b/docs/api/updater.md @@ -29,6 +29,14 @@ app.update('bar', function(err) { ## Related +**Docs** + +* [faq](../faq.md) + +**CLI** + +* [commands](../cli/commands.md) + **API** * [register](register.md) diff --git a/docs/features.md b/docs/features.md index 304b15e..60e9e14 100644 --- a/docs/features.md +++ b/docs/features.md @@ -3,6 +3,7 @@ Update offers an elegant and robust suite of methods, carefully organized to help you accomplish common activities in less time, including: * **unparalleled flow control**: through the use of [updaters](https://github.com/update/getting-started), [sub-updaters](https://github.com/update/getting-started) and [tasks](https://github.com/update/getting-started) +* **templates, scaffolds and boilerplates**: update a single file, initialize an entire project, or provide ad-hoc "components" throughout the duration of a project using any combination of [templates, scaffolds and boilerplates](#templates-scaffolds-and-boilerplates) * **any engine**: use any template engine to render templates, including [handlebars](http://www.handlebarsjs.com/), [lodash](https://lodash.com/), [swig](https://github.com/paularmstrong/swig) and [pug](http://jade-lang.com) * **prompts**: asks you for data when it can't find what it needs, and it's easy to customize prompts for any data you want * **data**: gathers data from the user's environment to populate "hints" in user prompts and to use when rendering templates diff --git a/docs/tasks.md b/docs/tasks.md index 8cfc923..117b52d 100644 --- a/docs/tasks.md +++ b/docs/tasks.md @@ -19,6 +19,13 @@ Tasks are used for wrapping code that should be executed at a later point, eithe Tasks are asynchronous functions that are registered by name using the `.task` method, and can be run using the `.build` method. +```js +app.task('foo', function(cb) { + // since tasks are asynchronous, you must call the callback when the task is complete + cb(); +}); +``` + ## Running tasks Tasks can be run by command line or API. @@ -169,6 +176,6 @@ app.build(function(err) { **Docs** -* [updaters](updaters.md#running-updaters) +* [running-updaters](updaters.md#running-updaters) * [updaters](updaters.md) * [updatefile](updatefile.md) diff --git a/package.json b/package.json index b49c13e..c668a87 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "base-questions": "^0.6.6", "base-runtimes": "^0.1.11", "base-store": "^0.4.4", + "data-store": "^0.16.0", "export-files": "^2.1.1", "extend-shallow": "^2.0.1", "find-pkg": "^0.1.2", @@ -48,8 +49,11 @@ "lazy-cache": "^2.0.1", "log-utils": "^0.1.4", "os-homedir": "^1.0.1", + "resolve-dir": "^0.1.0", "resolve-file": "^0.2.0", "set-blocking": "^2.0.0", + "strip-color": "^0.1.0", + "text-table": "^0.2.0", "through2": "^2.0.1", "yargs-parser": "^2.4.0" }, diff --git a/support/assemblefile.js b/support/assemblefile.js index cc0d92a..44c2c13 100644 --- a/support/assemblefile.js +++ b/support/assemblefile.js @@ -23,6 +23,9 @@ module.exports = function(app) { .pipe(lib.plugins.buildPaths(dest)) .pipe(lib.plugins.lintPaths(dest)) .pipe(app.renderFile()) - .pipe(app.dest(dest)); + .pipe(app.dest(function(file) { + // file.path = path.join(file.dirname, file.stem, 'index.html'); + return dest; + })); }); }; diff --git a/support/docs/api.updater.md b/support/docs/api.updater.md index 9619bdd..287e16d 100644 --- a/support/docs/api.updater.md +++ b/support/docs/api.updater.md @@ -1,7 +1,9 @@ --- title: Updater related: + cli: ['commands'] api: ['register', 'plugins'] + doc: ['faq'] --- Register an updater function by name. Similar to [.register](register.md) but immediately invokes the updater function upon registering it. diff --git a/support/docs/layouts/default.md b/support/docs/layouts/default.md index 3cc8261..6ef541f 100644 --- a/support/docs/layouts/default.md +++ b/support/docs/layouts/default.md @@ -2,12 +2,15 @@ {% body %} -<%= hasAny([related.doc, related.api, related.url], '## Related') %> +<%= hasAny([related.doc, related.api, related.url, related.cli], '## Related') %> <%= hasValue(related.doc, '**Docs**') %> <%= links(related, 'doc') %> +<%= hasValue(related.cli, '**CLI**') %> +<%= links(related, 'cli') %> + <%= hasValue(related.api, '**API**') %> <%= links(related, 'api') %> <%= hasValue(related.url, '**Links**') %> -<%= links(related, 'url') %> \ No newline at end of file +<%= links(related, 'url') %> diff --git a/support/lib/helpers.js b/support/lib/helpers.js index 14d0e75..0087d62 100644 --- a/support/lib/helpers.js +++ b/support/lib/helpers.js @@ -48,19 +48,23 @@ module.exports = function(options) { function createLink(dir, prop, link) { var key = (prop === 'doc') ? 'docs' : prop; + var filename = link; var name = link; var anchor = ''; var segs = link.split('#'); - if (segs.length > 1) { - name = segs.shift(); - anchor = '#' + segs.pop(); + if (segs.length === 2) { + name = segs[segs.length - 1]; + anchor = '#' + name; + filename = segs[0]; } - var filename = name; + if (!/\.md$/.test(filename) && !/#/.test(filename)) { filename += '.md'; } if (dir !== key) { - if (key === 'docs') key = ''; + if (key === 'docs') { + key = ''; + } filename = path.join('..', key, filename); } else if (key !== 'docs' && dir !== key) { filename = path.join(key, filename); diff --git a/support/lib/middleware.js b/support/lib/middleware.js index 2cf44f1..5dac7d2 100644 --- a/support/lib/middleware.js +++ b/support/lib/middleware.js @@ -13,6 +13,7 @@ module.exports = function(options) { var related = view.data.related || (view.data.related = {}); related.doc = utils.arrayify(related.doc); related.api = utils.arrayify(related.api); + related.cli = utils.arrayify(related.cli); related.url = utils.arrayify(related.url); if (view.content.indexOf('') !== -1) { diff --git a/support/lib/plugins.js b/support/lib/plugins.js index 18e0bdf..c2e515d 100644 --- a/support/lib/plugins.js +++ b/support/lib/plugins.js @@ -74,7 +74,7 @@ function getAnchors(file, paths) { function getLinks(file, paths) { var md = new utils.Remarkable({paths: paths, file: file}); md.use(utils.prettify); - md.use(utils.lintLinks); + md.use(utils.lintLinks()); md.render(file.content); } diff --git a/support/lib/utils.js b/support/lib/utils.js index a9b8fa2..37aa8be 100644 --- a/support/lib/utils.js +++ b/support/lib/utils.js @@ -2,6 +2,7 @@ var utils = module.exports = require('lazy-cache')(require); var rules = require('pretty-remarkable/lib/rules'); + var fn = require; require = utils; @@ -35,12 +36,18 @@ utils.slugify = function(anchor) { }; utils.links = function(rules) { + if (rules._added) return; + rules._added = true; var open = rules.link_open; rules.link_open = function(tokens, idx, options, env) { open.apply(rules, arguments); var token = tokens[idx]; + if (/[.\\\/]+issues/.test(token.href)) { + return; + } + if (options.paths && !/http/.test(token.href)) { - var href = token.href.replace(/^[.\\\/]+/, '').split(/[\\\/]+/).join('/'); + var href = token.href.replace(/^\.?[\\\/]+/, '').split(/[\\\/]+/).join('/'); var paths = options.paths; var anchors = paths.anchors; var file = options.file; @@ -54,12 +61,20 @@ utils.links = function(rules) { } } else { var segs = href.split('/'); - if (segs.length === 1) { + console.log(segs) + var len = segs.length; + if (len === 1) { segs.unshift(file.dir); + } else if (len === 2 && segs[0] === '..') { + segs[0] = 'docs'; + } else if (len > 2 && segs[0] === '..') { + segs.shift(); } + var seg = segs.shift(); var rest = segs.join('/'); var group = paths[seg]; + if (typeof group === 'undefined') { throw new Error('directory group: ' + seg + ' is not defined'); } @@ -73,32 +88,32 @@ utils.links = function(rules) { return rules; }; -utils.lintLinks = function(md) { - md.renderer.renderInline = function(tokens, options, env) { - utils.links(rules); - var _rules = rules; - var len = tokens.length, i = 0; - var str = ''; +utils.lintLinks = function(options) { + utils.links(rules); - while (len--) { - str += _rules[tokens[i].type](tokens, i++, options, env, this); - } - return str; - }; + return function(md) { + md.renderer.renderInline = function(tokens, options, env) { + var len = tokens.length, i = 0; + var str = ''; - md.renderer.render = function(tokens, options, env) { - utils.links(rules); - var _rules = rules; - var len = tokens.length, i = -1; - var str = ''; + while (len--) { + str += rules[tokens[i].type](tokens, i++, options, env, this); + } + return str; + }; - while (++i < len) { - if (tokens[i].type === 'inline') { - str += this.renderInline(tokens[i].children, options, env); - } else { - str += _rules[tokens[i].type](tokens, i, options, env, this); + md.renderer.render = function(tokens, options, env) { + var len = tokens.length, i = -1; + var str = ''; + + while (++i < len) { + if (tokens[i].type === 'inline') { + str += this.renderInline(tokens[i].children, options, env); + } else { + str += rules[tokens[i].type](tokens, i, options, env, this); + } } - } - return str; + return str; + }; }; }; From 5277838b227b1b37d580019be912d90dd7f46e7c Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 5 Jul 2016 16:28:40 -0400 Subject: [PATCH 244/274] update deps --- package.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index c668a87..1ced678 100644 --- a/package.json +++ b/package.json @@ -29,13 +29,13 @@ }, "dependencies": { "arr-union": "^3.1.0", - "assemble-core": "^0.23.0", + "assemble-core": "^0.25.0", "assemble-loader": "^0.6.1", - "base-cli-process": "^0.1.13", + "base-cli-process": "^0.1.14", "base-config-process": "^0.1.7", - "base-generators": "^0.4.1", + "base-generators": "^0.4.4", "base-questions": "^0.6.6", - "base-runtimes": "^0.1.11", + "base-runtimes": "^0.2.0", "base-store": "^0.4.4", "data-store": "^0.16.0", "export-files": "^2.1.1", @@ -60,14 +60,14 @@ "devDependencies": { "base-runner": "^0.8.2", "base-test-runner": "^0.2.0", - "base-test-suite": "^0.1.11", + "base-test-suite": "^0.1.12", "cross-spawn": "^4.0.0", "generate-foo": "^0.1.5", "graceful-fs": "^4.1.4", "gulp": "^3.9.1", - "gulp-eslint": "^2.0.0", + "gulp-eslint": "^3.0.1", "gulp-format-md": "^0.1.9", - "gulp-istanbul": "^0.10.4", + "gulp-istanbul": "^1.0.0", "gulp-mocha": "^2.2.0", "gulp-unused": "^0.1.2", "is-absolute": "^0.2.5", @@ -77,7 +77,7 @@ "resolve": "^1.1.7", "should": "^9.0.2", "sinon": "^1.17.4", - "updater-example": "^0.1.1" + "updater-example": "^0.1.2" }, "keywords": [ "convention", From 5b71fc56dd31a7f57e89f2bbd7a9e20979874b50 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 5 Jul 2016 18:51:39 -0400 Subject: [PATCH 245/274] use utils for parsing --- lib/cli.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cli.js b/lib/cli.js index 962d911..ee94c2a 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -6,7 +6,7 @@ var path = require('path'); var find = require('find-pkg'); var log = require('log-utils'); var utils = require('./utils'); -var argv = require('yargs-parser')(process.argv.slice(2), utils.opts); +var argv = utils.parseArgs(process.argv.slice(2)); module.exports = function(Update, config, cb) { if (typeof cb !== 'function') { From a8ba4a9e6b6531d892fc92a06253d81847621a54 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 5 Jul 2016 18:51:48 -0400 Subject: [PATCH 246/274] update docs --- .verb.md | 67 +++++++++++++++++++------------------- README.md | 97 ++++++++++++++++++++++++++++--------------------------- 2 files changed, 83 insertions(+), 81 deletions(-) diff --git a/.verb.md b/.verb.md index f0c6fa2..1a271bf 100644 --- a/.verb.md +++ b/.verb.md @@ -1,8 +1,9 @@ Please read our [contributing guide](.github/contributing.md) if you'd like to learn more about contributing to this project. -## About +You might also be interested in [generate](https://github.com/generate/generate). + -### What does it do? +## What does Update do? All updating is accomplished using plugins called _updaters_, which are run by command line or API, and can be installed globally, locally, or created in a local `updatefile.js`. @@ -19,24 +20,20 @@ You can create your own [updaters](docs/updaters.md) using Update's API, or inst - update settings in [runtime config](https://github.com/update/updater-eslint) files, preferences in [dotfiles](https://github.com/update/updater-editorconfig) * after initializing a new project with a project generator, like [generate][] or Google's Yeoman, you can "normalize" all of the generated files to use your own preferences -### Why update? -- **be more productive**: Update makes you more productive by eliminating time spent on things that _can be automated_, but typically aren't since they don't need to be done often, don't fit into the build cycle or a project's deliverables, or they're usually updated by hand. As code projects mature, time spent on these things tend to stay linear or increase as the size of a community grows. And this only compounds with each new project under your stewardship. +## Why should I use Update? + +- **be more productive**: Update eliminates time spent on things that _can be automated_, but typically aren't since they either don't need to be done often, don't fit into the build cycle or a project's deliverables, or because they're usually updated manually. As code projects mature, time spent on these things tend to stay linear or increase as the size of a community grows. If you maintain more than a handful of projects, time spent on these things compounds with each new project under your stewardship. - **your way, instantly**: updaters can be published to and installed from npm, but you can also easily create your own [personal updaters](docs/symlinking-updaters.md). Once your updaters are setup, just run `update init`, then projects under your maintenance will convert to the the conventions you prefer within milliseconds after running `update`. - **plugin ecosystem**: any plugins that work with [Base applications](#discovering-plugins) will work also with Update. Which means you can use plugins (or generators) from [assemble][], [verb][], and [generate][], to name a few. -- **well tested**: with more than 1,200 unit tests +- **well tested**: with more than 1,250 unit tests -### Who should use Update? -* anyone who cares about having consistency across all of their projects -* developers or organizations with many projects under their stewardship -* agencies or consultants who maintain and/or create client projects and would like to reduce time spent on maintainance +## Usage -## Getting started +### Install update -The following instructions are intended to provide a basic demonstration of how Update works. Visit the links after this section for more information. - -**1. Install update** +**Install update** To use Update's CLI, `update` must first be installed globally with [npm](https://www.npmjs.com/): @@ -46,19 +43,20 @@ $ npm install --global update This adds the `update` command to your system path, allowing it to be run from anywhere. -**2. Install an "updater"** -To see how updaters work, install `updater-example`: +### Install an updater + +Updaters can be [found on npm](https://www.npmjs.com/browse/keyword/update-updater), but if you're not familiar with how Update works, we recommend installing `updater-example`: ```sh $ npm install --global updater-example ``` -**3. Create "example.txt"** +**Create "example.txt"** In the current working directory, create an empty file named `example.txt`. -**4. Run** +**Run** As a habit, when using `update` make sure your work is committed, then run: @@ -68,15 +66,8 @@ $ update example This appends the string `foo` to the contents of `example.txt`. Visit the [updater-example][] project for additional steps and guidance. -**Next steps** - -- Browse the [documentation](docs) -- Learn about [updaters](docs/updaters.md) -- Learn about the [built-in updaters](docs/cli/built-in-updaters.md) -- Learn more about [base][] -- Get [Sublime Text Snippets][snippets] for creating tasks and updaters -## Init +### Init Tell Update's CLI to automatically run certain updaters every time the `update` command is given: @@ -86,6 +77,8 @@ $ update init You can run this command whenever you want to update your preferences, like after installing new updaters. +### Help + ```console $ update help @@ -147,12 +140,14 @@ $ update help * **stores**: persist configuration settings, global defaults, project-specific defaults, answers to prompts, and so on. * much more! -## Discovering updaters +## Updaters + +### Discovering updaters * Find updaters to install by [searching npm](https://www.npmjs.com/browse/keyword/update-updater) for packages with the keyword `updateupdater` * Visit [Update's GitHub org](https://github.com/update) to see the updaters maintained by the core team -## Discovering plugins +### Discovering plugins Plugins from any applications built on [base][] should work with Update (and can be used in your updater): @@ -163,17 +158,23 @@ Plugins from any applications built on [base][] should work with Update (and can * [update][update-plugin]: find update plugins on npm using the `updateplugin` keyword * [verb][verb-plugin]: find verb plugins on npm using the `verbplugin` keyword -## Authoring updaters +### Authoring updaters Visit the [updater documentation](docs/updaters.md) guide to learn how to use, author and publish updaters. ## More information -* [Documentation](docs) -* [API documentation](docs/api) -* [Updaters maintained by the core team](https://github.com/update) +- See the [updaters maintained by the core team](https://github.com/update) +- Browse the [documentation](docs) +- Browse the [API documentation](docs/api) +- Learn about [updaters](docs/updaters.md) +- Learn about the [built-in updaters](docs/cli/built-in-updaters.md) +- Learn more about [base][] +- Get [Sublime Text Snippets][snippets] for creating tasks and updaters + +## About -## Community +### Community Are you using Update in your project? Have you published an [updater](docs/updaters.md) and want to share your Update project with the world? Here are some suggestions: @@ -182,7 +183,7 @@ Are you using Update in your project? Have you published an [updater](docs/updat * **Gitter** Discuss Update with us on [Gitter](https://gitter.im/update) * If you publish an updater, thank you! To make your project as discoverable as possible, please add the keyword `updateupdater` to package.json. -## History +### History {%= doc('CHANGELOG.md') %} [getting-started]: https://github.com/update/getting-started diff --git a/README.md b/README.md index 8e262b2..fb63e62 100644 --- a/README.md +++ b/README.md @@ -8,22 +8,24 @@ Be scalable! Update is a new, open source developer framework and CLI for automating updates of any kind in code projects. -You might also be interested in [generate](https://github.com/generate/generate). - ## TOC -- [What does it do?](#what-does-it-do) -- [Why update?](#why-update) -- [Who should use Update?](#who-should-use-update) -- [Getting started](#getting-started) -- [Init](#init) +- [What does Update do?](#what-does-update-do) +- [Why should I use Update?](#why-should-i-use-update) +- [Usage](#usage) + * [Install update](#install-update) + * [Install an updater](#install-an-updater) + * [Init](#init) + * [Help](#help) - [Features](#features) -- [Discovering updaters](#discovering-updaters) -- [Discovering plugins](#discovering-plugins) -- [Authoring updaters](#authoring-updaters) +- [Updaters](#updaters) + * [Discovering updaters](#discovering-updaters) + * [Discovering plugins](#discovering-plugins) + * [Authoring updaters](#authoring-updaters) - [More information](#more-information) -- [Community](#community) -- [History](#history) +- [About](#about) + * [Community](#community) + * [History](#history) - [Related projects](#related-projects) - [Contributing](#contributing) - [Running tests](#running-tests) @@ -34,12 +36,15 @@ _(TOC generated by [verb](https://github.com/verbose/verb) using [markdown-toc]( Please read our [contributing guide](.github/contributing.md) if you'd like to learn more about contributing to this project. -## What does it do? +You might also be interested in [generate](https://github.com/generate/generate). + +## What does Update do? All updating is accomplished using plugins called _updaters_, which are run by command line or API, and can be installed globally, locally, or created in a local `updatefile.js`. You can create your own [updaters](docs/updaters.md) using Update's API, or install updaters using [npm](https://www.npmjs.com/), to do things like: +* create continuity and consistency across projects * enforce conventions across all of your projects * instantly update an old or inherited project to your latest personal preferences (convert tabs to spaces, convert from `jshint` to `eslint` or the other way around, or any other detail) * reformat code to meet your standards @@ -50,24 +55,18 @@ You can create your own [updaters](docs/updaters.md) using Update's API, or inst - update settings in [runtime config](https://github.com/update/updater-eslint) files, preferences in [dotfiles](https://github.com/update/updater-editorconfig) * after initializing a new project with a project generator, like [generate](https://github.com/generate/generate) or Google's Yeoman, you can "normalize" all of the generated files to use your own preferences -## Why update? +## Why should I use Update? -* **be more productive**: Update makes you more productive by eliminating time spent on things that _can be automated_, but typically aren't since they don't need to be done often, don't fit into the build cycle or a project's deliverables, or they're usually updated by hand. As code projects mature, time spent on these things tend to stay linear or increase as the size of a community grows. And this only compounds with each new project under your stewardship. +* **be more productive**: Update eliminates time spent on things that _can be automated_, but typically aren't since they either don't need to be done often, don't fit into the build cycle or a project's deliverables, or because they're usually updated manually. As code projects mature, time spent on these things tend to stay linear or increase as the size of a community grows. If you maintain more than a handful of projects, time spent on these things compounds with each new project under your stewardship. * **your way, instantly**: updaters can be published to and installed from npm, but you can also easily create your own [personal updaters](docs/symlinking-updaters.md). Once your updaters are setup, just run `update init`, then projects under your maintenance will convert to the the conventions you prefer within milliseconds after running `update`. * **plugin ecosystem**: any plugins that work with [Base applications](#discovering-plugins) will work also with Update. Which means you can use plugins (or generators) from [assemble](https://github.com/assemble/assemble), [verb](https://github.com/verbose/verb), and [generate](https://github.com/generate/generate), to name a few. -* **well tested**: with more than 1,200 unit tests - -## Who should use Update? - -* anyone who cares about having consistency across all of their projects -* developers or organizations with many projects under their stewardship -* agencies or consultants who maintain and/or create client projects and would like to reduce time spent on maintainance +* **well tested**: with more than 1,250 unit tests -## Getting started +## Usage -The following instructions are intended to provide a basic demonstration of how Update works. Visit the links after this section for more information. +### Install update -**1. Install update** +**Install update** To use Update's CLI, `update` must first be installed globally with [npm](https://www.npmjs.com/): @@ -77,19 +76,19 @@ $ npm install --global update This adds the `update` command to your system path, allowing it to be run from anywhere. -**2. Install an "updater"** +### Install an updater -To see how updaters work, install `updater-example`: +Updaters can be [found on npm](https://www.npmjs.com/browse/keyword/update-updater), but if you're not familiar with how Update works, we recommend installing `updater-example`: ```sh $ npm install --global updater-example ``` -**3. Create "example.txt"** +**Create "example.txt"** In the current working directory, create an empty file named `example.txt`. -**4. Run** +**Run** As a habit, when using `update` make sure your work is committed, then run: @@ -99,15 +98,7 @@ $ update example This appends the string `foo` to the contents of `example.txt`. Visit the [updater-example](https://github.com/update/updater-example) project for additional steps and guidance. -**Next steps** - -* Browse the [documentation](docs) -* Learn about [updaters](docs/updaters.md) -* Learn about the [built-in updaters](docs/cli/built-in-updaters.md) -* Learn more about [base](https://github.com/node-base/base) -* Get [Sublime Text Snippets](https://github.com/node-base/sublime-text-base-snippets) for creating tasks and updaters - -## Init +### Init Tell Update's CLI to automatically run certain updaters every time the `update` command is given: @@ -117,6 +108,8 @@ $ update init You can run this command whenever you want to update your preferences, like after installing new updaters. +### Help + ```console $ update help @@ -168,7 +161,7 @@ $ update help * **unparalleled flow control**: through the use of [updaters](docs/updaters.md), [sub-updaters](https://github.com/update/getting-started) and [tasks](docs/tasks.md) * **generators**: support for [generate](https://github.com/generate/generate) generators. If your updater needs to create new files, there might be a [generator for that](https://www.npmjs.com/browse/keyword/generate-generator). Just use the generator the same way you would use an [updater](docs/updaters.md). * **render templates**: use templates to create new files, or replace existing files -* **prompts**: It's easy to create custom prompts. Answers to prompts can be used as context for rendering templates, for settings options, determining file names, directory structure, and anything else that requires use feedback. +* **prompts**: It's easy to create custom prompts. Answers to prompts can be used as context for rendering templates, for settings options, determining file names, directory structure, and anything else that requires user feedback. * **any engine**: use any template engine to render templates, including [handlebars](http://www.handlebarsjs.com/), [lodash](https://lodash.com/), [swig](https://github.com/paularmstrong/swig) and [pug](http://jade-lang.com), or anything supported by [consolidate](https://github.com/visionmedia/consolidate.js). * **data**: gather data from the user's environment to populate "hints" in user prompts or for rendering templates * **fs**: in the spirit of [gulp](http://gulpjs.com), use `.src` and `.dest` to read and write globs of files. @@ -178,12 +171,14 @@ $ update help * **stores**: persist configuration settings, global defaults, project-specific defaults, answers to prompts, and so on. * much more! -## Discovering updaters +## Updaters + +### Discovering updaters * Find updaters to install by [searching npm](https://www.npmjs.com/browse/keyword/update-updater) for packages with the keyword `updateupdater` * Visit [Update's GitHub org](https://github.com/update) to see the updaters maintained by the core team -## Discovering plugins +### Discovering plugins Plugins from any applications built on [base](https://github.com/node-base/base) should work with Update (and can be used in your updater): @@ -194,17 +189,23 @@ Plugins from any applications built on [base](https://github.com/node-base/base) * [update][update-plugin]: find update plugins on npm using the `updateplugin` keyword * [verb](https://www.npmjs.com/browse/keyword/verbplugin): find verb plugins on npm using the `verbplugin` keyword -## Authoring updaters +### Authoring updaters Visit the [updater documentation](docs/updaters.md) guide to learn how to use, author and publish updaters. ## More information -* [Documentation](docs) -* [API documentation](docs/api) -* [Updaters maintained by the core team](https://github.com/update) +* See the [updaters maintained by the core team](https://github.com/update) +* Browse the [documentation](docs) +* Browse the [API documentation](docs/api) +* Learn about [updaters](docs/updaters.md) +* Learn about the [built-in updaters](docs/cli/built-in-updaters.md) +* Learn more about [base](https://github.com/node-base/base) +* Get [Sublime Text Snippets](https://github.com/node-base/sublime-text-base-snippets) for creating tasks and updaters + +## About -## Community +### Community Are you using Update in your project? Have you published an [updater](docs/updaters.md) and want to share your Update project with the world? Here are some suggestions: @@ -213,7 +214,7 @@ Are you using Update in your project? Have you published an [updater](docs/updat * **Gitter** Discuss Update with us on [Gitter](https://gitter.im/update) * If you publish an updater, thank you! To make your project as discoverable as possible, please add the keyword `updateupdater` to package.json. -## History +### History **v0.6.0** @@ -261,4 +262,4 @@ Released under the [MIT license](https://github.com/update/update/blob/master/LI *** -_This file was generated by [verb](https://github.com/verbose/verb), v0.9.0, on July 03, 2016._ \ No newline at end of file +_This file was generated by [verb](https://github.com/verbose/verb), v0.9.0, on July 05, 2016._ \ No newline at end of file From 25087a125849bcbb08063b7fd1d5134493ff5ec6 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Tue, 5 Jul 2016 18:51:55 -0400 Subject: [PATCH 247/274] 0.6.2 --- package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 1ced678..3c38da1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "update", "description": "Be scalable! Update is a new, open source developer framework and CLI for automating updates of any kind in code projects.", - "version": "0.6.1", + "version": "0.6.2", "homepage": "https://github.com/update/update", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", "repository": "update/update", @@ -100,7 +100,7 @@ }, "verb": { "run": true, - "toc": false, + "toc": true, "layout": "common-minimal", "tasks": [ "readme" @@ -109,7 +109,6 @@ "gulp-format-md" ], "related": { - "highlight": "generate", "description": "Update shares a common architecture and plugin ecosystem with the following libraries:", "list": [ "assemble", From 11859d66954f457ca3aa1aa91b2de407c2c4822b Mon Sep 17 00:00:00 2001 From: Brian Woodward Date: Wed, 6 Jul 2016 11:33:11 -0400 Subject: [PATCH 248/274] deps --- support/lib/utils.js | 2 +- support/package.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/support/lib/utils.js b/support/lib/utils.js index 37aa8be..98e3968 100644 --- a/support/lib/utils.js +++ b/support/lib/utils.js @@ -8,6 +8,7 @@ require = utils; require('is-valid-app', 'isValid'); require('has-value'); +require('pascalcase'); require('pretty-remarkable', 'prettify'); require('remarkable', 'Remarkable'); require('strip-color'); @@ -61,7 +62,6 @@ utils.links = function(rules) { } } else { var segs = href.split('/'); - console.log(segs) var len = segs.length; if (len === 1) { segs.unshift(file.dir); diff --git a/support/package.json b/support/package.json index 59dc55e..72babd7 100644 --- a/support/package.json +++ b/support/package.json @@ -27,6 +27,7 @@ "has-value": "^0.3.1", "is-valid-app": "^0.2.0", "memoize-path": "^0.1.2", + "pascalcase": "^0.1.1", "pretty-remarkable": "^0.3.6", "remarkable": "^1.6.2", "set-blocking": "^2.0.0", From 79af64ad38d0b8d8ea1b9b54dd1fdbcc0a24c1db Mon Sep 17 00:00:00 2001 From: Brian Woodward Date: Wed, 6 Jul 2016 11:34:06 -0400 Subject: [PATCH 249/274] relatedLinks helper Takes the `related` object and builds the related links list based on keys on the object. --- support/lib/helpers.js | 55 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/support/lib/helpers.js b/support/lib/helpers.js index 0087d62..1624dd8 100644 --- a/support/lib/helpers.js +++ b/support/lib/helpers.js @@ -24,6 +24,14 @@ module.exports = function(options) { return ''; }); + app.helper('relatedLinks', function(related) { + if (!related || typeof related !== 'object') { + return ''; + } + var links = app.getHelper('links').bind(this); + return relatedLinks(related, links); + }); + app.helper('links', function(related, prop) { var arr = related[prop] || (related[prop] = []); arr = utils.arrayify(arr); @@ -45,6 +53,32 @@ module.exports = function(options) { }; }; +function relatedLinks(related, links) { + var keys = Object.keys(related); + if (keys.length === 0) { + return ''; + } + + var hasLinks = false; + function reduce(acc, key) { + if (related[key].length === 0) { + return acc; + } + + hasLinks = true; + acc += `**${heading(key)}**\n${links(related, key)}\n`; + return acc; + } + + var list = `${keys.reduce(reduce, '')}`; + + if (!hasLinks) { + return ''; + } + + return `## Related\n${list}`; +} + function createLink(dir, prop, link) { var key = (prop === 'doc') ? 'docs' : prop; @@ -71,3 +105,24 @@ function createLink(dir, prop, link) { } return `- [${name}](${filename}${anchor})`; } + +function heading(title) { + switch (title.toLowerCase()) { + case 'doc': + title = 'docs'; + break; + case 'url': + title = 'links'; + break; + default: + if (/(i|s)$/.test(title.toLowerCase()) === false) { + title += 's'; + } + break; + } + + if (/s$/.test(title)) { + return utils.pascalcase(title); + } + return title.toUpperCase(); +} From 1695b81cab88a121b9977b6a051935f636fb694b Mon Sep 17 00:00:00 2001 From: Brian Woodward Date: Wed, 6 Jul 2016 11:34:17 -0400 Subject: [PATCH 250/274] use relatedLinks helper in layout --- support/docs/layouts/default.md | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/support/docs/layouts/default.md b/support/docs/layouts/default.md index 6ef541f..a06e910 100644 --- a/support/docs/layouts/default.md +++ b/support/docs/layouts/default.md @@ -2,15 +2,4 @@ {% body %} -<%= hasAny([related.doc, related.api, related.url, related.cli], '## Related') %> -<%= hasValue(related.doc, '**Docs**') %> -<%= links(related, 'doc') %> - -<%= hasValue(related.cli, '**CLI**') %> -<%= links(related, 'cli') %> - -<%= hasValue(related.api, '**API**') %> -<%= links(related, 'api') %> - -<%= hasValue(related.url, '**Links**') %> -<%= links(related, 'url') %> +<%= relatedLinks(related) %> From e289e38346f8692145a0aeb12c6ec97d307a4fa2 Mon Sep 17 00:00:00 2001 From: Brian Woodward Date: Wed, 6 Jul 2016 11:34:28 -0400 Subject: [PATCH 251/274] run assemble build using new relatedLinks helper --- docs/api/updater.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/api/updater.md b/docs/api/updater.md index 5bd5a88..2ffed48 100644 --- a/docs/api/updater.md +++ b/docs/api/updater.md @@ -29,10 +29,6 @@ app.update('bar', function(err) { ## Related -**Docs** - -* [faq](../faq.md) - **CLI** * [commands](../cli/commands.md) @@ -41,3 +37,7 @@ app.update('bar', function(err) { * [register](register.md) * [plugins](plugins.md) + +**Docs** + +* [faq](../faq.md) From 2e4a5fe6a06d8c88eb5473ec0413d67b63ad8d92 Mon Sep 17 00:00:00 2001 From: Brian Woodward Date: Wed, 6 Jul 2016 12:03:13 -0400 Subject: [PATCH 252/274] normalize related links into objects for customizing related links --- support/lib/helpers.js | 45 +++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/support/lib/helpers.js b/support/lib/helpers.js index 1624dd8..3205e42 100644 --- a/support/lib/helpers.js +++ b/support/lib/helpers.js @@ -81,29 +81,17 @@ function relatedLinks(related, links) { function createLink(dir, prop, link) { var key = (prop === 'doc') ? 'docs' : prop; + link = normalizeLink(link); - var filename = link; - var name = link; - var anchor = ''; - var segs = link.split('#'); - if (segs.length === 2) { - name = segs[segs.length - 1]; - anchor = '#' + name; - filename = segs[0]; - } - - if (!/\.md$/.test(filename) && !/#/.test(filename)) { - filename += '.md'; - } if (dir !== key) { if (key === 'docs') { key = ''; } - filename = path.join('..', key, filename); + link.filename = path.join('..', key, link.filename); } else if (key !== 'docs' && dir !== key) { - filename = path.join(key, filename); + link.filename = path.join(key, link.filename); } - return `- [${name}](${filename}${anchor})`; + return `- [${link.title}](${link.filename}${link.anchor})`; } function heading(title) { @@ -126,3 +114,28 @@ function heading(title) { } return title.toUpperCase(); } + +function normalizeLink(obj) { + if (typeof obj === 'string') { + obj = {link: obj}; + } + var link = obj.link; + var filename = link; + var name = link; + var anchor = ''; + var segs = link.split('#'); + if (segs.length === 2) { + name = segs[segs.length - 1]; + anchor = '#' + name; + filename = segs[0]; + } + + if (!/\.md$/.test(filename) && !/#/.test(filename)) { + filename += '.md'; + } + + obj.title = obj.title || name; + obj.filename = obj.filename || filename; + obj.anchor = obj.anchor || anchor; + return obj; +} From cdad93b2af539b3c36aa57e0471ff41d9c26df32 Mon Sep 17 00:00:00 2001 From: Brian Woodward Date: Wed, 6 Jul 2016 12:03:28 -0400 Subject: [PATCH 253/274] testing out related link formats --- docs/tasks.md | 2 +- support/docs/tasks.md | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/tasks.md b/docs/tasks.md index 117b52d..b2e44df 100644 --- a/docs/tasks.md +++ b/docs/tasks.md @@ -176,6 +176,6 @@ app.build(function(err) { **Docs** -* [running-updaters](updaters.md#running-updaters) +* [Running updaters](updaters.md#running-updaters) * [updaters](updaters.md) * [updatefile](updatefile.md) diff --git a/support/docs/tasks.md b/support/docs/tasks.md index 09e3149..1392a01 100644 --- a/support/docs/tasks.md +++ b/support/docs/tasks.md @@ -1,7 +1,12 @@ --- title: Tasks related: - doc: ['updaters#running-updaters', 'updaters', 'updatefile'] + doc: + - link: updaters + title: Running updaters + anchor: '#running-updaters' + - updaters + - updatefile --- Tasks are used for wrapping code that should be executed at a later point, either when specified by command line or explicitly run when using the API. From b123aa40e461fdba399be13b4a44b00aa8ad9512 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Thu, 21 Jul 2016 17:50:46 -0400 Subject: [PATCH 254/274] several changes: **added** - as of v0.7.0, we will begin using the [keep-a-changelog][] format for release history - adds support user-defined templates - adds support for `app.home()`, which resolves to `~/` or the user-defined `options.homedir`. This directory is used to determine the base directory for user-defined templates. - adds support for [common-config](https://github.com/jonschlinkert/common-config). Exposed on the `app.common` object (e.g. `app.common.set()` etc) - adds experimental support for a `home` updater. If an `updatefile.js` exists in the `~/update` directory (this will be customizable, but it's not yet), this file will be loaded and `.use()`d as a plugin before other updaters are loaded. You can use this to set options, add defaults, etc. But you can also run it explictly via commandline with the `update home` command. **fixed** - fixes `app.cwd` so that it's updated when `app.options.dest` (`--dest`) is set - ensure args are parsed consistently --- .travis.yml | 1 + .verb.md | 114 +++++++++++++++++--------- CHANGELOG.md | 44 +++++++++- README.md | 33 ++++---- bin/update.js | 14 +++- index.js | 54 +++++++++++- lib/cli.js | 15 ++++ lib/updatefile.js | 19 ++++- lib/utils.js | 37 +++++---- package.json | 2 +- support/docs/cli.built-in-updaters.md | 2 +- support/docs/introduction.md | 12 ++- support/lib/common.js | 1 - support/lib/utils.js | 1 - support/verbfile.js | 4 +- 15 files changed, 261 insertions(+), 92 deletions(-) diff --git a/.travis.yml b/.travis.yml index e9f3361..3932eaa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ sudo: false language: node_js node_js: + - '6' - '5' - '4' - '0.12' diff --git a/.verb.md b/.verb.md index 1a271bf..d025595 100644 --- a/.verb.md +++ b/.verb.md @@ -2,8 +2,9 @@ Please read our [contributing guide](.github/contributing.md) if you'd like to l You might also be interested in [generate](https://github.com/generate/generate). +## Overview -## What does Update do? +### What does Update do? All updating is accomplished using plugins called _updaters_, which are run by command line or API, and can be installed globally, locally, or created in a local `updatefile.js`. @@ -21,17 +22,32 @@ You can create your own [updaters](docs/updaters.md) using Update's API, or inst * after initializing a new project with a project generator, like [generate][] or Google's Yeoman, you can "normalize" all of the generated files to use your own preferences -## Why should I use Update? +### Why should I use Update? - **be more productive**: Update eliminates time spent on things that _can be automated_, but typically aren't since they either don't need to be done often, don't fit into the build cycle or a project's deliverables, or because they're usually updated manually. As code projects mature, time spent on these things tend to stay linear or increase as the size of a community grows. If you maintain more than a handful of projects, time spent on these things compounds with each new project under your stewardship. - **your way, instantly**: updaters can be published to and installed from npm, but you can also easily create your own [personal updaters](docs/symlinking-updaters.md). Once your updaters are setup, just run `update init`, then projects under your maintenance will convert to the the conventions you prefer within milliseconds after running `update`. - **plugin ecosystem**: any plugins that work with [Base applications](#discovering-plugins) will work also with Update. Which means you can use plugins (or generators) from [assemble][], [verb][], and [generate][], to name a few. - **well tested**: with more than 1,250 unit tests +### Features -## Usage +* **unparalleled flow control**: through the use of [updaters](docs/updaters.md), [sub-updaters][getting-started] and [tasks](docs/tasks.md) +* **generators**: support for [generate][] generators. If your updater needs to create new files, there might be a [generator for that](https://www.npmjs.com/browse/keyword/generate-generator). Just use the generator the same way you would use an [updater](docs/updaters.md). +* **render templates**: use templates to create new files, or replace existing files +* **prompts**: It's easy to create custom prompts. Answers to prompts can be used as context for rendering templates, for settings options, determining file names, directory structure, and anything else that requires user feedback. +* **any engine**: use any template engine to render templates, including [handlebars][], [lodash][], [swig][] and [pug][], or anything supported by [consolidate][]. +* **data**: gather data from the user's environment to populate "hints" in user prompts or for rendering templates +* **fs**: in the spirit of [gulp][], use `.src` and `.dest` to read and write globs of files. +* **vinyl**: files and templates are [vinyl][] files +* **streams**: full support for [gulp][] and [assemble][] plugins +* **smart plugins**: Update is built on [base][], so any "smart" plugin from the Base ecosystem can be used +* **stores**: persist configuration settings, global defaults, project-specific defaults, answers to prompts, and so on. +* much more! -### Install update + +## CLI + +### Installing update **Install update** @@ -44,7 +60,7 @@ $ npm install --global update This adds the `update` command to your system path, allowing it to be run from anywhere. -### Install an updater +### Installing updaters Updaters can be [found on npm](https://www.npmjs.com/browse/keyword/update-updater), but if you're not familiar with how Update works, we recommend installing `updater-example`: @@ -66,18 +82,12 @@ $ update example This appends the string `foo` to the contents of `example.txt`. Visit the [updater-example][] project for additional steps and guidance. +## Tasks +Update ships with the following built-in [tasks](docs/tasks.md). These will be externalized to an updater or [generate][] generator at some point. -### Init - -Tell Update's CLI to automatically run certain updaters every time the `update` command is given: - -```sh -$ update init -``` - -You can run this command whenever you want to update your preferences, like after installing new updaters. +{%= apidocs("lib/updatefile.js") %} -### Help +## Help menu ```console $ update help @@ -125,29 +135,16 @@ $ update help by specifying its default task. Example: `$ update foo:default` ``` -## Features +## API -* **unparalleled flow control**: through the use of [updaters](docs/updaters.md), [sub-updaters][getting-started] and [tasks](docs/tasks.md) -* **generators**: support for [generate][] generators. If your updater needs to create new files, there might be a [generator for that](https://www.npmjs.com/browse/keyword/generate-generator). Just use the generator the same way you would use an [updater](docs/updaters.md). -* **render templates**: use templates to create new files, or replace existing files -* **prompts**: It's easy to create custom prompts. Answers to prompts can be used as context for rendering templates, for settings options, determining file names, directory structure, and anything else that requires user feedback. -* **any engine**: use any template engine to render templates, including [handlebars][], [lodash][], [swig][] and [pug][], or anything supported by [consolidate][]. -* **data**: gather data from the user's environment to populate "hints" in user prompts or for rendering templates -* **fs**: in the spirit of [gulp][], use `.src` and `.dest` to read and write globs of files. -* **vinyl**: files and templates are [vinyl][] files -* **streams**: full support for [gulp][] and [assemble][] plugins -* **smart plugins**: Update is built on [base][], so any "smart" plugin from the Base ecosystem can be used -* **stores**: persist configuration settings, global defaults, project-specific defaults, answers to prompts, and so on. -* much more! - -## Updaters +### Updaters -### Discovering updaters +#### Discovering updaters * Find updaters to install by [searching npm](https://www.npmjs.com/browse/keyword/update-updater) for packages with the keyword `updateupdater` * Visit [Update's GitHub org](https://github.com/update) to see the updaters maintained by the core team -### Discovering plugins +#### Discovering plugins Plugins from any applications built on [base][] should work with Update (and can be used in your updater): @@ -158,10 +155,42 @@ Plugins from any applications built on [base][] should work with Update (and can * [update][update-plugin]: find update plugins on npm using the `updateplugin` keyword * [verb][verb-plugin]: find verb plugins on npm using the `verbplugin` keyword -### Authoring updaters +#### Authoring updaters Visit the [updater documentation](docs/updaters.md) guide to learn how to use, author and publish updaters. +## Configuration + +Customize settings and default behavior using the `update` property in package.json. These values will override global defaults. + +```js +{ + "update": { + "updaters": ["package", "license", "keywords"] + } +} +``` + +### Options + +The following options may be defined in package.json. + +#### updaters + +The updaters to run on the current project. + +**Example** + +Run `updater-license` and `updater-package` on the current project: + +```js +{ + "update": { + "updaters": ["package", "license"] + } +} +``` + ## More information - See the [updaters maintained by the core team](https://github.com/update) @@ -170,21 +199,24 @@ Visit the [updater documentation](docs/updaters.md) guide to learn how to use, a - Learn about [updaters](docs/updaters.md) - Learn about the [built-in updaters](docs/cli/built-in-updaters.md) - Learn more about [base][] -- Get [Sublime Text Snippets][snippets] for creating tasks and updaters - -## About - -### Community +- Get [Sublime Text Snippets][st] for creating tasks and updaters +{{#block "community" heading="### Community"}} Are you using Update in your project? Have you published an [updater](docs/updaters.md) and want to share your Update project with the world? Here are some suggestions: * If you get like Update and want to tweet about it, please use the hashtag `#updatejs` * Get implementation help on [StackOverflow](http://stackoverflow.com/questions/tagged/update) (please use the `update` tag in questions) * **Gitter** Discuss Update with us on [Gitter](https://gitter.im/update) * If you publish an updater, thank you! To make your project as discoverable as possible, please add the keyword `updateupdater` to package.json. +{{/block}} + +## Release History +{%= increaseHeadings(changelog('CHANGELOG.md', { + changelogFooter: true, + stripHeading: true, + repo: repo +})) %} -### History -{%= doc('CHANGELOG.md') %} [getting-started]: https://github.com/update/getting-started [base-plugin]: https://www.npmjs.com/browse/keyword/baseplugin @@ -192,4 +224,4 @@ Are you using Update in your project? Have you published an [updater](docs/updat [generate-plugin]: https://www.npmjs.com/browse/keyword/generateplugin [templates-plugin]: https://www.npmjs.com/browse/keyword/templatesplugin [verb-plugin]: https://www.npmjs.com/browse/keyword/verbplugin -[snippets]: https://github.com/node-base/sublime-text-base-snippets +[st]: https://github.com/node-base/sublime-text-base-snippets diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ac7b5e..4a97011 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,46 @@ -**v0.6.0** +# Release History + +## key + +Changelog entries are classified using the following labels from [keep-a-changelog][]: + +* `added`: for new features +* `changed`: for changes in existing functionality +* `deprecated`: for once-stable features removed in upcoming releases +* `removed`: for deprecated features removed in this release +* `fixed`: for any bug fixes + +Custom labels used in this changelog: + +* `dependencies`: bumps dependencies +* `housekeeping`: code re-organization, minor edits, or other changes that don't fit in one of the other categories. + +**Heads up!** + +Please [let us know](../../issues) if any of the following heading links are broken. Thanks! + +## [0.7.0] - 2016-07-21 + +**added** + +- as of v0.7.0, we will begin using the [keep-a-changelog][] format for release history +- adds support user-defined templates +- adds support for `app.home()`, which resolves to `~/` or the user-defined `options.homedir`. This directory is used to determine the base directory for user-defined templates. +- adds support for [common-config](https://github.com/jonschlinkert/common-config). Exposed on the `app.common` object (e.g. `app.common.set()` etc) +- adds experimental support for a `home` updater. If an `updatefile.js` exists in the `~/update` directory (this will be customizable, but it's not yet), this file will be loaded and `.use()`d as a plugin before other updaters are loaded. You can use this to set options, add defaults, etc. But you can also run it explictly via commandline with the `update home` command. + +**fixed** + +- fixes `app.cwd` so that it's updated when `app.options.dest` (`--dest`) is set +- ensure args are parsed consistently + +## [0.6.0] - Swap out [base][] for [assemble-core][] (which uses Base via [templates][]). This allows updaters to seamlessly run generators from [generate][], [assemble][], or [verb][] (when a file needs to be created, or re-created for example) - Adds [assemble-loader][] to support glob patterns in collection methods -**v0.5.0** +## [0.5.0] + +First stable release! -First stable release! \ No newline at end of file +[keep-a-changelog]: https://github.com/olivierlacan/keep-a-changelog diff --git a/README.md b/README.md index fb63e62..0244fb7 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Be scalable! Update is a new, open source developer framework and CLI for automating updates of any kind in code projects. -## TOC +## Table of Contents - [What does Update do?](#what-does-update-do) - [Why should I use Update?](#why-should-i-use-update) @@ -26,11 +26,12 @@ Be scalable! Update is a new, open source developer framework and CLI for automa - [About](#about) * [Community](#community) * [History](#history) -- [Related projects](#related-projects) -- [Contributing](#contributing) -- [Running tests](#running-tests) -- [Author](#author) -- [License](#license) +- [About](#about-1) + * [Related projects](#related-projects) + * [Contributing](#contributing) + * [Running tests](#running-tests) + * [Author](#author) + * [License](#license) _(TOC generated by [verb](https://github.com/verbose/verb) using [markdown-toc](https://github.com/jonschlinkert/markdown-toc))_ @@ -165,7 +166,7 @@ $ update help * **any engine**: use any template engine to render templates, including [handlebars](http://www.handlebarsjs.com/), [lodash](https://lodash.com/), [swig](https://github.com/paularmstrong/swig) and [pug](http://jade-lang.com), or anything supported by [consolidate](https://github.com/visionmedia/consolidate.js). * **data**: gather data from the user's environment to populate "hints" in user prompts or for rendering templates * **fs**: in the spirit of [gulp](http://gulpjs.com), use `.src` and `.dest` to read and write globs of files. -* **vinyl**: files and templates are [vinyl](http://github.com/wearefractal/vinyl) files +* **vinyl**: files and templates are [vinyl](http://github.com/gulpjs/vinyl) files * **streams**: full support for [gulp](http://gulpjs.com) and [assemble](https://github.com/assemble/assemble) plugins * **smart plugins**: Update is built on [base](https://github.com/node-base/base), so any "smart" plugin from the Base ecosystem can be used * **stores**: persist configuration settings, global defaults, project-specific defaults, answers to prompts, and so on. @@ -225,22 +226,22 @@ Are you using Update in your project? Have you published an [updater](docs/updat First stable release! -## Related projects +## About -Update shares a common architecture and plugin ecosystem with the following libraries: +### Related projects -* [assemble](https://www.npmjs.com/package/assemble): Assemble is a powerful, extendable and easy to use static site generator for node.js. Used… [more](https://github.com/assemble/assemble) | [homepage](https://github.com/assemble/assemble "Assemble is a powerful, extendable and easy to use static site generator for node.js. Used by thousands of projects for much more than building websites, Assemble is also used for creating themes, scaffolds, boilerplates, e-books, UI components, API docum") +* [assemble](https://www.npmjs.com/package/assemble): Get the rocks out of your socks! Assemble makes you fast at creating web projects… [more](https://github.com/assemble/assemble) | [homepage](https://github.com/assemble/assemble "Get the rocks out of your socks! Assemble makes you fast at creating web projects. Assemble is used by thousands of projects for rapid prototyping, creating themes, scaffolds, boilerplates, e-books, UI components, API documentation, blogs, building websit") * [base](https://www.npmjs.com/package/base): base is the foundation for creating modular, unit testable and highly pluggable node.js applications, starting… [more](https://github.com/node-base/base) | [homepage](https://github.com/node-base/base "base is the foundation for creating modular, unit testable and highly pluggable node.js applications, starting with a handful of common methods, like `set`, `get`, `del` and `use`.") -* [generate](https://www.npmjs.com/package/generate): The Santa Claus machine for GitHub projects. Scaffolds out new projects, or creates any kind… [more](https://github.com/generate/generate) | [homepage](https://github.com/generate/generate "The Santa Claus machine for GitHub projects. Scaffolds out new projects, or creates any kind of required file or document from any given templates or source materials.") +* [generate](https://www.npmjs.com/package/generate): Command line tool and developer framework for scaffolding out new GitHub projects. Generate offers the… [more](https://github.com/generate/generate) | [homepage](https://github.com/generate/generate "Command line tool and developer framework for scaffolding out new GitHub projects. Generate offers the robustness and configurability of Yeoman, the expressiveness and simplicity of Slush, and more powerful flow control and composability than either.") * [verb](https://www.npmjs.com/package/verb): Documentation generator for GitHub projects. Verb is extremely powerful, easy to use, and is used… [more](https://github.com/verbose/verb) | [homepage](https://github.com/verbose/verb "Documentation generator for GitHub projects. Verb is extremely powerful, easy to use, and is used on hundreds of projects of all sizes to generate everything from API docs to readmes.") -## Contributing +### Contributing Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new). Please read the [contributing guide](.github/contributing.md) for avice on opening issues, pull requests, and coding standards. -## Running tests +### Running tests Install dev dependencies: @@ -248,18 +249,18 @@ Install dev dependencies: $ npm install -d && npm test ``` -## Author +### Author **Jon Schlinkert** * [github/jonschlinkert](https://github.com/jonschlinkert) * [twitter/jonschlinkert](http://twitter.com/jonschlinkert) -## License +### License Copyright © 2016, [Jon Schlinkert](https://github.com/jonschlinkert). Released under the [MIT license](https://github.com/update/update/blob/master/LICENSE). *** -_This file was generated by [verb](https://github.com/verbose/verb), v0.9.0, on July 05, 2016._ \ No newline at end of file +_This file was generated by [verb](https://github.com/verbose/verb), v0.9.0, on July 21, 2016._ \ No newline at end of file diff --git a/bin/update.js b/bin/update.js index 053ea0c..e9fdc13 100755 --- a/bin/update.js +++ b/bin/update.js @@ -1,7 +1,10 @@ #!/usr/bin/env node -require('set-blocking')(true); +process.on('exit', function() { + require('set-blocking')(true); +}); +var os = require('os'); var Update = require('..'); var commands = require('../lib/commands'); var utils = require('../lib/utils'); @@ -42,7 +45,7 @@ Update.cli(Update, argv, function(err, app) { app.once('task', function() { if (!app.base.enabled('silent')) { - app.log.success('running:', tasks); + app.log.success('running:', logRunning(app, tasks.join(', '))); } }); @@ -53,3 +56,10 @@ Update.cli(Update, argv, function(err, app) { }); }); }); + +function logRunning(app, str) { + if (os.platform() === 'win32') { + return app.log.bold(app.log.cyan(str)); + } + return app.log.bold(app.log.blue(str)); +} diff --git a/index.js b/index.js index 19090c3..c27e51b 100644 --- a/index.js +++ b/index.js @@ -7,8 +7,10 @@ 'use strict'; +var fs = require('fs'); +var os = require('os'); +var path = require('path'); var Base = require('assemble-core'); -var resolve = require('resolve-file'); var utils = require('./lib/utils'); var cli = require('./lib/cli'); @@ -57,6 +59,13 @@ Update.prototype.initDefaults = function() { this.define('generators', this.generators); this.define('updater', this.generator); this.updaters = this.generators; + var self = this; + + this.define('home', function() { + var args = [].slice.call(arguments); + var home = path.resolve(self.options.homedir || os.homedir()); + return path.resolve.apply(path, [home].concat(args)); + }); this.option('help', {configname: 'updater', appname: 'update'}); this.define('update', this.generate); @@ -82,6 +91,27 @@ Update.prototype.initDefaults = function() { } }); + Object.defineProperty(this, 'common', { + configurable: true, + get: function() { + return utils.common; + } + }); + + this.onLoad(/(^|[\\\/])templates[\\\/]/, function(view, next) { + var userDefined = self.home('templates', view.basename); + if (utils.exists(userDefined)) { + view.contents = fs.readFileSync(userDefined); + view.homePath = userDefined; + view.isUserDefined = true; + } + if (/^templates[\\\/]/.test(view.relative)) { + view.path = path.join(self.cwd, view.basename); + } + utils.stripPrefixes(view); + utils.parser.parse(view, next); + }); + this.option('lookup', function(name) { var patterns = []; if (!isUpdater(name)) { @@ -92,11 +122,31 @@ Update.prototype.initDefaults = function() { this.on('unresolved', function(search, app) { if (!isUpdater(search.name)) return; - var resolved = resolve.file(search.name) || resolve.file(search.name, {cwd: utils.gm}); + var resolved = utils.resolve.file(search.name) || utils.resolve.file(search.name, {cwd: utils.gm}); if (resolved) { search.app = app.generator(search.name, require(resolved.path)); } }); + + this.on('option', function(key, val) { + if (key === 'dest') { + self.base.cwd = val; + self.cwd = val; + } + }); + + this.on('ask', function(val, key, question, answers) { + val = val || question.default; + if (typeof val === 'undefined') { + question.default = self.common.get(key); + } + }); + + this.on('task:starting', function(event, task) { + if (task && task.app) { + task.app.cwd = self.base.cwd; + } + }); }; /** diff --git a/lib/cli.js b/lib/cli.js index ee94c2a..502793b 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -2,6 +2,7 @@ process.ORIGINAL_CWD = process.cwd(); +var os = require('os'); var path = require('path'); var find = require('find-pkg'); var log = require('log-utils'); @@ -54,6 +55,12 @@ module.exports = function(Update, config, cb) { base.set('cache.argv', opts); + /** + * Check for user `~/update/updatefile.js` + */ + + var homeUpdatefile = path.resolve(os.homedir(), 'update/updatefile.js'); + /** * Check for config file */ @@ -85,6 +92,14 @@ module.exports = function(Update, config, cb) { base.register('default', fn); } + /** + * Register updater in user home + */ + + if (utils.exists(homeUpdatefile)) { + base.register('home', require(homeUpdatefile)); + } + /** * Handle errors */ diff --git a/lib/updatefile.js b/lib/updatefile.js index e10b3fd..ac89591 100644 --- a/lib/updatefile.js +++ b/lib/updatefile.js @@ -15,8 +15,9 @@ module.exports = function(app, base) { var cwd = path.resolve.bind(path, app.cwd); /** - * Prompts the user to select the updaters to run with the `update` command. Use `--add` to - * add addtional updaters, and `--remove` to remove them. + * Select the updaters to run every time `update` is run. Use `--add` to + * add additional updaters, and `--remove` to remove them. You can run this command + * whenever you want to update your preferences, like after installing new updaters. * * ```sh * $ update init @@ -42,6 +43,18 @@ module.exports = function(app, base) { })); }); + /** + * Display a list of currently installed updaters. + * + * ```sh + * $ update defaults:list + * # aliased as + * $ update list + * ``` + * @name list + * @api public + */ + app.task('list', { silent: true }, function() { return app.src([gm('updater-*'), cwd('node_modules/updater-*')]) .pipe(through.obj(function(file, enc, next) { @@ -52,7 +65,7 @@ module.exports = function(app, base) { }); /** - * Display a help menu of available commands and flags. + * Display a help [menu](#help-menu) of available commands and flags. * * ```sh * $ update defaults:help diff --git a/lib/utils.js b/lib/utils.js index 53908ea..d4e9b3c 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -17,33 +17,22 @@ require('base-questions', 'questions'); require('base-runtimes', 'runtimes'); require('base-store', 'store'); require('data-store', 'Store'); +require('common-config', 'common'); require('extend-shallow', 'extend'); require('fs-exists-sync', 'exists'); require('isobject', 'isObject'); require('is-valid-app', 'isValid'); require('global-modules', 'gm'); require('log-utils', 'log'); -require('os-homedir', 'home'); require('resolve-dir'); +require('resolve-file', 'resolve'); +require('parser-front-matter', 'parser'); require('yargs-parser', 'parse'); require = fn; // eslint-disable-line utils.stripPrefixes = function(file) { - file.basename = file.basename.replace(/^_/, '.'); - file.basename = file.basename.replace(/^\$/, ''); -}; - -utils.parseArgs = function(argv) { - var obj = utils.parse(argv, utils.opts); - if (obj.init) { - obj._.push('init'); - delete obj.init; - } - if (obj.help) { - obj._.push('help'); - delete obj.help; - } - return obj; + file.stem = file.stem.replace(/^_/, '.'); + file.stem = file.stem.replace(/^\$/, ''); }; /** @@ -51,19 +40,35 @@ utils.parseArgs = function(argv) { */ utils.opts = { + boolean: ['diff'], alias: { add: 'a', config: 'c', configfile: 'f', + diff: 'diffOnly', global: 'g', help: 'h', init: 'i', silent: 'S', + verbose: 'v', version: 'V', remove: 'r' } }; +utils.parseArgs = function(argv) { + var obj = utils.parse(argv, utils.opts); + if (obj.init) { + obj._.push('init'); + delete obj.init; + } + if (obj.help) { + obj._.push('help'); + delete obj.help; + } + return obj; +}; + utils.remove = function(arr, names) { return arr.filter(function(ele) { return names.indexOf(ele) === -1; diff --git a/package.json b/package.json index 3c38da1..6115c3b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "update", "description": "Be scalable! Update is a new, open source developer framework and CLI for automating updates of any kind in code projects.", - "version": "0.6.2", + "version": "0.7.0", "homepage": "https://github.com/update/update", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", "repository": "update/update", diff --git a/support/docs/cli.built-in-updaters.md b/support/docs/cli.built-in-updaters.md index 84101a9..4765631 100644 --- a/support/docs/cli.built-in-updaters.md +++ b/support/docs/cli.built-in-updaters.md @@ -16,7 +16,7 @@ Update only has a few built-in [updaters](docs/updaters.md) (these might be exte ### init -Choose the updaters to run by default each time `update` is run from the command line: +Prompts you to choose one or more updaters to run by default each time `update` is run from the command line: ```sh $ update init diff --git a/support/docs/introduction.md b/support/docs/introduction.md index 9e14ed4..0de03da 100644 --- a/support/docs/introduction.md +++ b/support/docs/introduction.md @@ -21,14 +21,20 @@ Update is a new, open-source developer framework for automating updates of any k Update's API has methods for [creating](#creating-updaters), [registering](#registering-updaters), [resolving](#resolving-updaters) and [running](#running-updaters) updaters. +### Who should use Update? + +* developers or organizations with many projects under their stewardship +* agencies or consultants who maintain and/or create client projects and would like to reduce time spent on maintainance +* anyone who cares about having consistency across all of their projects + ## Updaters -All "updates" are accomplished using plugins called [updaters](#updaters). +All "updates" are accomplished using plugins called [updaters](#updaters). **What are updaters?** - Updaters are functions that are registered by name, and can be run by [command line](#command-line) or [API](#api). -- Updaters may be published to [npm](https://www.npmjs.com) using the `updater-foo` naming convention, where `foo` is the [alias](#aliases) of your updater. +- Updaters may be published to [npm](https://www.npmjs.com) using the `updater-foo` naming convention, where `foo` is the [alias](#aliases) of your updater. - Published updaters can be installed locally or globally. ## Command line @@ -69,7 +75,7 @@ Updaters via CLI or API. (tasks are powered by [bach][], the same library used i The main export of the library is the `Update` constructor function. -Updaters themselves are just functions that take an instance of `Update`. +Updaters themselves are just functions that take an instance of `Update`. Update gives you a way to automate the maintenance of files that are typically excluded from the automated parts of the software lifecycle, and thus are mostly forgotten about after they're created. diff --git a/support/lib/common.js b/support/lib/common.js index f95b396..c1f0ddb 100644 --- a/support/lib/common.js +++ b/support/lib/common.js @@ -11,7 +11,6 @@ module.exports = function(options) { app.use(require('generate-defaults')); app.use(require('verb-toc')); app.use(helpers()); - app.use(helpers()); if (!app.docs) app.create('docs'); app.option('renameKey', function(key, file) { diff --git a/support/lib/utils.js b/support/lib/utils.js index 98e3968..794cc34 100644 --- a/support/lib/utils.js +++ b/support/lib/utils.js @@ -8,7 +8,6 @@ require = utils; require('is-valid-app', 'isValid'); require('has-value'); -require('pascalcase'); require('pretty-remarkable', 'prettify'); require('remarkable', 'Remarkable'); require('strip-color'); diff --git a/support/verbfile.js b/support/verbfile.js index cf7c8ef..e436c4c 100644 --- a/support/verbfile.js +++ b/support/verbfile.js @@ -1,11 +1,11 @@ 'use strict'; var path = require('path'); +var copy = require('copy'); +var del = require('delete'); var drafts = require('gulp-drafts'); var reflinks = require('gulp-reflinks'); var format = require('gulp-format-md'); -var copy = require('copy'); -var del = require('delete'); var paths = require('./lib/paths'); var lib = require('./lib'); From d25c026db7d5155198de35bc8e051e4a836a505a Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Thu, 21 Jul 2016 17:52:29 -0400 Subject: [PATCH 255/274] update deps --- package.json | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 6115c3b..159fc6b 100644 --- a/package.json +++ b/package.json @@ -31,31 +31,32 @@ "arr-union": "^3.1.0", "assemble-core": "^0.25.0", "assemble-loader": "^0.6.1", - "base-cli-process": "^0.1.14", - "base-config-process": "^0.1.7", - "base-generators": "^0.4.4", - "base-questions": "^0.6.6", + "base-cli-process": "^0.1.18", + "base-config-process": "^0.1.9", + "base-generators": "^0.4.5", + "base-questions": "^0.7.3", "base-runtimes": "^0.2.0", "base-store": "^0.4.4", - "data-store": "^0.16.0", + "common-config": "^0.1.0", + "data-store": "^0.16.1", "export-files": "^2.1.1", "extend-shallow": "^2.0.1", "find-pkg": "^0.1.2", "fs-exists-sync": "^0.1.0", "global-modules": "^0.2.2", - "gulp-choose-files": "^0.1.2", + "gulp-choose-files": "^0.1.3", "is-valid-app": "^0.2.0", "isobject": "^2.1.0", "lazy-cache": "^2.0.1", - "log-utils": "^0.1.4", - "os-homedir": "^1.0.1", + "log-utils": "^0.2.1", + "parser-front-matter": "^1.4.1", "resolve-dir": "^0.1.0", "resolve-file": "^0.2.0", "set-blocking": "^2.0.0", "strip-color": "^0.1.0", "text-table": "^0.2.0", "through2": "^2.0.1", - "yargs-parser": "^2.4.0" + "yargs-parser": "^2.4.1" }, "devDependencies": { "base-runner": "^0.8.2", From 7712f3b7bc1025f8879c605fba45182e69fc1da3 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Thu, 21 Jul 2016 17:52:42 -0400 Subject: [PATCH 256/274] 0.7.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 159fc6b..37c21af 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "update", "description": "Be scalable! Update is a new, open source developer framework and CLI for automating updates of any kind in code projects.", - "version": "0.7.0", + "version": "0.7.1", "homepage": "https://github.com/update/update", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", "repository": "update/update", From f1e0b71e7f3a8b98324200a7f29d48e4fbf84973 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Thu, 21 Jul 2016 18:03:51 -0400 Subject: [PATCH 257/274] lint docs, rebuild --- README.md | 227 ++++++++++++++++++++++++++-------- docs/cli/built-in-updaters.md | 2 +- docs/tasks.md | 1 - docs/updaters.md | 1 - package.json | 6 +- support/lib/utils.js | 1 + 6 files changed, 182 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 0244fb7..6057c16 100644 --- a/README.md +++ b/README.md @@ -10,23 +10,26 @@ Be scalable! Update is a new, open source developer framework and CLI for automa ## Table of Contents -- [What does Update do?](#what-does-update-do) -- [Why should I use Update?](#why-should-i-use-update) -- [Usage](#usage) - * [Install update](#install-update) - * [Install an updater](#install-an-updater) - * [Init](#init) - * [Help](#help) -- [Features](#features) -- [Updaters](#updaters) - * [Discovering updaters](#discovering-updaters) - * [Discovering plugins](#discovering-plugins) - * [Authoring updaters](#authoring-updaters) +- [Overview](#overview) + * [What does Update do?](#what-does-update-do) + * [Why should I use Update?](#why-should-i-use-update) + * [Features](#features) +- [CLI](#cli) + * [Installing update](#installing-update) + * [Installing updaters](#installing-updaters) +- [Tasks](#tasks) +- [Help menu](#help-menu) +- [API](#api) + * [Updaters](#updaters) + + [Discovering updaters](#discovering-updaters) + + [Discovering plugins](#discovering-plugins) + + [Authoring updaters](#authoring-updaters) +- [Configuration](#configuration) + * [Options](#options) + + [updaters](#updaters) - [More information](#more-information) +- [Release History](#release-history) - [About](#about) - * [Community](#community) - * [History](#history) -- [About](#about-1) * [Related projects](#related-projects) * [Contributing](#contributing) * [Running tests](#running-tests) @@ -39,7 +42,9 @@ Please read our [contributing guide](.github/contributing.md) if you'd like to l You might also be interested in [generate](https://github.com/generate/generate). -## What does Update do? +## Overview + +### What does Update do? All updating is accomplished using plugins called _updaters_, which are run by command line or API, and can be installed globally, locally, or created in a local `updatefile.js`. @@ -56,16 +61,31 @@ You can create your own [updaters](docs/updaters.md) using Update's API, or inst - update settings in [runtime config](https://github.com/update/updater-eslint) files, preferences in [dotfiles](https://github.com/update/updater-editorconfig) * after initializing a new project with a project generator, like [generate](https://github.com/generate/generate) or Google's Yeoman, you can "normalize" all of the generated files to use your own preferences -## Why should I use Update? +### Why should I use Update? * **be more productive**: Update eliminates time spent on things that _can be automated_, but typically aren't since they either don't need to be done often, don't fit into the build cycle or a project's deliverables, or because they're usually updated manually. As code projects mature, time spent on these things tend to stay linear or increase as the size of a community grows. If you maintain more than a handful of projects, time spent on these things compounds with each new project under your stewardship. * **your way, instantly**: updaters can be published to and installed from npm, but you can also easily create your own [personal updaters](docs/symlinking-updaters.md). Once your updaters are setup, just run `update init`, then projects under your maintenance will convert to the the conventions you prefer within milliseconds after running `update`. * **plugin ecosystem**: any plugins that work with [Base applications](#discovering-plugins) will work also with Update. Which means you can use plugins (or generators) from [assemble](https://github.com/assemble/assemble), [verb](https://github.com/verbose/verb), and [generate](https://github.com/generate/generate), to name a few. * **well tested**: with more than 1,250 unit tests -## Usage +### Features + +* **unparalleled flow control**: through the use of [updaters](docs/updaters.md), [sub-updaters](https://github.com/update/getting-started) and [tasks](docs/tasks.md) +* **generators**: support for [generate](https://github.com/generate/generate) generators. If your updater needs to create new files, there might be a [generator for that](https://www.npmjs.com/browse/keyword/generate-generator). Just use the generator the same way you would use an [updater](docs/updaters.md). +* **render templates**: use templates to create new files, or replace existing files +* **prompts**: It's easy to create custom prompts. Answers to prompts can be used as context for rendering templates, for settings options, determining file names, directory structure, and anything else that requires user feedback. +* **any engine**: use any template engine to render templates, including [handlebars](http://www.handlebarsjs.com/), [lodash](https://lodash.com/), [swig](https://github.com/paularmstrong/swig) and [pug](http://jade-lang.com), or anything supported by [consolidate](https://github.com/visionmedia/consolidate.js). +* **data**: gather data from the user's environment to populate "hints" in user prompts or for rendering templates +* **fs**: in the spirit of [gulp](http://gulpjs.com), use `.src` and `.dest` to read and write globs of files. +* **vinyl**: files and templates are [vinyl](http://github.com/gulpjs/vinyl) files +* **streams**: full support for [gulp](http://gulpjs.com) and [assemble](https://github.com/assemble/assemble) plugins +* **smart plugins**: Update is built on [base](https://github.com/node-base/base), so any "smart" plugin from the Base ecosystem can be used +* **stores**: persist configuration settings, global defaults, project-specific defaults, answers to prompts, and so on. +* much more! + +## CLI -### Install update +### Installing update **Install update** @@ -77,7 +97,7 @@ $ npm install --global update This adds the `update` command to your system path, allowing it to be run from anywhere. -### Install an updater +### Installing updaters Updaters can be [found on npm](https://www.npmjs.com/browse/keyword/update-updater), but if you're not familiar with how Update works, we recommend installing `updater-example`: @@ -99,17 +119,67 @@ $ update example This appends the string `foo` to the contents of `example.txt`. Visit the [updater-example](https://github.com/update/updater-example) project for additional steps and guidance. -### Init +## Tasks + +Update ships with the following built-in [tasks](docs/tasks.md). These will be externalized to an updater or [generate](https://github.com/generate/generate) generator at some point. + +### [init](lib/updatefile.js#L29) + +Select the updaters to run every time `update` is run. Use `--add` to add additional updaters, and `--remove` to remove them. You can run this command whenever you want to update your preferences, like after installing new updaters. -Tell Update's CLI to automatically run certain updaters every time the `update` command is given: +**Example** ```sh $ update init ``` -You can run this command whenever you want to update your preferences, like after installing new updaters. +### [list](lib/updatefile.js#L58) + +Display a list of currently installed updaters. + +**Example** + +```sh +$ update defaults:list +# aliased as +$ update list +``` + +### [help](lib/updatefile.js#L79) -### Help +Display a help [menu](#help-menu) of available commands and flags. + +**Example** + +```sh +$ update defaults:help +# aliased as +$ update help +``` + +### [show](lib/updatefile.js#L95) + +Show the list of updaters that are registered to run on the current project. + +**Example** + +```sh +$ update defaults:show +# aliased as +$ update show +``` + +### [help](lib/updatefile.js#L114) + +Default task for the built-in `defaults` generator. + +**Example** + +```sh +$ update help +``` + +## Help menu ```console $ update help @@ -157,29 +227,16 @@ $ update help by specifying its default task. Example: `$ update foo:default` ``` -## Features +## API -* **unparalleled flow control**: through the use of [updaters](docs/updaters.md), [sub-updaters](https://github.com/update/getting-started) and [tasks](docs/tasks.md) -* **generators**: support for [generate](https://github.com/generate/generate) generators. If your updater needs to create new files, there might be a [generator for that](https://www.npmjs.com/browse/keyword/generate-generator). Just use the generator the same way you would use an [updater](docs/updaters.md). -* **render templates**: use templates to create new files, or replace existing files -* **prompts**: It's easy to create custom prompts. Answers to prompts can be used as context for rendering templates, for settings options, determining file names, directory structure, and anything else that requires user feedback. -* **any engine**: use any template engine to render templates, including [handlebars](http://www.handlebarsjs.com/), [lodash](https://lodash.com/), [swig](https://github.com/paularmstrong/swig) and [pug](http://jade-lang.com), or anything supported by [consolidate](https://github.com/visionmedia/consolidate.js). -* **data**: gather data from the user's environment to populate "hints" in user prompts or for rendering templates -* **fs**: in the spirit of [gulp](http://gulpjs.com), use `.src` and `.dest` to read and write globs of files. -* **vinyl**: files and templates are [vinyl](http://github.com/gulpjs/vinyl) files -* **streams**: full support for [gulp](http://gulpjs.com) and [assemble](https://github.com/assemble/assemble) plugins -* **smart plugins**: Update is built on [base](https://github.com/node-base/base), so any "smart" plugin from the Base ecosystem can be used -* **stores**: persist configuration settings, global defaults, project-specific defaults, answers to prompts, and so on. -* much more! - -## Updaters +### Updaters -### Discovering updaters +#### Discovering updaters * Find updaters to install by [searching npm](https://www.npmjs.com/browse/keyword/update-updater) for packages with the keyword `updateupdater` * Visit [Update's GitHub org](https://github.com/update) to see the updaters maintained by the core team -### Discovering plugins +#### Discovering plugins Plugins from any applications built on [base](https://github.com/node-base/base) should work with Update (and can be used in your updater): @@ -190,10 +247,42 @@ Plugins from any applications built on [base](https://github.com/node-base/base) * [update][update-plugin]: find update plugins on npm using the `updateplugin` keyword * [verb](https://www.npmjs.com/browse/keyword/verbplugin): find verb plugins on npm using the `verbplugin` keyword -### Authoring updaters +#### Authoring updaters Visit the [updater documentation](docs/updaters.md) guide to learn how to use, author and publish updaters. +## Configuration + +Customize settings and default behavior using the `update` property in package.json. These values will override global defaults. + +```js +{ + "update": { + "updaters": ["package", "license", "keywords"] + } +} +``` + +### Options + +The following options may be defined in package.json. + +#### updaters + +The updaters to run on the current project. + +**Example** + +Run `updater-license` and `updater-package` on the current project: + +```js +{ + "update": { + "updaters": ["package", "license"] + } +} +``` + ## More information * See the [updaters maintained by the core team](https://github.com/update) @@ -204,28 +293,53 @@ Visit the [updater documentation](docs/updaters.md) guide to learn how to use, a * Learn more about [base](https://github.com/node-base/base) * Get [Sublime Text Snippets](https://github.com/node-base/sublime-text-base-snippets) for creating tasks and updaters -## About +## Release History -### Community +### key -Are you using Update in your project? Have you published an [updater](docs/updaters.md) and want to share your Update project with the world? Here are some suggestions: +Changelog entries are classified using the following labels from [keep-a-changelog](https://github.com/olivierlacan/keep-a-changelog): -* If you get like Update and want to tweet about it, please use the hashtag `#updatejs` -* Get implementation help on [StackOverflow](http://stackoverflow.com/questions/tagged/update) (please use the `update` tag in questions) -* **Gitter** Discuss Update with us on [Gitter](https://gitter.im/update) -* If you publish an updater, thank you! To make your project as discoverable as possible, please add the keyword `updateupdater` to package.json. +* `added`: for new features +* `changed`: for changes in existing functionality +* `deprecated`: for once-stable features removed in upcoming releases +* `removed`: for deprecated features removed in this release +* `fixed`: for any bug fixes + +Custom labels used in this changelog: + +* `dependencies`: bumps dependencies +* `housekeeping`: code re-organization, minor edits, or other changes that don't fit in one of the other categories. + +**Heads up!** + +Please [let us know](../../issues) if any of the following heading links are broken. Thanks! + +### [0.7.0](https://github.com/update/update/compare/0.6.0...0.7.0) - 2016-07-21 + +**added** + +* as of v0.7.0, we will begin using the [keep-a-changelog](https://github.com/olivierlacan/keep-a-changelog) format for release history +* adds support user-defined templates +* adds support for `app.home()`, which resolves to `~/` or the user-defined `options.homedir`. This directory is used to determine the base directory for user-defined templates. +* adds support for [common-config](https://github.com/jonschlinkert/common-config). Exposed on the `app.common` object (e.g. `app.common.set()` etc) +* adds experimental support for a `home` updater. If an `updatefile.js` exists in the `~/update` directory (this will be customizable, but it's not yet), this file will be loaded and `.use()`d as a plugin before other updaters are loaded. You can use this to set options, add defaults, etc. But you can also run it explictly via commandline with the `update home` command. -### History +**fixed** -**v0.6.0** +* fixes `app.cwd` so that it's updated when `app.options.dest` (`--dest`) is set +* ensure args are parsed consistently + +### [0.6.0](https://github.com/update/update/compare/0.5.0...0.6.0) * Swap out [base](https://github.com/node-base/base) for [assemble-core](https://github.com/assemble/assemble-core) (which uses Base via [templates](https://github.com/jonschlinkert/templates)). This allows updaters to seamlessly run generators from [generate](https://github.com/generate/generate), [assemble](https://github.com/assemble/assemble), or [verb](https://github.com/verbose/verb) (when a file needs to be created, or re-created for example) * Adds [assemble-loader](https://github.com/assemble/assemble-loader) to support glob patterns in collection methods -**v0.5.0** +### [0.5.0] First stable release! +_(Changelog generated by [helper-changelog](https://github.com/jonschlinkert/helper-changelog))_ + ## About ### Related projects @@ -235,6 +349,15 @@ First stable release! * [generate](https://www.npmjs.com/package/generate): Command line tool and developer framework for scaffolding out new GitHub projects. Generate offers the… [more](https://github.com/generate/generate) | [homepage](https://github.com/generate/generate "Command line tool and developer framework for scaffolding out new GitHub projects. Generate offers the robustness and configurability of Yeoman, the expressiveness and simplicity of Slush, and more powerful flow control and composability than either.") * [verb](https://www.npmjs.com/package/verb): Documentation generator for GitHub projects. Verb is extremely powerful, easy to use, and is used… [more](https://github.com/verbose/verb) | [homepage](https://github.com/verbose/verb "Documentation generator for GitHub projects. Verb is extremely powerful, easy to use, and is used on hundreds of projects of all sizes to generate everything from API docs to readmes.") +### Community + +Are you using Update in your project? Have you published an [updater](docs/updaters.md) and want to share your Update project with the world? Here are some suggestions: + +* If you get like Update and want to tweet about it, please use the hashtag `#updatejs` +* Get implementation help on [StackOverflow](http://stackoverflow.com/questions/tagged/update) (please use the `update` tag in questions) +* **Gitter** Discuss Update with us on [Gitter](https://gitter.im/update) +* If you publish an updater, thank you! To make your project as discoverable as possible, please add the keyword `updateupdater` to package.json. + ### Contributing Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new). diff --git a/docs/cli/built-in-updaters.md b/docs/cli/built-in-updaters.md index de53fd5..6b40573 100644 --- a/docs/cli/built-in-updaters.md +++ b/docs/cli/built-in-updaters.md @@ -12,7 +12,7 @@ Update only has a few built-in [updaters](docs/updaters.md) (these might be exte ### init -Choose the updaters to run by default each time `update` is run from the command line: +Prompts you to choose one or more updaters to run by default each time `update` is run from the command line: ```sh $ update init diff --git a/docs/tasks.md b/docs/tasks.md index b2e44df..9ca80ba 100644 --- a/docs/tasks.md +++ b/docs/tasks.md @@ -13,7 +13,6 @@ Tasks are used for wrapping code that should be executed at a later point, eithe + [Task dependencies](#task-dependencies) + [Alias tasks](#alias-tasks) * [default task](#default-task) -- [Related](#related) ## Creating tasks diff --git a/docs/updaters.md b/docs/updaters.md index a7b27ef..46c8de2 100644 --- a/docs/updaters.md +++ b/docs/updaters.md @@ -15,7 +15,6 @@ This document describes how to create, register and run updaters. * [Order of precendence](#order-of-precendence) - [Discovering updaters](#discovering-updaters) - [Default updater](#default-updater) -- [Related](#related) ## TODO diff --git a/package.json b/package.json index 37c21af..8e5f1fa 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "gulp-istanbul": "^1.0.0", "gulp-mocha": "^2.2.0", "gulp-unused": "^0.1.2", + "helper-changelog": "^0.3.0", "is-absolute": "^0.2.5", "load-pkg": "^3.0.1", "mocha": "^2.5.3", @@ -109,6 +110,9 @@ "plugins": [ "gulp-format-md" ], + "helpers": [ + "helper-changelog" + ], "related": { "description": "Update shares a common architecture and plugin ecosystem with the following libraries:", "list": [ @@ -140,4 +144,4 @@ "reflinks": true } } -} +} \ No newline at end of file diff --git a/support/lib/utils.js b/support/lib/utils.js index 794cc34..09286ed 100644 --- a/support/lib/utils.js +++ b/support/lib/utils.js @@ -11,6 +11,7 @@ require('has-value'); require('pretty-remarkable', 'prettify'); require('remarkable', 'Remarkable'); require('strip-color'); +require('pascalcase'); require('template-helpers', 'helpers'); require('through2', 'through'); From 4274e6b054490d79360fec5e393f2b86f6d3f074 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Thu, 21 Jul 2016 18:04:14 -0400 Subject: [PATCH 258/274] 0.7.2 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 8e5f1fa..80a8d71 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "update", "description": "Be scalable! Update is a new, open source developer framework and CLI for automating updates of any kind in code projects.", - "version": "0.7.1", + "version": "0.7.2", "homepage": "https://github.com/update/update", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", "repository": "update/update", @@ -144,4 +144,4 @@ "reflinks": true } } -} \ No newline at end of file +} From f2cb45bed143599cda1d63e2b53703d7cfa2d66f Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Thu, 21 Jul 2016 19:39:12 -0400 Subject: [PATCH 259/274] fix app.cwd --- index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index c27e51b..2681299 100644 --- a/index.js +++ b/index.js @@ -143,8 +143,11 @@ Update.prototype.initDefaults = function() { }); this.on('task:starting', function(event, task) { + if (event && event.app) { + event.app.cwd = self.base.options.dest || self.base.cwd || event.app.cwd; + } if (task && task.app) { - task.app.cwd = self.base.cwd; + task.app.cwd = self.base.options.dest || self.base.cwd || task.app.cwd; } }); }; From cacb59996b835a70bf5668e9bcfdffb536b01777 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Thu, 21 Jul 2016 19:39:22 -0400 Subject: [PATCH 260/274] update changelog --- CHANGELOG.md | 6 ++++++ README.md | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a97011..170cc9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,12 @@ Custom labels used in this changelog: Please [let us know](../../issues) if any of the following heading links are broken. Thanks! +## [0.7.3] - 2016-07-21 + +**fixed** + +- ensure `app.cwd` in the current instance is the cwd defined by the user on the options or argv. + ## [0.7.0] - 2016-07-21 **added** diff --git a/README.md b/README.md index 6057c16..7dc4623 100644 --- a/README.md +++ b/README.md @@ -314,6 +314,12 @@ Custom labels used in this changelog: Please [let us know](../../issues) if any of the following heading links are broken. Thanks! +### [0.7.3](https://github.com/update/update/compare/0.7.0...0.7.3) - 2016-07-21 + +**fixed** + +* ensure `app.cwd` in the current instance is the cwd defined by the user on the options or argv. + ### [0.7.0](https://github.com/update/update/compare/0.6.0...0.7.0) - 2016-07-21 **added** From f6b03b765c3de86e2d4b27d864a112e2b13c710f Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Thu, 21 Jul 2016 19:39:27 -0400 Subject: [PATCH 261/274] 0.7.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 80a8d71..f0e9d01 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "update", "description": "Be scalable! Update is a new, open source developer framework and CLI for automating updates of any kind in code projects.", - "version": "0.7.2", + "version": "0.7.3", "homepage": "https://github.com/update/update", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", "repository": "update/update", From eefc5251ebd022ef790befc604ee3fe758d78bc5 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Thu, 21 Jul 2016 21:36:35 -0400 Subject: [PATCH 262/274] add example commits --- .verb.md | 14 ++++++++++++++ README.md | 13 ++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/.verb.md b/.verb.md index d025595..9fbe965 100644 --- a/.verb.md +++ b/.verb.md @@ -29,6 +29,16 @@ You can create your own [updaters](docs/updaters.md) using Update's API, or inst - **plugin ecosystem**: any plugins that work with [Base applications](#discovering-plugins) will work also with Update. Which means you can use plugins (or generators) from [assemble][], [verb][], and [generate][], to name a few. - **well tested**: with more than 1,250 unit tests +### Examples + +Here are some random example commits after running `$ update`. + +**Project**/**Commit** | **Updaters used** +--- | --- +[generate-scaffold][generate-scaffold-commit] | `editorconfig`, `travis` +[updater-editorconfig][updater-editorconfig-commit] | `editorconfig`, `eslint`, `travis`, `license` +[expand-target][et] | `editorconfig`, `eslint`, `travis`, `package` + ### Features * **unparalleled flow control**: through the use of [updaters](docs/updaters.md), [sub-updaters][getting-started] and [tasks](docs/tasks.md) @@ -225,3 +235,7 @@ Are you using Update in your project? Have you published an [updater](docs/updat [templates-plugin]: https://www.npmjs.com/browse/keyword/templatesplugin [verb-plugin]: https://www.npmjs.com/browse/keyword/verbplugin [st]: https://github.com/node-base/sublime-text-base-snippets + +[generate-scaffold-commit]: https://github.com/generate/generate-scaffold/commit/440d71f7293cb1f79445c0161440afbb266a2fbe +[updater-editorconfig-commit]: https://github.com/update/updater-editorconfig/commit/b7bd0aa616519440fa4a0d29d3aefac26787cbaf +[et]: https://github.com/jonschlinkert/expand-target/commit/48d70a0bc95d8eb3f7def615b7e231e8f93816e8 \ No newline at end of file diff --git a/README.md b/README.md index 7dc4623..17d425f 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@

- Be scalable! Update is a new, open source developer framework and CLI for automating updates of any kind in code projects. ## Table of Contents @@ -13,6 +12,7 @@ Be scalable! Update is a new, open source developer framework and CLI for automa - [Overview](#overview) * [What does Update do?](#what-does-update-do) * [Why should I use Update?](#why-should-i-use-update) + * [Examples](#examples) * [Features](#features) - [CLI](#cli) * [Installing update](#installing-update) @@ -68,6 +68,17 @@ You can create your own [updaters](docs/updaters.md) using Update's API, or inst * **plugin ecosystem**: any plugins that work with [Base applications](#discovering-plugins) will work also with Update. Which means you can use plugins (or generators) from [assemble](https://github.com/assemble/assemble), [verb](https://github.com/verbose/verb), and [generate](https://github.com/generate/generate), to name a few. * **well tested**: with more than 1,250 unit tests +### Examples + +Here are some random example commits after running `$ update`. + +**Project**/**Commit** | **Updaters used** + +--- | --- +[generate-scaffold](https://github.com/generate/generate-scaffold/commit/440d71f7293cb1f79445c0161440afbb266a2fbe) | `editorconfig`, `travis` +[updater-editorconfig](https://github.com/update/updater-editorconfig/commit/b7bd0aa616519440fa4a0d29d3aefac26787cbaf) | `editorconfig`, `eslint`, `travis`, `license` +[expand-target](https://github.com/jonschlinkert/expand-target/commit/48d70a0bc95d8eb3f7def615b7e231e8f93816e8) | `editorconfig`, `eslint`, `travis`, `package` + ### Features * **unparalleled flow control**: through the use of [updaters](docs/updaters.md), [sub-updaters](https://github.com/update/getting-started) and [tasks](docs/tasks.md) From c113b5a9821250e811740b6d7a650fc4f913bf5f Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Wed, 31 Aug 2016 23:56:55 -0400 Subject: [PATCH 263/274] add/update docs --- ...t-in-updaters.md => cli.built-in-tasks.md} | 6 ++-- support/docs/cli.commands.md | 36 +++++++++++++++++-- support/docs/installing-the-cli.md | 33 ----------------- support/docs/updatefile.md | 22 ++++++++++++ 4 files changed, 58 insertions(+), 39 deletions(-) rename support/docs/{cli.built-in-updaters.md => cli.built-in-tasks.md} (78%) diff --git a/support/docs/cli.built-in-updaters.md b/support/docs/cli.built-in-tasks.md similarity index 78% rename from support/docs/cli.built-in-updaters.md rename to support/docs/cli.built-in-tasks.md index 4765631..56d5858 100644 --- a/support/docs/cli.built-in-updaters.md +++ b/support/docs/cli.built-in-tasks.md @@ -1,10 +1,10 @@ --- -title: Built in updaters +title: Built in tasks related: doc: [] --- -Update only has a few built-in [updaters](docs/updaters.md) (these might be externalized at some point): +Update only has a few built-in [tasks](docs/tasks.md) (these might be externalized at some point): * [init](#init): Choose the updaters to run by default each time `update` is run from the command line * [list](#list): List all globally and locally installed updaters @@ -16,7 +16,7 @@ Update only has a few built-in [updaters](docs/updaters.md) (these might be exte ### init -Prompts you to choose one or more updaters to run by default each time `update` is run from the command line: +Prompts you to choose one or more "default updaters" to run automatically each time to `update` command is given: ```sh $ update init diff --git a/support/docs/cli.commands.md b/support/docs/cli.commands.md index e3fc621..8dc6d92 100644 --- a/support/docs/cli.commands.md +++ b/support/docs/cli.commands.md @@ -1,7 +1,7 @@ --- title: Command line flags related: - doc: [] + docs: [''] --- Supported command line flags. @@ -12,10 +12,40 @@ By default, when an `updatefile.js` exists in the current working directory, the The `--run` flag forces `update` to run stored tasks and the `default` task or explicitly specified tasks in `updatefile.js`. Stored tasks are executed first, in the order defined, then the `default` task or explicitly defined tasks. -**Default**: `undefined` - **Example** ```sh $ update --run ``` + +## --help + +See a help menu in the terminal: + +```sh +Usage: update [options] + +Command: Updater or tasks to run + +Examples: + + # run the "foo" updater + $ update foo + + # run the "bar" task on updater "foo" + $ update foo:bar + + # run multiple tasks on updater "foo" + $ update foo:bar,baz,qux + + # run a sub-updater on updater "foo" + $ update foo.abc + + # run task "xyz" on sub-updater "foo.abc" + $ update foo.abc:xyz + + Update attempts to automatically determine if "foo" is a task or updater. + If there is a conflict, you can force update to run updater "foo" + by specifying a task on the updater. Example: `update foo:default` +``` + diff --git a/support/docs/installing-the-cli.md b/support/docs/installing-the-cli.md index b12e37d..b65bad9 100644 --- a/support/docs/installing-the-cli.md +++ b/support/docs/installing-the-cli.md @@ -13,36 +13,3 @@ $ npm install --global update This adds the `update` command to your system path, allowing it to be run from any directory. You should now be able to use the `update` command to execute code in a local `updatefile.js` file, or to run any locally or globally installed updaters by their [aliases](tasks.md#alias-tasks) or full names. - -**Init** - -If it's your first time using update, run `update init` to set your global defaults. - -**CLI help** - -``` -Usage: update [options] - -Command: Updater or tasks to run - -Examples: - - # run the "foo" updater - $ update foo - - # run the "bar" task on updater "foo" - $ update foo:bar - - # run multiple tasks on updater "foo" - $ update foo:bar,baz,qux - - # run a sub-updater on updater "foo" - $ update foo.abc - - # run task "xyz" on sub-updater "foo.abc" - $ update foo.abc:xyz - - Update attempts to automatically determine if "foo" is a task or updater. - If there is a conflict, you can force update to run updater "foo" - by specifying a task on the updater. Example: `update foo:default` -``` diff --git a/support/docs/updatefile.md b/support/docs/updatefile.md index 6537e51..e4ef3b0 100644 --- a/support/docs/updatefile.md +++ b/support/docs/updatefile.md @@ -4,6 +4,28 @@ related: doc: ['installing-updaters', 'updaters', 'tasks'] --- +If you're authoring (and maybe even publishing) an updater, you can use the same conventions you would with any other node.js library. If node.js/npm can find your module, then so can Update. You can write your code in `index.js`, or `lib/foo.js` or wherever you want. + +However, Update's CLI shows a little unfair favoratism for files with the name `updatefile.js`. + +When the current working directory has an `updatefile.js`, Update's CLI will try to run any tasks or code in that file _instead of running your [default updaters](cli/built-in-tasks.md#init)_. + +**Force default updaters to run** + +You can force Update's CLI to run all of your default updaters by passing the `--run` flag on the command line, or to _always run default updaters in a project that has an `updatefile.js`, you can add the following in package.json: + +```json +{ + "update": { + "run": true + } +} +``` + +Note that that this will run _both_ the default updaters, and the `updatefile.js`, in that order. + +## How the CLI uses updatefile.js + Each time `update` is run, Update's CLI looks for an `updatefile.js` in the current working directory. **If `updatefile.js` exists** From 9c35a9be9ab7f82bd6cff5124567f6fd9b77673b Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Wed, 31 Aug 2016 23:56:59 -0400 Subject: [PATCH 264/274] reminder --- support/docs/redirects.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 support/docs/redirects.json diff --git a/support/docs/redirects.json b/support/docs/redirects.json new file mode 100644 index 0000000..63b9ef9 --- /dev/null +++ b/support/docs/redirects.json @@ -0,0 +1,3 @@ +{ + "old": "new" +} From b872d9e6d5433617f66e5d657d73742d0f6c0555 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Wed, 31 Aug 2016 23:57:11 -0400 Subject: [PATCH 265/274] improve init/add/remove --- lib/commands/add.js | 44 ++++++++++++++++++++++++++++++++++++++++++ lib/commands/remove.js | 24 +++++++++++++---------- lib/updatefile.js | 18 +++++++++++++++-- 3 files changed, 74 insertions(+), 12 deletions(-) create mode 100644 lib/commands/add.js diff --git a/lib/commands/add.js b/lib/commands/add.js new file mode 100644 index 0000000..624901e --- /dev/null +++ b/lib/commands/add.js @@ -0,0 +1,44 @@ +'use strict'; + +var utils = require('../utils'); + +/** + * Remove names from the list of locally or globally stored updaters. + * + * ```sh + * # remove updater `foo` + * $ update -r foo + * # or + * $ update -rc foo + * # sugar for + * $ update --remove --config foo + * # remove globally stored updaters + * $ update -rg foo + * # sugar for + * $ update --remove --global foo + * ``` + * @name tasks + * @api public + * @cli public + */ + +module.exports = function(app, options) { + return function(names, key, config, next) { + var updaters = []; + + if (typeof names === 'string') { + names = utils.toArray(names); + } + + if (!config.config) { + updaters = app.globals.get('updaters') || []; + app.globals.set('updaters', updaters.concat(names)); + + } else { + updaters = app.pkg.get('update.updaters') || []; + app.pkg.union('update.updaters', updaters.concat(names)); + app.pkg.save(); + } + next(); + }; +}; diff --git a/lib/commands/remove.js b/lib/commands/remove.js index f93cdd2..392a219 100644 --- a/lib/commands/remove.js +++ b/lib/commands/remove.js @@ -24,22 +24,26 @@ var utils = require('../utils'); module.exports = function(app, options) { return function(names, key, config, next) { - var updaters = []; - if (typeof names === 'string') { names = utils.toArray(names); } - if (config.global) { - updaters = app.globals.get('updaters') || []; - app.globals.del('updaters'); - app.globals.set('updaters', utils.remove(updaters, names)); - } + var updaters = names.map(function(name) { + return 'updaters.' + name; + }); - if (config.config || !config.global) { - updaters = app.pkg.get('update.updaters') || []; + if (!config.config) { + app.globals.del(updaters); + + } else { + var list = app.pkg.get('update.updaters'); app.pkg.del('update.updaters'); - app.pkg.set('update.updaters', utils.remove(updaters, names)); + + var rest = utils.remove(list, names); + if (rest.length) { + app.pkg.set('update.updaters', rest); + app.pkg.save(); + } } next(); }; diff --git a/lib/updatefile.js b/lib/updatefile.js index ac89591..afacff8 100644 --- a/lib/updatefile.js +++ b/lib/updatefile.js @@ -27,7 +27,13 @@ module.exports = function(app, base) { */ app.task('init', { silent: true }, function() { + var list = Update.resolveTasks(app, argv); var updaters = []; + + console.log(); + console.log(' Current updaters:', app.log.cyan(list.join(', '))); + console.log(); + return app.src([gm('updater-*'), cwd('node_modules/updater-*')]) .pipe(through.obj(function(file, enc, next) { file.basename = app.toAlias(file.basename); @@ -146,8 +152,16 @@ function save(app, list) { + gray(' ---') + '\n' + '\n' - + ' To make changes, run: ' + bold(`$ ${command} init`); - console.log(msg.split('\n').join('\n')); + + bold(' Cheetsheet:') + + '\n' + + '\n' + + (` $ ${command} init`) + gray(' start over\n') + + (` $ ${command} --remove `) + gray(' remove updaters\n') + + (` $ ${command} --add `) + gray(' add updaters\n') + + (` $ ${command} show`) + gray(' show your queued updaters\n') + + (` $ ${command} list`) + gray(' list all installed updaters\n') + + console.log(msg); console.log(); } From cccb9920132c009cd1103d4724cd6c5731c73eb7 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Wed, 31 Aug 2016 23:57:24 -0400 Subject: [PATCH 266/274] docs --- support/lib/common.js | 4 +++- support/lib/helpers.js | 5 +++++ support/lib/paths.js | 5 +++-- support/lib/utils.js | 7 ++++--- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/support/lib/common.js b/support/lib/common.js index c1f0ddb..b43179d 100644 --- a/support/lib/common.js +++ b/support/lib/common.js @@ -1,8 +1,9 @@ 'use strict'; +var path = require('path'); var helpers = require('./helpers'); var isValid = require('is-valid-app'); -var path = require('path'); +var pkg = require('base-pkg'); module.exports = function(options) { return function(app) { @@ -11,6 +12,7 @@ module.exports = function(options) { app.use(require('generate-defaults')); app.use(require('verb-toc')); app.use(helpers()); + app.use(pkg()); if (!app.docs) app.create('docs'); app.option('renameKey', function(key, file) { diff --git a/support/lib/helpers.js b/support/lib/helpers.js index 3205e42..9fbea96 100644 --- a/support/lib/helpers.js +++ b/support/lib/helpers.js @@ -7,6 +7,11 @@ module.exports = function(options) { return function(app) { if (!utils.isValid(app, 'update-support-helpers')) return; app.helpers(utils.helpers()); + app.helper('raw', function(str) { + console.log(str); + return str; + }); + app.helper('hasValue', function(val, str) { return utils.hasValue(val) ? str : ''; }); diff --git a/support/lib/paths.js b/support/lib/paths.js index d87fa88..11b64a6 100644 --- a/support/lib/paths.js +++ b/support/lib/paths.js @@ -1,8 +1,9 @@ 'use strict'; var path = require('path'); -exports.cwd = require('memoize-path')(path.resolve(__dirname, '..')); -exports.memo = require('memoize-path')(path.resolve(__dirname, '..')); +var base = path.resolve(__dirname, '..'); +exports.cwd = require('memoize-path')(base); +exports.memo = require('memoize-path')(base); exports.docs = function(fp) { var res = exports.memo('../docs')(fp); diff --git a/support/lib/utils.js b/support/lib/utils.js index 09286ed..5f3c791 100644 --- a/support/lib/utils.js +++ b/support/lib/utils.js @@ -58,7 +58,8 @@ utils.links = function(rules) { var pre = href.slice(0, idx); var anc = anchors[pre]; if (anc && anc.indexOf(val) === -1) { - throw new Error('cannot find anchor: #' + val + ' in (' + token.href + ') in ' + file.path); + console.log(pre) + throw new Error(`cannot find anchor: #${val} in "${pre}" (defined in ${file.path})`); } } else { var segs = href.split('/'); @@ -76,10 +77,10 @@ utils.links = function(rules) { var group = paths[seg]; if (typeof group === 'undefined') { - throw new Error('directory group: ' + seg + ' is not defined'); + throw new Error(`directory group: "${seg}" is not defined`); } if (group.indexOf(rest) === -1 && !/issues/.test(rest)) { - throw new Error(`cannot find filepath: ${rest} in "${seg}" (${file.path})`); + throw new Error(`cannot find filepath: "${rest}" in "${seg}" (${file.path})`); } } } From faf3ff6b8110674a362f25f2b91d59a3bc8cabed Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Thu, 1 Sep 2016 00:36:08 -0400 Subject: [PATCH 267/274] run update --- .gitignore | 24 ++++++++++++++---------- package.json | 4 ++++ test/app.questions.js | 14 +++++++------- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index 752f934..7988154 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,21 @@ +# always ignore files *.DS_Store *.sublime-* -_gh_pages -bower_components + +# test related, or directories generated by tests +test/actual +actual +coverage + +# npm node_modules npm-debug.log -actual -test/actual + +# misc +_gh_pages +benchmark +bower_components +vendor temp tmp TODO.md -vendor -.idea -benchmark -coverage -_notes.md -_drafts diff --git a/package.json b/package.json index f0e9d01..8ebae8e 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,10 @@ "version": "0.7.3", "homepage": "https://github.com/update/update", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", + "contributors": [ + "Brian Woodward (https://github.com/doowb)", + "Jon Schlinkert (http://twitter.com/jonschlinkert)" + ], "repository": "update/update", "bugs": { "url": "https://github.com/update/update/issues" diff --git a/test/app.questions.js b/test/app.questions.js index eb9d670..1c36472 100644 --- a/test/app.questions.js +++ b/test/app.questions.js @@ -101,7 +101,7 @@ describe('app.questions', function() { app.data('name', 'Brian Woodward'); app.ask('name', function(err, answers) { - if(err) return cb(err) + if (err) return cb(err) assert.equal(answers.name, 'Brian Woodward'); cb(); }); @@ -112,12 +112,12 @@ describe('app.questions', function() { app.data('a', 'b'); app.ask('a', function(err, answers) { - if(err) return cb(err) + if (err) return cb(err) assert.equal(answers.a, 'b'); app.data('a', 'zzz'); app.ask('a', function(err, answers) { - if(err) return cb(err) + if (err) return cb(err) assert.equal(answers.a, 'zzz'); cb(); }) @@ -208,12 +208,12 @@ describe('app.questions', function() { app.data('package.name', 'base-questions'); app.ask('package.name', function(err, answers) { - if(err) return cb(err) + if (err) return cb(err) assert.equal(answers.package.name, 'base-questions'); app.data('package.name', 'foo-bar-baz'); app.ask('package.name', function(err, answers) { - if(err) return cb(err) + if (err) return cb(err) assert.equal(answers.package.name, 'foo-bar-baz'); cb(); }) @@ -225,12 +225,12 @@ describe('app.questions', function() { site.data('package.name', 'base-questions'); site.ask('package.name', function(err, answers) { - if(err) return cb(err) + if (err) return cb(err) assert.equal(answers.package.name, 'base-questions'); site.data('package.name', 'foo-bar-baz'); site.ask('package.name', function(err, answers) { - if(err) return cb(err) + if (err) return cb(err) assert.equal(answers.package.name, 'foo-bar-baz'); cb(); }) From 2374a5f2f85f797ac9d39c7ebbec33b5623a3ce9 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Thu, 1 Sep 2016 00:45:28 -0400 Subject: [PATCH 268/274] update docs --- .verb.md | 43 +++++++++++++++----- README.md | 101 ++++++++++++++++++++++------------------------ lib/updatefile.js | 6 +-- package.json | 6 +-- 4 files changed, 88 insertions(+), 68 deletions(-) diff --git a/.verb.md b/.verb.md index 9fbe965..8f4d994 100644 --- a/.verb.md +++ b/.verb.md @@ -1,6 +1,28 @@ -Please read our [contributing guide](.github/contributing.md) if you'd like to learn more about contributing to this project. +You might also be interested in: + +- [generate](https://github.com/generate/generate) +- [assemble](https://github.com/assemble/assemble) +- [verb](https://github.com/verbose/verb) + +## Quickstart + +Install Update's CLI and an example [updater](#updaters) globally: + +```sh +$ npm install --global update && updater-example +``` + +Initialize `update`: -You might also be interested in [generate](https://github.com/generate/generate). +```sh +$ update init +``` + +Run update: + +```sh +$ update +``` ## Overview @@ -93,6 +115,7 @@ $ update example This appends the string `foo` to the contents of `example.txt`. Visit the [updater-example][] project for additional steps and guidance. ## Tasks + Update ships with the following built-in [tasks](docs/tasks.md). These will be externalized to an updater or [generate][] generator at some point. {%= apidocs("lib/updatefile.js") %} @@ -110,13 +133,10 @@ $ update help --config, -c Save a configuration value to the `update` object in package.json --cwd Set or display the current working directory - --data, -d Define data. API equivalent of `app.data()` - --disable Disable an option. API equivalent of "app.disable('foo')" - --enable Enable an option. API equivalent of "app.enable('foo')" - --global, -g Save a global configuration value to use as a default --help, -h Display this help menu - --init, -i Prompts for configuration values and stores the answers - --option, -o Define options. API equivalent of `app.option()` + --init, -i Prompts you to choose the updaters to automatically run (your "queue") + --add Add updaters to your queue + --remove Remove updaters from your queue --run Force tasks to run regardless of command line flags used --silent, -S Silence all tasks and updaters in the terminal --show Display the value of @@ -218,6 +238,11 @@ Are you using Update in your project? Have you published an [updater](docs/updat * Get implementation help on [StackOverflow](http://stackoverflow.com/questions/tagged/update) (please use the `update` tag in questions) * **Gitter** Discuss Update with us on [Gitter](https://gitter.im/update) * If you publish an updater, thank you! To make your project as discoverable as possible, please add the keyword `updateupdater` to package.json. + +**More information** + +Please read our [contributing guide](.github/contributing.md) if you'd like to learn more about contributing to this project. + {{/block}} ## Release History @@ -238,4 +263,4 @@ Are you using Update in your project? Have you published an [updater](docs/updat [generate-scaffold-commit]: https://github.com/generate/generate-scaffold/commit/440d71f7293cb1f79445c0161440afbb266a2fbe [updater-editorconfig-commit]: https://github.com/update/updater-editorconfig/commit/b7bd0aa616519440fa4a0d29d3aefac26787cbaf -[et]: https://github.com/jonschlinkert/expand-target/commit/48d70a0bc95d8eb3f7def615b7e231e8f93816e8 \ No newline at end of file +[et]: https://github.com/jonschlinkert/expand-target/commit/48d70a0bc95d8eb3f7def615b7e231e8f93816e8 diff --git a/README.md b/README.md index 17d425f..625ed43 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,41 @@ -# update [![NPM version](https://img.shields.io/npm/v/update.svg?style=flat)](https://www.npmjs.com/package/update) [![NPM downloads](https://img.shields.io/npm/dm/update.svg?style=flat)](https://npmjs.org/package/update) [![Build Status](https://img.shields.io/travis/update/update.svg?style=flat)](https://travis-ci.org/update/update) -

+

+ Be scalable! Update is a new, open source developer framework and CLI for automating updates of any kind in code projects. -## Table of Contents - -- [Overview](#overview) - * [What does Update do?](#what-does-update-do) - * [Why should I use Update?](#why-should-i-use-update) - * [Examples](#examples) - * [Features](#features) -- [CLI](#cli) - * [Installing update](#installing-update) - * [Installing updaters](#installing-updaters) -- [Tasks](#tasks) -- [Help menu](#help-menu) -- [API](#api) - * [Updaters](#updaters) - + [Discovering updaters](#discovering-updaters) - + [Discovering plugins](#discovering-plugins) - + [Authoring updaters](#authoring-updaters) -- [Configuration](#configuration) - * [Options](#options) - + [updaters](#updaters) -- [More information](#more-information) -- [Release History](#release-history) -- [About](#about) - * [Related projects](#related-projects) - * [Contributing](#contributing) - * [Running tests](#running-tests) - * [Author](#author) - * [License](#license) - -_(TOC generated by [verb](https://github.com/verbose/verb) using [markdown-toc](https://github.com/jonschlinkert/markdown-toc))_ - -Please read our [contributing guide](.github/contributing.md) if you'd like to learn more about contributing to this project. - -You might also be interested in [generate](https://github.com/generate/generate). +# update + +[![NPM version](https://img.shields.io/npm/v/update.svg?style=flat)](https://www.npmjs.com/package/update) [![NPM downloads](https://img.shields.io/npm/dm/update.svg?style=flat)](https://npmjs.org/package/update) [![Build Status](https://img.shields.io/travis/update/update.svg?style=flat)](https://travis-ci.org/update/update) + +You might also be interested in: + +* [generate](https://github.com/generate/generate) +* [assemble](https://github.com/assemble/assemble) +* [verb](https://github.com/verbose/verb) + +## Quickstart + +Install Update's CLI and an example [updater](#updaters) globally: + +```sh +$ npm install --global update && updater-example +``` + +Initialize `update`: + +```sh +$ update init +``` + +Run update: + +```sh +$ update +``` ## Overview @@ -144,7 +139,7 @@ Select the updaters to run every time `update` is run. Use `--add` to add additi $ update init ``` -### [list](lib/updatefile.js#L58) +### [list](lib/updatefile.js#L64) Display a list of currently installed updaters. @@ -156,7 +151,7 @@ $ update defaults:list $ update list ``` -### [help](lib/updatefile.js#L79) +### [help](lib/updatefile.js#L85) Display a help [menu](#help-menu) of available commands and flags. @@ -168,7 +163,7 @@ $ update defaults:help $ update help ``` -### [show](lib/updatefile.js#L95) +### [show](lib/updatefile.js#L101) Show the list of updaters that are registered to run on the current project. @@ -180,7 +175,7 @@ $ update defaults:show $ update show ``` -### [help](lib/updatefile.js#L114) +### [help](lib/updatefile.js#L120) Default task for the built-in `defaults` generator. @@ -203,13 +198,10 @@ $ update help --config, -c Save a configuration value to the `update` object in package.json --cwd Set or display the current working directory - --data, -d Define data. API equivalent of `app.data()` - --disable Disable an option. API equivalent of "app.disable('foo')" - --enable Enable an option. API equivalent of "app.enable('foo')" - --global, -g Save a global configuration value to use as a default --help, -h Display this help menu - --init, -i Prompts for configuration values and stores the answers - --option, -o Define options. API equivalent of `app.option()` + --init, -i Prompts you to choose the updaters to automatically run (your "queue") + --add Add updaters to your queue + --remove Remove updaters from your queue --run Force tasks to run regardless of command line flags used --silent, -S Silence all tasks and updaters in the terminal --show Display the value of @@ -355,7 +347,7 @@ Please [let us know](../../issues) if any of the following heading links are bro First stable release! -_(Changelog generated by [helper-changelog](https://github.com/jonschlinkert/helper-changelog))_ +_(Changelog generated by [helper-changelog](https://github.com/helpers/helper-changelog))_ ## About @@ -368,12 +360,15 @@ _(Changelog generated by [helper-changelog](https://github.com/jonschlinkert/hel ### Community -Are you using Update in your project? Have you published an [updater](docs/updaters.md) and want to share your Update project with the world? Here are some suggestions: +Are you using [Generate](https://github.com/generate/generate) in your project? Have you published a [generator](https://github.com/generate/generate/blob/master/docs/generators.md) and want to share your project with the world? + +Here are some suggestions! -* If you get like Update and want to tweet about it, please use the hashtag `#updatejs` -* Get implementation help on [StackOverflow](http://stackoverflow.com/questions/tagged/update) (please use the `update` tag in questions) -* **Gitter** Discuss Update with us on [Gitter](https://gitter.im/update) -* If you publish an updater, thank you! To make your project as discoverable as possible, please add the keyword `updateupdater` to package.json. +* If you get like Generate and want to tweet about it, please feel free to mention `@generatejs` or use the `#generatejs` hashtag +* Show your love by starring [Generate](https://github.com/generate/generate) and `update` +* Get implementation help on [StackOverflow](http://stackoverflow.com/questions/tagged/generate) (please use the `generatejs` tag in questions) +* **Gitter** Discuss Generate with us on [Gitter](https://gitter.im/generate/generate) +* If you publish an generator, thank you! To make your project as discoverable as possible, please add the keyword `generategenerator` to package.json. ### Contributing @@ -403,4 +398,4 @@ Released under the [MIT license](https://github.com/update/update/blob/master/LI *** -_This file was generated by [verb](https://github.com/verbose/verb), v0.9.0, on July 21, 2016._ \ No newline at end of file +_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.1.30, on September 01, 2016._ \ No newline at end of file diff --git a/lib/updatefile.js b/lib/updatefile.js index afacff8..f12e8ec 100644 --- a/lib/updatefile.js +++ b/lib/updatefile.js @@ -155,9 +155,9 @@ function save(app, list) { + bold(' Cheetsheet:') + '\n' + '\n' - + (` $ ${command} init`) + gray(' start over\n') - + (` $ ${command} --remove `) + gray(' remove updaters\n') - + (` $ ${command} --add `) + gray(' add updaters\n') + + (` $ ${command} --init`) + gray(' initialize update (start over)\n') + + (` $ ${command} --remove `) + gray(' remove updaters from your queue\n') + + (` $ ${command} --add `) + gray(' add updaters to your queue\n') + (` $ ${command} show`) + gray(' show your queued updaters\n') + (` $ ${command} list`) + gray(' list all installed updaters\n') diff --git a/package.json b/package.json index 8ebae8e..a548256 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,7 @@ "verb": { "run": true, "toc": true, - "layout": "common-minimal", + "layout": "app", "tasks": [ "readme" ], @@ -130,12 +130,12 @@ "assemble", "assemble-core", "assemble-loader", - "bach", "base", "consolidate", "generate", "gulp", "handlebars", + "helper-changelog", "lodash", "pug", "swig", @@ -148,4 +148,4 @@ "reflinks": true } } -} +} \ No newline at end of file From 8d4b479f62801f666633d034c2eed971af4d8fbc Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Thu, 1 Sep 2016 00:45:49 -0400 Subject: [PATCH 269/274] 0.7.4 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index a548256..4d142c8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "update", "description": "Be scalable! Update is a new, open source developer framework and CLI for automating updates of any kind in code projects.", - "version": "0.7.3", + "version": "0.7.4", "homepage": "https://github.com/update/update", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", "contributors": [ @@ -148,4 +148,4 @@ "reflinks": true } } -} \ No newline at end of file +} From 48017cfa0779d6c4013f6a700cd5dc4b4ce83d58 Mon Sep 17 00:00:00 2001 From: Felix Krause Date: Wed, 26 Oct 2016 11:30:34 -0700 Subject: [PATCH 270/274] Fix markdown table in README Previously it didn't render due to an extra space --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 625ed43..3334558 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,6 @@ You can create your own [updaters](docs/updaters.md) using Update's API, or inst Here are some random example commits after running `$ update`. **Project**/**Commit** | **Updaters used** - --- | --- [generate-scaffold](https://github.com/generate/generate-scaffold/commit/440d71f7293cb1f79445c0161440afbb266a2fbe) | `editorconfig`, `travis` [updater-editorconfig](https://github.com/update/updater-editorconfig/commit/b7bd0aa616519440fa4a0d29d3aefac26787cbaf) | `editorconfig`, `eslint`, `travis`, `license` @@ -398,4 +397,4 @@ Released under the [MIT license](https://github.com/update/update/blob/master/LI *** -_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.1.30, on September 01, 2016._ \ No newline at end of file +_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.1.30, on September 01, 2016._ From 543c645017ffff504a965717089c61d08cc67deb Mon Sep 17 00:00:00 2001 From: Kaelig Deloumeau-Prigent Date: Wed, 1 Feb 2017 20:31:06 -0800 Subject: [PATCH 271/274] Fix package keyword in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 625ed43..7df5a3f 100644 --- a/README.md +++ b/README.md @@ -236,7 +236,7 @@ $ update help #### Discovering updaters -* Find updaters to install by [searching npm](https://www.npmjs.com/browse/keyword/update-updater) for packages with the keyword `updateupdater` +* Find updaters to install by [searching npm](https://www.npmjs.com/browse/keyword/update-updater) for packages with the keyword `update-updater` * Visit [Update's GitHub org](https://github.com/update) to see the updaters maintained by the core team #### Discovering plugins @@ -398,4 +398,4 @@ Released under the [MIT license](https://github.com/update/update/blob/master/LI *** -_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.1.30, on September 01, 2016._ \ No newline at end of file +_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.1.30, on September 01, 2016._ From 4b32609a22d0788e17ed02c733d8e8bb8e874f28 Mon Sep 17 00:00:00 2001 From: AJ Jordan Date: Tue, 23 Jan 2018 13:10:46 -0500 Subject: [PATCH 272/274] Fix broken link --- .verb.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.verb.md b/.verb.md index 8f4d994..64034dc 100644 --- a/.verb.md +++ b/.verb.md @@ -258,6 +258,7 @@ Please read our [contributing guide](.github/contributing.md) if you'd like to l [assemble-plugin]: https://www.npmjs.com/browse/keyword/assembleplugin [generate-plugin]: https://www.npmjs.com/browse/keyword/generateplugin [templates-plugin]: https://www.npmjs.com/browse/keyword/templatesplugin +[update-plugin]: https://www.npmjs.com/browse/keyword/updateplugin [verb-plugin]: https://www.npmjs.com/browse/keyword/verbplugin [st]: https://github.com/node-base/sublime-text-base-snippets From 323de4c1207cabf7af4920a35383f29c1c0703d7 Mon Sep 17 00:00:00 2001 From: doowb Date: Tue, 23 Jan 2018 13:51:01 -0500 Subject: [PATCH 273/274] run verb to generate readme --- .verb.md | 20 +++----------------- README.md | 43 ++++++++++++++++++++++--------------------- 2 files changed, 25 insertions(+), 38 deletions(-) diff --git a/.verb.md b/.verb.md index 64034dc..062714a 100644 --- a/.verb.md +++ b/.verb.md @@ -94,7 +94,7 @@ This adds the `update` command to your system path, allowing it to be run from a ### Installing updaters -Updaters can be [found on npm](https://www.npmjs.com/browse/keyword/update-updater), but if you're not familiar with how Update works, we recommend installing `updater-example`: +Updaters can be [found on npm](https://www.npmjs.com/browse/keyword/updateupdater), but if you're not familiar with how Update works, we recommend installing `updater-example`: ```sh $ npm install --global updater-example @@ -171,7 +171,7 @@ $ update help #### Discovering updaters -* Find updaters to install by [searching npm](https://www.npmjs.com/browse/keyword/update-updater) for packages with the keyword `updateupdater` +* Find updaters to install by [searching npm](https://www.npmjs.com/browse/keyword/updateupdater) for packages with the keyword `updateupdater` * Visit [Update's GitHub org](https://github.com/update) to see the updaters maintained by the core team #### Discovering plugins @@ -231,20 +231,6 @@ Run `updater-license` and `updater-package` on the current project: - Learn more about [base][] - Get [Sublime Text Snippets][st] for creating tasks and updaters -{{#block "community" heading="### Community"}} -Are you using Update in your project? Have you published an [updater](docs/updaters.md) and want to share your Update project with the world? Here are some suggestions: - -* If you get like Update and want to tweet about it, please use the hashtag `#updatejs` -* Get implementation help on [StackOverflow](http://stackoverflow.com/questions/tagged/update) (please use the `update` tag in questions) -* **Gitter** Discuss Update with us on [Gitter](https://gitter.im/update) -* If you publish an updater, thank you! To make your project as discoverable as possible, please add the keyword `updateupdater` to package.json. - -**More information** - -Please read our [contributing guide](.github/contributing.md) if you'd like to learn more about contributing to this project. - -{{/block}} - ## Release History {%= increaseHeadings(changelog('CHANGELOG.md', { changelogFooter: true, @@ -252,7 +238,7 @@ Please read our [contributing guide](.github/contributing.md) if you'd like to l repo: repo })) %} - +[Update]: https://github.com/update/update [getting-started]: https://github.com/update/getting-started [base-plugin]: https://www.npmjs.com/browse/keyword/baseplugin [assemble-plugin]: https://www.npmjs.com/browse/keyword/assembleplugin diff --git a/README.md b/README.md index 6242d78..59df07b 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Be scalable! Update is a new, open source developer framework and CLI for automa # update -[![NPM version](https://img.shields.io/npm/v/update.svg?style=flat)](https://www.npmjs.com/package/update) [![NPM downloads](https://img.shields.io/npm/dm/update.svg?style=flat)](https://npmjs.org/package/update) [![Build Status](https://img.shields.io/travis/update/update.svg?style=flat)](https://travis-ci.org/update/update) +[![NPM version](https://img.shields.io/npm/v/update.svg?style=flat)](https://www.npmjs.com/package/update) [![NPM monthly downloads](https://img.shields.io/npm/dm/update.svg?style=flat)](https://npmjs.org/package/update) [![Build Status](https://img.shields.io/travis/update/update.svg?style=flat)](https://travis-ci.org/update/update) You might also be interested in: @@ -68,8 +68,9 @@ You can create your own [updaters](docs/updaters.md) using Update's API, or inst Here are some random example commits after running `$ update`. **Project**/**Commit** | **Updaters used** + --- | --- -[generate-scaffold](https://github.com/generate/generate-scaffold/commit/440d71f7293cb1f79445c0161440afbb266a2fbe) | `editorconfig`, `travis` +[generate-scaffold](https://github.com/generate/generate-scaffold/commit/440d71f7293cb1f79445c0161440afbb266a2fbe) | `editorconfig`, `travis` [updater-editorconfig](https://github.com/update/updater-editorconfig/commit/b7bd0aa616519440fa4a0d29d3aefac26787cbaf) | `editorconfig`, `eslint`, `travis`, `license` [expand-target](https://github.com/jonschlinkert/expand-target/commit/48d70a0bc95d8eb3f7def615b7e231e8f93816e8) | `editorconfig`, `eslint`, `travis`, `package` @@ -79,10 +80,10 @@ Here are some random example commits after running `$ update`. * **generators**: support for [generate](https://github.com/generate/generate) generators. If your updater needs to create new files, there might be a [generator for that](https://www.npmjs.com/browse/keyword/generate-generator). Just use the generator the same way you would use an [updater](docs/updaters.md). * **render templates**: use templates to create new files, or replace existing files * **prompts**: It's easy to create custom prompts. Answers to prompts can be used as context for rendering templates, for settings options, determining file names, directory structure, and anything else that requires user feedback. -* **any engine**: use any template engine to render templates, including [handlebars](http://www.handlebarsjs.com/), [lodash](https://lodash.com/), [swig](https://github.com/paularmstrong/swig) and [pug](http://jade-lang.com), or anything supported by [consolidate](https://github.com/visionmedia/consolidate.js). +* **any engine**: use any template engine to render templates, including [handlebars](http://www.handlebarsjs.com/), [lodash](https://lodash.com/), [swig](https://github.com/paularmstrong/swig) and [pug](https://pugjs.org), or anything supported by [consolidate](https://github.com/visionmedia/consolidate.js). * **data**: gather data from the user's environment to populate "hints" in user prompts or for rendering templates * **fs**: in the spirit of [gulp](http://gulpjs.com), use `.src` and `.dest` to read and write globs of files. -* **vinyl**: files and templates are [vinyl](http://github.com/gulpjs/vinyl) files +* **vinyl**: files and templates are [vinyl](https://github.com/gulpjs/vinyl) files * **streams**: full support for [gulp](http://gulpjs.com) and [assemble](https://github.com/assemble/assemble) plugins * **smart plugins**: Update is built on [base](https://github.com/node-base/base), so any "smart" plugin from the Base ecosystem can be used * **stores**: persist configuration settings, global defaults, project-specific defaults, answers to prompts, and so on. @@ -104,7 +105,7 @@ This adds the `update` command to your system path, allowing it to be run from a ### Installing updaters -Updaters can be [found on npm](https://www.npmjs.com/browse/keyword/update-updater), but if you're not familiar with how Update works, we recommend installing `updater-example`: +Updaters can be [found on npm](https://www.npmjs.com/browse/keyword/updateupdater), but if you're not familiar with how Update works, we recommend installing `updater-example`: ```sh $ npm install --global updater-example @@ -235,7 +236,7 @@ $ update help #### Discovering updaters -* Find updaters to install by [searching npm](https://www.npmjs.com/browse/keyword/update-updater) for packages with the keyword `update-updater` +* Find updaters to install by [searching npm](https://www.npmjs.com/browse/keyword/updateupdater) for packages with the keyword `updateupdater` * Visit [Update's GitHub org](https://github.com/update) to see the updaters maintained by the core team #### Discovering plugins @@ -246,7 +247,7 @@ Plugins from any applications built on [base](https://github.com/node-base/base) * [assemble](https://www.npmjs.com/browse/keyword/assembleplugin): find assemble plugins on npm using the `assembleplugin` keyword * [generate](https://www.npmjs.com/browse/keyword/generateplugin): find generate plugins on npm using the `generateplugin` keyword * [templates](https://www.npmjs.com/browse/keyword/templatesplugin): find templates plugins on npm using the `templatesplugin` keyword -* [update][update-plugin]: find update plugins on npm using the `updateplugin` keyword +* [update](https://www.npmjs.com/browse/keyword/updateplugin): find update plugins on npm using the `updateplugin` keyword * [verb](https://www.npmjs.com/browse/keyword/verbplugin): find verb plugins on npm using the `verbplugin` keyword #### Authoring updaters @@ -353,34 +354,34 @@ _(Changelog generated by [helper-changelog](https://github.com/helpers/helper-ch ### Related projects * [assemble](https://www.npmjs.com/package/assemble): Get the rocks out of your socks! Assemble makes you fast at creating web projects… [more](https://github.com/assemble/assemble) | [homepage](https://github.com/assemble/assemble "Get the rocks out of your socks! Assemble makes you fast at creating web projects. Assemble is used by thousands of projects for rapid prototyping, creating themes, scaffolds, boilerplates, e-books, UI components, API documentation, blogs, building websit") -* [base](https://www.npmjs.com/package/base): base is the foundation for creating modular, unit testable and highly pluggable node.js applications, starting… [more](https://github.com/node-base/base) | [homepage](https://github.com/node-base/base "base is the foundation for creating modular, unit testable and highly pluggable node.js applications, starting with a handful of common methods, like `set`, `get`, `del` and `use`.") +* [base](https://www.npmjs.com/package/base): Framework for rapidly creating high quality, server-side node.js applications, using plugins like building blocks | [homepage](https://github.com/node-base/base "Framework for rapidly creating high quality, server-side node.js applications, using plugins like building blocks") * [generate](https://www.npmjs.com/package/generate): Command line tool and developer framework for scaffolding out new GitHub projects. Generate offers the… [more](https://github.com/generate/generate) | [homepage](https://github.com/generate/generate "Command line tool and developer framework for scaffolding out new GitHub projects. Generate offers the robustness and configurability of Yeoman, the expressiveness and simplicity of Slush, and more powerful flow control and composability than either.") * [verb](https://www.npmjs.com/package/verb): Documentation generator for GitHub projects. Verb is extremely powerful, easy to use, and is used… [more](https://github.com/verbose/verb) | [homepage](https://github.com/verbose/verb "Documentation generator for GitHub projects. Verb is extremely powerful, easy to use, and is used on hundreds of projects of all sizes to generate everything from API docs to readmes.") ### Community -Are you using [Generate](https://github.com/generate/generate) in your project? Have you published a [generator](https://github.com/generate/generate/blob/master/docs/generators.md) and want to share your project with the world? +Are you using [Update](https://github.com/update/update) in your project? Have you published an [updater](https://github.com/update/update/blob/master/docs/updaters.md) and want to share your Update project with the world? Here are some suggestions! -* If you get like Generate and want to tweet about it, please feel free to mention `@generatejs` or use the `#generatejs` hashtag -* Show your love by starring [Generate](https://github.com/generate/generate) and `update` -* Get implementation help on [StackOverflow](http://stackoverflow.com/questions/tagged/generate) (please use the `generatejs` tag in questions) -* **Gitter** Discuss Generate with us on [Gitter](https://gitter.im/generate/generate) -* If you publish an generator, thank you! To make your project as discoverable as possible, please add the keyword `generategenerator` to package.json. +* If you get like Update and want to tweet about it, please use the hashtag `#updatejs` (not `@`) +* Show your love by starring [Update](https://github.com/update/update) and `update` +* Get implementation help on [StackOverflow](http://stackoverflow.com/questions/tagged/update) (please use the `updatejs` tag in questions) +* **Gitter** Discuss Update with us on [Gitter](https://gitter.im/update/update) +* If you publish an updater, thank you! To make your project as discoverable as possible, please add the keyword `updateupdater` to package.json. ### Contributing Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new). -Please read the [contributing guide](.github/contributing.md) for avice on opening issues, pull requests, and coding standards. +Please read the [contributing guide](.github/contributing.md) for advice on opening issues, pull requests, and coding standards. ### Running tests -Install dev dependencies: +Running and reviewing unit tests is a great way to get familiarized with a library and its API. You can install dependencies and run tests with the following command: ```sh -$ npm install -d && npm test +$ npm install && npm test ``` ### Author @@ -388,13 +389,13 @@ $ npm install -d && npm test **Jon Schlinkert** * [github/jonschlinkert](https://github.com/jonschlinkert) -* [twitter/jonschlinkert](http://twitter.com/jonschlinkert) +* [twitter/jonschlinkert](https://twitter.com/jonschlinkert) ### License -Copyright © 2016, [Jon Schlinkert](https://github.com/jonschlinkert). -Released under the [MIT license](https://github.com/update/update/blob/master/LICENSE). +Copyright © 2018, [Jon Schlinkert](https://github.com/jonschlinkert). +Released under the [MIT License](LICENSE). *** -_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.1.30, on September 01, 2016._ +_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.6.0, on January 23, 2018._ \ No newline at end of file From 50f67af58a8aac83019cebbe415b0f98be50af88 Mon Sep 17 00:00:00 2001 From: doowb Date: Tue, 23 Jan 2018 13:51:58 -0500 Subject: [PATCH 274/274] fix table in readme --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 59df07b..e129f2d 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,6 @@ You can create your own [updaters](docs/updaters.md) using Update's API, or inst Here are some random example commits after running `$ update`. **Project**/**Commit** | **Updaters used** - --- | --- [generate-scaffold](https://github.com/generate/generate-scaffold/commit/440d71f7293cb1f79445c0161440afbb266a2fbe) | `editorconfig`, `travis` [updater-editorconfig](https://github.com/update/updater-editorconfig/commit/b7bd0aa616519440fa4a0d29d3aefac26787cbaf) | `editorconfig`, `eslint`, `travis`, `license`