<commit_before>import {inject} from 'aurelia-framework';
import {App} from './app';
import {AuthService} from 'aurelia-auth';
import {HttpClient, json} from 'aurelia-fetch-client';
import { ValidationControllerFactory, ValidationRules, Validator, validateTrigger } from 'aurelia-validation';
//import { ValidationControllerFactory, ValidationRules, Validator } from 'aurelia-validation';
class FormValidator {
  constructor(validator, cb) {
    this.validator = validator;
    this.cb = cb;
  }
  /**
  * This method will be called by the controller when validating a specific field. For instance,
  * when the user is interacting with the title input. So, we need to validate the whole form
  * and call our callback in order for TodoPage.canSave to be correctly updated.
  */
  validateProperty(object, propertyName, rules) {
    let validationDefered = this.validator.validateProperty(object, propertyName, rules);
    validationDefered.then(() => this.validateObject(object, rules));

    return validationDefered;
  }

  /**
  * Each time the whole form is validated, we call the registered callback to do whatever
  * the user wants to do with the results of the validation. In our case: update
  * TodoPage.canSave.
  */
  validateObject(object, rules) {
    return this.validator.validateObject(object, rules).then(results => {
      this.cb(results);
      return results;
    });
  }

  /**
  * Implemented so the interface is complete.
  */
  ruleExists(rules, rule) {
    return this.validator(rules, rule);
  }
}

@inject(AuthService, HttpClient, App, ValidationControllerFactory, Validator)
export class Dashboard {
  controller = null;
  validator = null;
  constructor(auth, httpClient, app, controllerFactory, validator){
    this.app = app;
    this.auth = auth;
    this.httpClient = httpClient;
    this.validator = new FormValidator(validator, results => this.updateCanSubmit(results)); //if the form is valid then set to true
    this.controller = controllerFactory.createForCurrentScope(this.validator);
    this.controller.validateTrigger = validateTrigger.changeOrBlur;
    this.canSubmit = false;  //the button on the form
  }

  selectedValue;
  userTypes=JSON.parse(process.env.userRoles).roles;

  async activate() {
    this.configHttpClient();
    let uid = this.auth.getTokenPayload().sub;
    this.user = await this.app.appState.getUser(uid);
    console.log('this is the user ' + this.user.name);
    this.childRoute();
    this.setupValidation();
  }

  updateCanSubmit(validationResults) {
    let valid = true;
    for (let result of validationResults) {
      valid = valid && result.valid;
    }
    this.canSubmit = valid;
    if (this.user.userType !== '' && this.canSubmit){
      let nub = document.getElementById('newUserButton');
      nub.style.display = 'block';
    }
  }

  dropdownChanged() {
    let nub = document.getElementById('newUserButton');
    if (this.user.userType !== '' && this.canSubmit){
      nub.style.display = 'block';
    } else {
      nub.style.display = 'none';
    }
  }

  configHttpClient(){
    this.backend = '';
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production'){
      this.backend = process.env.BackendUrl;
    }
    this.httpClient.configure(config => {
      config
      .useStandardConfiguration()
      .withBaseUrl(this.backend);
    });
  }

  childRoute(){
    if (this.user.userType === 'Charity'){
      this.app.router.navigate('dashboard/charity');
    } else if (this.user.userType === 'Volunteer'){
      this.app.router.navigate('dashboard/volunteer');
    } else if (this.user.userType === 'Reader'){
      this.app.router.navigate('dashboard/reader');
    } else if (this.user.userType === 'Librarian'){
      this.app.router.navigate('dashboard/librarian');
    } else if (this.user.userType === 'Developer'){
      this.app.router.navigate('dashboard/developer');
    }
  }

  setupValidation() {
    ValidationRules
    .ensure('userPhone').required().minLength(10).withMessage('phone number must be at least 10 digits long.')
    .ensure('userType').required().minLength(5).withMessage('select a user type')
    .on(this.user);
  }

  validate() {
    this.validator.validateObject(this.user);
  }

  async updateUser(){
    let uid = this.auth.getTokenPayload().sub;
    await fetch;
    //this.user.userType = this.selectedValue;
    this.httpClient.fetch('/user/' + uid, {
      method: 'put',
      body: json(this.user)
    })
    .then(response=>response.json())
    .then(data=> {
      this.app.appState.setUser(this.user);
      this.app.appState.checkUserRole();
      this.childRoute();
    });
  }
}
<commit_msg>minor changes. please review<commit_after>import {inject} from 'aurelia-framework';
import {App} from './app';
import {AuthService} from 'aurelia-auth';
import {HttpClient, json} from 'aurelia-fetch-client';
import { ValidationControllerFactory, ValidationRules, Validator, validateTrigger } from 'aurelia-validation';
//import { ValidationControllerFactory, ValidationRules, Validator } from 'aurelia-validation';
class FormValidator {
  constructor(validator, cb) {
    this.validator = validator;
    this.cb = cb;
  }
  /**
  * This method will be called by the controller when validating a specific field. For instance,
  * when the user is interacting with the title input. So, we need to validate the whole form
  * and call our callback in order for TodoPage.canSave to be correctly updated.
  */
  validateProperty(object, propertyName, rules) {
    let validationDefered = this.validator.validateProperty(object, propertyName, rules);
    validationDefered.then(() => this.validateObject(object, rules));

    return validationDefered;
  }

  /**
  * Each time the whole form is validated, we call the registered callback to do whatever
  * the user wants to do with the results of the validation. In our case: update
  * TodoPage.canSave.
  */
  validateObject(object, rules) {
    return this.validator.validateObject(object, rules).then(results => {
      this.cb(results);
      return results;
    });
  }

  /**
  * Implemented so the interface is complete.
  */
  ruleExists(rules, rule) {
    return this.validator(rules, rule);
  }
}

@inject(AuthService, HttpClient, App, ValidationControllerFactory, Validator)
export class Dashboard {
  controller = null;
  validator = null;
  constructor(auth, httpClient, app, controllerFactory, validator){
    this.app = app;
    this.auth = auth;
    this.httpClient = httpClient;
    this.validator = new FormValidator(validator, this.updateCanSubmit); //if the form is valid then set to true. you cannot set callback which is null. the earlier argument is a function that return null. I changed it to this callback.
    this.controller = controllerFactory.createForCurrentScope(this.validator);
    this.controller.validateTrigger = validateTrigger.changeOrBlur;
    this.canSubmit = false;  //the button on the form
  }

  selectedValue;
  userTypes=JSON.parse(process.env.userRoles).roles;

  async activate() {
    this.configHttpClient();
    let uid = this.auth.getTokenPayload().sub;
    this.user = await this.app.appState.getUser(uid);
    console.log('this is the user ' + this.user.name);
    this.childRoute();
    this.setupValidation();
  }

  updateCanSubmit(validationResults) {
    let valid = true;
    for (let result of validationResults) {
      valid = valid && result.valid;
    }
    this.canSubmit = valid;
    console.log(this.user, this.canSubmit);
    if (this.user.userType !== '' && this.canSubmit){
      let nub = document.getElementById('newUserButton');
      nub.style.display = 'block';
    }
  }

  dropdownChanged() {
    let nub = document.getElementById('newUserButton');
    if (this.user.userType !== '' && this.canSubmit){
      nub.style.display = 'block';
    } else {
      nub.style.display = 'none';
    }
  }

