<commit_before>/**
 * @file Class file for Clara, extending the Eris client.
 * @author Ovyerus
 */

const Eris = require('eris');
const got = require('got');
const fs = require('fs');
const path = require('path');
const {URL} = require('url');
const Redite = require('redite');
const {CommandHolder} = require(`${__dirname}/modules/CommandHolder`);
const LocaleManager = require(`${__dirname}/modules/LocaleManager`);
const Lookups = require(`${__dirname}/modules/Lookups`);

/**
 * Main class for Clara.
 *
 * @prop {String[]} admins Array of people who have owner-like permissions over the bot.
 * @prop {Boolean} allowCommandUse If commands can be run.
 * @prop {String[]} blacklist Array of people who cannot use the bot.
 * @prop {CommandHolder} commands Command holder object.
 * @prop {String[]} commandFolders todo
 * @prop {ClaraConfig} config Configuration passed during construction.
 * @prop {Redite} db Database connection manager.
 * @prop {Boolean} loadCommands If the bot should load commands or not.
 * @prop {String[]} prefixes Array of all the prefixes that are able to be used by the bot.
 * @prop {Object} settings Settings cache for users and guilds.
 */
class Clara extends Eris.Client {
    /**
     * Creates a new Clara instance.
     *
     * @param {ClaraConfig} config Configuration settings
     * @param {Object} options Eris client options.
     * @see https://abal.moe/Eris/docs/Client
     */
    constructor(config, options = {}) {
        if (!config && typeof config !== 'object') throw new TypeError('config is not an object.');

        super(config.general.token, options);

        this.lookups = new Lookups(this);
        this.localeManager = new LocaleManager();
        this.commands = new CommandHolder(this);
        this.db = new Redite({url: config.general.redisURL || config.general.redisUrl || 'redis://127.0.0.1/0'});

        this.config = config;

        this.loadCommands = true;
        this.allowCommandUse = false;
    }

    async connect() {
        let {general} = this.config;

        if (!general.ownerID) throw new Error('Configuration is missing general.ownerID');
        if (!general.token) throw new Error('Configuration is missing general.token');
        if (!general.mainPrefix) throw new Error('Configuration is missing general.mainPrefix')
        if (general.maxShards === 0) throw new Error('config.general.maxShards cannot be 0.');
        if (!general.maxShards) throw new Error('Configuration is missing general.maxShards');

        if (!general.redisURL) throw new Error('Configuration is missing general.redisURL');
        else {
            let parsed = new URL(general.redisURL);

            if (parsed.protocol !== 'redis:') throw new Error(`Invalid protocol for config.general.redisURL: "${parsed.protocol}" must instead be "redis:"`);
            if (!parsed.host) throw new Error('config.general.redisURL is missing host.');

            if (parsed.pathname && parsed.pathname !== '/') {
                if (isNaN(parsed.pathname.slice(1))) throw new Error('Invalid database for config.general.redisURL. Must be a number.');
                if (!(0 <= Number(parsed.pathname.slice(1)) < 16)) throw new Error(`Invalid database for config.general.redisURL. Must be between 0 and 15, inclusive.`);
            }
        }

        await super.connect();
    }

    /**
    * Wait for a message in the specified channel from the specified user.
    *
    * @param {String} channelID ID of channel to wait in.
    * @param {String} userID ID of user to wait for.
    * @param {Function} [filter] Filter to pass to message. Must return true.
    * @param {Number} [timeout=15000] Time in milliseconds to wait for message.
    * @returns {Promise<Eris.Message>} Awaited message.
    */
    awaitMessage(channelID, userID, filter = () => true, timeout = 15000) {
        return new Promise((resolve, reject) => {
            if (!channelID || typeof channelID !== 'string') {
                reject(new TypeError('channelID is not string.'));
            } else if (!userID || typeof userID !== 'string') {
                reject(new TypeError('userId is not string.'));
            } else {
                var responded, rmvTimeout;

                var onCrt = msg => {
                    if (msg.channel.id === channelID && msg.author.id === userID && filter(msg)) responded = true;

                    if (responded) {
                        this.removeListener('messageCreate', onCrt);
                        clearInterval(rmvTimeout);
                        resolve(msg);
                    }
                };

                this.on('messageCreate', onCrt);

                rmvTimeout = setTimeout(() => {
                    if (!responded) {
                        this.removeListener('messageCreate', onCrt);
                        reject(new Error('Message await expired.'));
                    }
                }, timeout);
            }
        });
    }

