/**
* @file Apeman app configuration file.
* @namespace apemanfile
* @param {object} apeman - Apeman module.
* @returns {object} - An apeman file object.
* @see {@link http://www.npmjs.org/package/apeman | apeman}
* @author Taka Okunishi
*
*/
var path = require('path'),
fs = require('fs'),
util = require('util'),
format = util.format;
module.exports = function (apeman) {
var cwd = process.cwd();
var Apemanfile = apeman.ApemanfileObject,
defineChainable = Apemanfile.defineChainable,
basedir = __dirname;
process.chdir(basedir);
/**
* Helper modules. This includes functions and pathnames which could be used in apeman files.
* @namespace apemanfile.helper
*/
var h = {
/**
* Directory of the working apemanfile.
* @name apemanfile.helper.cwd
* @type string
*
*/
get cwd() {
return process.cwd();
},
/**
* Directory which contains task worker functions.
* @name apemanfile.helper.taskWorkerPath
* @type string
*/
get taskWorkerPath() {
var h = this;
return h.resolve('task/worker');
},
/**
* Unit tests directory path
* @name apemanfile.helper.unitTestsDirPath
* @type string
*/
get unitTestsDirPath() {
var h = this;
return 'test/unit_tests';
},
/**
* Scenario tests directory path
* @name apemanfile.helper.scenarioTestsDirPath
* @type string
*/
get scenarioTestsDirPath() {
var h = this;
return h.resolve('test/scenario_tests');
},
/**
* Template directory path.
* @name apemanfile.helper.tmplDirPath
* @type string
*/
get tmplDirPath() {
var h = this;
return h.resolve('tmpl');
},
/**
* Directory paths to lookup files.
*/
lookupPaths: [basedir],
/**
* Define a constructor with chainable methods.
* @function apemanfile.helper.defineChainable
* @param {object} initialValues - Values for the constructor prototype. Each key should begin with underscore.
* @param {string} accessors - Chainable accessor method names.
* @returns - Constructor function.
* @example
* defineChainable(
* {_type: 'file'},
* ['tmpl', 'data', 'force', 'mode']
* );
*/
/**
* Resolve filenames in dir.
* @function apemanfile.helper.resolveFilenamesInDir
* @param {string} dirname - Directory name.
* @returns {string[]} - Resolved file names.
*/
resolveFilenamesInDir: function (dirname) {
var h = this;
dirname = h.resolve(dirname);
if (!dirname) {
return null;
}
var readdirRecursiveSync = require('./lib/file/readdir_recursive_sync');
return readdirRecursiveSync(dirname).map(function (filename) {
return path.resolve(dirname, filename);
});
},
defineChainable: defineChainable,
/**
* Look up a file.
* @function apemanfile.helper.resolve
* @param {string} filename - Filename to resolve.
* @returns {string} - Resolved file name.
*/
lookup: function (filename) {
var h = this,
lookupPaths = h.lookupPaths || [];
for (var i = 0; i < lookupPaths.length; i++) {
var resolved = path.resolve(lookupPaths[i], filename);
var hit = fs.existsSync(resolved);
if (hit) {
return resolved;
}
}
return null;
},
/**
* Resolve a file.
* @function apemanfile.helper.resolve
* @param {string} filename - Filename to resolve.
* @returns {string} - Resolved file name.
*/
resolve: function (filename) {
var h = this;
var resolved = path.resolve(process.cwd(), filename);
if (!fs.existsSync(resolved)) {
resolved = h.lookup(filename);
}
return resolved;
},
/**
* Object to represent a file for apemanfile configuration.
* @constructor apemanfile.helper.File
*/
File: defineChainable({_type: 'file'}, ['tmpl', 'data', 'force', 'mode']),
/**
* Create a new File object.
* @function apemanfile.helper.newFile
* @returns {object} - A file object.
*/
newFile: function (tmpl) {
return new h.File().tmpl(tmpl).force(true).mode('444');
},
/**
* Underscorize string.
* @function apemanfile.helper.underscorizeString
* @param {string} stringValue - String to underscorize.
* @returns {string} - Underscorized string.
*/
underscorizeString: function (stringValue) {
return require('./lib/string/underscorize')(stringValue);
},
/**
* Camelize string.
* @function apemanfile.helper.camelizeString
* @param {string} stringValue - String to camelize.
* @returns {string} - Camelized string.
*/
camelizeString: function (stringValue) {
return require('./lib/string/camelize')(stringValue);
},
/**
* Capitalize string.
* @function apemanfile.helper.capitalizeString
* @param {string} stringValue - String to capitalize.
* @returns {string} - Capitalize string.
*/
capitalizeString: function (stringValue) {
return require('./lib/string/capitalize')(stringValue);
},
/**
* Copy object.
* @function apemanfile.helper.copy
* @param {object} src - Source object.
* @param {object} dest - Destination object.
* @returns {object} - Destination object.
*/
copy: function (src, dest) {
return require('./lib/object/copy')(src, dest);
},
/**
* Copy object deeply.
* @function apemanfile.helper.deepCopy
* @param {object} src - Source object.
* @param {object} dest - Destination object.
* @returns {object} - Destination object.
*/
deepCopy: function (src, dest) {
return require('./lib/object/deep_copy')(src, dest);
},
/**
* Copy objects each properties only if the destination object has it.
* @function apemanfile.helper.fallbackCopy
* @param {object} src - Source object.
* @param {object} dest - Destination object.
* @returns {object} - Destination object.
*/
fallbackCopy: function (src, dest) {
return require('./lib/object/fallback_copy')(src, dest);
},
/**
* Load local helper function to this helper.
* @function apemanfile.helper.loadLocalHelperFile
*/
loadLocalHelperFile: function () {
var h = this,
resolved = h.resolve('_apemanfile_helper.js');
if (!fs.existsSync(resolved)) {
return;
}
var required = require(resolved);
h.fallbackCopy(required, h);
},
/**
* Require a module safely. If not found, returns null.
* @param {string} moduleId - Module id to require
* @returns {object|null} - Required module
*/
requireSafely: function (moduleId) {
try {
if (moduleId.match(/^\./)) {
moduleId = h.resolve(moduleId);
}
return require(moduleId);
} catch (e) {
return null;
}
},
get apemanHelperData() {
var h = this;
var tmplDir = h.resolve('tmpl'),
tmplFilenames = tmplDir && h.resolveFilenamesInDir(tmplDir).filter(function (filename) {
// Skip directories which name start with underscore.
var subDirnames = path.relative(tmplDir, path.dirname(filename)).split(path.sep);
for (var i = 0; i < subDirnames.length; i++) {
var isProtected = subDirnames[i].match(/^_/);
if (isProtected) {
return false;
}
var isDbStore = path.basename(filename) === '.DS_Store';
if (isDbStore) {
return false;
}
}
return true;
}) || [];
var taskWorker = h.requireSafely(h.taskWorkerPath) || {};
return {
workerPath: 'task/worker/index.js',
tmplDir: 'tmpl',
task: {
workers: Object.keys(taskWorker)
.filter(function (key) {
return !key.match(/^_/);
})
.map(function (key) {
return {
name: key,
readableName: h.underscorizeString(key).replace(/_/, ' ')
};
})
},
tmpl: {
files: tmplFilenames.map(function (filename) {
var name = h.camelizeString(path.basename(filename, '.hbs').replace(/[\.\-]/g, '_')).replace(/_/g, '');
return {
Name: h.capitalizeString(name),
relativePath: path.relative(tmplDir, filename)
};
})
}
};
},
readLocalVERSION: function () {
var h = this,
versionFile = h.resolve('VERSION');
if (!fs.existsSync(versionFile)) {
return null;
}
return fs.readFileSync(versionFile).toString();
}
};
// Add helper functions if _apemanfile_helper.js found.
h.loadLocalHelperFile();
var meta = {
name: 'apc-abstract',
description: 'Abstract apeman core app. The progenitor of all apeman apps.',
version: h.readLocalVERSION() || '0.0.0',
githubOwner: 'apeman-apps',
repository: 'https://github.com/apeman-apps/apc-abstract.git',
author: {
'name': 'Taka Okunishi',
'email': 'okunishinishi@apeman.info'
}
};
meta.heirTmpl = path.resolve('tmpl/Apemanfile.js.hbs');
Object.defineProperty(meta, 'copyright', {
configurable: true,
enumerable: true,
get: function () {
return format('%s Copyright %s %s',
this.name,
new Date().getFullYear(),
decodeURIComponent('%C2%A9'));
}
});
var libModuleNames = 'array,log,file,object,string,env'.split(',');
var licenseData = {year: new Date().getFullYear()};
var newLibIndexTestJsData = {
names: libModuleNames,
libPath: '../../../lib'
};
/**
* File system structure data.
* An empty object represent a directory.
* @type object
* @private
*/
var structureData = {
bin: {
build: h.newTaskBinFile({taskName: 'build'}).mode('755').force(false),
doc: h.newTaskBinFile({taskName: 'doc'}).mode('755').force(false),
install: h.newTaskBinFile({taskName: 'install'}).mode('755').force(false),
pack: h.newTaskBinFile({taskName: 'pack'}).mode('755').force(false),
release: h.newReleaseBinFile().mode('755').force(false),
tag: h.newTagBinFile().mode('755').force(false),
test: h.newTaskBinFile({taskName: 'test'}).mode('755').force(false)
},
doc: {
'index.html': h.newDocIndexHtmlFile({name: meta.name}),
apiguide: {}
},
lib: {
array: {},
file: {},
log: {},
string: {},
object: {},
env: {}
},
task: {
data: {},
worker: {}
},
tmpl: {
_doc: {
},
bin: {},
html: {},
js: {},
md: {}
},
test: {
mock: {},
unit_tests: {
lib: {
array: {},
file: {},
log: {},
string: {},
object: {},
env: {},
'lib_index_test.js': h.newLibIndexTestJsFile(newLibIndexTestJsData)
}
},
scenario_tests: {
},
work: {},
'test_resource.js': h.newTestResourceJsFile(),
'mock_injector.js': h.newMockInjectorJsFile()
},
'_apemanfile_helper.js': h.newApemanfileHelperJsFile(h.apemanHelperData),
'.gitignore': h.newGitignoreFile({patterns: ['.coveralls.yml']}),
LICENSE: h.newLICENSEFile(licenseData),
'README.md': h.newREADMEMdFile().force(false).mode('644'),
'VERSION': h.newVERSIONFile().force(false).mode('644')
};
/**
* Packages installed by npm.
* @type object
* @private
*/
var nodePackages = {
"async": "~0.2.10",
"glob": "~3.2.8",
"minimatch": "~0.2.14",
"cli-color": "~0.2.3",
"sprintf": "~0.1.3",
"yesno": "~0.0.1"
};
/**
* Packages installed by npm for development user.
* @type {object}
* @private
*/
var nodePackages$dev = {
"handlebars": "~1.3.0",
"nodeunit": "~0.8.4",
"mocha": "~1.17.1",
"jsdoc": "~3.3.0",
"jscoverage": "~0.3.8",
"coveralls": "~2.8.0"
};
var tasks = {
structure: h.generateStructureTask({
data: structureData
}),
generateTaskWorkerUnitTestFiles: h.generateUnitTestFilesTask({
srcDir: 'task/worker',
destDir: 'test/unit_tests/task/worker'
}),
generateIndexTests: h.generateIndexTestTask({
srcDir: [
'lib/array',
'lib/env',
'lib/file',
'lib/log',
'lib/object',
'lib/string'
],
destDir: 'test/unit_tests'
}),
generateLibUnitTestFiles: h.generateUnitTestFilesTask({
srcDir: 'lib/',
destDir: 'test/unit_tests/lib',
pattern: '*/*.js',
data: {
testResourcePath: '../../../test_resource',
mockInjectorPath: '../../../mock_injector'
}
}),
generateUnitTestFiles: [
'generateTaskWorkerUnitTestFiles',
'generateLibUnitTestFiles',
'generateIndexTests'
],
generateTestFiles: [
'generateUnitTestFiles'
],
testFiles: [
'generateTestFiles'
],
generateLibIndex: h.generateIndexTask({
dir: 'lib',
doc: {
overview: 'Lib modules',
namespace: 'lib'
}
}),
generateTaskWorkerIndex: h.generateIndexTask({
dir: 'task/worker',
doc: {
overview: 'Task worker modules',
namespace: 'task.worker'
}
}),
generateTaskIndex: h.generateIndexTask({
dir: 'task',
doc: {
overview: 'Task modules',
namespace: 'task'
}
}),
index: [
'generateLibIndex',
'generateTaskWorkerIndex',
'generateTaskIndex'
],
linkApemanModule: h.linkNpmPackageTask({
packageName: 'apeman'
}),
cleanDeadSymlinks: h.cleanDeadSymlinksTask({
}),
cleanWorkDirectory: h.cleanDirectoryTask({
dir: ['test/work', 'work']
}),
clean: [
'cleanDeadSymlinks',
'cleanWorkDirectory'
],
build: [
'clean',
'linkApemanModule',
'structure',
'index',
'testFiles'
],
runRootUnitTests: h.runNodeunitTask({
files: h.unitTestsDirPath + '/*_test.js'
}),
runLibUnitTests: h.runNodeunitTask({
files: h.unitTestsDirPath + '/lib/**/*_test.js'
}),
runTaskUnitTests: h.runNodeunitTask({
files: h.unitTestsDirPath + '/task/**/*_test.js'
}),
unitTest: [
'runRootUnitTests',
'runLibUnitTests',
'runTaskUnitTests'
],
runDocScenarioTest: h.runMochaTask({
files: h.scenarioTestsDirPath + '/doc_scenario/*.js',
timeout: 4000
}),
scenarioTest: [
'runDocScenarioTest'
],
test: [
'unitTest',
'scenarioTest'
],
packNpmPackage: h.packNpmPackageTask({
basedir: process.cwd()
}),
pack: [
'packNpmPackage'
],
installNodePackages: h.installNodeModulesTask({
packages: nodePackages
}),
installNodeDevPackages: h.installNodeModulesTask({
packages: nodePackages$dev
}),
install: [
'linkApemanModule',
'installNodePackages',
'installNodeDevPackages'
],
generateApiguide: h.generateApiguideTask({
src: [
'lib/**/*.js',
'task/**/*.js',
'README.md',
'Apemanfile.js',
'_apemanfile_helper.js'
],
destDir: 'doc/apiguide',
// Available themes:
// [ "amelia","cerulean","cosmo","cyborg","flatly","journal","readable",
// "simplex","slate","spacelab","spruce","superhero","united"]
theme: 'amelia'
}),
doc: [
'generateApiguide'
],
generateInstruments: h.generateInstrumentsTask({
srcDir: [
'lib',
'task'
],
instrumentsDir: 'work/instruments',
testDir: [
'test',
'test/unit_tests',
'test/mock',
'tmpl'
],
filesToCopy: [
'test/test_resource.js',
'test/mock_injector.js'
]
}),
reportToCoverallsIO: h.reportToCoverallsTask({
files: [
'work/instruments/test/unit_tests/lib/**',
'work/instruments/test/unit_tests/task/**'
]
}),
coveralls: [
'clean',
'generateInstruments',
'reportToCoverallsIO'
]
};
libModuleNames.forEach(function (name) {
var taskName = h.camelizeString(['generate', 'lib', name, 'index'].join('_'));
tasks[taskName] = h.generateIndexTask({
dir: format('lib/%s', name),
doc: {
overview: format('%s module.', h.capitalizeString(name)),
namespace: format('lib.%s', name)
},
capitalize: (function () {
switch (name) {
case 'log':
return ['logger'];
default:
return false;
}
})()
});
tasks.index.push(taskName);
});
//Create a new apeman file object.
var apemanfile = new Apemanfile(
/** @namespace apemanfile **/
{
/**
* Get base directory.
* @function apemanfile.basedir()
* @returns {string} - Apemanfile base directory.
*/
basedir: basedir,
/**
* Get meta data.
* @function apemanfile.meta
* @returns {object} - Meta data.
*/
meta: meta,
/**
* Get tasks data. Each key is a task name which will be passed to "apeman task" command.
* @function apemanfile.tasks()
* @returns {object} - Tasks data.
*/
tasks: tasks,
/**
* Get apeman helper functions, which will be called in another Apeman file.
* @function apemanfile.helper
* @returns {object} - Apeman helper object.
* @example
* //Helper could be used in descendants of this apeman file.
* module.exports = function(apeman){
* var Apemanfile = apeman.ApemanfileObject,
* var prototype = require('__another_apeman_file_path')(apeman);
* h = prototype.helper();
* return new Apemanfile({
* tasks:{
* doSomething: h.doSomethingTask({foo: 'bar'})
* }
* });
* };
*
*/
helper: h
}
);
process.chdir(cwd);
return apemanfile;
};