<commit_before>var fs = require('fs-promise'),
    Path = require('path'),
    loadPackageMeta = require('read-package-json'),
    logger = require('../utils/logger')('ModuleLoader');

/**
 * Represents an object capable of loading and handling modules.
 * @constructor
 */
var ModuleManager = function() {
    this.basePath = Path.resolve(Path.join(__dirname, '..', '..', 'bot_modules'));
    this.modules = {};
    this.help = [];
    global.requireBaseModule = global.requireBaseModule || function() {
        return require(Path.resolve(__dirname, '..', 'base_module.js'));
    }
};

/**
 * Preloads module metadata from the modules pool
 * @param  {String} path Module path to be loaded
 * @return {Promise}      Promise that will be always resolved, but will have a falsey value
 *                        if a problem is found during the load process.
 * @since 2.0.0
 * @static
 */
ModuleManager.preloadModule = function(path) {
    const packageFile = Path.join(path, 'package.json');
    const name = Path.basename(path);
    return fs.stat(packageFile)
        .then(s => {
            return new Promise((resolve) => {
                loadPackageMeta(packageFile, (err, packageMeta) => {
                    if(err) {
                        logger.warning(`INVALID: Module ${name} has an invalid package.json file.`);
                        resolve(null);
                    } else {
                        resolve({
                            meta: {
                                version: packageMeta.version,
                                root: path,
                                rootName: name,
                                entrypoint: Path.join(path, packageMeta.main),
                                author: packageMeta.author,
                                contributors: packageMeta.contributors,
                                moduleName: packageMeta.giskard.module,
                                created: packageMeta.giskard.created,
                                help: packageMeta.giskard.help,
                                dependencies: packageMeta.dependencies,
                                description: packageMeta.description
                            },
                            instance: null
                        });
                    }
                });
            });
        })
        .catch(ex => {
            if(ex.code === 'ENOENT' && ex.path.endsWith('package.json')) {
                logger.warning(`INVALID: Module ${name} lacks a package.json file.`);
            } else {
                logger.error('Unexpected Error: ');
                logger.error(ex);
            }
            return null;
        });
}