  configHttpClient(){
    this.backend = '';
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production'){
      this.backend = process.env.BackendUrl;
    }
    this.httpClient.configure(config => {
      config
      .useStandardConfiguration()
      .withBaseUrl(this.backend);
    });
  }

  childRoute(){
    if (this.user.userType === 'Charity'){
      this.app.router.navigate('dashboard/charity');
    } else if (this.user.userType === 'Volunteer'){
      this.app.router.navigate('dashboard/volunteer');
    } else if (this.user.userType === 'Reader'){
      this.app.router.navigate('dashboard/reader');
    } else if (this.user.userType === 'Librarian'){
      this.app.router.navigate('dashboard/librarian');
    } else if (this.user.userType === 'Developer'){
      this.app.router.navigate('dashboard/developer');
    }
  }

  setupValidation() {
    ValidationRules
    .ensure('userPhone').required().minLength(10).withMessage('phone number must be at least 10 digits long.')
    .ensure('userType').required().minLength(5).withMessage('select a user type')
    .on(this.user);
  }

  validate() {
    this.validator.validateObject(this.user);
  }

  async updateUser(){
    let uid = this.auth.getTokenPayload().sub;
    await fetch;
    //this.user.userType = this.selectedValue;
    this.httpClient.fetch('/user/' + uid, {
      method: 'put',
      body: json(this.user)
    })
    .then(response=>response.json())
    .then(data=> {
      this.app.appState.setUser(this.user);
      this.app.appState.checkUserRole();
      this.childRoute();
    });
  }
}
<|endoftext|>


