Source: helpers/PathBuild.js

/**
 * @author Donii Sergii <doniysa@gmail.com>
 */
/**
 * @ignore _
 * @ignore gulp
 * @ignore fs
 */

const _       = require('lodash'),
      pathObj = require('path'),
      fs      = require('fs');

/**
 * @class {PathBuild}
 * Prepare paths helper
 *
 * @property {Object|Array} pathConfig Path config
 * @property {Object} originalConfig Original config
 * @property {String} defaultOutPath Default out dir
 * @property {String} distDir Distributive config default dit
 * @property {String} outDir Output config default dir
 * @property {Boolean} destinationArray If false, set src array or dest as array otherwise
 *
 * @author Donii Sergii<doniysa@gmail.com>
 */
class PathBuild {

    /**
     * PathBuild constructor
     * @param {Object|Array} pathConfig
     * @param {String|undefined} defaultOutFolder
     * @param {{outDir: {String}, distDir: {String}}|undefined} options Default path config option
     * @param {Boolean} arrDestination If false, set src array or dest as array otherwise
     *
     * @author Donii Sergii<doniysa@gmail.com>
     */
    constructor(pathConfig, defaultOutFolder, options, arrDestination) {
        options               = options || {};
        this.originalConfig   = pathConfig;
        this._pathConfig      = [];
        this.defaultOutFolder = defaultOutFolder;
        this.distDir          = options.distDir;
        this.outDir           = options.outDir;
        this.destinationArray = arrDestination || false;

        this.process();
    };

    /**
     * Format path config
     *
     * @returns {Array}
     *
     * @author Donii Sergii<doniysa@gmail.com>
     */
    process() {
        this._pathConfig = [];
        let _self        = this;

        _.forEach(this.originalConfig, (value, index) => {

            let path = {};

            if (_.isString(value)) {
                path.src = value;
            }

            if (_.isObject(value)) {
                if (value.src) {
                    path.src = value.src;
                }


                if (value.dest) {
                    path.dest = value.dest;
                }
            }

            if (_.isArray(value)) {
                if (_self.destinationArray) {
                    path.dest = value;
                } else {
                    path.src = value;
                }
            }

            if (_.isUndefined(path.src)) {
                path.src = _self.distDir || (_self.outDir || _self.defaultOutFolder);
            }
            if (_.isUndefined(path.dest)) {
                path.dest = _self.outDir || _self.defaultOutFolder;
            }

            _self._pathConfig.push(path);
        });

        return this._pathConfig;
    }

    /**
     * Get path config
     *
     * @returns {Array}
     *
     * @author Donii Sergii<doniysa@gmail.com>
     */
    get pathConfig() {
        return this._pathConfig;
    }