ModuleManager.prototype = {
    preloadModule: ModuleManager.preloadModule,

    /**
     * Gets the path for the given module
     * @param  {String} name Module name
     * @return {String}      Path to the module with the given name
     */
    pathForModuleNamed: function(name) {
        return Path.join(this.basePath, name);
    },

    /**
     * Reads the modules directory and returns all possible loadable JavaScript files.
     * @return {String[]}       A list of possibly loadable JavaScript modules.
     */
    getModules: function() {
        const loaderResult = fs.readdirSync(this.basePath)
            .map(f => {
                try {
                    var path = this.pathForModuleNamed(f),
                        stat = fs.statSync(path);
                    if(stat.isDirectory()) {
                        return ModuleManager.preloadModule(path);
                    } else {
                        logger.warning(`DEPRECATED: Module ${f} is using an old module pattern and will not be loaded.`);
                        return Promise.resolve(null);
                    }
                } catch(ex) {
                    logger.error(ex);
                    return Promise.resolve(null);
                }
            });
        return Promise.all(loaderResult);
    },

    /**
     * Loads all possible modules, ignoring failed ones.
     * Also parses module's documentations and fills metadata required by any subsystem.
     * @return {Promise}          A Promise that will be resolved whenever all modules have been loaded
     *                            or failed to load. The resulting list will contain all modules
     *                            constructors.
     */
    loadModules: function() {
        logger.info('Loading modules...');

        return this.getModules()
            .then(mods => {
                mods
                    .filter(m => !!m)
                    .forEach(m => this.loadModule(m));
            })
            .catch(ex => {
                logger.error(ex);
            })
    },

    /**
     * Loads a single module into the system
     * @param  {Object} mod Module metadata
     * @return {Boolean}     True if the module was successfully loaded, otherwise, false.
     * @since  2.0.0
     */
    loadModule: function(mod) {
         try {
            const Ctor = require(mod.meta.entrypoint);
            Ctor.prototype._meta = mod.meta;
            mod.instance = new Ctor();
            logger.info(`Loaded module: ${mod.meta.moduleName}@${mod.meta.version}`);
            this.modules[mod.meta.rootName] = mod;
            this.help = this.help.concat(Object.keys(mod.meta.help).map(k => `${k}: ${mod.meta.help[k]}`));
            return true;
        } catch(ex) {
            logger.error(`Error loading ${mod.meta.moduleName}@${mod.meta.version}:`);
            logger.error(ex);
            return false
        }
    },

    /**
     * Completely unloads a given module by its name<commit_msg>Update help parser on ModuleManager<commit_after>var fs = require('fs-promise'),
    Path = require('path'),
    loadPackageMeta = require('read-package-json'),
    logger = require('../utils/logger')('ModuleLoader');

/**
 * Represents an object capable of loading and handling modules.
 * @constructor
 */
var ModuleManager = function() {
    this.basePath = Path.resolve(Path.join(__dirname, '..', '..', 'bot_modules'));
    this.modules = {};
    this.help = {};
    global.requireBaseModule = global.requireBaseModule || function() {
        return require(Path.resolve(__dirname, '..', 'base_module.js'));
    }
};

/**
 * Preloads module metadata from the modules pool
 * @param  {String} path Module path to be loaded
 * @return {Promise}      Promise that will be always resolved, but will have a falsey value
 *                        if a problem is found during the load process.
 * @since 2.0.0
 * @static
 */
ModuleManager.preloadModule = function(path) {
    const packageFile = Path.join(path, 'package.json');
    const name = Path.basename(path);
    return fs.stat(packageFile)
        .then(s => {
            return new Promise((resolve) => {
                loadPackageMeta(packageFile, (err, packageMeta) => {
                    if(err) {
                        logger.warning(`INVALID: Module ${name} has an invalid package.json file.`);
                        resolve(null);
                    } else {
                        resolve({
                            meta: {
                                version: packageMeta.version,
                                root: path,
                                rootName: name,
                                entrypoint: Path.join(path, packageMeta.main),
                                author: packageMeta.author,
                                contributors: packageMeta.contributors,
                                moduleName: packageMeta.giskard.module,
                                created: packageMeta.giskard.created,
                                help: packageMeta.giskard.help,
                                dependencies: packageMeta.dependencies,
                                description: packageMeta.description
                            },
                            instance: null
                        });
                    }
                });
            });
        })
        .catch(ex => {
            if(ex.code === 'ENOENT' && ex.path.endsWith('package.json')) {
                logger.warning(`INVALID: Module ${name} lacks a package.json file.`);
            } else {
                logger.error('Unexpected Error: ');
                logger.error(ex);
            }
            return null;
        });
}

ModuleManager.prototype = {
    preloadModule: ModuleManager.preloadModule,

    /**
     * Gets the path for the given module
     * @param  {String} name Module name
     * @return {String}      Path to the module with the given name
     */
    pathForModuleNamed: function(name) {
        return Path.join(this.basePath, name);
    },

    /**
     * Reads the modules directory and returns all possible loadable JavaScript files.
     * @return {String[]}       A list of possibly loadable JavaScript modules.
     */
    getModules: function() {
        const loaderResult = fs.readdirSync(this.basePath)
            .map(f => {
                try {
                    var path = this.pathForModuleNamed(f),
                        stat = fs.statSync(path);
                    if(stat.isDirectory()) {
                        return ModuleManager.preloadModule(path);
                    } else {
                        logger.warning(`DEPRECATED: Module ${f} is using an old module pattern and will not be loaded.`);
                        return Promise.resolve(null);
                    }
                } catch(ex) {
                    logger.error(ex);
                    return Promise.resolve(null);
                }
            });
        return Promise.all(loaderResult);
    },

    /**
     * Loads all possible modules, ignoring failed ones.
     * Also parses module's documentations and fills metadata required by any subsystem.
     * @return {Promise}          A Promise that will be resolved whenever all modules have been loaded
     *                            or failed to load. The resulting list will contain all modules
     *                            constructors.
     */
    loadModules: function() {
        logger.info('Loading modules...');

        return this.getModules()
            .then(mods => {
                mods
                    .filter(m => !!m)
                    .forEach(m => this.loadModule(m));
            })
            .catch(ex => {
                logger.error(ex);
            })
    },

    /**
     * Loads a single module into the system
     * @param  {Object} mod Module metadata
     * @return {Boolean}     True if the module was successfully loaded, otherwise, false.
     * @since  2.0.0
     */
    loadModule: function(mod) {
         try {
            const Ctor = require(mod.meta.entrypoint);
            Ctor.prototype._meta = mod.meta;
            mod.instance = new Ctor();
            logger.info(`Loaded module: ${mod.meta.moduleName}@${mod.meta.version}`);
            this.modules[mod.meta.rootName] = mod;
            this.help[mod.meta.rootName] = { title: mod.meta.moduleName, contents: {} };
            Object.keys(mod.meta.help)
                .forEach(k => this.help[mod.meta.rootName].contents[k] = mod.meta.help[k]);
            return true;
        } catch(ex) {
            logger.error(`Error loading ${mod.meta.moduleName}@${mod.meta.version}:`);
            logger.error(ex);
            return false
        }
    },

    /**
     * Completely unloads a given module by its name
{'fraction_non_alphanumeric': 0.10964378801042572, 'fraction_numerical': 0.0010425716768027802, 'mean_word_length': 1.455941967143162, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4799101', 'n_tokens_mistral': 2890, 'n_tokens_neox': 2803, 'n_words': 717}