<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 {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 TypeError('Configuration is missing general.ownerID');
        if (!general.token) throw new TypeError('Configuration is missing general.token');
        if (!general.mainPrefix) throw new TypeError('Configuration is missing general.mainPrefix');
        if (general.maxShards === 0 || !general.maxShards) general.maxShards = 1;

        if (!general.redisURL) throw new TypeError('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>fucking compressionsssssssssssssssss<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 TypeError('Configuration is missing general.ownerID');
        if (!general.token) throw new TypeError('Configuration is missing general.token');
        if (!general.mainPrefix) throw new TypeError('Configuration is missing general.mainPrefix');
        if (!general.maxShards) general.maxShards = 1;

        if (!general.redisURL) throw new TypeError('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>var path = require('path');
var rimraf = require('rimraf');
var util = require('../lib/util');

var counter = 0;
var configCache = {};

function cacheConfig(config, changedFiles, override) {
  ++counter;
  configCache[counter] = {
      config: config,
      changedFiles: changedFiles,
      override: override
  };
  return counter;
}

function pluckConfig(id) {
  if (!configCache.hasOwnProperty(id)) {
    throw new Error('Failed to find id in cache');
  }
  var config = configCache[id];
  delete configCache[id];
  return config;
}

function nullOverride(details, include) {
  include(false);
}

function createTask(grunt) {
  return function(taskName, targetName) {
    var tasks = [];
    var prefix = this.name;
    if (!targetName) {
      if (!grunt.config(taskName)) {
        grunt.fatal('The "' + prefix + '" prefix is not supported for aliases');
        return;
      }
      Object.keys(grunt.config(taskName)).forEach(function(targetName) {
        if (!/^_|^options$/.test(targetName)) {
          tasks.push(prefix + ':' + taskName + ':' + targetName);
        }
      });
      return grunt.task.run(tasks);
    }
    var args = Array.prototype.slice.call(arguments, 2).join(':');
    var options = this.options({
      override: nullOverride,
      cache: path.join(__dirname, '..', '.cache')
    });

    var done = this.async();

    var originalConfig = grunt.config.get([taskName, targetName]);
    var config = grunt.util._.clone(originalConfig);

    /**
     * Special handling for tasks that expect the `files` config to be a string
     * or array of string source paths.
     */
    var srcFiles = true;
    if (typeof config.files === 'string') {
      config.src = [config.files];
      delete config.files;
      srcFiles = false;
    } else if (Array.isArray(config.files) &&
        typeof config.files[0] === 'string') {
      config.src = config.files;
      delete config.files;
      srcFiles = false;
    }

    function override(filePath, include) {
      var details = {
        task: taskName,
        target: targetName,
        path: filePath,
        getExistingHash: function (fp, cb) {
          util.getExistingHash(fp,
            options.cache,
            taskName,
            targetName,
            cb);
        },
        generateFileHash: function (fp, cb) {
          util.generateFileHash(filePath,
            cb);
        }
      };
      options.override(details, include);
    }

    var files = grunt.task.normalizeMultiTaskFiles(config, targetName);
    util.filterFilesByHash(
        files,
        options.cache,
        taskName,
        targetName,
        override,
        function(e, changedFiles) {
          if (e) {
            return done(e);
          } else if (changedFiles.length === 0) {
            grunt.log.writeln('No changed files to process.');
            return done();
          }

          /**
           * If we started out with only src files in the files config,
           * transform the changedFiles array into an array of source files.
           */
          if (!srcFiles) {
            changedFiles = changedFiles.map(function(obj) {
              return obj.src;
            });
          }

          // configure target with only changed files
          config.files = changedFiles;
          delete config.src;
          delete config.dest;
          grunt.config.set([taskName, targetName], config);

          // because we modified the task config, cache the original
          var id = cacheConfig(originalConfig, changedFiles, override);

            // run the task, and attend to postrun tasks
          var qualified = taskName + ':' + targetName;
          var tasks = [
            qualified + (args ? ':' + args : ''),
            'changed-postrun:' + qualified + ':' + id + ':' + options.cache
          ];
          grunt.task.run(tasks);

          done();
        }
    );

  };
}

/** @param {Object} grunt Grunt. */
module.exports = function(grunt) {

  grunt.registerTask(
      'changed', 'Run a task with only those source files that have been ' +
      'modified since the last successful run.', createTask(grunt));

  var internal = 'Internal task.';
  grunt.registerTask(
      'changed-postrun',
      internal,
      function(taskName, targetName, id, cacheDir) {
          var done = this.async();

          var config = pluckConfig(id);
          // if cacheDir includes a ':', grunt will split it among multiple args
          cacheDir = Array.prototype.slice.call(arguments, 3).join(':');

          util.generateFileHashes(
              config.changedFiles,
              cacheDir,
              taskName,
              targetName,
              config.override,
              function (err) {
                  if (err) {
                      throw err;
                  }

                  // reconfigure task with original config
                  grunt.config.set([ taskName, targetName ], config.config);

                  done();
              }
          );

      });

  var clean = 'Remove cached hashes.';
  grunt.registerTask(
      'changed-clean', clean, function(taskName, targetName) {
        var done = this.async();

        /**
         * This intentionally only works with the default cache dir.  If a
         * custom cache dir is provided, it is up to the user to keep it clean.
         */
        var cacheDir = path.join(__dirname, '..', '.cache');
        if (taskName && targetName) {
          cacheDir = path.join(cacheDir, taskName, targetName);
        } else if (taskName) {
          cacheDir = path.join(cacheDir, taskName);
        }
        if (grunt.file.exists(cacheDir)) {
          grunt.log.writeln('Cleaning ' + cacheDir);
          rimraf(cacheDir, done);
        } else {
          done();
        }
      });

};
<commit_msg>fix override generateFileHash (#51)<commit_after>var path = require('path');
var rimraf = require('rimraf');
var util = require('../lib/util');

var counter = 0;
var configCache = {};

function cacheConfig(config, changedFiles, override) {
  ++counter;
  configCache[counter] = {
      config: config,
      changedFiles: changedFiles,
      override: override
  };
  return counter;
}

function pluckConfig(id) {
  if (!configCache.hasOwnProperty(id)) {
    throw new Error('Failed to find id in cache');
  }
  var config = configCache[id];
  delete configCache[id];
  return config;
}

function nullOverride(details, include) {
  include(false);
}

function createTask(grunt) {
  return function(taskName, targetName) {
    var tasks = [];
    var prefix = this.name;
    if (!targetName) {
      if (!grunt.config(taskName)) {
        grunt.fatal('The "' + prefix + '" prefix is not supported for aliases');
        return;
      }
      Object.keys(grunt.config(taskName)).forEach(function(targetName) {
        if (!/^_|^options$/.test(targetName)) {
          tasks.push(prefix + ':' + taskName + ':' + targetName);
        }
      });
      return grunt.task.run(tasks);
    }
    var args = Array.prototype.slice.call(arguments, 2).join(':');
    var options = this.options({
      override: nullOverride,
      cache: path.join(__dirname, '..', '.cache')
    });

    var done = this.async();

    var originalConfig = grunt.config.get([taskName, targetName]);
    var config = grunt.util._.clone(originalConfig);

    /**
     * Special handling for tasks that expect the `files` config to be a string
     * or array of string source paths.
     */
    var srcFiles = true;
    if (typeof config.files === 'string') {
      config.src = [config.files];
      delete config.files;
      srcFiles = false;
    } else if (Array.isArray(config.files) &&
        typeof config.files[0] === 'string') {
      config.src = config.files;
      delete config.files;
      srcFiles = false;
    }

    function override(filePath, include) {
      var details = {
        task: taskName,
        target: targetName,
        path: filePath,
        getExistingHash: function (fp, cb) {
          util.getExistingHash(fp,
            options.cache,
            taskName,
            targetName,
            cb);
        },
        generateFileHash: function (fp, cb) {
          util.generateFileHash(fp,
            cb);
        }
      };
      options.override(details, include);
    }

    var files = grunt.task.normalizeMultiTaskFiles(config, targetName);
    util.filterFilesByHash(
        files,
        options.cache,
        taskName,
        targetName,
        override,
        function(e, changedFiles) {
          if (e) {
            return done(e);
          } else if (changedFiles.length === 0) {
            grunt.log.writeln('No changed files to process.');
            return done();
          }

          /**
           * If we started out with only src files in the files config,
           * transform the changedFiles array into an array of source files.
           */
          if (!srcFiles) {
            changedFiles = changedFiles.map(function(obj) {
              return obj.src;
            });
          }

          // configure target with only changed files
          config.files = changedFiles;
          delete config.src;
          delete config.dest;
          grunt.config.set([taskName, targetName], config);

          // because we modified the task config, cache the original
          var id = cacheConfig(originalConfig, changedFiles, override);

            // run the task, and attend to postrun tasks
          var qualified = taskName + ':' + targetName;
          var tasks = [
            qualified + (args ? ':' + args : ''),
            'changed-postrun:' + qualified + ':' + id + ':' + options.cache
          ];
          grunt.task.run(tasks);

          done();
        }
    );

  };
}

/** @param {Object} grunt Grunt. */
module.exports = function(grunt) {

  grunt.registerTask(
      'changed', 'Run a task with only those source files that have been ' +
      'modified since the last successful run.', createTask(grunt));

  var internal = 'Internal task.';
  grunt.registerTask(
      'changed-postrun',
      internal,
      function(taskName, targetName, id, cacheDir) {
          var done = this.async();

          var config = pluckConfig(id);
          // if cacheDir includes a ':', grunt will split it among multiple args
          cacheDir = Array.prototype.slice.call(arguments, 3).join(':');

          util.generateFileHashes(
              config.changedFiles,
              cacheDir,
              taskName,
              targetName,
              config.override,
              function (err) {
                  if (err) {
                      throw err;
                  }

                  // reconfigure task with original config
                  grunt.config.set([ taskName, targetName ], config.config);

                  done();
              }
          );

      });

  var clean = 'Remove cached hashes.';
  grunt.registerTask(
      'changed-clean', clean, function(taskName, targetName) {
        var done = this.async();

        /**
         * This intentionally only works with the default cache dir.  If a
         * custom cache dir is provided, it is up to the user to keep it clean.
         */
        var cacheDir = path.join(__dirname, '..', '.cache');
        if (taskName && targetName) {
          cacheDir = path.join(cacheDir, taskName, targetName);
        } else if (taskName) {
          cacheDir = path.join(cacheDir, taskName);
        }
        if (grunt.file.exists(cacheDir)) {
          grunt.log.writeln('Cleaning ' + cacheDir);
          rimraf(cacheDir, done);
        } else {
          done();
        }
      });

};
<|endoftext|>


<commit_before>/*
 * grunt-divshot
 * https://github.com/divshot/grunt-divshot
 *
 * Copyright (c) 2013 Divshot
 * Licensed under the MIT license.
 */

'use strict';

module.exports = function(grunt) {
  var path = require('path');
  var _ = require('lodash');
  var chalk = require('chalk');
  var superstaticDefaults = require('superstatic/lib/defaults');
  var environments = [
    'development',
    'staging',
    'production'
  ];

  _.each(environments, function (environment) {
    grunt.registerTask('divshot:push:' + environment, function () {
      push.call(this, environment);
    });
  });

  // Allow promotion up or down.
  grunt.registerTask('divshot:promote', function(src, dest) {
    promote.call(this, src, dest);
  });

  grunt.registerMultiTask('divshot', 'Run Divshot.io locally', function() {
    var createdConfigFile = false;
    var done = this.async();
    var options = this.options({
      port: superstaticDefaults.PORT,
      hostname: superstaticDefaults.HOST,
      root: './',
      clean_urls: false,
      routes: {},
      cache_control: {},
      exclude: [],
      binPath: path.resolve(__dirname, '../node_modules/.bin')
    });

    var config = configFile(this.options());

    // Start the server
    var server = grunt.util.spawn({
      cmd: path.join(config.binPath, 'superstatic'),
      args: [
        '--port', options.port,
        '--host', options.hostname,
        '--config', JSON.stringify(config)
      ]
    }, function (err, result, code) {
      if (err) {
        grunt.fail.fatal(err);
      }
    });

    // Info
    server.stdout.on('data', function (data) {
      grunt.log.write(data);
      process.nextTick(function () {
        if (grunt.file.exists(process.cwd() + '/divshot.json') && createdConfigFile) {
          grunt.file.delete(process.cwd() + '/divshot.json');
        }
      });
    });

    // Errors
    server.stderr.on('data', function (data) {
      process.stderr.write(chalk.red(data.toString()));
    });

    // Quit process?
    if (!options.keepAlive) {
      process.nextTick(done);
    }
  });

  function configFile (options) {
    var config = {};

    if (ssExists()) {
      config = grunt.file.readJSON(process.cwd() + '/superstatic.json');
    }
    if (dioExists()) {
      config = grunt.file.readJSON(process.cwd() + '/divshot.json');
    }

    return _.extend(config, options);
  }

  function dioExists () {
    return grunt.file.exists(process.cwd() + '/divshot.json');
  }

  function ssExists() {
    return grunt.file.exists(process.cwd() + '/superstatic.json');
  }

  function push (env) {
    var done = this.async();
    var config = configFile(this.options());
    var args = ['push', env];

    if (config.token) {
      args = args.concat(['--token', config.token]);
    }

    divshot(args, done, config);
  }

  function promote(src, dest) {
    var done = this.async();
    var config = configFile(this.options());
    var args = ['promote', src, dest];

    if (config.token) {
      args = args.concat(['--token', config.token]);
    }

    divshot(args, done, config);
  }

  function divshot(args, done, config) {
    var push = grunt.util.spawn({
      cmd: path.join(config.binPath, 'divshot'),
      args: args
    }, function(err, result, code) {
      if (err) {
        grunt.fail.fatal(err);
      }
    });

    push.stdout.on('data', function(data) {
      grunt.log.write(data.toString());
    });

    push.stderr.on('data', function(data) {
      process.stderr.write(data.toString());
    });

    push.on('close', done);
  }
};
<commit_msg>Revert "Allow overriding node bin path."<commit_after>/*
 * grunt-divshot
 * https://github.com/divshot/grunt-divshot
 *
 * Copyright (c) 2013 Divshot
 * Licensed under the MIT license.
 */

'use strict';

module.exports = function(grunt) {
  var path = require('path');
  var _ = require('lodash');
  var chalk = require('chalk');
  var superstaticDefaults = require('superstatic/lib/defaults');
  var environments = [
    'development',
    'staging',
    'production'
  ];

  _.each(environments, function (environment) {
    grunt.registerTask('divshot:push:' + environment, function () {
      push.call(this, environment);
    });
  });

  // Allow promotion up or down.
  grunt.registerTask('divshot:promote', function(src, dest) {
    promote.call(this, src, dest);
  });

  grunt.registerMultiTask('divshot', 'Run Divshot.io locally', function() {
    var createdConfigFile = false;
    var done = this.async();
    var options = this.options({
      port: superstaticDefaults.PORT,
      hostname: superstaticDefaults.HOST,
      root: './',
      clean_urls: false,
      routes: {},
      cache_control: {},
      exclude: []
    });

    var config = configFile(this.options());

    // Start the server
    var server = grunt.util.spawn({
      cmd: path.resolve(__dirname, '../node_modules/.bin/superstatic'),
      args: [
        '--port', options.port,
        '--host', options.hostname,
        '--config', JSON.stringify(config)
      ]
    }, function (err, result, code) {
      if (err) {
        grunt.fail.fatal(err);
      }
    });

    // Info
    server.stdout.on('data', function (data) {
      grunt.log.write(data);
      process.nextTick(function () {
        if (grunt.file.exists(process.cwd() + '/divshot.json') && createdConfigFile) {
          grunt.file.delete(process.cwd() + '/divshot.json');
        }
      });
    });

    // Errors
    server.stderr.on('data', function (data) {
      process.stderr.write(chalk.red(data.toString()));
    });

    // Quit process?
    if (!options.keepAlive) {
      process.nextTick(done);
    }
  });

  function configFile (options) {
    var config = {};

    if (ssExists()) {
      config = grunt.file.readJSON(process.cwd() + '/superstatic.json');
    }
    if (dioExists()) {
      config = grunt.file.readJSON(process.cwd() + '/divshot.json');
    }

    return _.extend(config, options);
  }

  function dioExists () {
    return grunt.file.exists(process.cwd() + '/divshot.json');
  }

  function ssExists() {
    return grunt.file.exists(process.cwd() + '/superstatic.json');
  }

  function push (env) {
    var done = this.async();
    var config = configFile(this.options());
    var args = ['push', env];

    if (config.token) {
      args = args.concat(['--token', config.token]);
    }

    divshot(args, done);
  }

  function promote(src, dest) {
    var done = this.async();
    var config = configFile(this.options());
    var args = ['promote', src, dest];

    if (config.token) {
      args = args.concat(['--token', config.token]);
    }

    divshot(args, done);
  }

  function divshot(args, done) {
    var cmd = path.resolve(__dirname, '../node_modules/.bin/divshot');
    var push = grunt.util.spawn({
      cmd: cmd,
      args: args
    }, function(err, result, code) {
      if (err) {
        grunt.fail.fatal(err);
      }
    });

    push.stdout.on('data', function(data) {
      grunt.log.write(data.toString());
    });

    push.stderr.on('data', function(data) {
      process.stderr.write(data.toString());
    });

    push.on('close', done);
  }
};
<|endoftext|>


<commit_before>// Perceptual image hash calculation tool based on algorithm descibed in
// Block Mean Value Based Image Perceptual Hashing by Bian Yang, Fan Gu and Xiamu Niu
//
// Copyright 2014 Commons Machinery http://commonsmachinery.se/
// Distributed under an MIT license, please see LICENSE in the top dir.

(function (root, undef) {

    var median = function(data) {
        var mdarr = data.slice(0);
        mdarr.sort(function(a, b) { return a-b; });
        if (mdarr.length % 2 === 0) {
            return (mdarr[mdarr.length/2] + mdarr[mdarr.length/2 + 1]) / 2.0;
        }
        return mdarr[Math.floor(mdarr.length/2)];
    };

    var method1 = function(data, bits) {
        var blocksize_x = data.width / bits;
        var blocksize_y = data.height / bits;

        var result = [];

        for (var y = 0; y < bits; y++) {
            for (var x = 0; x < bits; x++) {
                var total = 0;

                for (var iy = 0; iy < blocksize_y; iy++) {
                    for (var ix = 0; ix < blocksize_x; ix++) {
                        var cx = x * blocksize_x + ix;
                        var cy = y * blocksize_y + iy;
                        var ii = (cy * data.width + cx) * 4;
                        total += (data.data[ii] + data.data[ii+1] + data.data[ii+2] + data.data[ii+3]) / 4.0;
                    }
                }

                result.push(total);
            }
        }

        var m = median(result);
        for (var i = 0; i < bits * bits; i++) {
            result[i] = result[i] < m ? 0 : 1;
        }

        return result;
    };

    var method2 = function(data, bits) {
        var overlap_x = Math.floor(data.width / (bits + 1));
        var overlap_y = Math.floor(data.height / (bits + 1));
        var blocksize_x = overlap_x * 2;
        var blocksize_y = overlap_y * 2;

        var result = [];

        for (var y = 0; y < bits; y++) {
            for (var x = 0; x < bits; x++) {
                var total = 0;

                for (var iy = 0; iy < blocksize_y; iy++) {
                    for (var ix = 0; ix < blocksize_x; ix++) {
                        var cx = x * overlap_x + ix;
                        var cy = y * overlap_y + iy;
                        var ii = (cy * data.width + cx) * 4;
                        total += (data.data[ii] + data.data[ii+1] + data.data[ii+2] + data.data[ii+3]) / 4.0;
                    }
                }

                result.push(total);
            }
        }

        var m = median(result);
        for (var i = 0; i < bits * bits; i++) {
            result[i] = result[i] < m ? 0 : 1;
        }

        return result;
    };

    var bmvbhash = function(src, bits, method, callback) {
        var xhr;

        xhr = new XMLHttpRequest();
        xhr.open('GET', src, true);
        xhr.responseType = "arraybuffer";

        xhr.onload = function() {
            var data, imgData, jpg, png, hash;
            data = new Uint8Array(xhr.response || xhr.mozResponseArrayBuffer);

            try {
                if (data[0] === 0x89 &&
                    data[1] === 0x50 &&
                    data[2] === 0x4e &&
                    data[3] === 0x47 &&
                    data[4] === 0x0d &&
                    data[5] === 0x0a &&
                    data[6] === 0x1a &&
                    data[7] === 0x0a) {
                    png = new PNG(data);

                    imgData = {
                        width: png.width,
                        height: png.height,
                        data: new Uint8Array(png.width * png.height * 4)
                    };

                    png.copyToImageData(imgData, png.decodePixels());
                }
                else if (data[0] == 0xff &&
                         data[1] == 0xd8) {
                    jpg = new JpegImage();
                    jpg.parse(data);

                    imgData = {
                        width: jpg.width,
                        height: jpg.height,
                        data: new Uint8Array(jpg.width * jpg.height * 4)
                    };

                    jpg.copyToImageData(imgData);
                }

                if (!imgData) {
                    throw new Error("Couldn't decode image");
                }

                // TODO: resize if required

                if (method === 1) {
                    hash = method1(imgData, bits);
                }
                else if (method === 2) {
                    hash = method2(imgData, bits);
                }
                else {
                    throw new Error("Bad hashing method");
                }

                callback(null, hash);
            } catch (err) {
                callback(err, null);
            }
        };

        xhr.onerror = function(err) {
            callback(err, null);
        }

        xhr.send();
    };

    root.bmvbhash = bmvbhash;
})(this);
<commit_msg>Use content-type header to choose image decoder.<commit_after>// Perceptual image hash calculation tool based on algorithm descibed in
// Block Mean Value Based Image Perceptual Hashing by Bian Yang, Fan Gu and Xiamu Niu
//
// Copyright 2014 Commons Machinery http://commonsmachinery.se/
// Distributed under an MIT license, please see LICENSE in the top dir.

(function (root, undef) {

    var median = function(data) {
        var mdarr = data.slice(0);
        mdarr.sort(function(a, b) { return a-b; });
        if (mdarr.length % 2 === 0) {
            return (mdarr[mdarr.length/2] + mdarr[mdarr.length/2 + 1]) / 2.0;
        }
        return mdarr[Math.floor(mdarr.length/2)];
    };

    var method1 = function(data, bits) {
        var blocksize_x = data.width / bits;
        var blocksize_y = data.height / bits;

        var result = [];

        for (var y = 0; y < bits; y++) {
            for (var x = 0; x < bits; x++) {
                var total = 0;

                for (var iy = 0; iy < blocksize_y; iy++) {
                    for (var ix = 0; ix < blocksize_x; ix++) {
                        var cx = x * blocksize_x + ix;
                        var cy = y * blocksize_y + iy;
                        var ii = (cy * data.width + cx) * 4;
                        total += (data.data[ii] + data.data[ii+1] + data.data[ii+2] + data.data[ii+3]) / 4.0;
                    }
                }

                result.push(total);
            }
        }

        var m = median(result);
        for (var i = 0; i < bits * bits; i++) {
            result[i] = result[i] < m ? 0 : 1;
        }

        return result;
    };

    var method2 = function(data, bits) {
        var overlap_x = Math.floor(data.width / (bits + 1));
        var overlap_y = Math.floor(data.height / (bits + 1));
        var blocksize_x = overlap_x * 2;
        var blocksize_y = overlap_y * 2;

        var result = [];

        for (var y = 0; y < bits; y++) {
            for (var x = 0; x < bits; x++) {
                var total = 0;

                for (var iy = 0; iy < blocksize_y; iy++) {
                    for (var ix = 0; ix < blocksize_x; ix++) {
                        var cx = x * overlap_x + ix;
                        var cy = y * overlap_y + iy;
                        var ii = (cy * data.width + cx) * 4;
                        total += (data.data[ii] + data.data[ii+1] + data.data[ii+2] + data.data[ii+3]) / 4.0;
                    }
                }

                result.push(total);
            }
        }

        var m = median(result);
        for (var i = 0; i < bits * bits; i++) {
            result[i] = result[i] < m ? 0 : 1;
        }

        return result;
    };

    var bmvbhash = function(src, bits, method, callback) {
        var xhr;

        xhr = new XMLHttpRequest();
        xhr.open('GET', src, true);
        xhr.responseType = "arraybuffer";

        xhr.onload = function() {
            var data, contentType, imgData, jpg, png, hash;

            data = new Uint8Array(xhr.response || xhr.mozResponseArrayBuffer);
            contentType = xhr.getResponseHeader('content-type');

            try {
                if (contentType === 'image/png') {
                    png = new PNG(data);

                    imgData = {
                        width: png.width,
                        height: png.height,
                        data: new Uint8Array(png.width * png.height * 4)
                    };

                    png.copyToImageData(imgData, png.decodePixels());
                }
                else if (contentType === 'image/jpeg') {
                    jpg = new JpegImage();
                    jpg.parse(data);

                    imgData = {
                        width: jpg.width,
                        height: jpg.height,
                        data: new Uint8Array(jpg.width * jpg.height * 4)
                    };

                    jpg.copyToImageData(imgData);
                }

                if (!imgData) {
                    throw new Error("Couldn't decode image");
                }

                // TODO: resize if required

                if (method === 1) {
                    hash = method1(imgData, bits);
                }
                else if (method === 2) {
                    hash = method2(imgData, bits);
                }
                else {
                    throw new Error("Bad hashing method");
                }

                callback(null, hash);
            } catch (err) {
                callback(err, null);
            }
        };

        xhr.onerror = function(err) {
            callback(err, null);
        }

        xhr.send();
    };

    root.bmvbhash = bmvbhash;
})(this);
<|endoftext|>


<commit_before>// Bookshelf.js 0.6.2
// ---------------

//     (c) 2013 Tim Griesser
//     Bookshelf may be freely distributed under the MIT license.
//     For all details and documentation:
//     http://bookshelfjs.org

// All external libraries needed in this scope.
var _          = require('lodash');
var Knex       = require('knex');

// All local dependencies... These are the main objects that
// need to be augmented in the constructor to work properly.
var SqlModel      = require('./dialects/sql/model').Model;
var SqlCollection = require('./dialects/sql/collection').Collection;
var SqlRelation   = require('./dialects/sql/relation').Relation;

// Finally, the `Events`, which we've supplemented with a `triggerThen`
// method to allow for asynchronous event handling via promises. We also
// mix this into the prototypes of the main objects in the library.
var Events        = require('./dialects/base/events').Events;

// Constructor for a new `Bookshelf` object, it accepts
// an active `knex` instance and initializes the appropriate
// `Model` and `Collection` constructors for use in the current instance.
var Bookshelf = function(knex) {

  // Allows you to construct the library with either `Bookshelf(opts)`
  // or `new Bookshelf(opts)`.
  if (!(this instanceof Bookshelf)) {
    return new Bookshelf(knex);
  }

  // If the knex isn't a `Knex` instance, we'll assume it's
  // a compatible config object and pass it through to create a new instance.
  if (!knex.client || !(knex.client instanceof Knex.ClientBase)) {
    knex = new Knex(knex);
  }

  // The `Model` constructor is referenced as a property on the `Bookshelf` instance,
  // mixing in the correct `builder` method, as well as the `relation` method,
  // passing in the correct `Model` & `Collection` constructors for later reference.
  var ModelCtor = this.Model = SqlModel.extend({
    _builder: function(tableName) {
      return knex(tableName);
    },
    _relation: function(type, Target, options) {
      return new Relation(type, Target, options);
    }
  });

  // The collection also references the correct `Model`, specified above, for creating
  // new `Model` instances in the collection. We also extend with the correct builder /
  // `knex` combo.
  var CollectionCtor = this.Collection = SqlCollection.extend({
    model: ModelCtor,
    _builder: function(tableName) {
      return knex(tableName);
    }
  });

  // Used internally, the `Relation` helps in simplifying the relationship building,
  // centralizing all logic dealing with type & option handling.
  var Relation = Bookshelf.Relation = SqlRelation.extend({
    Model: ModelCtor,
    Collection: CollectionCtor
  });

  // Grab a reference to the `knex` instance passed (or created) in this constructor,
  // for convenience.
  this.knex = knex;
};

// A `Bookshelf` instance may be used as a top-level pub-sub bus, as it mixes in the
// `Events` object. It also contains the version number, and a `Transaction` method
// referencing the correct version of `knex` passed into the object.
_.extend(Bookshelf.prototype, Events, {

  // Keep in sync with `package.json`.
  VERSION: '0.6.2',

  // Helper method to wrap a series of Bookshelf actions in a `knex` transaction block;
  transaction: function() {
    return this.knex.transaction.apply(this, arguments);
  },

  // Provides a nice, tested, standardized way of adding plugins to a `Bookshelf` instance,
  // injecting the current instance into the plugin, which should be a module.exports.
  plugin: function(plugin) {
    if (_.isString(plugin)) {
      plugin = require('./plugins/' + plugin);
    } else if (_.isArray(plugin)) {
      _.each(plugin, this.plugin, this);
    } else {
      plugin(this);
    }
    return this;
  }

});

// Alias to `new Bookshelf(opts)`.
Bookshelf.initialize = function(knex) {
  return new this(knex);
};

// The `forge` function properly instantiates a new Model or Collection
// without needing the `new` operator... to make object creation cleaner
// and more chainable.
SqlModel.forge = SqlCollection.forge = function() {
  var inst = Object.create(this.prototype);
  var obj = this.apply(inst, arguments);
  return (Object(obj) === obj ? obj : inst);
};

// Finally, export `Bookshelf` to the world.
module.exports = Bookshelf;
<commit_msg>Fix for plugins to actually be executed<commit_after>// Bookshelf.js 0.6.2
// ---------------

//     (c) 2013 Tim Griesser
//     Bookshelf may be freely distributed under the MIT license.
//     For all details and documentation:
//     http://bookshelfjs.org

// All external libraries needed in this scope.
var _          = require('lodash');
var Knex       = require('knex');

// All local dependencies... These are the main objects that
// need to be augmented in the constructor to work properly.
var SqlModel      = require('./dialects/sql/model').Model;
var SqlCollection = require('./dialects/sql/collection').Collection;
var SqlRelation   = require('./dialects/sql/relation').Relation;

// Finally, the `Events`, which we've supplemented with a `triggerThen`
// method to allow for asynchronous event handling via promises. We also
// mix this into the prototypes of the main objects in the library.
var Events        = require('./dialects/base/events').Events;

// Constructor for a new `Bookshelf` object, it accepts
// an active `knex` instance and initializes the appropriate
// `Model` and `Collection` constructors for use in the current instance.
var Bookshelf = function(knex) {

  // Allows you to construct the library with either `Bookshelf(opts)`
  // or `new Bookshelf(opts)`.
  if (!(this instanceof Bookshelf)) {
    return new Bookshelf(knex);
  }

  // If the knex isn't a `Knex` instance, we'll assume it's
  // a compatible config object and pass it through to create a new instance.
  if (!knex.client || !(knex.client instanceof Knex.ClientBase)) {
    knex = new Knex(knex);
  }

  // The `Model` constructor is referenced as a property on the `Bookshelf` instance,
  // mixing in the correct `builder` method, as well as the `relation` method,
  // passing in the correct `Model` & `Collection` constructors for later reference.
  var ModelCtor = this.Model = SqlModel.extend({
    _builder: function(tableName) {
      return knex(tableName);
    },
    _relation: function(type, Target, options) {
      return new Relation(type, Target, options);
    }
  });

  // The collection also references the correct `Model`, specified above, for creating
  // new `Model` instances in the collection. We also extend with the correct builder /
  // `knex` combo.
  var CollectionCtor = this.Collection = SqlCollection.extend({
    model: ModelCtor,
    _builder: function(tableName) {
      return knex(tableName);
    }
  });

  // Used internally, the `Relation` helps in simplifying the relationship building,
  // centralizing all logic dealing with type & option handling.
  var Relation = Bookshelf.Relation = SqlRelation.extend({
    Model: ModelCtor,
    Collection: CollectionCtor
  });

  // Grab a reference to the `knex` instance passed (or created) in this constructor,
  // for convenience.
  this.knex = knex;
};

// A `Bookshelf` instance may be used as a top-level pub-sub bus, as it mixes in the
// `Events` object. It also contains the version number, and a `Transaction` method
// referencing the correct version of `knex` passed into the object.
_.extend(Bookshelf.prototype, Events, {

  // Keep in sync with `package.json`.
  VERSION: '0.6.2',

  // Helper method to wrap a series of Bookshelf actions in a `knex` transaction block;
  transaction: function() {
    return this.knex.transaction.apply(this, arguments);
  },

  // Provides a nice, tested, standardized way of adding plugins to a `Bookshelf` instance,
  // injecting the current instance into the plugin, which should be a module.exports.
  plugin: function(plugin) {
    if (_.isString(plugin)) {
      require('./plugins/' + plugin)(this);
    } else if (_.isArray(plugin)) {
      _.each(plugin, this.plugin, this);
    } else {
      plugin(this);
    }
    return this;
  }

});

// Alias to `new Bookshelf(opts)`.
Bookshelf.initialize = function(knex) {
  return new this(knex);
};

// The `forge` function properly instantiates a new Model or Collection
// without needing the `new` operator... to make object creation cleaner
// and more chainable.
SqlModel.forge = SqlCollection.forge = function() {
  var inst = Object.create(this.prototype);
  var obj = this.apply(inst, arguments);
  return (Object(obj) === obj ? obj : inst);
};

// Finally, export `Bookshelf` to the world.
module.exports = Bookshelf;
<|endoftext|>


<commit_before>define(['smplAssert/assert', 'smplTpl/smpl.tpl', 'smplTplWL/smpl.tpl.WidgetLib'], function(assert, smpl, smplWL) {
	suite('smpl.tpl', function() {
		test('dependencies', function() {
			assert.equals(Object.keys(smpl), ['string', 'utils', 'dom', 'tpl']);
		});

		test('base', function() {
			var tpl = new smpl.tpl.Template('base');
			tpl.init('this is a test template');
			tpl.parse();
			assert.equals(tpl.retrieve(), 'this is a test template');
		});

		test('variables', function() {
			var tpl = new smpl.tpl.Template('variables');
			tpl.onParse = function (a, b) {
				this.set('a', a);
				this.set('b', {b: b});
				this.set('c', {});
			};
			tpl.init('{$a} {$b.b}{$c.c}{$d}');
			tpl.parse('this is a', 'test template');
			assert.equals(tpl.retrieve(), 'this is a test template');
		});

		test('blocks', function() {
			var tpl = new smpl.tpl.Template();
			tpl.onParse = function () {
				this.set('a', 1);
				this.set('a', 'a', 2);
				this.parseBlock('a');
				this.parseBlock('a');
			};
			tpl.init('{$a} block:{$a}; not parsed{$a}');
			tpl.parse();
			assert.equals(tpl.retrieve(), '1 block:2; block:1; 1');
		});

		test('subblocks', function() {
			var tpl = new smpl.tpl.Template();
			tpl.onParse = function () {
				this.parseBlock('b');
				this.parseBlock('b');
				this.parseBlock('a');
			};
			tpl.init('mabam');
			tpl.parse();
			assert.equals(tpl.retrieve(), 'mabbam');
		});

		test('non-existing block', function() {
			var tpl = new smpl.tpl.Template('non-existing block');
			tpl.onParse = function () {
				this.parseBlock('a');
			};
			tpl.init('testb');

			var e = assert.throws(tpl.parse.bind(tpl), Error);
			assert.equals(e.message, 'Template <non-existing block>: tried to parse non-existing block .');
		});

		suite('widgets', function() {
			var smpl = smplWL;
			suiteSetup(function() {
				var wl = new smpl.tpl.WidgetLib('test');
				var Section = function() {};
				Section.prototype.getHTML = function(config, content) {
					this.config = config;
					var html = '
' + content + '
';
					return html;
				};
				Section.prototype.sayHello = function() {
					return 'hello from ' + this.config.name;
				};
				wl.registerWidget('section', Section);
			});
			test('widget', function() {
				var tpl = new smpl.tpl.Template('widget');
				tpl.onParse = function () {
					this.w = this.parseWidget('a', {
						name: 'test'
					});
				};
				tpl.init('mbm');

				tpl.parse();
				assert.equals(tpl.retrieve(), 'm
b
m');
			});
			test('autoclose', function() {
				var tpl = new smpl.tpl.Template('autoclose');
				tpl.onParse = function () {
					this.parseWidget('a', {
						name: 'test'
					});
				};
				tpl.init('mm');

				tpl.parse();
				assert.equals(tpl.retrieve(), 'm

m');
			});

			test('arguments', function() {
				var tpl = new smpl.tpl.Template('autoclose');
				tpl.onParse = function () {
					this.parseWidget('a');
				};
				tpl.init('mm');

				tpl.parse();
				assert.equals(tpl.retrieve(), 'm

m');
			});
		});
	});
});
<commit_msg>Fix unit test<commit_after>define(['smplAssert/assert', 'smplTpl/smpl.tpl', 'smplTplWL/smpl.tpl.WidgetLib'], function(assert, smpl, smplWL) {
	suite('smpl.tpl', function() {
		test('dependencies', function() {
			assert.equals(Object.keys(smpl).sort(), ['string', 'utils', 'dom', 'tpl'].sort());
		});

		test('base', function() {
			var tpl = new smpl.tpl.Template('base');
			tpl.init('this is a test template');
			tpl.parse();
			assert.equals(tpl.retrieve(), 'this is a test template');
		});

		test('variables', function() {
			var tpl = new smpl.tpl.Template('variables');
			tpl.onParse = function (a, b) {
				this.set('a', a);
				this.set('b', {b: b});
				this.set('c', {});
			};
			tpl.init('{$a} {$b.b}{$c.c}{$d}');
			tpl.parse('this is a', 'test template');
			assert.equals(tpl.retrieve(), 'this is a test template');
		});

		test('blocks', function() {
			var tpl = new smpl.tpl.Template();
			tpl.onParse = function () {
				this.set('a', 1);
				this.set('a', 'a', 2);
				this.parseBlock('a');
				this.parseBlock('a');
			};
			tpl.init('{$a} block:{$a}; not parsed{$a}');
			tpl.parse();
			assert.equals(tpl.retrieve(), '1 block:2; block:1; 1');
		});

		test('subblocks', function() {
			var tpl = new smpl.tpl.Template();
			tpl.onParse = function () {
				this.parseBlock('b');
				this.parseBlock('b');
				this.parseBlock('a');
			};
			tpl.init('mabam');
			tpl.parse();
			assert.equals(tpl.retrieve(), 'mabbam');
		});

		test('non-existing block', function() {
			var tpl = new smpl.tpl.Template('non-existing block');
			tpl.onParse = function () {
				this.parseBlock('a');
			};
			tpl.init('testb');

			var e = assert.throws(tpl.parse.bind(tpl), Error);
			assert.equals(e.message, 'Template <non-existing block>: tried to parse non-existing block .');
		});

		suite('widgets', function() {
			var smpl = smplWL;
			suiteSetup(function() {
				var wl = new smpl.tpl.WidgetLib('test');
				var Section = function() {};
				Section.prototype.getHTML = function(config, content) {
					this.config = config;
					var html = '
' + content + '
';
					return html;
				};
				Section.prototype.sayHello = function() {
					return 'hello from ' + this.config.name;
				};
				wl.registerWidget('section', Section);
			});
			test('widget', function() {
				var tpl = new smpl.tpl.Template('widget');
				tpl.onParse = function () {
					this.w = this.parseWidget('a', {
						name: 'test'
					});
				};
				tpl.init('mbm');

				tpl.parse();
				assert.equals(tpl.retrieve(), 'm
b
m');
			});
			test('autoclose', function() {
				var tpl = new smpl.tpl.Template('autoclose');
				tpl.onParse = function () {
					this.parseWidget('a', {
						name: 'test'
					});
				};
				tpl.init('mm');

				tpl.parse();
				assert.equals(tpl.retrieve(), 'm

m');
			});

			test('arguments', function() {
				var tpl = new smpl.tpl.Template('autoclose');
				tpl.onParse = function () {
					this.parseWidget('a');
				};
				tpl.init('mm');

				tpl.parse();
				assert.equals(tpl.retrieve(), 'm

m');
			});
		});
	});
});
<|endoftext|>


<commit_before>var DATE_FORMAT = 'YYYY-MM-DD';

describe('Adding weekdays', function() {
  describe('starting at Sunday', function() {
    beforeEach(function() {
      this.start = moment.utc('2015-03-01', DATE_FORMAT);
    });

    _.each(fixtures.add.sunday, function(solution, addition) {
      it('should calculate the correct number of workdays between every two dates; ' + addition, function() {
        this.solution = moment(solution, DATE_FORMAT);
        this.calculated = moment(this.start).addWorkdays(addition);
        expect(this.calculated.isSame(this.solution, 'day')).to.be.true;
      });
    }, this);
  });

  describe('starting at Monday', function() {
    beforeEach(function() {
      this.start = moment.utc('2015-03-02', DATE_FORMAT);
    });

    _.each(fixtures.add.monday, function(solution, addition) {
      it('should calculate the correct number of workdays between every two dates; ' + addition, function() {
        this.solution = moment(solution, DATE_FORMAT);
        this.calculated = moment(this.start).addWorkdays(addition);
        console.log(this.start.format('MM-DD'), this.solution.format('MM-DD'), this.calculated.format('MM-DD'));
        expect(this.calculated.isSame(this.solution, 'day')).to.be.true;
      });
    }, this);
  });

  describe('starting at Tuesday', function() {
    beforeEach(function() {
      this.start = moment.utc('2015-03-03', DATE_FORMAT);
    });

    _.each(fixtures.add.tuesday, function(solution, addition) {
      it('should calculate the correct number of workdays between every two dates; ' + addition, function() {
        this.solution = moment(solution, DATE_FORMAT);
        this.calculated = moment(this.start).addWorkdays(addition);
        expect(this.calculated.isSame(this.solution, 'day')).to.be.true;
      });
    }, this);
  });

  describe('starting at Wednesday', function() {
    beforeEach(function() {
      this.start = moment.utc('2015-03-04', DATE_FORMAT);
    });

    _.each(fixtures.add.wednesday, function(solution, addition) {
      it('should calculate the correct number of workdays between every two dates; ' + addition, function() {
        this.solution = moment(solution, DATE_FORMAT);
        this.calculated = moment(this.start).addWorkdays(addition);
        expect(this.calculated.isSame(this.solution, 'day')).to.be.true;
      });
    }, this);
  });

  describe('starting at Thursday', function() {
    beforeEach(function() {
      this.start = moment.utc('2015-03-05', DATE_FORMAT);
    });

    _.each(fixtures.add.thursday, function(solution, addition) {
      it('should calculate the correct number of workdays between every two dates; ' + addition, function() {
        this.solution = moment(solution, DATE_FORMAT);
        this.calculated = moment(this.start).addWorkdays(addition);
        expect(this.calculated.isSame(this.solution, 'day')).to.be.true;
      });
    }, this);
  });

  describe('starting at Friday', function() {
    beforeEach(function() {
      this.start = moment.utc('2015-03-06', DATE_FORMAT);
    });

    _.each(fixtures.add.friday, function(solution, addition) {
      it('should calculate the correct number of workdays between every two dates; ' + addition, function() {
        this.solution = moment(solution, DATE_FORMAT);
        this.calculated = moment(this.start).addWorkdays(addition);
        expect(this.calculated.isSame(this.solution, 'day')).to.be.true;
      });
    }, this);
  });

  describe('starting at Saturday', function() {
    beforeEach(function() {
      this.start = moment.utc('2015-03-07', DATE_FORMAT);
    });

    _.each(fixtures.add.saturday, function(solution, addition) {
      it('should calculate the correct number of workdays between every two dates; ' + addition, function() {
        this.solution = moment(solution, DATE_FORMAT);
        this.calculated = moment(this.start).addWorkdays(addition);
        expect(this.calculated.isSame(this.solution, 'day')).to.be.true;
      });
    }, this);
  });
});
<commit_msg>Add tests for subtracting negative.<commit_after>var DATE_FORMAT = 'YYYY-MM-DD';

describe('Adding weekdays', function() {
  describe('starting at Sunday', function() {
    beforeEach(function() {
      this.start = moment.utc('2015-03-01', DATE_FORMAT);
    });

    _.each(fixtures.add.sunday, function(solution, addition) {
      it('when adding, should calculate the correct number of workdays between every two dates; ' + addition, function() {
        this.solution = moment(solution, DATE_FORMAT);
        this.calculated = moment(this.start).addWorkdays(addition);
        expect(this.calculated.isSame(this.solution, 'day')).to.be.true;
      });
    }, this);

    _.each(fixtures.add.sunday, function(solution, addition) {
      it('when subtracting negative, should calculate the correct number of workdays between every two dates; ' + addition, function() {
        this.solution = moment(solution, DATE_FORMAT);
        this.calculated = moment(this.start).subtractWorkdays(-addition);
        expect(this.calculated.isSame(this.solution, 'day')).to.be.true;
      });
    }, this);
  });

  describe('starting at Monday', function() {
    beforeEach(function() {
      this.start = moment.utc('2015-03-02', DATE_FORMAT);
    });

    _.each(fixtures.add.monday, function(solution, addition) {
      it('when adding, should calculate the correct number of workdays between every two dates; ' + addition, function() {
        this.solution = moment(solution, DATE_FORMAT);
        this.calculated = moment(this.start).addWorkdays(addition);
        console.log(this.start.format('MM-DD'), this.solution.format('MM-DD'), this.calculated.format('MM-DD'));
        expect(this.calculated.isSame(this.solution, 'day')).to.be.true;
      });
    }, this);

    _.each(fixtures.add.monday, function(solution, addition) {
      it('when subtracting negative, should calculate the correct number of workdays between every two dates; ' + addition, function() {
        this.solution = moment(solution, DATE_FORMAT);
        this.calculated = moment(this.start).subtractWorkdays(-addition);
        console.log(this.start.format('MM-DD'), this.solution.format('MM-DD'), this.calculated.format('MM-DD'));
        expect(this.calculated.isSame(this.solution, 'day')).to.be.true;
      });
    }, this);
  });

  describe('starting at Tuesday', function() {
    beforeEach(function() {
      this.start = moment.utc('2015-03-03', DATE_FORMAT);
    });

    _.each(fixtures.add.tuesday, function(solution, addition) {
      it('when adding, should calculate the correct number of workdays between every two dates; ' + addition, function() {
        this.solution = moment(solution, DATE_FORMAT);
        this.calculated = moment(this.start).addWorkdays(addition);
        expect(this.calculated.isSame(this.solution, 'day')).to.be.true;
      });
    }, this);

    _.each(fixtures.add.tuesday, function(solution, addition) {
      it('when subtracting negative, should calculate the correct number of workdays between every two dates; ' + addition, function() {
        this.solution = moment(solution, DATE_FORMAT);
        this.calculated = moment(this.start).subtractWorkdays(-addition);
        expect(this.calculated.isSame(this.solution, 'day')).to.be.true;
      });
    }, this);
  });

  describe('starting at Wednesday', function() {
    beforeEach(function() {
      this.start = moment.utc('2015-03-04', DATE_FORMAT);
    });

    _.each(fixtures.add.wednesday, function(solution, addition) {
      it('when adding, should calculate the correct number of workdays between every two dates; ' + addition, function() {
        this.solution = moment(solution, DATE_FORMAT);
        this.calculated = moment(this.start).addWorkdays(addition);
        expect(this.calculated.isSame(this.solution, 'day')).to.be.true;
      });
    }, this);

    _.each(fixtures.add.wednesday, function(solution, addition) {
      it('when subtracting negative, should calculate the correct number of workdays between every two dates; ' + addition, function() {
        this.solution = moment(solution, DATE_FORMAT);
        this.calculated = moment(this.start).subtractWorkdays(-addition);
        expect(this.calculated.isSame(this.solution, 'day')).to.be.true;
      });
    }, this);
  });

  describe('starting at Thursday', function() {
    beforeEach(function() {
      this.start = moment.utc('2015-03-05', DATE_FORMAT);
    });

    _.each(fixtures.add.thursday, function(solution, addition) {
      it('when adding, should calculate the correct number of workdays between every two dates; ' + addition, function() {
        this.solution = moment(solution, DATE_FORMAT);
        this.calculated = moment(this.start).addWorkdays(addition);
        expect(this.calculated.isSame(this.solution, 'day')).to.be.true;
      });
    }, this);

    _.each(fixtures.add.thursday, function(solution, addition) {
      it('when subtracting negative, should calculate the correct number of workdays between every two dates; ' + addition, function() {
        this.solution = moment(solution, DATE_FORMAT);
        this.calculated = moment(this.start).subtractWorkdays(-addition);
        expect(this.calculated.isSame(this.solution, 'day')).to.be.true;
      });
    }, this);
  });

  describe('starting at Friday', function() {
    beforeEach(function() {
      this.start = moment.utc('2015-03-06', DATE_FORMAT);
    });

    _.each(fixtures.add.friday, function(solution, addition) {
      it('when adding, should calculate the correct number of workdays between every two dates; ' + addition, function() {
        this.solution = moment(solution, DATE_FORMAT);
        this.calculated = moment(this.start).addWorkdays(addition);
        expect(this.calculated.isSame(this.solution, 'day')).to.be.true;
      });
    }, this);

    _.each(fixtures.add.friday, function(solution, addition) {
      it('when subtracting negative, should calculate the correct number of workdays between every two dates; ' + addition, function() {
        this.solution = moment(solution, DATE_FORMAT);
        this.calculated = moment(this.start).subtractWorkdays(-addition);
        expect(this.calculated.isSame(this.solution, 'day')).to.be.true;
      });
    }, this);
  });

  describe('starting at Saturday', function() {
    beforeEach(function() {
      this.start = moment.utc('2015-03-07', DATE_FORMAT);
    });

    _.each(fixtures.add.saturday, function(solution, addition) {
      it('when adding, should calculate the correct number of workdays between every two dates; ' + addition, function() {
        this.solution = moment(solution, DATE_FORMAT);
        this.calculated = moment(this.start).addWorkdays(addition);
        expect(this.calculated.isSame(this.solution, 'day')).to.be.true;
      });
    }, this);

    _.each(fixtures.add.saturday, function(solution, addition) {
      it('when subtracting negative, should calculate the correct number of workdays between every two dates; ' + addition, function() {
        this.solution = moment(solution, DATE_FORMAT);
        this.calculated = moment(this.start).subtractWorkdays(-addition);
        expect(this.calculated.isSame(this.solution, 'day')).to.be.true;
      });
    }, this);
  });
});
<|endoftext|>