    /**
     * Clear path
     *
     * @param {String} path Path for clear
     *
     * @returns {Object}
     *
     * @author Donii Sergii<doniysa@gmail.com>
     */
    clearPath(path) {
        let pathParts  = path.replace(/\/\//g, '/').split('/'),
            retPath    = '',
            additional = '',
            end        = false;

        for (let i in pathParts) {
            if (!pathParts.hasOwnProperty(i)) {
                continue
            }

            if (pathParts[i].indexOf('*') === 0) {
                end = true;
            }

            if (!end) {
                retPath += '/' + pathParts[i];
            } else {
                additional += '/' + pathParts[i];
            }
        }

        return [retPath.replace(/\/\//g, '/'), path, additional];
    }

    /**
     * Check directory exists
     *
     * @param {String} path
     *
     * @returns {boolean}
     *
     * @author Donii Sergii<doniysa@gmail.com>
     */
    checkDirectoryExists(path) {
        try {
            fs.statSync(path);
        } catch (e) {
            return false;
        }

        return true;
    }

    /**
     * Get real path if exists
     *
     * @param {String} path Checking path
     * @param {Boolean} checkDestination Check path exists flag
     * @param {Object} context Context which called checking
     * @param {Boolean} destination Destination folder flag for correctly check exists folders
     * @returns {*}
     *
     * @author Donii Sergii<doniysa@gmail.com>
     */
    getRealPath(path, checkDestination, context, destination) {
        if (_.isArray(path)) {
            let __self   = this,
                newPaths = [];
            path.forEach((_path, index) => {
                let buildPath = __self.getRealPath(_path, checkDestination, context, destination);

                if (!_.isEmpty(buildPath)) {
                    newPaths.push(buildPath);
                }
            });

            return _.uniq(newPaths);
        }

        checkDestination = _.isUndefined(checkDestination) ? false : checkDestination;

        if (checkDestination === false) {
            return path;
        }

        path = this.clearPath(path);

        if (_.isEmpty(path) || !path.length) {
            path = destination ? context.outDir : context.distDir;
        }

        let allPaths = [];

        if (this.destinationArray) {

            let base      = (destination ? this.outDir : this.distDir) || '',
                buildPath = (_path, level) => {
                    let newPath    = _path,
                        additional = [];

                    for (let i = 0; i < level; i++) {
                        let p = pathObj.dirname(newPath);
                        if (_.isEmpty(newPath)) {
                            break;
                        }

                        additional.push(pathObj.basename(newPath));
                        newPath = p;
                    }

                    return [newPath, additional.reverse().join('/')];
                };

            let dirnames = [];

            for (let i = 0; i < 3; i++) {
                dirnames[i]     = buildPath(path[0], i);
                dirnames[i + 3] = [base + dirnames[i][0], dirnames[i][1]];

                dirnames[i + 6] = buildPath(path[1], i + 3);
                dirnames[i + 9] = [base + dirnames[i + 6][0], dirnames[i + 6][1]];

            }

            dirnames.forEach((value) => {
                if (!_.isEmpty(value)) {
                    allPaths.push(value);
                }
            });
        }

        let _path = (__dirname + '/../' + path[0]).replace(/\/\//g, '/');
        (destination ? [
            ((context.outDir || '') + '/' + path[0]).replace(/\/\//g, '/'),
            ((context.outDir || '') + '/' + path[1]).replace(/\/\//g, '/'),
        ] : [
            ((context.distDir || '') + '/' + path[0]).replace(/\/\//g, '/'),
            ((context.distDir || '') + '/' + path[1]).replace(/\/\//g, '/'),
        ]).forEach((value) => {
            allPaths.push(value);
        });

        if (destination) {
            [
                ((context.outDir || '') + '/' + path[0]).replace(/\/\//g, '/'),
                ((context.outDir || '') + '/' + path[1]).replace(/\/\//g, '/'),
                context.outDir,
                ((context.distDir || '') + '/' + path[0]).replace(/\/\//g, '/'),
                ((context.distDir || '') + '/' + path[1]).replace(/\/\//g, '/'),
            ].forEach((value) => {
                allPaths.push(value);
            });
        }

        let additional = path[2],
            firstAdd   = path[1];

        [
            _path,
            path[0],
            (__dirname + '/' + path[0]).replace(/\/\//g, '/'),
            path[1],
            context.outDir
        ].forEach((value) => {
            allPaths.push(value);
        });

        allPaths = _.uniq(allPaths);

        (destination ? [
            ((context.distDir || '') + '/' + path[0]).replace(/\/\//g, '/'),
            ((context.distDir || '') + '/' + path[1]).replace(/\/\//g, '/'),
        ] : [
            ((context.outDir || '') + '/' + path[0]).replace(/\/\//g, '/'),
            ((context.outDir || '') + '/' + path[1]).replace(/\/\//g, '/'),
        ]).forEach((value) => {
            allPaths.push(value);
        });

        path = '';

        let keys = _.keys(allPaths);

        for (let i = 0; i <= keys.length; i++) {
            let ind = keys[i];
            if (_.isUndefined(allPaths[ind])) {
                continue;
            }

            let addToPath = '';
            if (_.isArray(allPaths[ind])) {
                path      = allPaths[ind][0].replace(/\/\//g, '/').replace(/\/$/, '');
                addToPath = allPaths[ind][1];
            } else {
                path      = allPaths[ind].replace(/\/\//g, '/').replace(/\/$/, '');
                addToPath = additional;
            }

            if (this.checkDirectoryExists(path)) {

                if (path === context.outDir && !this.destinationArray) {
                    if (path === firstAdd) {
                        return path;
                    }

                    return (path + '/' + firstAdd).replace(/\/\//g, '/').replace(/\/$/, '');
                }

                return (path + '/' + addToPath).replace(/\/\//g, '/').replace(/\/$/, '');
            }
        }

        return '';
    }

    /**
     * Generate full path and check existing
     *
     * @param {Boolean} checkDestination Check destination
     * @param {Boolean} checkSource Check source
     * @returns {Array}
     *
     * @author Donii Sergii<doniysa@gmail.com>
     */
    processFullPath(checkDestination, checkSource) {
        checkSource   = _.isUndefined(checkSource) ? true : checkSource;
        let realPaths = [],
            path, dest,
            _self     = this;

        _.each(this._pathConfig, (value, index) => {
            if (_.isUndefined(value.dest)) {
                throw 'Destination is empty. Check paths option';
            }

            if ((path = _self.getRealPath(value.src, checkSource, _self)) && (dest = _self.getRealPath(value.dest, checkDestination, _self, true))) {
                realPaths.push({
                    src : path,
                    dest: dest || value.dest
                });
            }
        });

        return realPaths;
    }

    /**
     * Build watch file list
     *
     * @param {Array} paths Path config
     * @param {String} opt Option path name
     *
     * @returns {Array}
     */
    static buildWatchPaths(paths, opt) {
        opt = opt || 'src';
        let nPaths = [];
        _.each(paths, (path) => {
            if (_.isArray(path[opt])) {
                for (let i in path[opt]) {
                    if (!path[opt].hasOwnProperty(i)) {
                        continue;
                    }

                    nPaths.push(path[opt][i]);
                }
            } else {
                nPaths.push(path[opt]);
            }
        });

        return nPaths;
    }
}

module.exports = PathBuild;