Source: apc-abstract/Apemanfile.js

/**
 * @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;
};