    /**
     * Sends guild count to various bot sites.
     */
    async postGuildCount() {
        if (this.config.botlistTokens.dbots) {
            try {
                await got(`https://bots.discord.pw/api/bots/${this.user.id}/stats`, {
                    method: 'POST',
                    headers: {
                        Authorization: this.config.botlistTokens.dbots,
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify({
                        server_count: this.guilds.size
                    })
                });
            } catch(err) {
                logger.error(`Error sending stats to bots.discord.pw:\n${err}`);
                return;
            }

            logger.info('Sent stats to bots.discord.pw.');
        }

        if (this.config.botlistTokens.dbl) {
            try {
                await got(`https://discordbots.org/api/bots/stats`, {
                    method: 'POST',
                    headers: {
                        Authorization: this.config.botlistTokens.dbl,
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify({
                        server_count: this.guilds.size,
                        shard_count: this.shards.size
                    })
                });
            } catch(err) {
                logger.error(`Error sending stats to discordbots.org:\n${err}`);
                return;
            }

            logger.info('Sent stats to discordbots.org.');
        }
    }

    /**
     * POST something to Hastebin.
     *
     * @param {String} str Content to POST.
     * @returns {Promise<String>} Returned key.
     */
    async hastePost(str) {
        if (typeof str !== 'string') throw new TypeError('str is not a string');

        let res = await got('https://hastebin.com/documents', {
            method: 'POST',
            body: str
        });

        return JSON.parse(res.body).key;
    }

    /**
     * Check if a user has elevated bot permissions.
     *
     * @param {String} userID ID of the user to check.
     * @returns {Boolean} If the user has perms.
     */
    checkBotPerms(userID) {
        return userID === this.config.general.ownerID || this.admins.includes(userID);
    }

    /**
     * Check if a user is blacklisted.
     *
     * @param {String} userID ID of the user to check.
     * @returns {Boolean} If the user is blacklisted.
     */
    isBlacklisted(userID) {
        return this.blacklist.includes(userID);
    }

    /**
     * Gets settings from the database, and sets them if they don't exist.
     *
     * @returns {Object} Settings from the database.
     */
    async getDataSettings() {
        if (!await this.db.settings.get) {
            await this.db.settings.set({
                prefixes: [],
                admins: [],
                blacklist: [],
                unloadedModules: []
            });
        } else {
            let res = await this.db.settings.get;
            let noHave = ['prefixes', 'admins', 'blacklist', 'unloadedModules'].filter(v => !Object.keys(res).includes(v));

            for (let key of noHave) await this.db.settings[key].set([]);
        }

        return this.db.settings.get;
    }

    /**
     * Adds a prefix.
     *
     * @param {String} prefix Prefix to add.
     */
    async addPrefix(prefix) {
        if (typeof prefix !== 'string') throw new TypeError('prefix is not a string.');

        await this.db.settings.prefixes.push(prefix);
        this.prefixes.push(prefix);
    }

    /**
     * Removes a prefix.
     *
     * @param {String} prefix Prefix to remove.
     */
    async removePrefix(prefix) {
        if (typeof prefix !== 'string') throw new TypeError('prefix is not a string.');

        await this.db.settings.prefixes.remove(prefix);
        this.prefixes.splice(this.prefixes.indexOf(prefix), 1);
    }

    /**
     * Add's a user as an admin.
     *
     * @param {String} userID User to add as an admin.
     */
    async addAdmin(userID) {
        if (typeof userID !== 'string') throw new TypeError('userId is not a string.');

        await this.db.settings.admins.push(userID);
        this.admins.push(userID);
    }

    /**
     * Removes a user as an admin.
     *
     * @param {String} userID User to remove as an admin.
     */
    async removeAdmin(userID) {
        if (typeof userID !== 'string') throw new TypeError('userId is not a string.');

        await this.db.settings.admins.remove(userID);
        this.admins.splice(this.admins.indexOf(userID), 1);
    }

    /**
     * Adds a user to the blacklist.
     *
     * @param {String} userID User to add to the blacklist.
     */
    async addBlacklist(userID) {
        if (typeof userID !== 'string') throw new TypeError('userId is not a string.');

        await this.db.settings.blacklist.push(userID);
        this.blacklist.push(userID);
    }

    /**
     * Removes a user from the blacklist.
     *
     * @param {String} userID User to remove from the blacklist.
     */
    async removeBlacklist(userID) {
        if (typeof userID !== 'string') throw new TypeError('userId is not a string.');

        await this.db.settings.blacklist.remove(userID);
        this.blacklist.splice(this.blacklist.indexOf(userID), 1);
    }

    /**
     * Marks a module as persistently unloaded.
     *
     * @param {String} mod Module to mark as unloaded.
     */
    async addUnloadedModule(mod) {
        if (typeof mod !== 'string') throw new TypeError('mod is not a string.');

        await this.db.settings.unloadedModules.push(mod);
        this.unloadedModules.push(mod);
    }

    /**
     * Unmarks a module as persistently unloaded.
     *
     * @param {String} mod Module to unmark as unloaded.
     */
    async removeUnloadedModule(mod) {
        if (typeof mod !== 'string') throw new TypeError('mod is not a string.');

        await this.db.settings.unloadedModules.remove(mod);
        this.unloadedModules.splice(this.unloadedModules.indexOf(mod), 1);
    }

    /**
    * Initialise settings for a guild.
    *
    * @param {String} guildID ID of guild to init settings for.
    * @returns {Object} Settings for the guild.
    */
    async initGuildSettings(guildID) {
        if (typeof guildID !== 'string') throw new TypeError('guildID is not a string.');
        if (await this.db.has(guildID)) return await this.db.guildID.get;

        let settings = {
            id: guildID,
            locale: 'en-UK',
            greeting: {
                enabled: false,
                channelID: null,
                message: null
            },
            goodbye: {
                enabled: false,
                channelID: null,
                message: null
            },
            ranks: {
                limit: 0,
                roles: [],
                userlimits: {}
            }
        };

        await this.db[guildID].set(settings);
        return settings;
    }

    /**
    * Get the settings for a guild.
    *
    * @param {String} guildID ID of guild to get settings for
    * @returns {Object} Settings for the guild.
    */
    async getGuildSettings(guildID) {
        if (typeof guildID !== 'string') throw new TypeError('guildID is not a string.');
        if (!await this.db.has(guildID)) return await this.initGuildSettings(guildID);

        return await this.db[guildID].get;
    }

    /**
    * Initialize settings for a user.
    *
    * @param {String} userID ID of user to init settings for.
    * @returns {Object} Settings for the user.
    */
    async initUserSettings(userID) {
        if (typeof userID !== 'string') throw new TypeError('userID is not a string.');
        if (await this.db.has(userID)) return await this.db[userID].get;

        let settings = {
            id: userID,
            locale: 'en-UK',
            partner: null
        };

        await this.db[userID].set(settings);
        return settings;
    }

    /**
    * Get the settings for a user.
    *
    * @param {String} userID ID of user to get settings for.
    * @returns {Object} Settings for the user.
    */
    async getUserSettings(userID) {
        if (typeof userID !== 'string') throw new TypeError('userID is not a string.');
        if (!await this.db.has(userID)) return await this.initUserSettings(userID);

        return await this.db[userID].get;
    }

    /**
     * Check if the bot has a permission in a channel.
     *
     * @param {String} permission The permission to check.
     * @param {Eris.Channel} channel The channel to check.
     * @returns {Boolean} If the user has the permission.
     */
    hasPermission(permission, channel) {
        // Check if permission actually exists.
        if (!Object.keys(Eris.Constants.Permissions).includes(permission)) return false;

        return channel.permissionsOf(this.user.id).has(permission);
    }

    get commandFolders() {
        let cmdDirs = fs.readdirSync(this.commandsDir).map(d => ({[d]: fs.readdirSync(`${this.commandsDir}/${d}`)}));
        let allCmds = {};

        // Go from an array of objects to an object of arrays.
        cmdDirs.forEach(d => Object.assign(allCmds, d));
        cmdDirs = cmdDirs.map(e => Object.keys(e)[0]);

        // Turn folder names into proper paths for future ease (also make sure we only get folders).
        allCmds = Object.entries(allCmds).map(x => x[1].filter(y => fs.statSync(`${this.commandsDir}/${x[0]}/${y}`).isDirectory()));
        allCmds = allCmds.map((v, i) => v.map(x => `${this.commandsDir}/${cmdDirs[i]}/${x}`));
        allCmds = [].concat.apply([], allCmds);

        return allCmds;
    }
}

/**
 * Configuration used for Clara instances.
 * @see config.example.yaml
 *
 * @prop {Object} tokens Tokens used by APIs for different commands.
 * @prop {String} tokens.youtube
 * @prop {String} tokens.soundcloud If this is not set, this will be automatically scraped.
 * @prop {String} tokens.ibsearch
 * @prop {String} tokens.osu
 * @prop {String} tokens.saucenao
 * @prop {String} tokens.nasa
 *
 * @prop {Object} botlistTokens Tokens used for sending stats to various bot list sites.
 * @prop {String} botlistTokens.dbl discordbots.org token.
 * @prop {String} botlistTokens.dbots bots.discord.pw token.
 *
 * @prop {Object} development Various debugging options.
 * @prop {Boolean} development.debug
 * @prop {Boolean} development.promiseWarnings
 *
 * @prop {Object} general Configuration options for the main bot.
 * @prop {String} general.ownerID
 * @prop {String} general.token
 * @prop {String} general.redisURL
 * @prop {String} general.mainPrefix
 * @prop {Number} general.maxShards
 *
 *
 * @prop {Object} discord Miscellaneous Discord-related options.
 * @prop {String} discord.status
 * @prop {Object} discord.game
 * @prop {String} discord.game.url
 * @prop {Integer} discord.game.type
 * @prop {String} discord.game.name
 */
class ClaraConfig { // eslint-disable-line
    // Only here for documentation purposes.
}

module.exports = Clara;<commit_msg>ESLint<commit_after>/**
 * @file Class file for Clara, extending the Eris client.
 * @author Ovyerus
 */

const Eris = require('eris');
const got = require('got');
const fs = require('fs');
const {URL} = require('url');
const Redite = require('redite');
const {CommandHolder} = require(`${__dirname}/modules/CommandHolder`);
const LocaleManager = require(`${__dirname}/modules/LocaleManager`);
const Lookups = require(`${__dirname}/modules/Lookups`);

/**
 * Main class for Clara.
 *
 * @prop {String[]} admins Array of people who have owner-like permissions over the bot.
 * @prop {Boolean} allowCommandUse If commands can be run.
 * @prop {String[]} blacklist Array of people who cannot use the bot.
 * @prop {CommandHolder} commands Command holder object.
 * @prop {String[]} commandFolders todo
 * @prop {ClaraConfig} config Configuration passed during construction.
 * @prop {Redite} db Database connection manager.
 * @prop {Boolean} loadCommands If the bot should load commands or not.
 * @prop {String[]} prefixes Array of all the prefixes that are able to be used by the bot.
 * @prop {Object} settings Settings cache for users and guilds.
 */
class Clara extends Eris.Client {
    /**
     * Creates a new Clara instance.
     *
     * @param {ClaraConfig} config Configuration settings
     * @param {Object} options Eris client options.
     * @see https://abal.moe/Eris/docs/Client
     */
    constructor(config, options = {}) {
        if (!config && typeof config !== 'object') throw new TypeError('config is not an object.');

        super(config.general.token, options);

        this.lookups = new Lookups(this);
        this.localeManager = new LocaleManager();
        this.commands = new CommandHolder(this);
        this.db = new Redite({url: config.general.redisURL || config.general.redisUrl || 'redis://127.0.0.1/0'});

        this.config = config;

        this.loadCommands = true;
        this.allowCommandUse = false;
    }

    async connect() {
        let {general} = this.config;

        if (!general.ownerID) throw new Error('Configuration is missing general.ownerID');
        if (!general.token) throw new Error('Configuration is missing general.token');
        if (!general.mainPrefix) throw new Error('Configuration is missing general.mainPrefix');
        if (general.maxShards === 0) throw new Error('config.general.maxShards cannot be 0.');
        if (!general.maxShards) throw new Error('Configuration is missing general.maxShards');

        if (!general.redisURL) throw new Error('Configuration is missing general.redisURL');
        else {
            let parsed = new URL(general.redisURL);

            if (parsed.protocol !== 'redis:') throw new Error(`Invalid protocol for config.general.redisURL: "${parsed.protocol}" must instead be "redis:"`);
            if (!parsed.host) throw new Error('config.general.redisURL is missing host.');

            if (parsed.pathname && parsed.pathname !== '/') {
                if (isNaN(parsed.pathname.slice(1))) throw new Error('Invalid database for config.general.redisURL. Must be a number.');
                if (!(0 <= Number(parsed.pathname.slice(1)) < 16)) throw new Error(`Invalid database for config.general.redisURL. Must be between 0 and 15, inclusive.`);
            }
        }

        await super.connect();
    }

    /**
    * Wait for a message in the specified channel from the specified user.
    *
    * @param {String} channelID ID of channel to wait in.
    * @param {String} userID ID of user to wait for.
    * @param {Function} [filter] Filter to pass to message. Must return true.
    * @param {Number} [timeout=15000] Time in milliseconds to wait for message.
    * @returns {Promise<Eris.Message>} Awaited message.
    */
    awaitMessage(channelID, userID, filter = () => true, timeout = 15000) {
        return new Promise((resolve, reject) => {
            if (!channelID || typeof channelID !== 'string') {
                reject(new TypeError('channelID is not string.'));
            } else if (!userID || typeof userID !== 'string') {
                reject(new TypeError('userId is not string.'));
            } else {
                var responded, rmvTimeout;

                var onCrt = msg => {
                    if (msg.channel.id === channelID && msg.author.id === userID && filter(msg)) responded = true;

                    if (responded) {
                        this.removeListener('messageCreate', onCrt);
                        clearInterval(rmvTimeout);
                        resolve(msg);
                    }
                };

                this.on('messageCreate', onCrt);

                rmvTimeout = setTimeout(() => {
                    if (!responded) {
                        this.removeListener('messageCreate', onCrt);
                        reject(new Error('Message await expired.'));
                    }
                }, timeout);
            }
        });
    }

    /**
     * Sends guild count to various bot sites.
     */
    async postGuildCount() {
        if (this.config.botlistTokens.dbots) {
            try {
                await got(`https://bots.discord.pw/api/bots/${this.user.id}/stats`, {
                    method: 'POST',
                    headers: {
                        Authorization: this.config.botlistTokens.dbots,
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify({
                        server_count: this.guilds.size
                    })
                });
            } catch(err) {
                logger.error(`Error sending stats to bots.discord.pw:\n${err}`);
                return;
            }

            logger.info('Sent stats to bots.discord.pw.');
        }

        if (this.config.botlistTokens.dbl) {
            try {
                await got(`https://discordbots.org/api/bots/stats`, {
                    method: 'POST',
                    headers: {
                        Authorization: this.config.botlistTokens.dbl,
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify({
                        server_count: this.guilds.size,
                        shard_count: this.shards.size
                    })
                });
            } catch(err) {
                logger.error(`Error sending stats to discordbots.org:\n${err}`);
                return;
            }

            logger.info('Sent stats to discordbots.org.');
        }
    }

    /**
     * POST something to Hastebin.
     *
     * @param {String} str Content to POST.
     * @returns {Promise<String>} Returned key.
     */
    async hastePost(str) {
        if (typeof str !== 'string') throw new TypeError('str is not a string');

        let res = await got('https://hastebin.com/documents', {
            method: 'POST',
            body: str
        });

        return JSON.parse(res.body).key;
    }

    /**
     * Check if a user has elevated bot permissions.
     *
     * @param {String} userID ID of the user to check.
     * @returns {Boolean} If the user has perms.
     */
    checkBotPerms(userID) {
        return userID === this.config.general.ownerID || this.admins.includes(userID);
    }

    /**
     * Check if a user is blacklisted.
     *
     * @param {String} userID ID of the user to check.
     * @returns {Boolean} If the user is blacklisted.
     */
    isBlacklisted(userID) {
        return this.blacklist.includes(userID);
    }

    /**
     * Gets settings from the database, and sets them if they don't exist.
     *
     * @returns {Object} Settings from the database.
     */
    async getDataSettings() {
        if (!await this.db.settings.get) {
            await this.db.settings.set({
                prefixes: [],
                admins: [],
                blacklist: [],
                unloadedModules: []
            });
        } else {
            let res = await this.db.settings.get;
            let noHave = ['prefixes', 'admins', 'blacklist', 'unloadedModules'].filter(v => !Object.keys(res).includes(v));

            for (let key of noHave) await this.db.settings[key].set([]);
        }

        return this.db.settings.get;
    }

    /**
     * Adds a prefix.
     *
     * @param {String} prefix Prefix to add.
     */
    async addPrefix(prefix) {
        if (typeof prefix !== 'string') throw new TypeError('prefix is not a string.');

        await this.db.settings.prefixes.push(prefix);
        this.prefixes.push(prefix);
    }

    /**
     * Removes a prefix.
     *
     * @param {String} prefix Prefix to remove.
     */
    async removePrefix(prefix) {
        if (typeof prefix !== 'string') throw new TypeError('prefix is not a string.');

        await this.db.settings.prefixes.remove(prefix);
        this.prefixes.splice(this.prefixes.indexOf(prefix), 1);
    }

    /**
     * Add's a user as an admin.
     *
     * @param {String} userID User to add as an admin.
     */
    async addAdmin(userID) {
        if (typeof userID !== 'string') throw new TypeError('userId is not a string.');

        await this.db.settings.admins.push(userID);
        this.admins.push(userID);
    }

    /**
     * Removes a user as an admin.
     *
     * @param {String} userID User to remove as an admin.
     */
    async removeAdmin(userID) {
        if (typeof userID !== 'string') throw new TypeError('userId is not a string.');

        await this.db.settings.admins.remove(userID);
        this.admins.splice(this.admins.indexOf(userID), 1);
    }

    /**
     * Adds a user to the blacklist.
     *
     * @param {String} userID User to add to the blacklist.
     */
    async addBlacklist(userID) {
        if (typeof userID !== 'string') throw new TypeError('userId is not a string.');

        await this.db.settings.blacklist.push(userID);
        this.blacklist.push(userID);
    }

    /**
     * Removes a user from the blacklist.
     *
     * @param {String} userID User to remove from the blacklist.
     */
    async removeBlacklist(userID) {
        if (typeof userID !== 'string') throw new TypeError('userId is not a string.');

        await this.db.settings.blacklist.remove(userID);
        this.blacklist.splice(this.blacklist.indexOf(userID), 1);
    }

    /**
     * Marks a module as persistently unloaded.
     *
     * @param {String} mod Module to mark as unloaded.
     */
    async addUnloadedModule(mod) {
        if (typeof mod !== 'string') throw new TypeError('mod is not a string.');

        await this.db.settings.unloadedModules.push(mod);
        this.unloadedModules.push(mod);
    }

    /**
     * Unmarks a module as persistently unloaded.
     *
     * @param {String} mod Module to unmark as unloaded.
     */
    async removeUnloadedModule(mod) {
        if (typeof mod !== 'string') throw new TypeError('mod is not a string.');

        await this.db.settings.unloadedModules.remove(mod);
        this.unloadedModules.splice(this.unloadedModules.indexOf(mod), 1);
    }

    /**
    * Initialise settings for a guild.
    *
    * @param {String} guildID ID of guild to init settings for.
    * @returns {Object} Settings for the guild.
    */
    async initGuildSettings(guildID) {
        if (typeof guildID !== 'string') throw new TypeError('guildID is not a string.');
        if (await this.db.has(guildID)) return await this.db.guildID.get;

        let settings = {
            id: guildID,
            locale: 'en-UK',
            greeting: {
                enabled: false,
                channelID: null,
                message: null
            },
            goodbye: {
                enabled: false,
                channelID: null,
                message: null
            },
            ranks: {
                limit: 0,
                roles: [],
                userlimits: {}
            }
        };

        await this.db[guildID].set(settings);
        return settings;
    }

    /**
    * Get the settings for a guild.
    *
    * @param {String} guildID ID of guild to get settings for
    * @returns {Object} Settings for the guild.
    */
    async getGuildSettings(guildID) {
        if (typeof guildID !== 'string') throw new TypeError('guildID is not a string.');
        if (!await this.db.has(guildID)) return await this.initGuildSettings(guildID);

        return await this.db[guildID].get;
    }

    /**
    * Initialize settings for a user.
    *
    * @param {String} userID ID of user to init settings for.
    * @returns {Object} Settings for the user.
    */
    async initUserSettings(userID) {
        if (typeof userID !== 'string') throw new TypeError('userID is not a string.');
        if (await this.db.has(userID)) return await this.db[userID].get;

        let settings = {
            id: userID,
            locale: 'en-UK',
            partner: null
        };

        await this.db[userID].set(settings);
        return settings;
    }

    /**
    * Get the settings for a user.
    *
    * @param {String} userID ID of user to get settings for.
    * @returns {Object} Settings for the user.
    */
    async getUserSettings(userID) {
        if (typeof userID !== 'string') throw new TypeError('userID is not a string.');
        if (!await this.db.has(userID)) return await this.initUserSettings(userID);

        return await this.db[userID].get;
    }

    /**
     * Check if the bot has a permission in a channel.
     *
     * @param {String} permission The permission to check.
     * @param {Eris.Channel} channel The channel to check.
     * @returns {Boolean} If the user has the permission.
     */
    hasPermission(permission, channel) {
        // Check if permission actually exists.
        if (!Object.keys(Eris.Constants.Permissions).includes(permission)) return false;

        return channel.permissionsOf(this.user.id).has(permission);
    }

    get commandFolders() {
        let cmdDirs = fs.readdirSync(this.commandsDir).map(d => ({[d]: fs.readdirSync(`${this.commandsDir}/${d}`)}));
        let allCmds = {};

        // Go from an array of objects to an object of arrays.
        cmdDirs.forEach(d => Object.assign(allCmds, d));
        cmdDirs = cmdDirs.map(e => Object.keys(e)[0]);

        // Turn folder names into proper paths for future ease (also make sure we only get folders).
        allCmds = Object.entries(allCmds).map(x => x[1].filter(y => fs.statSync(`${this.commandsDir}/${x[0]}/${y}`).isDirectory()));
        allCmds = allCmds.map((v, i) => v.map(x => `${this.commandsDir}/${cmdDirs[i]}/${x}`));
        allCmds = [].concat.apply([], allCmds);

        return allCmds;
    }
}

/**
 * Configuration used for Clara instances.
 * @see config.example.yaml
 *
 * @prop {Object} tokens Tokens used by APIs for different commands.
 * @prop {String} tokens.youtube
 * @prop {String} tokens.soundcloud If this is not set, this will be automatically scraped.
 * @prop {String} tokens.ibsearch
 * @prop {String} tokens.osu
 * @prop {String} tokens.saucenao
 * @prop {String} tokens.nasa
 *
 * @prop {Object} botlistTokens Tokens used for sending stats to various bot list sites.
 * @prop {String} botlistTokens.dbl discordbots.org token.
 * @prop {String} botlistTokens.dbots bots.discord.pw token.
 *
 * @prop {Object} development Various debugging options.
 * @prop {Boolean} development.debug
 * @prop {Boolean} development.promiseWarnings
 *
 * @prop {Object} general Configuration options for the main bot.
 * @prop {String} general.ownerID
 * @prop {String} general.token
 * @prop {String} general.redisURL
 * @prop {String} general.mainPrefix
 * @prop {Number} general.maxShards
 *
 *
 * @prop {Object} discord Miscellaneous Discord-related options.
 * @prop {String} discord.status
 * @prop {Object} discord.game
 * @prop {String} discord.game.url
 * @prop {Integer} discord.game.type
 * @prop {String} discord.game.name
 */
class ClaraConfig { // eslint-disable-line
    // Only here for documentation purposes.
}

module.exports = Clara;<|endoftext|>


<commit_before>// Generated on <%= (new Date).toISOString().split('T')[0] %> using <%= pkg.name %> <%= pkg.version %>
'use strict';
var lrSnippet = require('grunt-contrib-livereload/lib/utils').livereloadSnippet;
var mountFolder = function (connect, dir) {
    return connect.static(require('path').resolve(dir));
};

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to match all subfolders:
// 'test/spec/**/*.js'

module.exports = function (grunt) {
    // load all grunt tasks
    require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);

    // configurable paths
    var yeomanConfig = {
        app: 'app',
        dist: 'dist'
    };

    grunt.initConfig({
        yeoman: yeomanConfig,
        watch: {
            coffee: {
                files: ['<%%= yeoman.app %>/scripts/{,*/}*.coffee'],
                tasks: ['coffee:dist']
            },
            coffeeTest: {
                files: ['test/spec/{,*/}*.coffee'],
                tasks: ['coffee:test']
            },
            compass: {
                files: ['<%%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
                tasks: ['compass']
            },
            livereload: {
                files: [
                    '<%%= yeoman.app %>/*.html',
                    '{.tmp,<%%= yeoman.app %>}/styles/{,*/}*.css',
                    '{.tmp,<%%= yeoman.app %>}/scripts/{,*/}*.js',
                    '<%%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,webp}'
                ],
                tasks: ['livereload']
            }
        },
        connect: {
            options: {
                port: 9000,
                // change this to '0.0.0.0' to access the server from outside
                hostname: 'localhost'
            },
            livereload: {
                options: {
                    middleware: function (connect) {
                        return [
                            lrSnippet,
                            mountFolder(connect, '.tmp'),
                            mountFolder(connect, 'app')
                        ];
                    }
                }
            },
            test: {
                options: {
                    middleware: function (connect) {
                        return [
                            mountFolder(connect, '.tmp'),
                            mountFolder(connect, 'test')
                        ];
                    }
                }
            },
            dist: {
                options: {
                    middleware: function (connect) {
                        return [
                            mountFolder(connect, 'dist')
                        ];
                    }
                }
            }
        },
        open: {
            server: {
                path: 'http://localhost:<%%= connect.options.port %>'
            }
        },
        clean: {
            dist: ['.tmp', '<%%= yeoman.dist %>/*'],
            server: '.tmp'
        },
        jshint: {
            options: {
                jshintrc: '.jshintrc'
            },
            all: [
                'Gruntfile.js',
                '<%%= yeoman.app %>/scripts/{,*/}*.js',
                '!<%%= yeoman.app %>/scripts/vendor/*',
                'test/spec/{,*/}*.js'
            ]
        },
        mocha: {
            all: {
                options: {
                    run: true,
                    urls: ['http://localhost:<%%= connect.options.port %>/index.html']
                }
            }
        },
        coffee: {
            dist: {
                files: [{
                    // rather than compiling multiple files here you should
                    // require them into your main .coffee file
                    expand: true,
                    cwd: '<%%= yeoman.app %>/scripts',
                    src: '*.coffee',
                    dest: '.tmp/scripts',
                    ext: '.js'
                }]
            },
            test: {
                files: [{
                    expand: true,
                    cwd: '.tmp/spec',
                    src: '*.coffee',
                    dest: 'test/spec'
                }]
            }
        },
        compass: {
            options: {
                sassDir: '<%%= yeoman.app %>/styles',
                cssDir: '.tmp/styles',
                imagesDir: '<%%= yeoman.app %>/images',
                javascriptsDir: '<%%= yeoman.app %>/scripts',
                fontsDir: '<%%= yeoman.app %>/styles/fonts',
                importPath: 'app/components',
                relativeAssets: true
            },
            dist: {},
            server: {
                options: {
                    debugInfo: true
                }
            }
        },
        // not used since Uglify task does concat,
        // but still available if needed
        /*concat: {
            dist: {}
        },*/
        <% if (includeRequireJS) { %>requirejs: {
            dist: {
                // Options: https://github.com/jrburke/r.js/blob/master/build/example.build.js
                options: {
                    // `name` and `out` is set by grunt-usemin
                    baseUrl: 'app/scripts',
                    optimize: 'none',
                    // TODO: Figure out how to make sourcemaps work with grunt-usemin
                    // https://github.com/yeoman/grunt-usemin/issues/30
                    //generateSourceMaps: true,
                    // required to support SourceMaps
                    // http://requirejs.org/docs/errors.html#sourcemapcomments
                    preserveLicenseComments: false,
                    useStrict: true,
                    wrap: true,
                    //uglify2: {} // https://github.com/mishoo/UglifyJS2
                }
            }
        },<% } else { %>
        uglify: {
            dist: {
                files: {
                    '<%%= yeoman.dist %>/scripts/main.js': [
                        '<%%= yeoman.app %>/scripts/{,*/}*.js'
                    ],
                }
            }
        },<% } %>
        useminPrepare: {
            html: '<%%= yeoman.app %>/index.html',
            options: {
                dest: '<%%= yeoman.dist %>'
            }
        },
        usemin: {
            html: ['<%%= yeoman.dist %>/{,*/}*.html'],
            css: ['<%%= yeoman.dist %>/styles/{,*/}*.css'],
            options: {
                dirs: ['<%%= yeoman.dist %>']
            }
        },
        imagemin: {
            dist: {
                files: [{
                    expand: true,
                    cwd: '<%%= yeoman.app %>/images',
                    src: '{,*/}*.{png,jpg,jpeg}',
                    dest: '<%%= yeoman.dist %>/images'
                }]
            }
        },
        cssmin: {
            dist: {
                files: {
                    '<%%= yeoman.dist %>/styles/main.css': [
                        '.tmp/styles/{,*/}*.css',
                        '<%%= yeoman.app %>/styles/{,*/}*.css'
                    ]
                }
            }
        },
        htmlmin: {
            dist: {
                options: {
                    /*removeCommentsFromCDATA: true,
                    // https://github.com/yeoman/grunt-usemin/issues/44
                    //collapseWhitespace: true,
                    collapseBooleanAttributes: true,
                    removeAttributeQuotes: true,
                    removeRedundantAttributes: true,
                    useShortDoctype: true,
                    removeEmptyAttributes: true,
                    removeOptionalTags: true*/
                },
                files: [{
                    expand: true,
                    cwd: '<%%= yeoman.app %>',
                    src: '*.html',
                    dest: '<%%= yeoman.dist %>'
                }]
            }
        },
        copy: {
            dist: {
                files: [{
                    expand: true,
                    dot: true,
                    cwd: '<%%= yeoman.app %>',
                    dest: '<%%= yeoman.dist %>',
                    src: [
                        '*.{ico,txt}',
                        '.htaccess'
                    ]
                }]
            }
        },
        bower: {
            all: {
                rjsConfig: '<%%= yeoman.app %>/scripts/main.js'
            }
        }
    });

    grunt.renameTask('regarde', 'watch');

    grunt.registerTask('server', function (target) {
        if (target === 'dist') {
            return grunt.task.run(['build', 'open', 'connect:dist:keepalive']);
        }

        grunt.task.run([
            'clean:server',
            'coffee:dist',
            'compass:server',
            'livereload-start',
            'connect:livereload',
            'open',
            'watch'
        ]);
    });

    grunt.registerTask('test', [
        'clean:server',
        'coffee',
        'compass',
        'connect:test',
        'mocha'
    ]);

    grunt.registerTask('build', [
        'clean:dist',
        'coffee',
        'compass:dist',
        'useminPrepare',<% if (includeRequireJS) { %>
        'requirejs',<% } %>
        'imagemin',
        'htmlmin',
        'concat',
        'cssmin',
        'uglify',
        'copy',
        'usemin'
    ]);

    grunt.registerTask('default', [
        'jshint',
        'test',
        'build'
    ]);
};
<commit_msg>Copy webp/gif to dist folder<commit_after>// Generated on <%= (new Date).toISOString().split('T')[0] %> using <%= pkg.name %> <%= pkg.version %>
'use strict';
var lrSnippet = require('grunt-contrib-livereload/lib/utils').livereloadSnippet;
var mountFolder = function (connect, dir) {
    return connect.static(require('path').resolve(dir));
};

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to match all subfolders:
// 'test/spec/**/*.js'

module.exports = function (grunt) {
    // load all grunt tasks
    require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);

    // configurable paths
    var yeomanConfig = {
        app: 'app',
        dist: 'dist'
    };

    grunt.initConfig({
        yeoman: yeomanConfig,
        watch: {
            coffee: {
                files: ['<%%= yeoman.app %>/scripts/{,*/}*.coffee'],
                tasks: ['coffee:dist']
            },
            coffeeTest: {
                files: ['test/spec/{,*/}*.coffee'],
                tasks: ['coffee:test']
            },
            compass: {
                files: ['<%%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
                tasks: ['compass']
            },
            livereload: {
                files: [
                    '<%%= yeoman.app %>/*.html',
                    '{.tmp,<%%= yeoman.app %>}/styles/{,*/}*.css',
                    '{.tmp,<%%= yeoman.app %>}/scripts/{,*/}*.js',
                    '<%%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp}'
                ],
                tasks: ['livereload']
            }
        },
        connect: {
            options: {
                port: 9000,
                // change this to '0.0.0.0' to access the server from outside
                hostname: 'localhost'
            },
            livereload: {
                options: {
                    middleware: function (connect) {
                        return [
                            lrSnippet,
                            mountFolder(connect, '.tmp'),
                            mountFolder(connect, 'app')
                        ];
                    }
                }
            },
            test: {
                options: {
                    middleware: function (connect) {
                        return [
                            mountFolder(connect, '.tmp'),
                            mountFolder(connect, 'test')
                        ];
                    }
                }
            },
            dist: {
                options: {
                    middleware: function (connect) {
                        return [
                            mountFolder(connect, 'dist')
                        ];
                    }
                }
            }
        },
        open: {
            server: {
                path: 'http://localhost:<%%= connect.options.port %>'
            }
        },
        clean: {
            dist: ['.tmp', '<%%= yeoman.dist %>/*'],
            server: '.tmp'
        },
        jshint: {
            options: {
                jshintrc: '.jshintrc'
            },
            all: [
                'Gruntfile.js',
                '<%%= yeoman.app %>/scripts/{,*/}*.js',
                '!<%%= yeoman.app %>/scripts/vendor/*',
                'test/spec/{,*/}*.js'
            ]
        },
        mocha: {
            all: {
                options: {
                    run: true,
                    urls: ['http://localhost:<%%= connect.options.port %>/index.html']
                }
            }
        },
        coffee: {
            dist: {
                files: [{
                    // rather than compiling multiple files here you should
                    // require them into your main .coffee file
                    expand: true,
                    cwd: '<%%= yeoman.app %>/scripts',
                    src: '*.coffee',
                    dest: '.tmp/scripts',
                    ext: '.js'
                }]
            },
            test: {
                files: [{
                    expand: true,
                    cwd: '.tmp/spec',
                    src: '*.coffee',
                    dest: 'test/spec'
                }]
            }
        },
        compass: {
            options: {
                sassDir: '<%%= yeoman.app %>/styles',
                cssDir: '.tmp/styles',
                imagesDir: '<%%= yeoman.app %>/images',
                javascriptsDir: '<%%= yeoman.app %>/scripts',
                fontsDir: '<%%= yeoman.app %>/styles/fonts',
                importPath: 'app/components',
                relativeAssets: true
            },
            dist: {},
            server: {
                options: {
                    debugInfo: true
                }
            }
        },
        // not used since Uglify task does concat,
        // but still available if needed
        /*concat: {
            dist: {}
        },*/
        <% if (includeRequireJS) { %>requirejs: {
            dist: {
                // Options: https://github.com/jrburke/r.js/blob/master/build/example.build.js
                options: {
                    // `name` and `out` is set by grunt-usemin
                    baseUrl: 'app/scripts',
                    optimize: 'none',
                    // TODO: Figure out how to make sourcemaps work with grunt-usemin
                    // https://github.com/yeoman/grunt-usemin/issues/30
                    //generateSourceMaps: true,
                    // required to support SourceMaps
                    // http://requirejs.org/docs/errors.html#sourcemapcomments
                    preserveLicenseComments: false,
                    useStrict: true,
                    wrap: true,
                    //uglify2: {} // https://github.com/mishoo/UglifyJS2
                }
            }
        },<% } else { %>
        uglify: {
            dist: {
                files: {
                    '<%%= yeoman.dist %>/scripts/main.js': [
                        '<%%= yeoman.app %>/scripts/{,*/}*.js'
                    ],
                }
            }
        },<% } %>
        useminPrepare: {
            html: '<%%= yeoman.app %>/index.html',
            options: {
                dest: '<%%= yeoman.dist %>'
            }
        },
        usemin: {
            html: ['<%%= yeoman.dist %>/{,*/}*.html'],
            css: ['<%%= yeoman.dist %>/styles/{,*/}*.css'],
            options: {
                dirs: ['<%%= yeoman.dist %>']
            }
        },
        imagemin: {
            dist: {
                files: [{
                    expand: true,
                    cwd: '<%%= yeoman.app %>/images',
                    src: '{,*/}*.{png,jpg,jpeg}',
                    dest: '<%%= yeoman.dist %>/images'
                }]
            }
        },
        cssmin: {
            dist: {
                files: {
                    '<%%= yeoman.dist %>/styles/main.css': [
                        '.tmp/styles/{,*/}*.css',
                        '<%%= yeoman.app %>/styles/{,*/}*.css'
                    ]
                }
            }
        },
        htmlmin: {
            dist: {
                options: {
                    /*removeCommentsFromCDATA: true,
                    // https://github.com/yeoman/grunt-usemin/issues/44
                    //collapseWhitespace: true,
                    collapseBooleanAttributes: true,
                    removeAttributeQuotes: true,
                    removeRedundantAttributes: true,
                    useShortDoctype: true,
                    removeEmptyAttributes: true,
                    removeOptionalTags: true*/
                },
                files: [{
                    expand: true,
                    cwd: '<%%= yeoman.app %>',
                    src: '*.html',
                    dest: '<%%= yeoman.dist %>'
                }]
            }
        },
        copy: {
            dist: {
                files: [{
                    expand: true,
                    dot: true,
                    cwd: '<%%= yeoman.app %>',
                    dest: '<%%= yeoman.dist %>',
                    src: [
                        '*.{ico,txt}',
                        '.htaccess',
                        'images/{,*/}*.{webp,gif}'
                    ]
                }]
            }
        },
        bower: {
            all: {
                rjsConfig: '<%%= yeoman.app %>/scripts/main.js'
            }
        }
    });

    grunt.renameTask('regarde', 'watch');

    grunt.registerTask('server', function (target) {
        if (target === 'dist') {
            return grunt.task.run(['build', 'open', 'connect:dist:keepalive']);
        }

        grunt.task.run([
            'clean:server',
            'coffee:dist',
            'compass:server',
            'livereload-start',
            'connect:livereload',
            'open',
            'watch'
        ]);
    });

    grunt.registerTask('test', [
        'clean:server',
        'coffee',
        'compass',
        'connect:test',
        'mocha'
    ]);

    grunt.registerTask('build', [
        'clean:dist',
        'coffee',
        'compass:dist',
        'useminPrepare',<% if (includeRequireJS) { %>
        'requirejs',<% } %>
        'imagemin',
        'htmlmin',
        'concat',
        'cssmin',
        'uglify',
        'copy',
        'usemin'
    ]);

    grunt.registerTask('default', [
        'jshint',
        'test',
        'build'
    ]);
};
<|endoftext|>


<commit_before>/* jshint unused: false */
(function() {
  'use strict';

  angular.module('myapp')
    .factory('aFactory', function() {

    })
    .run([
      '$q',
      '$timeout',
      'extensionRegistry',
      function($q, $timeout, extensionRegistry) {

          // collecting all the registered extensions in an array
          var registries = [

            // illustrates returning nothing, but using the extension hook to
            // do some arbitrary work with the provided args as context
            extensionRegistry.register('thing', function(args) {
              args.name.last = args.name.last.toUpperCase();
              return undefined;
            }),

            // illustrates returning a single object, rather than an array
            extensionRegistry.register('thing', function(args) {
              return {
                type: 'text',
                text: 'this is some text'
              };
            }),

            // illustrates the html type
            extensionRegistry.register('thing', function(args) {
              return [
                {
                  type: 'html',
                  html: [
                          '<div>',
                          args.name.first,
                          ' is an employee.'
                        ].join('')
                }
              ];
            }),

            // illustrates returning a list rather than a single object
            extensionRegistry.register('thing', function(args) {
              return [
                {
                  type: 'select',
                  nameText: 'select-name',
                  className: 'i am a select box test',
                  options: [
                    {
                      label: 'bar 1 - 1',
                      value: 'bar'
                    },{
                      label: 'bar 1 - 2',
                      value: 'thing'
                    },{
                      label: 'bar 1 - 3',
                      value: 'other'
                    }
                  ],
                  // a select box gets an onChange function
                  // note that deregistering a function will trigger a change
                  // event as well as re-render the UI.  A select will be
                  // reset to an initial state, possibly undesirable for
                  // a user in the midst of an interaction.
                  onChange: function(item) {
                    console.log('selected', item);
                  }
                }
              ];
            }),

            // illustrates returning a promise, such as would be done
            // if making an API call
            extensionRegistry.register('thing', function(args) {
              return $q.when([
                {
                  type: 'link',
                  href: 'http://redhat.com',
                  linkText: name + 'redhat link',
                  target: '_blank'
                },
                {
                  type: 'link',
                  linkText: name + 'redhat alert',
                  onClick: function() {
                    alert('redhat!');
                  }
                }
              ]);
            }),

            // illustrates doing some arbitrary work & a promise for a
            // list of extensions....
            extensionRegistry.register('thing', function(args) {
              // doing a little arbitrary work inside a registered function
              var name;
              if(args) {
                if(args.name && args.name.first) {
                  name = [
                    args.name.first,
                    ' ',
                    args.name.last,
                    '\'s '
                  ].join('');
                } else {
                  name = args.name + '\'s ';
                }
              }

              // simulate async operation, such as an API call
              return $q.when([
                {
                  type: 'link',
                  href: 'http://google.com',
                  linkText: name + 'google link',
                  target: '_blank'
                },
                {
                  type: 'link',
                  linkText: name + 'google alert',
                  onClick: function() {
                    alert('google!');
                  }
                },
                {
                  type: 'not_a_thing_i_can_be',
                  href: 'http://example.com',
                  linkText: name + 'i should never appear',
                  target: '_blank'
                },
              ]);
            })
          ];

        // test some side cases
        // you must register a function, but should not error out...
        extensionRegistry.register('thing');
        extensionRegistry.register('thing', []);

        // deregistering will trigger a UI update & re-render
        $timeout(function() {
          registries[0].deregister();
        }, 4000);

      }
    ]);

})();
<commit_msg>Improve comments in demo on registry examples<commit_after>/* jshint unused: false */
(function() {
  'use strict';

  angular.module('myapp')
    .factory('aFactory', function() {

    })
    .run([
      '$q',
      '$timeout',
      'extensionRegistry',
      function($q, $timeout, extensionRegistry) {

          // collecting all the registered extensions in an array
          var registries = [

            // illustrates returning nothing, but using the extension hook to
            // do some arbitrary work with the provided args as context
            extensionRegistry.register('thing', function(args) {
              args.name.last = args.name.last.toUpperCase();
              return undefined;
            }),

            // illustrates returning a single object, rather than an array
            extensionRegistry.register('thing', function(args) {
              return {
                type: 'text',
                text: 'this is some text'
              };
            }),

            // illustrates the html type
            extensionRegistry.register('thing', function(args) {
              return [
                {
                  type: 'html',
                  html: [
                          '<div>',
                          args.name.first,
                          ' is an employee.'
                        ].join('')
                }
              ];
            }),

            // illustrates returning a list rather than a single object
            extensionRegistry.register('thing', function(args) {
              return [
                {
                  type: 'select',
                  nameText: 'select-name',
                  className: 'i am a select box test',
                  options: [
                    {
                      label: 'bar 1 - 1',
                      value: 'bar'
                    },{
                      label: 'bar 1 - 2',
                      value: 'thing'
                    },{
                      label: 'bar 1 - 3',
                      value: 'other'
                    }
                  ],
                  // a select box gets an onChange function
                  // note that deregistering a function will trigger a change
                  // event as well as re-render the UI.  A select will be
                  // reset to an initial state, possibly undesirable for
                  // a user in the midst of an interaction.
                  onChange: function(item) {
                    console.log('selected', item);
                  }
                }
              ];
            }),

            // illustrates returning a promise, such as would be done
            // if making an API call
            extensionRegistry.register('thing', function(args) {
              return $q.when([
                {
                  type: 'link',
                  href: 'http://redhat.com',
                  linkText: name + 'redhat link',
                  target: '_blank'
                },
                {
                  type: 'link',
                  linkText: name + 'redhat alert',
                  onClick: function() {
                    alert('redhat!');
                  }
                }
              ]);
            }),

            // illustrates doing some arbitrary work & a promise for a
            // list of extensions....
            extensionRegistry.register('thing', function(args) {
              // doing a little arbitrary work inside a registered function
              var name;
              if(args) {
                if(args.name && args.name.first) {
                  name = [
                    args.name.first,
                    ' ',
                    args.name.last,
                    '\'s '
                  ].join('');
                } else {
                  name = args.name + '\'s ';
                }
              }

              // simulate async operation, such as an API call
              return $q.when([
                {
                  type: 'link',
                  href: 'http://google.com',
                  linkText: name + 'google link',
                  target: '_blank'
                },
                {
                  type: 'link',
                  linkText: name + 'google alert',
                  onClick: function() {
                    alert('google!');
                  }
                },
                {
                  type: 'not_a_thing_i_can_be',
                  href: 'http://example.com',
                  linkText: name + 'i should never appear',
                  target: '_blank'
                },
              ]);
            })
          ];

        // test some side cases
        // you must register a function, but should not error out...
        extensionRegistry.register('thing');
        extensionRegistry.register('thing', []);

        // the registry system is tolerant of errors to avoid one extension
        // causing other extensions to fail.  registering an error or returning
        // an error will do nothing.
        extensionRegistry.register('thing', new Error('Oh no, it broke!'));
        extensionRegistry.register('thing', function() {
          return new Error('Oh no, bad things!!!!');
        });

        // deregistering will trigger a UI update & re-render
        $timeout(function() {
          registries[0].deregister();
        }, 4000);

      }
    ]);

})();
<|endoftext|>


<commit_before>// Generated on <%= (new Date).toISOString().split('T')[0] %> using <%= pkg.name %> <%= pkg.version %>
'use strict';
var fs = require('fs');
<% if (buildTool == 'maven') { %>
var parseString = require('xml2js').parseString;
// Returns the second occurence of the version number
var parseVersionFromPomXml = function() {
    var version;
    var pomXml = fs.readFileSync('pom.xml', "utf8");
    parseString(pomXml, function (err, result){
        version = result.project.version[0];
    });
    return version;
};<% } else { %>
// Returns the first occurence of the version number
var parseVersionFromBuildGradle = function() {
    var versionRegex = /^version\s*=\s*[',"]([^',"]*)[',"]/gm; // Match and group the version number
    var buildGradle = fs.readFileSync('build.gradle', "utf8");
    return versionRegex.exec(buildGradle)[1];
};<% } %>

// usemin custom step
var useminAutoprefixer = {
    name: 'autoprefixer',
    createConfig: require('grunt-usemin/lib/config/cssmin').createConfig // Reuse cssmins createConfig
};

module.exports = function (grunt) {
    require('load-grunt-tasks')(grunt);
    require('time-grunt')(grunt);

    grunt.initConfig({
        yeoman: {
            // configurable paths
            app: require('./bower.json').appPath || 'app',
            dist: 'src/main/webapp/dist'
        },
        watch: {
            bower: {
                files: ['bower.json'],
                tasks: ['wiredep']
            },
            ngconstant: {
                files: ['Gruntfile.js', <% if(buildTool == 'maven') { %>'pom.xml'<% } else { %>'build.gradle'<% } %>],
                tasks: ['ngconstant:dev']
            },<% if (useCompass) { %>
            compass: {
                files: ['src/main/scss/**/*.{scss,sass}'],
                tasks: ['compass:server']
            },<% } %>
            styles: {
                files: ['src/main/webapp/assets/styles/**/*.css']
            }
        },
        autoprefixer: {
        // not used since Uglify task does autoprefixer,
        //    options: ['last 1 version'],
        //    dist: {
        //        files: [{
        //            expand: true,
        //            cwd: '.tmp/styles/',
        //            src: '**/*.css',
        //            dest: '.tmp/styles/'
        //        }]
        //    }
        },
        wiredep: {
            app: {<% if (useCompass) { %>
                src: ['src/main/webapp/index.html', 'src/main/scss/main.scss'],
                exclude: [
                    /angular-i18n/, // localizations are loaded dynamically
                    /swagger-ui/,
                    'bower_components/bootstrap/' // Exclude Bootstrap LESS as we use bootstrap-sass
                ],
                ignorePath: /\.\.\/webapp\/bower_components\// // remove ../webapp/bower_components/ from paths of injected sass files <% } else { %>
                src: ['src/main/webapp/index.html'],
                exclude: [
                    /angular-i18n/,  // localizations are loaded dynamically
                    /swagger-ui/
                ]<% } %>
            },
            test: {
                src: 'src/test/javascript/karma.conf.js',
                exclude: [/angular-i18n/, /swagger-ui/, /angular-scenario/],
                ignorePath: /\.\.\/\.\.\//, // remove ../../ from paths of injected javascripts
                devDependencies: true,
                fileTypes: {
                    js: {
                        block: /(([\s\t]*)\/\/\s*bower:*(\S*))(\n|\r|.)*?(\/\/\s*endbower)/gi,
                        detect: {
                            js: /'(.*\.js)'/gi
                        },
                        replace: {
                            js: '\'{{filePath}}\','
                        }
                    }
                }
            }
        },
        browserSync: {
            dev: {
                bsFiles: {
                    src : [
                        'src/main/webapp/**/*.html',
                        'src/main/webapp/**/*.json',
                        '{.tmp/,}src/main/webapp/assets/styles/**/*.css',
                        '{.tmp/,}src/main/webapp/scripts/**/*.js',
                        'src/main/webapp/assets/images/**/*.{png,jpg,jpeg,gif,webp,svg}'
                    ]
                }
            },
            options: {
                watchTask: true,
                proxy: "localhost:8080"
            }
        },
        clean: {
            dist: {
                files: [{
                    dot: true,
                    src: [
                        '.tmp',
                        '<%%= yeoman.dist %>/*',
                        '!<%%= yeoman.dist %>/.git*'
                    ]
                }]
            },
            server: '.tmp'
        },
        jshint: {
            options: {
                jshintrc: '.jshintrc'
            },
            all: [
                'Gruntfile.js',
                'src/main/webapp/scripts/app.js',
                'src/main/webapp/scripts/app/**/*.js',
                'src/main/webapp/scripts/components/**/*.js'
            ]
        },
        coffee: {
            options: {
                sourceMap: true,
                sourceRoot: ''
            },
            dist: {
                files: [{
                    expand: true,
                    cwd: 'src/main/webapp/scripts',
                    src: ['scripts/app/**/*.coffee', 'scripts/components/**/*.coffee'],
                    dest: '.tmp/scripts',
                    ext: '.js'
                }]
            },
            test: {
                files: [{
                    expand: true,
                    cwd: 'test/spec',
                    src: '**/*.coffee',
                    dest: '.tmp/spec',
                    ext: '.js'
                }]
            }
        },<% if (useCompass) { %>
        compass: {
            options: {
                sassDir: 'src/main/scss',
                cssDir: 'src/main/webapp/assets/styles',
                generatedImagesDir: '.tmp/assets/images/generated',
                imagesDir: 'src/main/webapp/assets/images',
                javascriptsDir: 'src/main/webapp/scripts',
                fontsDir: 'src/main/webapp/assets/fonts',
                importPath: 'src/main/webapp/bower_components',
                httpImagesPath: '/assets/images',
                httpGeneratedImagesPath: '/assets/images/generated',
                httpFontsPath: '/assets/fonts',
                relativeAssets: false
            },
            dist: {},
            server: {
                options: {
                    debugInfo: true
                }
            }
        },<% } %>
        concat: {
        // not used since Uglify task does concat,
        // but still available if needed
        //    dist: {}
        },
        rev: {
            dist: {
                files: {
                    src: [
                        '<%%= yeoman.dist %>/scripts/**/*.js',
                        '<%%= yeoman.dist %>/assets/styles/**/*.css',
                        '<%%= yeoman.dist %>/assets/images/**/*.{png,jpg,jpeg,gif,webp,svg}',
                        '<%%= yeoman.dist %>/assets/fonts/*'
                    ]
                }
            }
        },
        useminPrepare: {
            html: 'src/main/webapp/**/*.html',
            options: {
                dest: '<%%= yeoman.dist %>',
                flow: {
                    html: {
                        steps: {
                            js: ['concat', 'uglifyjs'],
                            css: ['cssmin', useminAutoprefixer] // Let cssmin concat files so it corrects relative paths to fonts and images
                        },
                            post: {}
                        }
                    }
            }
        },
        usemin: {
            html: ['<%%= yeoman.dist %>/**/*.html'],
            css: ['<%%= yeoman.dist %>/assets/styles/**/*.css'],
            js: ['<%%= yeoman.dist %>/scripts/**/*.js'],
            options: {
                assetsDirs: ['<%%= yeoman.dist %>', '<%%= yeoman.dist %>/assets/styles', '<%%= yeoman.dist %>/assets/images', '<%%= yeoman.dist %>/assets/fonts'],
                patterns: {
                    js: [
                        [/(assets\/images\/.*?\.(?:gif|jpeg|jpg|png|webp|svg))/gm, 'Update the JS to reference our revved images']
                    ]
                },
                dirs: ['<%%= yeoman.dist %>']
            }
        },
        imagemin: {
            dist: {
                files: [{
                    expand: true,
                    cwd: 'src/main/webapp/assets/images',
                src: '**/*.{jpg,jpeg}', // we don't optimize PNG files as it doesn't work on Linux. If you are not on Linux, feel free to use '**/*.{png,jpg,jpeg}'
                    dest: '<%%= yeoman.dist %>/assets/images'
                }]
            }
        },
        svgmin: {
            dist: {
                files: [{
                    expand: true,
                    cwd: 'src/main/webapp/assets/images',
                    src: '**/*.svg',
                    dest: '<%%= yeoman.dist %>/assets/images'
                }]
            }
        },
        cssmin: {
            // By default, your `index.html` <!-- Usemin Block --> will take care of
            // minification. This option is pre-configured if you do not wish to use
            // Usemin blocks.
            // dist: {
            //     files: {
            //         '<%%= yeoman.dist %>/styles/main.css': [
            //             '.tmp/styles/**/*.css',
            //             'styles/**/*.css'
            //         ]
            //     }
            // }
            options: {
                root: 'src/main/webapp' // Replace relative paths for static resources with absolute path
            }
        },
        ngtemplates:    {
            dist: {
                cwd: 'src/main/webapp',
                src: ['scripts/app/**/*.html', 'scripts/components/**/*.html',],
                dest: '.tmp/templates/templates.js',
                options: {
                    module: '<%= angularAppName%>',
                    usemin: 'scripts/app.js',
                    htmlmin:  {
                        removeCommentsFromCDATA: true,
                        // https://github.com/yeoman/grunt-usemin/issues/44
                        collapseWhitespace: true,
                        collapseBooleanAttributes: true,
                        conservativeCollapse: true,
                        removeAttributeQuotes: true,
                        removeRedundantAttributes: true,
                        useShortDoctype: true,
                        removeEmptyAttributes: true
                    }
                }
            }
        },
        htmlmin: {
            dist: {
                options: {
                    removeCommentsFromCDATA: true,
                    // https://github.com/yeoman/grunt-usemin/issues/44
                    collapseWhitespace: true,
                    collapseBooleanAttributes: true,
                    conservativeCollapse: true,
                    removeAttributeQuotes: true,
                    removeRedundantAttributes: true,
                    useShortDoctype: true,
                    removeEmptyAttributes: true,
                    keepClosingSlash: true
                },
                files: [{
                    expand: true,
                    cwd: '<%%= yeoman.dist %>',
                    src: ['*.html'],
                    dest: '<%%= yeoman.dist %>'
                }]
            }
        },
        // Put files not handled in other tasks here
        copy: {
            dist: {
                files: [{
                    expand: true,
                    dot: true,
                    cwd: 'src/main/webapp',
                    dest: '<%%= yeoman.dist %>',
                    src: [
                        '*.html',
                        'scripts/**/*.html',
                        'assets/images/**/*.{png,gif,webp}',
                        'assets/fonts/*'
                    ]
                }, {
                    expand: true,
                    cwd: '.tmp/assets/images',
                    dest: '<%%= yeoman.dist %>/assets/images',
                    src: [
                        'generated/*'
                    ]
                }]
            },
            generateHerokuDirectory: {
                    expand: true,
                    dest: 'deploy/heroku',
                    src: [
                        'pom.xml',
                        'src/main/**'
                ]
            },
            generateOpenshiftDirectory: {
                    expand: true,
                    dest: 'deploy/openshift',
                    src: [
                        'pom.xml',
                        'src/main/**'
                ]
            }
        },
        concurrent: {
            server: [<% if (useCompass) { %>
                'compass:server'<% } %>
            ],
            test: [<% if (useCompass) { %>
                'compass'<% } %>
            ],
            dist: [<% if (useCompass) { %>
                'compass:dist',<% } %>
                'imagemin',
                'svgmin'
            ]
        },
        karma: {
            unit: {
                configFile: 'src/test/javascript/karma.conf.js',
                singleRun: true
            }
        },
        cdnify: {
            dist: {
                html: ['<%%= yeoman.dist %>/*.html']
            }
        },
        ngAnnotate: {
            dist: {
                files: [{
                    expand: true,
                    cwd: '.tmp/concat/scripts',
                    src: '*.js',
                    dest: '.tmp/concat/scripts'
                }]
            }
        },
        buildcontrol: {
            options: {
                commit: true,
                push: false,
                connectCommits: false,
                message: 'Built %sourceName% from commit %sourceCommit% on branch %sourceBranch%'
            },
            heroku: {
                options: {
                    dir: 'deploy/heroku',
                    remote: 'heroku',
                    branch: 'master'
                }
            },
            openshift: {
                options: {
                    dir: 'deploy/openshift',
                    remote: 'openshift',
                    branch: 'master'
                }
            }
        },
        ngconstant: {
            options: {
                name: '<%= angularAppName%>',
                deps: false,
                wrap: '"use strict";\n// DO NOT EDIT THIS FILE, EDIT THE GRUNT TASK NGCONSTANT SETTINGS INSTEAD WHICH GENERATES THIS FILE\n{%= __ngModule %}'
            },
            dev: {
                options: {
                    dest: 'src/main/webapp/scripts/app/app.constants.js',
                },
                constants: {
                    ENV: 'dev',
                    VERSION: <% if(buildTool == 'maven') { %>parseVersionFromPomXml()<% } else { %>parseVersionFromBuildGradle()<% } %>
                }
            },
            prod: {
                options: {
                    dest: '.tmp/scripts/app/app.constants.js',
                },
                constants: {
                    ENV: 'prod',
                    VERSION: <% if(buildTool == 'maven') { %>parseVersionFromPomXml()<% } else { %>parseVersionFromBuildGradle()<% } %>
                }
            }
        }
    });

    grunt.registerTask('serve', [
        'clean:server',
        'wiredep',
        'ngconstant:dev',
        'concurrent:server',
        'browserSync',
        'watch'
    ]);

    grunt.registerTask('server', function (target) {
        grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
        grunt.task.run([target ? ('serve:' + target) : 'serve']);
    });

    grunt.registerTask('test', [
        'clean:server',
        'wiredep:test',
        'ngconstant:dev',
        'concurrent:test',
        'karma'
    ]);

    grunt.registerTask('build', [
        'clean:dist',
        'wiredep:app',
        'ngconstant:prod',
        'useminPrepare',
        'ngtemplates',
        'concurrent:dist',
        'concat',
        'copy:dist',
        'ngAnnotate',
        'cssmin',
        'autoprefixer',
        'uglify',
        'rev',
        'usemin',
        'htmlmin'
    ]);

    grunt.registerTask('buildHeroku', [
        'test',
        'build',
        'copy:generateHerokuDirectory',
    ]);

    grunt.registerTask('deployHeroku', [
        'test',
        'build',
        'copy:generateHerokuDirectory',
        'buildcontrol:heroku'
    ]);

    grunt.registerTask('buildOpenshift', [
        'test',
        'build',
        'copy:generateOpenshiftDirectory',
    ]);

    grunt.registerTask('deployOpenshift', [
        'test',
        'build',
        'copy:generateOpenshiftDirectory',
        'buildcontrol:openshift'
    ]);

    grunt.registerTask('default', [
        'test',
        'build'
    ]);
};
<commit_msg>Correct Gruntfile.js:copy:dist forgotten jpegs<commit_after>// Generated on <%= (new Date).toISOString().split('T')[0] %> using <%= pkg.name %> <%= pkg.version %>
'use strict';
var fs = require('fs');
<% if (buildTool == 'maven') { %>
var parseString = require('xml2js').parseString;
// Returns the second occurence of the version number
var parseVersionFromPomXml = function() {
    var version;
    var pomXml = fs.readFileSync('pom.xml', "utf8");
    parseString(pomXml, function (err, result){
        version = result.project.version[0];
    });
    return version;
};<% } else { %>
// Returns the first occurence of the version number
var parseVersionFromBuildGradle = function() {
    var versionRegex = /^version\s*=\s*[',"]([^',"]*)[',"]/gm; // Match and group the version number
    var buildGradle = fs.readFileSync('build.gradle', "utf8");
    return versionRegex.exec(buildGradle)[1];
};<% } %>

// usemin custom step
var useminAutoprefixer = {
    name: 'autoprefixer',
    createConfig: require('grunt-usemin/lib/config/cssmin').createConfig // Reuse cssmins createConfig
};

module.exports = function (grunt) {
    require('load-grunt-tasks')(grunt);
    require('time-grunt')(grunt);

    grunt.initConfig({
        yeoman: {
            // configurable paths
            app: require('./bower.json').appPath || 'app',
            dist: 'src/main/webapp/dist'
        },
        watch: {
            bower: {
                files: ['bower.json'],
                tasks: ['wiredep']
            },
            ngconstant: {
                files: ['Gruntfile.js', <% if(buildTool == 'maven') { %>'pom.xml'<% } else { %>'build.gradle'<% } %>],
                tasks: ['ngconstant:dev']
            },<% if (useCompass) { %>
            compass: {
                files: ['src/main/scss/**/*.{scss,sass}'],
                tasks: ['compass:server']
            },<% } %>
            styles: {
                files: ['src/main/webapp/assets/styles/**/*.css']
            }
        },
        autoprefixer: {
        // not used since Uglify task does autoprefixer,
        //    options: ['last 1 version'],
        //    dist: {
        //        files: [{
        //            expand: true,
        //            cwd: '.tmp/styles/',
        //            src: '**/*.css',
        //            dest: '.tmp/styles/'
        //        }]
        //    }
        },
        wiredep: {
            app: {<% if (useCompass) { %>
                src: ['src/main/webapp/index.html', 'src/main/scss/main.scss'],
                exclude: [
                    /angular-i18n/, // localizations are loaded dynamically
                    /swagger-ui/,
                    'bower_components/bootstrap/' // Exclude Bootstrap LESS as we use bootstrap-sass
                ],
                ignorePath: /\.\.\/webapp\/bower_components\// // remove ../webapp/bower_components/ from paths of injected sass files <% } else { %>
                src: ['src/main/webapp/index.html'],
                exclude: [
                    /angular-i18n/,  // localizations are loaded dynamically
                    /swagger-ui/
                ]<% } %>
            },
            test: {
                src: 'src/test/javascript/karma.conf.js',
                exclude: [/angular-i18n/, /swagger-ui/, /angular-scenario/],
                ignorePath: /\.\.\/\.\.\//, // remove ../../ from paths of injected javascripts
                devDependencies: true,
                fileTypes: {
                    js: {
                        block: /(([\s\t]*)\/\/\s*bower:*(\S*))(\n|\r|.)*?(\/\/\s*endbower)/gi,
                        detect: {
                            js: /'(.*\.js)'/gi
                        },
                        replace: {
                            js: '\'{{filePath}}\','
                        }
                    }
                }
            }
        },
        browserSync: {
            dev: {
                bsFiles: {
                    src : [
                        'src/main/webapp/**/*.html',
                        'src/main/webapp/**/*.json',
                        '{.tmp/,}src/main/webapp/assets/styles/**/*.css',
                        '{.tmp/,}src/main/webapp/scripts/**/*.js',
                        'src/main/webapp/assets/images/**/*.{png,jpg,jpeg,gif,webp,svg}'
                    ]
                }
            },
            options: {
                watchTask: true,
                proxy: "localhost:8080"
            }
        },
        clean: {
            dist: {
                files: [{
                    dot: true,
                    src: [
                        '.tmp',
                        '<%%= yeoman.dist %>/*',
                        '!<%%= yeoman.dist %>/.git*'
                    ]
                }]
            },
            server: '.tmp'
        },
        jshint: {
            options: {
                jshintrc: '.jshintrc'
            },
            all: [
                'Gruntfile.js',
                'src/main/webapp/scripts/app.js',
                'src/main/webapp/scripts/app/**/*.js',
                'src/main/webapp/scripts/components/**/*.js'
            ]
        },
        coffee: {
            options: {
                sourceMap: true,
                sourceRoot: ''
            },
            dist: {
                files: [{
                    expand: true,
                    cwd: 'src/main/webapp/scripts',
                    src: ['scripts/app/**/*.coffee', 'scripts/components/**/*.coffee'],
                    dest: '.tmp/scripts',
                    ext: '.js'
                }]
            },
            test: {
                files: [{
                    expand: true,
                    cwd: 'test/spec',
                    src: '**/*.coffee',
                    dest: '.tmp/spec',
                    ext: '.js'
                }]
            }
        },<% if (useCompass) { %>
        compass: {
            options: {
                sassDir: 'src/main/scss',
                cssDir: 'src/main/webapp/assets/styles',
                generatedImagesDir: '.tmp/assets/images/generated',
                imagesDir: 'src/main/webapp/assets/images',
                javascriptsDir: 'src/main/webapp/scripts',
                fontsDir: 'src/main/webapp/assets/fonts',
                importPath: 'src/main/webapp/bower_components',
                httpImagesPath: '/assets/images',
                httpGeneratedImagesPath: '/assets/images/generated',
                httpFontsPath: '/assets/fonts',
                relativeAssets: false
            },
            dist: {},
            server: {
                options: {
                    debugInfo: true
                }
            }
        },<% } %>
        concat: {
        // not used since Uglify task does concat,
        // but still available if needed
        //    dist: {}
        },
        rev: {
            dist: {
                files: {
                    src: [
                        '<%%= yeoman.dist %>/scripts/**/*.js',
                        '<%%= yeoman.dist %>/assets/styles/**/*.css',
                        '<%%= yeoman.dist %>/assets/images/**/*.{png,jpg,jpeg,gif,webp,svg}',
                        '<%%= yeoman.dist %>/assets/fonts/*'
                    ]
                }
            }
        },
        useminPrepare: {
            html: 'src/main/webapp/**/*.html',
            options: {
                dest: '<%%= yeoman.dist %>',
                flow: {
                    html: {
                        steps: {
                            js: ['concat', 'uglifyjs'],
                            css: ['cssmin', useminAutoprefixer] // Let cssmin concat files so it corrects relative paths to fonts and images
                        },
                            post: {}
                        }
                    }
            }
        },
        usemin: {
            html: ['<%%= yeoman.dist %>/**/*.html'],
            css: ['<%%= yeoman.dist %>/assets/styles/**/*.css'],
            js: ['<%%= yeoman.dist %>/scripts/**/*.js'],
            options: {
                assetsDirs: ['<%%= yeoman.dist %>', '<%%= yeoman.dist %>/assets/styles', '<%%= yeoman.dist %>/assets/images', '<%%= yeoman.dist %>/assets/fonts'],
                patterns: {
                    js: [
                        [/(assets\/images\/.*?\.(?:gif|jpeg|jpg|png|webp|svg))/gm, 'Update the JS to reference our revved images']
                    ]
                },
                dirs: ['<%%= yeoman.dist %>']
            }
        },
        imagemin: {
            dist: {
                files: [{
                    expand: true,
                    cwd: 'src/main/webapp/assets/images',
                src: '**/*.{jpg,jpeg}', // we don't optimize PNG files as it doesn't work on Linux. If you are not on Linux, feel free to use '**/*.{png,jpg,jpeg}'
                    dest: '<%%= yeoman.dist %>/assets/images'
                }]
            }
        },
        svgmin: {
            dist: {
                files: [{
                    expand: true,
                    cwd: 'src/main/webapp/assets/images',
                    src: '**/*.svg',
                    dest: '<%%= yeoman.dist %>/assets/images'
                }]
            }
        },
        cssmin: {
            // By default, your `index.html` <!-- Usemin Block --> will take care of
            // minification. This option is pre-configured if you do not wish to use
            // Usemin blocks.
            // dist: {
            //     files: {
            //         '<%%= yeoman.dist %>/styles/main.css': [
            //             '.tmp/styles/**/*.css',
            //             'styles/**/*.css'
            //         ]
            //     }
            // }
            options: {
                root: 'src/main/webapp' // Replace relative paths for static resources with absolute path
            }
        },
        ngtemplates:    {
            dist: {
                cwd: 'src/main/webapp',
                src: ['scripts/app/**/*.html', 'scripts/components/**/*.html',],
                dest: '.tmp/templates/templates.js',
                options: {
                    module: '<%= angularAppName%>',
                    usemin: 'scripts/app.js',
                    htmlmin:  {
                        removeCommentsFromCDATA: true,
                        // https://github.com/yeoman/grunt-usemin/issues/44
                        collapseWhitespace: true,
                        collapseBooleanAttributes: true,
                        conservativeCollapse: true,
                        removeAttributeQuotes: true,
                        removeRedundantAttributes: true,
                        useShortDoctype: true,
                        removeEmptyAttributes: true
                    }
                }
            }
        },
        htmlmin: {
            dist: {
                options: {
                    removeCommentsFromCDATA: true,
                    // https://github.com/yeoman/grunt-usemin/issues/44
                    collapseWhitespace: true,
                    collapseBooleanAttributes: true,
                    conservativeCollapse: true,
                    removeAttributeQuotes: true,
                    removeRedundantAttributes: true,
                    useShortDoctype: true,
                    removeEmptyAttributes: true,
                    keepClosingSlash: true
                },
                files: [{
                    expand: true,
                    cwd: '<%%= yeoman.dist %>',
                    src: ['*.html'],
                    dest: '<%%= yeoman.dist %>'
                }]
            }
        },
        // Put files not handled in other tasks here
        copy: {
            dist: {
                files: [{
                    expand: true,
                    dot: true,
                    cwd: 'src/main/webapp',
                    dest: '<%%= yeoman.dist %>',
                    src: [
                        '*.html',
                        'scripts/**/*.html',
                        'assets/images/**/*.{png,gif,webp,jpg,jpeg,svg}',
                        'assets/fonts/*'
                    ]
                }, {
                    expand: true,
                    cwd: '.tmp/assets/images',
                    dest: '<%%= yeoman.dist %>/assets/images',
                    src: [
                        'generated/*'
                    ]
                }]
            },
            generateHerokuDirectory: {
                    expand: true,
                    dest: 'deploy/heroku',
                    src: [
                        'pom.xml',
                        'src/main/**'
                ]
            },
            generateOpenshiftDirectory: {
                    expand: true,
                    dest: 'deploy/openshift',
                    src: [
                        'pom.xml',
                        'src/main/**'
                ]
            }
        },
        concurrent: {
            server: [<% if (useCompass) { %>
                'compass:server'<% } %>
            ],
            test: [<% if (useCompass) { %>
                'compass'<% } %>
            ],
            dist: [<% if (useCompass) { %>
                'compass:dist',<% } %>
                'imagemin',
                'svgmin'
            ]
        },
        karma: {
            unit: {
                configFile: 'src/test/javascript/karma.conf.js',
                singleRun: true
            }
        },
        cdnify: {
            dist: {
                html: ['<%%= yeoman.dist %>/*.html']
            }
        },
        ngAnnotate: {
            dist: {
                files: [{
                    expand: true,
                    cwd: '.tmp/concat/scripts',
                    src: '*.js',
                    dest: '.tmp/concat/scripts'
                }]
            }
        },
        buildcontrol: {
            options: {
                commit: true,
                push: false,
                connectCommits: false,
                message: 'Built %sourceName% from commit %sourceCommit% on branch %sourceBranch%'
            },
            heroku: {
                options: {
                    dir: 'deploy/heroku',
                    remote: 'heroku',
                    branch: 'master'
                }
            },
            openshift: {
                options: {
                    dir: 'deploy/openshift',
                    remote: 'openshift',
                    branch: 'master'
                }
            }
        },
        ngconstant: {
            options: {
                name: '<%= angularAppName%>',
                deps: false,
                wrap: '"use strict";\n// DO NOT EDIT THIS FILE, EDIT THE GRUNT TASK NGCONSTANT SETTINGS INSTEAD WHICH GENERATES THIS FILE\n{%= __ngModule %}'
            },
            dev: {
                options: {
                    dest: 'src/main/webapp/scripts/app/app.constants.js',
                },
                constants: {
                    ENV: 'dev',
                    VERSION: <% if(buildTool == 'maven') { %>parseVersionFromPomXml()<% } else { %>parseVersionFromBuildGradle()<% } %>
                }
            },
            prod: {
                options: {
                    dest: '.tmp/scripts/app/app.constants.js',
                },
                constants: {
                    ENV: 'prod',
                    VERSION: <% if(buildTool == 'maven') { %>parseVersionFromPomXml()<% } else { %>parseVersionFromBuildGradle()<% } %>
                }
            }
        }
    });

    grunt.registerTask('serve', [
        'clean:server',
        'wiredep',
        'ngconstant:dev',
        'concurrent:server',
        'browserSync',
        'watch'
    ]);

    grunt.registerTask('server', function (target) {
        grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
        grunt.task.run([target ? ('serve:' + target) : 'serve']);
    });

    grunt.registerTask('test', [
        'clean:server',
        'wiredep:test',
        'ngconstant:dev',
        'concurrent:test',
        'karma'
    ]);

    grunt.registerTask('build', [
        'clean:dist',
        'wiredep:app',
        'ngconstant:prod',
        'useminPrepare',
        'ngtemplates',
        'concurrent:dist',
        'concat',
        'copy:dist',
        'ngAnnotate',
        'cssmin',
        'autoprefixer',
        'uglify',
        'rev',
        'usemin',
        'htmlmin'
    ]);

    grunt.registerTask('buildHeroku', [
        'test',
        'build',
        'copy:generateHerokuDirectory',
    ]);

    grunt.registerTask('deployHeroku', [
        'test',
        'build',
        'copy:generateHerokuDirectory',
        'buildcontrol:heroku'
    ]);

    grunt.registerTask('buildOpenshift', [
        'test',
        'build',
        'copy:generateOpenshiftDirectory',
    ]);

    grunt.registerTask('deployOpenshift', [
        'test',
        'build',
        'copy:generateOpenshiftDirectory',
        'buildcontrol:openshift'
    ]);

    grunt.registerTask('default', [
        'test',
        'build'
    ]);
};
<|endoftext|>