<commit_before>var net = require("net"),
    crypto = require("crypto"),
    tlslib = require("tls");

module.exports = runClientMockup;

/**
 *
Runs a batch of commands against a server

 *
 *

 * var cmds = ["EHLO FOOBAR", "STARTTLS", "QUIT"];
 * runClientMockup(25, "mail.hot.ee", cmds, function(resp){
 *     console.log("Final:", resp.toString("utf-8").trim());
 * });
 *

 *
 * @param {Number} port Port number
 * @param {String} host Hostname to connect to
 * @param {Array} commands Command list to be sent to server
 * @param {Function} callback Callback function to run on completion,
 *        has the last response from the server as a param
 * @param {Boolean} [debug] if set to true log all input/output
 */
function runClientMockup(port, host, commands, callback, debug){
    host = host || "localhost";
    port = port || 25;
    commands = Array.isArray(commands) ? commands : [];

    var command, ignore_data = false, sslcontext, pair;

    var socket = net.connect(port, host, function(){
        socket.on("data", function(chunk){
            if(ignore_data)return;

            if(debug){
                console.log("S: "+chunk.toString("utf-8").trim());
            }

            if(!commands.length){
                socket.end();
                if(typeof callback == "function"){
                    callback(chunk);
                }
                return;
            }

            if((command || "").trim().toUpperCase() == "STARTTLS"){
                ignore_data = true;
                if(debug){
                    console.log("Initiated TLS connection");
                }
                sslcontext = crypto.createCredentials();
                pair = tlslib.createSecurePair(sslcontext, false);

                pair.encrypted.pipe(socket);
                socket.pipe(pair.encrypted);
                pair.fd = socket.fd;

                pair.on("secure", function(){
                    if(debug){
                        console.log("TLS connection secured");
                    }
                    command = commands.shift();
                    if(debug){
                        console.log("C: "+command);
                    }
                    pair.cleartext.write(command+"\r\n");

                    pair.cleartext.on("data", function(chunk){
                        if(debug){
                            console.log("S: "+chunk.toString("utf-8").trim());
                        }

                        if(!commands.length){
                            pair.cleartext.end();
                            if(typeof callback == "function"){
                                callback(chunk);
                            }
                            return;
                        }
                        command = commands.shift();
                        socket.write(command+"\r\n");
                        if(debug){
                            console.log("C: "+command);
                        }
                    });
                });
            }else{
                command = commands.shift();
                socket.write(command+"\r\n");
                if(debug){
                    console.log("C: "+command);
                }
            }
        });
    });

}<commit_msg>fixed mockup client bug<commit_after>var net = require("net"),
    crypto = require("crypto"),
    tlslib = require("tls");

module.exports = runClientMockup;

/**
 *
Runs a batch of commands against a server

 *
 *

 * var cmds = ["EHLO FOOBAR", "STARTTLS", "QUIT"];
 * runClientMockup(25, "mail.hot.ee", cmds, function(resp){
 *     console.log("Final:", resp.toString("utf-8").trim());
 * });
 *

 *
 * @param {Number} port Port number
 * @param {String} host Hostname to connect to
 * @param {Array} commands Command list to be sent to server
 * @param {Function} callback Callback function to run on completion,
 *        has the last response from the server as a param
 * @param {Boolean} [debug] if set to true log all input/output
 */
function runClientMockup(port, host, commands, callback, debug){
    host = host || "localhost";
    port = port || 25;
    commands = Array.isArray(commands) ? commands : [];

    var command, ignore_data = false, sslcontext, pair;

    var socket = net.connect(port, host, function(){
        socket.on("data", function(chunk){
            if(ignore_data)return;

            if(debug){
                console.log("S: "+chunk.toString("utf-8").trim());
            }

            if(!commands.length){
                socket.end();
                if(typeof callback == "function"){
                    callback(chunk);
                }
                return;
            }

            if((command || "").trim().toUpperCase() == "STARTTLS"){
                ignore_data = true;
                if(debug){
                    console.log("Initiated TLS connection");
                }
                sslcontext = crypto.createCredentials();
                pair = tlslib.createSecurePair(sslcontext, false);

                pair.encrypted.pipe(socket);
                socket.pipe(pair.encrypted);
                pair.fd = socket.fd;

                pair.on("secure", function(){
                    if(debug){
                        console.log("TLS connection secured");
                    }
                    command = commands.shift();
                    if(debug){
                        console.log("C: "+command);
                    }
                    pair.cleartext.write(command+"\r\n");

                    pair.cleartext.on("data", function(chunk){
                        if(debug){
                            console.log("S: "+chunk.toString("utf-8").trim());
                        }

                        if(!commands.length){
                            pair.cleartext.end();
                            if(typeof callback == "function"){
                                callback(chunk);
                            }
                            return;
                        }
                        command = commands.shift();
                        pair.cleartext.write(command+"\r\n");
                        if(debug){
                            console.log("C: "+command);
                        }
                    });
                });
            }else{
                command = commands.shift();
                socket.write(command+"\r\n");
                if(debug){
                    console.log("C: "+command);
                }
            }
        });
    });

}<|endoftext|>


<commit_before>// nodejs event emitter
var EventEmitter = require('events').EventEmitter;

// create a composition cache
var moduleInstanceCache = engine.cache.comp('moduleInstances', factory);

// module instance cache
engine.modules = {};

// listen to the core load module instance configuration event
engine.on('load', load);

// export check access method
exports.access = checkAccess;

// check if role has access to instance and operation
function checkAccess (role, module_instance, event) {

    // get instance with role check
    module_instance = moduleInstanceCache.get(module_instance, role);

    // check if this instance has acces to the requested operation
    if (module_instance && module_instance._access[event]) {
        return instance;
    }

    return;
}

/*
function linkHandler (link) {
    var self = this;

    // listen to event
    self[link['1'] ? 'once' : 'on'](link.event, function (event, data, callback) {
        self = this;

        // emit or call on a different instance
        // TODO check role access!
        if (link.to) {
            self = compInstances.pojo.get(link.to, event.session.role);
            if (!self) {
                return;
            }
        }

        // create an empty data object
        if (!data && (link.data || link.set)) {
            data = {};
        }

        // handle static data
        if (link.data) {

            // create copy
            try {
                var copy = JSON.parse(JSON.stringify(link.data));

                // merge static data in data
                for (var key in copy) {
                    data[key] = copy[key];
                }
            } catch (err) {

                // websocket requests
                if (event.ws) {
                    return callback(err);
                }

                // http requests
                return callback(500, err);
            }
        }

        // add dynamic data to the data object
        if (link.set) {
            for (var prop in link.set) {
                data[prop] = engine.path(link.set[prop], self);
            }
        }

        // handle method call
        if (link.call) {

            if (typeof link.call === 'string') {
                link.call = engine.path(link.call, self) || link.call;
            }

            if (typeof link.call !== 'function') {

                var err = new Error('links: Cannot find module method: ' + link.call);

                // websocket requests
                if (event.ws) {
                    return event.send(err);
                }

                // http requests
                return event.send(500, err);
            }

            // call linked method
            link.call.call(self, event, data, callback);
        }

        // emit event
        if (link.emit) {
            self.emit(link.emit, event, data, callback);
        }
    });

    // ensure clinet send array
    if (!self._client.send) {
        self._client.send = [];
    }

    // add send events to client
    self._client.send.push(link.event);
    self._access[link.event] = true;
}

// set up link configuration
function linkSetup (instance, links, callback) {

    // set up links
    for (var i = 0, link; i < links.length; ++i) {
        linkHandler.call(instance, links[i]);
    }

    callback(null, instance._client);
}
*/

// create instance
function factory (composition, callback) {

    // create new module instance
    var module_instance = new EventEmitter();

    // save composition data on the module instance
    module_instance._name = composition.name;
    module_instance._roles = composition.roles || {};
    module_instance._client = composition.client || {};
    module_instance._config = composition.config || {};

    // create properties
    module_instance._access = {};

    // pass module name to client
    module_instance._client.module = composition.module;

    // pass module instance name to client
    module_instance._client.name = composition.name;

    // attach create link method to module instance
    module_instance.link = engine.link;

    // require the serverside module
    if (composition.main) {

        try {

            // get absolute path from the repo and relative path from the module
            var path = composition.main === '/' ? engine.repo : engine.paths.app_modules + composition.module;
            var require_module = require(path + module.main);

            // extend module instance with module methods
            if (require_module) {
                for (var prop in require_module) {
                    module_instance[prop] = require_module[prop];
                }
            }

        } catch (err) {
            return callback(err);
        }
    }

    // TODO setup flow

    // return the module instance
    callback(null, module_instance);
}

// load instance configuration (ws)
function load (link) {
    var self = this;

    // receive data
    link.data(function (err, module_instance_name) {

        // load entrypoint module instance with hostname
        if (!module_instance_name) {

            module_instance_name = link.socket.upgradeReq.headers.host.split(':')[0].replace(/\./g, '_');

            // get the public version of the entrypoint module instance
            if (!link.role) {
                module_instance_name += '.pub';
            }
        }

        // get the module instance
        moduleInstanceCache.get(module_instance_name, link.role, function (err, module_instance) {

            // handle error
            if (err || !module_instance) {
                return link.end('Module instance "' + module_instance_name + '" ' + err || 'not found.');
            }

            // end link and send data
            link.end(null, module_instance._client);
        });
    });
}
<commit_msg>setup the event flow on a module instance<commit_after>// nodejs event emitter
var EventEmitter = require('events').EventEmitter;

// create a composition cache
var moduleInstanceCache = engine.cache.comp('moduleInstances', factory);

// module instance cache
engine.modules = {};

// listen to the core load module instance configuration event
engine.on('load', load);

// export check access method
exports.access = checkAccess;

// check if role has access to instance and operation
function checkAccess (role, module_instance, event) {

    // get instance with role check
    module_instance = moduleInstanceCache.get(module_instance, role);

    // check if this instance has acces to the requested operation
    if (module_instance && module_instance._access[event]) {
        return instance;
    }

    return;
}

/*
function linkHandler (link) {
    var self = this;

    // listen to event
    self[link['1'] ? 'once' : 'on'](link.event, function (event, data, callback) {
        self = this;

        // emit or call on a different instance
        // TODO check role access!
        if (link.to) {
            self = compInstances.pojo.get(link.to, event.session.role);
            if (!self) {
                return;
            }
        }

        // create an empty data object
        if (!data && (link.data || link.set)) {
            data = {};
        }

        // handle static data
        if (link.data) {

            // create copy
            try {
                var copy = JSON.parse(JSON.stringify(link.data));

                // merge static data in data
                for (var key in copy) {
                    data[key] = copy[key];
                }
            } catch (err) {

                // websocket requests
                if (event.ws) {
                    return callback(err);
                }

                // http requests
                return callback(500, err);
            }
        }

        // add dynamic data to the data object
        if (link.set) {
            for (var prop in link.set) {
                data[prop] = engine.path(link.set[prop], self);
            }
        }

        // handle method call
        if (link.call) {

            if (typeof link.call === 'string') {
                link.call = engine.path(link.call, self) || link.call;
            }

            if (typeof link.call !== 'function') {

                var err = new Error('links: Cannot find module method: ' + link.call);

                // websocket requests
                if (event.ws) {
                    return event.send(err);
                }

                // http requests
                return event.send(500, err);
            }

            // call linked method
            link.call.call(self, event, data, callback);
        }

        // emit event
        if (link.emit) {
            self.emit(link.emit, event, data, callback);
        }
    });

    // ensure clinet send array
    if (!self._client.send) {
        self._client.send = [];
    }

    // add send events to client
    self._client.send.push(link.event);
    self._access[link.event] = true;
}

// set up link configuration
function linkSetup (instance, links, callback) {

    // set up links
    for (var i = 0, link; i < links.length; ++i) {
        linkHandler.call(instance, links[i]);
    }

    callback(null, instance._client);
}
*/

// create instance
function factory (composition, callback) {

    // create new module instance
    var module_instance = new EventEmitter();

    // save composition data on the module instance
    module_instance._name = composition.name;
    module_instance._roles = composition.roles || {};
    module_instance._client = composition.client || {};
    module_instance._config = composition.config || {};

    // create properties
    module_instance._access = {};

    // pass module name to client
    module_instance._client.module = composition.module;

    // pass module instance name to client
    module_instance._client.name = composition.name;

    // attach create link method to module instance
    module_instance.link = engine.link;

    // require the serverside module
    if (composition.main) {

        try {

            // get absolute path from the repo and relative path from the module
            var path = composition.main === '/' ? engine.repo : engine.paths.app_modules + composition.module;
            var require_module = require(path + module.main);

            // extend module instance with module methods
            if (require_module) {
                for (var prop in require_module) {
                    module_instance[prop] = require_module[prop];
                }
            }

        } catch (err) {
            return callback(err);
        }
    }

    // setup the event flow
    if (composition.flow) {
        engine.flow(module_instance, composition.flow);
    }

    // return the module instance
    callback(null, module_instance);
}

// load instance configuration (ws)
function load (link) {
    var self = this;

    // receive data
    link.data(function (err, module_instance_name) {

        // load entrypoint module instance with hostname
        if (!module_instance_name) {

            module_instance_name = link.socket.upgradeReq.headers.host.split(':')[0].replace(/\./g, '_');

            // get the public version of the entrypoint module instance
            if (!link.role) {
                module_instance_name += '.pub';
            }
        }

        // get the module instance
        moduleInstanceCache.get(module_instance_name, link.role, function (err, module_instance) {

            // handle error
            if (err || !module_instance) {
                return link.end('Module instance "' + module_instance_name + '" ' + err || 'not found.');
            }

            // end link and send data
            link.end(null, module_instance._client);
        });
    });
}
<|endoftext|>


<commit_before>'use strict';
const logger = require('winston');
const config = require('../config');

module.exports = {

  guildCreate: (client, guild) => {
    const str = `${client.user.username} joined Discord guild: **${guild.name}** ` +
      `- Owner: **${guild.owner.user.username}** #${guild.owner.user.discriminator}`;
    logger.info(str);
    const owner = client.users.get(config.owner_id);
    if (owner) owner.send(str);
  },

  guildDelete: (client, guild) => {
    const str = `${client.user.username} parted Discord guild: **${guild.name}** ` +
      `-  Owner: **${guild.owner.user.username}** #${guild.owner.user.discriminator}`;
    logger.info(str);
    const owner = client.users.get(config.owner_id);
    if (owner) owner.send(str);
  },

  guildMemberAdd: async (client, member) => {
    const channels = await client.mongo.welcomeChannels.find({ server_id: member.guild.id });
    if (!channels) return;
    logger.debug(`Posting welcome messages in (${channels.length}) registered channels`);
    const embed = new client.Discord.MessageEmbed()
      .setColor([30, 210, 30])
      .setAuthor(`${member.user.username}#${member.user.discriminator} (${member.user.id})`,
        member.user.displayAvatarURL())
      .setFooter('User Joined')
      .setTimestamp();
    channels.forEach(c => {
      const channel = member.guild.channels.get(c.channel_id);
      if (channel) {
        if (!channel.permissionsFor(client.user).has(['SEND_MESSAGES', 'EMBED_LINKS'])) {
          client.utils.ownerError('guildMemberAdd', client, `Channel Permission Error for channel: ${channel.id}`);
          return;
        }
        channel.send({ embed }).catch(logger.error);
      }
    });
  },

  guildMemberRemove: async (client, member) => {
    const channels = await client.mongo.welcomeChannels.find({ server_id: member.guild.id });
    if (!channels) return;
    logger.debug(`Posting part messages in (${channels.length}) registered channels`);
    const embed = new client.Discord.MessageEmbed()
      .setColor([215, 215, 30])
      .setAuthor(`${member.user.username}#${member.user.discriminator} (${member.user.id})`,
        member.user.displayAvatarURL())
      .setFooter('User Left')
      .setTimestamp();
    channels.forEach(c => {
      const channel = member.guild.channels.get(c.channel_id);
      if (channel) {
        if (!channel.permissionsFor(client.user).has(['SEND_MESSAGES', 'EMBED_LINKS'])) {
          client.utils.ownerError('guildMemberAdd', client, `Channel Permission Error for channel: ${channel.id}`);
          return;
        }
        channel.send({ embed }).catch(logger.error);
      }
    });
  },

  messageDeleted: async (client, message) => {
    const channels = await client.mongo.auditChannels.find({ server_id: message.guild.id });
    if (!channels) return;
    logger.debug(`Posting deletion audit messages in (${channels.length}) registered channels`);
    const embed = new client.Discord.MessageEmbed()
      .setAuthor(`${message.author.tag} (${message.author.id})`, message.author.displayAvatarURL())
      .setDescription(message.cleanContent)
      .setFooter(`Deleted: (${message.id})`)
      .setTimestamp();
    channels.forEach(c => {
      const channel = message.guild.channels.get(c.channel_id);
      if (channel) {
        if (!channel.permissionsFor(client.user).has(['SEND_MESSAGES', 'EMBED_LINKS'])) {
          client.utils.ownerError('messageDelete', client, `Channel Permission Error for channel: ${channel.id}`);
          return;
        }
        channel.send({ embed }).catch(logger.error);
      }
    });
  },

  messageUpdated: async (client, oldMessage, newMessage) => {
    return;
    if (oldMessage.author.id === client.user.id) return;
    const channels = await client.mongo.auditChannels.find({ server_id: oldMessage.guild.id });
    if (!channels) return;
    const embed = new client.Discord.MessageEmbed()
      .setAuthor(`${oldMessage.author.tag} (${oldMessage.author.id})`, oldMessage.author.displayAvatarURL())
      .setFooter(`Updated: (${oldMessage.id})`)
      .setTimestamp();
    if (oldMessage.content) embed.addField('Original', oldMessage.cleanContent);
    if (newMessage.content) embed.addField('Updated', newMessage.cleanContent);
    channels.forEach(c => {
      const channel = oldMessage.guild.channels.get(c.channel_id);
      if (channel) {
        if (!channel.permissionsFor(client.user).has(['SEND_MESSAGES', 'EMBED_LINKS'])) {
          client.utils.ownerError('messageUpdate', client, `Channel Permission Error for channel: ${channel.id}`);
          return;
        }
        channel.send({ embed }).catch(logger.error);
      }
    });
  },
};
<commit_msg>channel name in deleted message audit logs<commit_after>'use strict';
const logger = require('winston');
const config = require('../config');

module.exports = {

  guildCreate: (client, guild) => {
    const str = `${client.user.username} joined Discord guild: **${guild.name}** ` +
      `- Owner: **${guild.owner.user.username}** #${guild.owner.user.discriminator}`;
    logger.info(str);
    const owner = client.users.get(config.owner_id);
    if (owner) owner.send(str);
  },

  guildDelete: (client, guild) => {
    const str = `${client.user.username} parted Discord guild: **${guild.name}** ` +
      `-  Owner: **${guild.owner.user.username}** #${guild.owner.user.discriminator}`;
    logger.info(str);
    const owner = client.users.get(config.owner_id);
    if (owner) owner.send(str);
  },

  guildMemberAdd: async (client, member) => {
    const channels = await client.mongo.welcomeChannels.find({ server_id: member.guild.id });
    if (!channels) return;
    logger.debug(`Posting welcome messages in (${channels.length}) registered channels`);
    const embed = new client.Discord.MessageEmbed()
      .setColor([30, 210, 30])
      .setAuthor(`${member.user.username}#${member.user.discriminator} (${member.user.id})`,
        member.user.displayAvatarURL())
      .setFooter('User Joined')
      .setTimestamp();
    channels.forEach(c => {
      const channel = member.guild.channels.get(c.channel_id);
      if (channel) {
        if (!channel.permissionsFor(client.user).has(['SEND_MESSAGES', 'EMBED_LINKS'])) {
          client.utils.ownerError('guildMemberAdd', client, `Channel Permission Error for channel: ${channel.id}`);
          return;
        }
        channel.send({ embed }).catch(logger.error);
      }
    });
  },

  guildMemberRemove: async (client, member) => {
    const channels = await client.mongo.welcomeChannels.find({ server_id: member.guild.id });
    if (!channels) return;
    logger.debug(`Posting part messages in (${channels.length}) registered channels`);
    const embed = new client.Discord.MessageEmbed()
      .setColor([215, 215, 30])
      .setAuthor(`${member.user.username}#${member.user.discriminator} (${member.user.id})`,
        member.user.displayAvatarURL())
      .setFooter('User Left')
      .setTimestamp();
    channels.forEach(c => {
      const channel = member.guild.channels.get(c.channel_id);
      if (channel) {
        if (!channel.permissionsFor(client.user).has(['SEND_MESSAGES', 'EMBED_LINKS'])) {
          client.utils.ownerError('guildMemberAdd', client, `Channel Permission Error for channel: ${channel.id}`);
          return;
        }
        channel.send({ embed }).catch(logger.error);
      }
    });
  },

  messageDeleted: async (client, message) => {
    const channels = await client.mongo.auditChannels.find({ server_id: message.guild.id });
    if (!channels) return;
    logger.debug(`Posting deletion audit messages in (${channels.length}) registered channels`);
    const embed = new client.Discord.MessageEmbed()
      .setAuthor(`${message.author.tag} (${message.author.id})`, message.author.displayAvatarURL())
      .setTitle(`#${message.channel.name} (${message.channel.id})`)
      .setDescription(message.cleanContent)
      .setFooter(`Deleted: (${message.id})`)
      .setTimestamp();
    channels.forEach(c => {
      const channel = message.guild.channels.get(c.channel_id);
      if (channel) {
        if (!channel.permissionsFor(client.user).has(['SEND_MESSAGES', 'EMBED_LINKS'])) {
          client.utils.ownerError('messageDelete', client, `Channel Permission Error for channel: ${channel.id}`);
          return;
        }
        channel.send({ embed }).catch(logger.error);
      }
    });
  },

  messageUpdated: async (client, oldMessage, newMessage) => {
    return;
    if (oldMessage.author.id === client.user.id) return;
    const channels = await client.mongo.auditChannels.find({ server_id: oldMessage.guild.id });
    if (!channels) return;
    const embed = new client.Discord.MessageEmbed()
      .setAuthor(`${oldMessage.author.tag} (${oldMessage.author.id})`, oldMessage.author.displayAvatarURL())
      .setFooter(`Updated: (${oldMessage.id})`)
      .setTimestamp();
    if (oldMessage.content) embed.addField('Original', oldMessage.cleanContent);
    if (newMessage.content) embed.addField('Updated', newMessage.cleanContent);
    channels.forEach(c => {
      const channel = oldMessage.guild.channels.get(c.channel_id);
      if (channel) {
        if (!channel.permissionsFor(client.user).has(['SEND_MESSAGES', 'EMBED_LINKS'])) {
          client.utils.ownerError('messageUpdate', client, `Channel Permission Error for channel: ${channel.id}`);
          return;
        }
        channel.send({ embed }).catch(logger.error);
      }
    });
  },
};
<|endoftext|>


<commit_before>
  /***
   * @package Object
   * @dependency core
   * @description Object manipulation, type checking (isNumber, isString, ...), extended objects with hash-like methods available as instance methods.
   *
   * Much thanks to kangax for his informative aricle about how problems with instanceof and constructor
   * http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/
   *
   ***/

  var ObjectTypeMethods = 'isObject,isNaN'.split(',');
  var ObjectHashMethods = 'keys,values,each,merge,clone,equal,watch,tap,has'.split(',');

  function setParamsObject(obj, param, value, deep) {
    var reg = /^(.+?)(\[.*\])$/, paramIsArray, match, allKeys, key;
    if(deep !== false && (match = param.match(reg))) {
      key = match[1];
      allKeys = match[2].replace(/^\[|\]$/g, '').split('][');
      allKeys.forEach(function(k) {
        paramIsArray = !k || k.match(/^\d+$/);
        if(!key && isArray(obj)) key = obj.length;
        if(!obj[key]) {
          obj[key] = paramIsArray ? [] : {};
        }
        obj = obj[key];
        key = k;
      });
      if(!key && paramIsArray) key = obj.length.toString();
      setParamsObject(obj, key, value);
    } else if(value.match(/^[\d.]+$/)) {
      obj[param] = parseFloat(value);
    } else if(value === 'true') {
      obj[param] = true;
    } else if(value === 'false') {
      obj[param] = false;
    } else {
      obj[param] = value;
    }
  }

  /***
   * @method Object.is[Type](<obj>)
   * @returns Boolean
   * @short Returns true if <obj> is an object of that type.
   * @extra %isObject% will return false on anything that is not an object literal, including instances of inherited classes. Note also that %isNaN% will ONLY return true if the object IS %NaN%. It does not mean the same as browser native %isNaN%, which returns true for anything that is "not a number".
   *
   * @set
   *   isArray
   *   isObject
   *   isBoolean
   *   isDate
   *   isFunction
   *   isNaN
   *   isNumber
   *   isString
   *   isRegExp
   *
   * @example
   *
   *   Object.isArray([1,2,3])            -> true
   *   Object.isDate(3)                   -> false
   *   Object.isRegExp(/wasabi/)          -> true
   *   Object.isObject({ broken:'wear' }) -> true
   *
   ***/
  function buildTypeMethods() {
    extendSimilar(object, false, false, ClassNames, function(methods, name) {
      var method = 'is' + name;
      ObjectTypeMethods.push(method);
      methods[method] = function(obj) {
        return className(obj) === '[object '+name+']';
      }
    });
  }

  function buildObjectExtend() {
    extend(object, false, function(){ return arguments.length === 0; }, {
      'extend': function() {
        buildObjectInstanceMethods(ObjectTypeMethods.concat(ObjectHashMethods), object);
      }
    });
  }

  extend(object, false, true, {
      /***
       * @method watch(<obj>, <prop>, <fn>)
       * @returns Nothing
       * @short Watches a property of <obj> and runs <fn> when it changes.
       * @extra <fn> is passed three arguments: the property <prop>, the old value, and the new value. The return value of [fn] will be set as the new value. This method is useful for things such as validating or cleaning the value when it is set. Warning: this method WILL NOT work in browsers that don't support %Object.defineProperty%. This notably includes IE 8 and below, and Opera. This is the only method in Sugar that is not fully compatible with all browsers. %watch% is available as an instance method on extended objects.
       * @example
       *
       *   Object.watch({ foo: 'bar' }, 'foo', function(prop, oldVal, newVal) {
       *     // Will be run when the property 'foo' is set on the object.
       *   });
       *   Object.extended().watch({ foo: 'bar' }, 'foo', function(prop, oldVal, newVal) {
       *     // Will be run when the property 'foo' is set on the object.
       *   });
       *
       ***/
    'watch': function(obj, prop, fn) {
      if(!definePropertySupport) return;
      var value = obj[prop];
      object.defineProperty(obj, prop, {
        'enumerable'  : true,
        'configurable': true,
        'get': function() {
          return value;
        },
        'set': function(to) {
          value = fn.call(obj, prop, value, to);
        }
      });
    }
  });

  extend(object, false, function(arg1, arg2) { return isFunction(arg2); }, {

    /***
     * @method keys(<obj>, [fn])
     * @returns Array
     * @short Returns an array containing the keys in <obj>. Optionally calls [fn] for each key.
     * @extra This method is provided for browsers that don't support it natively, and additionally is enhanced to accept the callback [fn]. Returned keys are in no particular order. %keys% is available as an instance method on extended objects.
     * @example
     *
     *   Object.keys({ broken: 'wear' }) -> ['broken']
     *   Object.keys({ broken: 'wear' }, function(key, value) {
     *     // Called once for each key.
     *   });
     *   Object.extended({ broken: 'wear' }).keys() -> ['broken']
     *
     ***/
    'keys': function(obj, fn) {
      var keys = object.keys(obj);
      keys.forEach(function(key) {
        fn.call(obj, key, obj[key]);
      });
      return keys;
    }

  });

  extend(object, false, false, {

    'isObject': function(obj) {
      return isObject(obj);
    },

    'isNaN': function(obj) {
      // This is only true of NaN
      return isNumber(obj) && obj.valueOf() !== obj.valueOf();
    },

    /***
     * @method equal(<a>, <b>)
     * @returns Boolean
     * @short Returns true if <a> and <b> are equal.
     * @extra %equal% in Sugar is "egal", meaning the values are equal if they are "not observably distinguishable". Note that on extended objects the name is %equals% for readability.
     * @example
     *
     *   Object.equal({a:2}, {a:2}) -> true
     *   Object.equal({a:2}, {a:3}) -> false
     *   Object.extended({a:2}).equals({a:3}) -> false
     *
     ***/
    'equal': function(a, b) {
      return isEqual(a, b);
    },

    /***
     * @method Object.extended(<obj> = {})
     * @returns Extended object
     * @short Creates a new object, equivalent to %new Object()% or %{}%, but with extended methods.
     * @extra See extended objects for more.
     * @example
     *
     *   Object.extended()
     *   Object.extended({ happy:true, pappy:false }).keys() -> ['happy','pappy']
     *   Object.extended({ happy:true, pappy:false }).values() -> [true, false]
     *
     ***/
    'extended': function(obj) {
      return new Hash(obj);
    },

    /***
     * @method merge(<target>, <source>, [deep] = false, [resolve] = true)
     * @returns Merged object
     * @short Merges all the properties of <source> into <target>.
     * @extra Merges are shallow unless [deep] is %true%. Properties of <source> will win in the case of conflicts, unless [resolve] is %false%. [resolve] can also be a function that resolves the conflict. In this case it will be passed 3 arguments, %key%, %targetVal%, and %sourceVal%, with the context set to <source>. This will allow you to solve conflict any way you want, ie. adding two numbers together, etc. %merge% is available as an instance method on extended objects.
     * @example
     *
     *   Object.merge({a:1},{b:2}) -> { a:1, b:2 }
     *   Object.merge({a:1},{a:2}, false, false) -> { a:1 }
     +   Object.merge({a:1},{a:2}, false, function(key, a, b) {
     *     return a + b;
     *   }); -> { a:3 }
     *   Object.extended({a:1}).merge({b:2}) -> { a:1, b:2 }
     *
     ***/
    'merge': function(target, source, deep, resolve) {
      var key, val;
      // Strings cannot be reliably merged thanks to
      // their properties not being enumerable in < IE8.
      if(target && typeof source != 'string') {
        for(key in source) {
          if(!hasOwnProperty(source, key) || !target) continue;
          val = source[key];
          // Conflict!
          if(isDefined(target[key])) {
            // Do not merge.
            if(resolve === false) {
              continue;
            }
            // Use the result of the callback as the result.
            if(isFunction(resolve)) {
              val = resolve.call(source, key, target[key], source[key])
            }
          }
          // Deep merging.
          if(deep === true && val && isObjectPrimitive(val)) {
            if(isDate(val)) {
              val = new date(val.getTime());
            } else if(isRegExp(val)) {
              val = new regexp(val.source, getRegExpFlags(val));
            } else {
              if(!target[key]) target[key] = array.isArray(val) ? [] : {};
              object.merge(target[key], source[key], deep, resolve);
              continue;
            }
          }
          target[key] = val;
        }
      }
      return target;
    },

    /***
     * @method values(<obj>, [fn])
     * @returns Array
     * @short Returns an array containing the values in <obj>. Optionally calls [fn] for each value.
     * @extra Returned values are in no particular order. %values% is available as an instance method on extended objects.
     * @example
     *
     *   Object.values({ broken: 'wear' }) -> ['wear']
     *   Object.values({ broken: 'wear' }, function(value) {
     *     // Called once for each value.
     *   });
     *   Object.extended({ broken: 'wear' }).values() -> ['wear']
     *
     ***/
    'values': function(obj, fn) {
      var values = [];
      iterateOverObject(obj, function(k,v) {
        values.push(v);
        if(fn) fn.call(obj,v);
      });
      return values;
    },

    /***
     * @method clone(<obj> = {}, [deep] = false)
     * @returns Cloned object
     * @short Creates a clone (copy) of <obj>.
     * @extra Default is a shallow clone, unless [deep] is true. %clone% is available as an instance method on extended objects.
     * @example
     *
     *   Object.clone({foo:'bar'})            -> { foo: 'bar' }
     *   Object.clone()                       -> {}
     *   Object.extended({foo:'bar'}).clone() -> { foo: 'bar' }
     *
     ***/
    'clone': function(obj, deep) {
      if(!isObjectPrimitive(obj)) return obj;
      if(array.isArray(obj)) return obj.concat();
      var target = obj instanceof Hash ? new Hash() : {};
      return object.merge(target, obj, deep);
    },

    /***
     * @method Object.fromQueryString(<str>, [deep] = true)
     * @returns Object
     * @short Converts the query string of a URL into an object.
     * @extra If [deep] is %false%, conversion will only accept shallow params (ie. no object or arrays with %[]% syntax) as these are not universally supported.
     * @example
     *
     *   Object.fromQueryString('foo=bar&broken=wear') -> { foo: 'bar', broken: 'wear' }
     *   Object.fromQueryString('foo[]=1&foo[]=2')     -> { foo: [1,2] }
     *
     ***/
    'fromQueryString': function(str, deep) {
      var result = object.extended(), split;
      str = str && str.toString ? str.toString() : '';
      str.replace(/^.*?\?/, '').split('&').forEach(function(p) {
        var split = p.split('=');
        if(split.length !== 2) return;
        setParamsObject(result, split[0], decodeURIComponent(split[1]), deep);
      });
      return result;
    },

    /***
     * @method tap(<obj>, <fn>)
     * @returns Object
     * @short Runs <fn> and returns <obj>.
     * @extra  A string can also be used as a shortcut to a method. This method is used to run an intermediary function in the middle of method chaining. As a standalone method on the Object class it doesn't have too much use. The power of %tap% comes when using extended objects or modifying the Object prototype with Object.extend().
     * @example
     *
     *   Object.extend();
     *   [2,4,6].map(Math.exp).tap(function(arr) {
     *     arr.pop()
     *   });
     *   [2,4,6].map(Math.exp).tap('pop').map(Math.round); ->  [7,55]
     *
     ***/
    'tap': function(obj, arg) {
      var fn = arg;
      if(!isFunction(arg)) {
        fn = function() {
          if(arg) obj[arg]();
        }
      }
      fn.call(obj, obj);
      return obj;
    },

    /***
     * @method has(<obj>, <key>)
     * @returns Boolean
     * @short Checks if <obj> has <key> using hasOwnProperty from Object.prototype.
     * @extra This method is considered safer than %Object#hasOwnProperty% when using objects as hashes. See http://www.devthought.com/2012/01/18/an-object-is-not-a-hash/ for more.
     * @example
     *
     *   Object.has({ foo: 'bar' }, 'foo') -> true
     *   Object.has({ foo: 'bar' }, 'baz') -> false
     *   Object.has({ hasOwnProperty: true }, 'foo') -> false
     *
     ***/
    'has': function (obj, key) {
      return hasOwnProperty(obj, key);
    }

  });

  buildTypeMethods();
  buildObjectExtend();
  buildObjectInstanceMethods(ObjectHashMethods, Hash);

<commit_msg>Fixed setParamsObject for better float handling<commit_after>  /***
   * @package Object
   * @dependency core
   * @description Object manipulation, type checking (isNumber, isString, ...), extended objects with hash-like methods available as instance methods.
   *
   * Much thanks to kangax for his informative aricle about how problems with instanceof and constructor
   * http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/
   *
   ***/

  var ObjectTypeMethods = 'isObject,isNaN'.split(',');
  var ObjectHashMethods = 'keys,values,each,merge,clone,equal,watch,tap,has'.split(',');

  function setParamsObject(obj, param, value, deep) {
    var reg = /^(.+?)(\[.*\])$/, paramIsArray, match, allKeys, key;
    if(deep !== false && (match = param.match(reg))) {
      key = match[1];
      allKeys = match[2].replace(/^\[|\]$/g, '').split('][');
      allKeys.forEach(function(k) {
        paramIsArray = !k || k.match(/^\d+$/);
        if(!key && isArray(obj)) key = obj.length;
        if(!obj[key]) {
          obj[key] = paramIsArray ? [] : {};
        }
        obj = obj[key];
        key = k;
      });
      if(!key && paramIsArray) key = obj.length.toString();
      setParamsObject(obj, key, value);
    } else if(value.match(/^[+-]?\d+(\.\d+)?$/)) {
      obj[param] = parseFloat(value);
    } else if(value === 'true') {
      obj[param] = true;
    } else if(value === 'false') {
      obj[param] = false;
    } else {
      obj[param] = value;
    }
  }

  /***
   * @method Object.is[Type](<obj>)
   * @returns Boolean
   * @short Returns true if <obj> is an object of that type.
   * @extra %isObject% will return false on anything that is not an object literal, including instances of inherited classes. Note also that %isNaN% will ONLY return true if the object IS %NaN%. It does not mean the same as browser native %isNaN%, which returns true for anything that is "not a number".
   *
   * @set
   *   isArray
   *   isObject
   *   isBoolean
   *   isDate
   *   isFunction
   *   isNaN
   *   isNumber
   *   isString
   *   isRegExp
   *
   * @example
   *
   *   Object.isArray([1,2,3])            -> true
   *   Object.isDate(3)                   -> false
   *   Object.isRegExp(/wasabi/)          -> true
   *   Object.isObject({ broken:'wear' }) -> true
   *
   ***/
  function buildTypeMethods() {
    extendSimilar(object, false, false, ClassNames, function(methods, name) {
      var method = 'is' + name;
      ObjectTypeMethods.push(method);
      methods[method] = function(obj) {
        return className(obj) === '[object '+name+']';
      }
    });
  }

  function buildObjectExtend() {
    extend(object, false, function(){ return arguments.length === 0; }, {
      'extend': function() {
        buildObjectInstanceMethods(ObjectTypeMethods.concat(ObjectHashMethods), object);
      }
    });
  }

  extend(object, false, true, {
      /***
       * @method watch(<obj>, <prop>, <fn>)
       * @returns Nothing
       * @short Watches a property of <obj> and runs <fn> when it changes.
       * @extra <fn> is passed three arguments: the property <prop>, the old value, and the new value. The return value of [fn] will be set as the new value. This method is useful for things such as validating or cleaning the value when it is set. Warning: this method WILL NOT work in browsers that don't support %Object.defineProperty%. This notably includes IE 8 and below, and Opera. This is the only method in Sugar that is not fully compatible with all browsers. %watch% is available as an instance method on extended objects.
       * @example
       *
       *   Object.watch({ foo: 'bar' }, 'foo', function(prop, oldVal, newVal) {
       *     // Will be run when the property 'foo' is set on the object.
       *   });
       *   Object.extended().watch({ foo: 'bar' }, 'foo', function(prop, oldVal, newVal) {
       *     // Will be run when the property 'foo' is set on the object.
       *   });
       *
       ***/
    'watch': function(obj, prop, fn) {
      if(!definePropertySupport) return;
      var value = obj[prop];
      object.defineProperty(obj, prop, {
        'enumerable'  : true,
        'configurable': true,
        'get': function() {
          return value;
        },
        'set': function(to) {
          value = fn.call(obj, prop, value, to);
        }
      });
    }
  });

  extend(object, false, function(arg1, arg2) { return isFunction(arg2); }, {

    /***
     * @method keys(<obj>, [fn])
     * @returns Array
     * @short Returns an array containing the keys in <obj>. Optionally calls [fn] for each key.
     * @extra This method is provided for browsers that don't support it natively, and additionally is enhanced to accept the callback [fn]. Returned keys are in no particular order. %keys% is available as an instance method on extended objects.
     * @example
     *
     *   Object.keys({ broken: 'wear' }) -> ['broken']
     *   Object.keys({ broken: 'wear' }, function(key, value) {
     *     // Called once for each key.
     *   });
     *   Object.extended({ broken: 'wear' }).keys() -> ['broken']
     *
     ***/
    'keys': function(obj, fn) {
      var keys = object.keys(obj);
      keys.forEach(function(key) {
        fn.call(obj, key, obj[key]);
      });
      return keys;
    }

  });

  extend(object, false, false, {

    'isObject': function(obj) {
      return isObject(obj);
    },

    'isNaN': function(obj) {
      // This is only true of NaN
      return isNumber(obj) && obj.valueOf() !== obj.valueOf();
    },

    /***
     * @method equal(<a>, <b>)
     * @returns Boolean
     * @short Returns true if <a> and <b> are equal.
     * @extra %equal% in Sugar is "egal", meaning the values are equal if they are "not observably distinguishable". Note that on extended objects the name is %equals% for readability.
     * @example
     *
     *   Object.equal({a:2}, {a:2}) -> true
     *   Object.equal({a:2}, {a:3}) -> false
     *   Object.extended({a:2}).equals({a:3}) -> false
     *
     ***/
    'equal': function(a, b) {
      return isEqual(a, b);
    },

    /***
     * @method Object.extended(<obj> = {})
     * @returns Extended object
     * @short Creates a new object, equivalent to %new Object()% or %{}%, but with extended methods.
     * @extra See extended objects for more.
     * @example
     *
     *   Object.extended()
     *   Object.extended({ happy:true, pappy:false }).keys() -> ['happy','pappy']
     *   Object.extended({ happy:true, pappy:false }).values() -> [true, false]
     *
     ***/
    'extended': function(obj) {
      return new Hash(obj);
    },

    /***
     * @method merge(<target>, <source>, [deep] = false, [resolve] = true)
     * @returns Merged object
     * @short Merges all the properties of <source> into <target>.
     * @extra Merges are shallow unless [deep] is %true%. Properties of <source> will win in the case of conflicts, unless [resolve] is %false%. [resolve] can also be a function that resolves the conflict. In this case it will be passed 3 arguments, %key%, %targetVal%, and %sourceVal%, with the context set to <source>. This will allow you to solve conflict any way you want, ie. adding two numbers together, etc. %merge% is available as an instance method on extended objects.
     * @example
     *
     *   Object.merge({a:1},{b:2}) -> { a:1, b:2 }
     *   Object.merge({a:1},{a:2}, false, false) -> { a:1 }
     +   Object.merge({a:1},{a:2}, false, function(key, a, b) {
     *     return a + b;
     *   }); -> { a:3 }
     *   Object.extended({a:1}).merge({b:2}) -> { a:1, b:2 }
     *
     ***/
    'merge': function(target, source, deep, resolve) {
      var key, val;
      // Strings cannot be reliably merged thanks to
      // their properties not being enumerable in < IE8.
      if(target && typeof source != 'string') {
        for(key in source) {
          if(!hasOwnProperty(source, key) || !target) continue;
          val = source[key];
          // Conflict!
          if(isDefined(target[key])) {
            // Do not merge.
            if(resolve === false) {
              continue;
            }
            // Use the result of the callback as the result.
            if(isFunction(resolve)) {
              val = resolve.call(source, key, target[key], source[key])
            }
          }
          // Deep merging.
          if(deep === true && val && isObjectPrimitive(val)) {
            if(isDate(val)) {
              val = new date(val.getTime());
            } else if(isRegExp(val)) {
              val = new regexp(val.source, getRegExpFlags(val));
            } else {
              if(!target[key]) target[key] = array.isArray(val) ? [] : {};
              object.merge(target[key], source[key], deep, resolve);
              continue;
            }
          }
          target[key] = val;
        }
      }
      return target;
    },

    /***
     * @method values(<obj>, [fn])
     * @returns Array
     * @short Returns an array containing the values in <obj>. Optionally calls [fn] for each value.
     * @extra Returned values are in no particular order. %values% is available as an instance method on extended objects.
     * @example
     *
     *   Object.values({ broken: 'wear' }) -> ['wear']
     *   Object.values({ broken: 'wear' }, function(value) {
     *     // Called once for each value.
     *   });
     *   Object.extended({ broken: 'wear' }).values() -> ['wear']
     *
     ***/
    'values': function(obj, fn) {
      var values = [];
      iterateOverObject(obj, function(k,v) {
        values.push(v);
        if(fn) fn.call(obj,v);
      });
      return values;
    },

    /***
     * @method clone(<obj> = {}, [deep] = false)
     * @returns Cloned object
     * @short Creates a clone (copy) of <obj>.
     * @extra Default is a shallow clone, unless [deep] is true. %clone% is available as an instance method on extended objects.
     * @example
     *
     *   Object.clone({foo:'bar'})            -> { foo: 'bar' }
     *   Object.clone()                       -> {}
     *   Object.extended({foo:'bar'}).clone() -> { foo: 'bar' }
     *
     ***/
    'clone': function(obj, deep) {
      if(!isObjectPrimitive(obj)) return obj;
      if(array.isArray(obj)) return obj.concat();
      var target = obj instanceof Hash ? new Hash() : {};
      return object.merge(target, obj, deep);
    },

    /***
     * @method Object.fromQueryString(<str>, [deep] = true)
     * @returns Object
     * @short Converts the query string of a URL into an object.
     * @extra If [deep] is %false%, conversion will only accept shallow params (ie. no object or arrays with %[]% syntax) as these are not universally supported.
     * @example
     *
     *   Object.fromQueryString('foo=bar&broken=wear') -> { foo: 'bar', broken: 'wear' }
     *   Object.fromQueryString('foo[]=1&foo[]=2')     -> { foo: [1,2] }
     *
     ***/
    'fromQueryString': function(str, deep) {
      var result = object.extended(), split;
      str = str && str.toString ? str.toString() : '';
      str.replace(/^.*?\?/, '').split('&').forEach(function(p) {
        var split = p.split('=');
        if(split.length !== 2) return;
        setParamsObject(result, split[0], decodeURIComponent(split[1]), deep);
      });
      return result;
    },

    /***
     * @method tap(<obj>, <fn>)
     * @returns Object
     * @short Runs <fn> and returns <obj>.
     * @extra  A string can also be used as a shortcut to a method. This method is used to run an intermediary function in the middle of method chaining. As a standalone method on the Object class it doesn't have too much use. The power of %tap% comes when using extended objects or modifying the Object prototype with Object.extend().
     * @example
     *
     *   Object.extend();
     *   [2,4,6].map(Math.exp).tap(function(arr) {
     *     arr.pop()
     *   });
     *   [2,4,6].map(Math.exp).tap('pop').map(Math.round); ->  [7,55]
     *
     ***/
    'tap': function(obj, arg) {
      var fn = arg;
      if(!isFunction(arg)) {
        fn = function() {
          if(arg) obj[arg]();
        }
      }
      fn.call(obj, obj);
      return obj;
    },

    /***
     * @method has(<obj>, <key>)
     * @returns Boolean
     * @short Checks if <obj> has <key> using hasOwnProperty from Object.prototype.
     * @extra This method is considered safer than %Object#hasOwnProperty% when using objects as hashes. See http://www.devthought.com/2012/01/18/an-object-is-not-a-hash/ for more.
     * @example
     *
     *   Object.has({ foo: 'bar' }, 'foo') -> true
     *   Object.has({ foo: 'bar' }, 'baz') -> false
     *   Object.has({ hasOwnProperty: true }, 'foo') -> false
     *
     ***/
    'has': function (obj, key) {
      return hasOwnProperty(obj, key);
    }

  });

  buildTypeMethods();
  buildObjectExtend();
  buildObjectInstanceMethods(ObjectHashMethods, Hash);

<|endoftext|>


<commit_before>"use strict";
/*global alert: true, console: true, ODSA, PARAMS */

/** This file should only be referenced by AVs (not modules) */

(function ($) {
  var settings = {},
      odsaUtils = {};

  // Eliminate dependence on OpenDSA framework by creating stubbed function definitions
  if (typeof ODSA === "undefined") {
    // If MODULE_ORIGIN is not specified, assume they are on the same domain
    if (!settings.MODULE_ORIGIN) {
      settings.MODULE_ORIGIN = location.protocol + '//' + location.host;
    }

    // Define the console object if it doesn't exist to support IE without developer tools
    if (!(window.console && console.log)) {
      console = {
        log: function () {},
        debug: function () {},
        info: function () {},
        warn: function () {},
        error: function () {}
      };
    }

    odsaUtils.serverEnabled = function () {return false; };
    odsaUtils.roundPercent = function (number) {
      return Math.round(number * 100) / 100;
    };
    odsaUtils.inDebugMode = function () {
      return localStorage.DEBUG_MODE === 'true';
    };

    odsaUtils.sendEventData = function () {};
    odsaUtils.logUserAction = function (type, desc, exerName, eventUiid) {};
    odsaUtils.logEvent = function (data) {};

    window.ODSA = {};
    window.ODSA.SETTINGS = settings;
    window.ODSA.UTILS = odsaUtils;

    console.warn('odsaUtils.js was not included, using fallback function definitions');
  } else {
    settings = ODSA.SETTINGS;
    odsaUtils = ODSA.UTILS;
  }

  /**
   * A timestamp when the user started looking at the page
   */
  var focusTime = +new Date();

  /**
   * The total amount of time the user has spent on the current exercise instance
   */
  var totalTime = 0;

  /**
   * Stores the empty contents of the avcontainer, used for reset
   */
  var emptyContent = '';

  /**
   * Set a flag indicating the user cannot receive credit for the
   * current exercise instance after viewing the model answer
   */
  var allowCredit = true;

  /**
   * A unique instance identifier, used to group interaction events from a single instance
   */
  var uiid = +new Date();

  //*****************************************************************************
  //*************                    AV FUNCTIONS                   *************
  //*****************************************************************************

  // Controls whether the AV submits its own event data or allows its parent page to handle event data
  var selfLoggingEnabled = PARAMS.selfLoggingEnabled === 'false' ? false : true;

  /**
   * Generates a JSAV event to log the initial state of an AV or exercise
   *   - initData - A JSON object that contains the initial state of an exercise
   *     Conventions:
   *       - The key for automatically generated data should have a prefix 'gen_'
   *         - Ex: an automatically generated array would be 'gen_array'
   *       - The key for user generated data should have a prefix 'user_'
   *         - Ex: Array data the user enters in the textbox should have a key 'user_array'
   */
  function logExerciseInit(initData) {
    // Reset the uiid (unique instance identifier)
    uiid = +new Date();
    totalTime = 0;

    var data = {av: settings.AV_NAME, type: 'odsa-exercise-init', desc: JSON.stringify(initData)};
    $("body").trigger("jsav-log-event", [data]);
  }

  /**
   * Generates a JSAV event which triggers the code to give a user credit for an exercise
   */
  function awardCompletionCredit() {
    var data = {av: settings.AV_NAME, type: 'odsa-award-credit'};
    $("body").trigger("jsav-log-event", [data]);
  }

  /**
   * Resets the AV to its initial state
   */
  function reset(flag) {
    // Replace the contents of the avcontainer with the save initial state
    $('.avcontainer').unbind().html(emptyContent);

    // Clear the array values field, when no params given and reset button hit
    if (flag !== true && !$('#arrayValues').prop("disabled")) {
      $('#arrayValues').val("");
    }
  }

  /**
   * Loads a JSON file that contains language and / or code translations
   */
  function getConfig(url) {
    var configurationFile;
    var result = {};

    // the selected language and code
    var lang = JSAV_EXERCISE_OPTIONS.lang || JSAV_OPTIONS.lang || "en";
    var code = JSAV_EXERCISE_OPTIONS.code || JSAV_OPTIONS.code;

    // download the entire configurationFileiguration file
    $.ajax({
      url: url,
      async: false,
      dataType: "json",
      success: function (data) { configurationFile = data; }
    });

    // URL where the language information can be downloaded from
    var langUrl = '';

    // set the language object for the result
    if (typeof configurationFile.translations === "string") {
      langUrl = configurationFile.translations.replace('{lang}', lang);
    } else if (typeof configurationFile.translations[lang] === "object") {
      // return the language object in the configurationFileiguration file
      result.language = configurationFile.translations[lang];
    } else if (typeof configurationFile.translations[lang] === "string") {
      langUrl = url.split("/");
      langUrl.pop();
      langUrl = langUrl.join("/");
      if (langUrl !== "") {
        langUrl += "/";
      }
      langUrl += configurationFile.translations[lang];
    }

    // If langUrl is initialized, download the language data
    if (langUrl !== '') {
      // download the language object from the url
      var langData;

      $.ajax({
        url: langUrl,
        async: false,
        dataType: "json",
        success: function (data) { langData = data; }
      });

      result.language = langData;
    }

    // set the code object for the result
    if (configurationFile.code) {
      result.code = configurationFile.code[code];
    }

    return result;
  }

  /**
   * Reset the text of the exercise title, instructions label and instructions to the specified language
   */
  function setTitleAndInstructions(container, translation) {
    if (typeof translation === 'undefined') {
      console.error('There is no translation support for language ' + JSAV_OPTIONS.lang);
    }

    var container = $(container);
    var elem;
    for (var field in translation) {
      if (translation.hasOwnProperty(field)) {
        elem = container.find(field);
        if (elem.size() > 0) {
          if (elem.is('input')) {
            elem.attr('value', translation[field]);
          } else {
            elem.html(translation[field]);
          }
        }
      }
    }

    //var $title = container.find(".exerciseTitle");
    //var $instructLabel = container.find(".instructLabel");
    //var $instructions = container.find(".instructions");

    //if ($title.length !== 0) {
    //  $title.html(translation.exerciseTitle);
    //}
    //if ($instructLabel.length !== 0) {
    //  $instructLabel.html(translation.instructLabel);
    //}
    //if ($instructions.length !== 0) {
    //  $instructions.html(translation.instructions);
    //}
  }

  // Add AV utility functions to the global ODSA object
  var av = {};
  av.logExerciseInit = logExerciseInit;
  av.awardCompletionCredit = awardCompletionCredit;
  av.reset = reset;
  av.getConfig = getConfig;
  av.setTitleAndInstructions = setTitleAndInstructions;
  window.ODSA.AV = av;

  //*****************************************************************************
  //*************                  JSAV Extensions                  *************
  //*****************************************************************************
  /**
   * Extends the JSAV AV array to have the slice functionality of JavaScript arrays
   */
  JSAV._types.ds.AVArray.prototype.slice = function (start, end) {
    var array = [];

    for (var i = 0; i < (end - start); i++) {
      array[i] = this.value(start + i);
    }

    return array;
  };

  /**
   * Convenience function for highlighting the pivot value in blue
   */
  JSAV._types.ds.AVArray.prototype.highlightBlue = function (index) {
    return this.addClass(index, "processing");
  };
  JSAV._types.ds.AVArray.prototype.unhighlightBlue = function (index) {
    return this.removeClass(index, "processing");
  };

  /**
   * Convenience function for highlighting sorted values
   */
  JSAV._types.ds.AVArray.prototype.markSorted = function (index) {
    this.removeClass(index, "processing");
    return this.addClass(index, "sorted");
  };

  /**
   * toString function for JSAV arrays, useful for debugging
   */
  JSAV._types.ds.AVArray.prototype.toString = function () {
    var size = this.size();
    var str = '[';
    for (var i = 0; i < size; i++) {
      str += this.value(i);

      if (i < size - 1) {
        str += ', ';
      }
    }
    str += ']';

    return str;
  };

  //*****************************************************************************
  //*************                      LOGGING                      *************
  //*****************************************************************************
  /**
   * Parses the name of the page from the URL
   */
  function getNameFromURL(url) {
    // If no URL is specified, uses the pathname of the current page
    url = (url) ? url : location.pathname;
    var start = url.lastIndexOf("/") + 1,
        end = url.lastIndexOf(".htm");

    // URL is a directory, redirecting to an index page
    if (start === url.length && end === -1) {
      return 'index';
    }

    return url.slice(start, end);
  }

  $(document).ready(function () {
    // Initialize settings.AV_NAME
    settings.AV_NAME = getNameFromURL();

    emptyContent = $('.avcontainer').html();

    // Listen for JSAV events and forward them to the parent page
    $("body").on("jsav-log-event", function (e, data) {
      var flush = false,
          discardEvents = ["jsav-init", "jsav-recorded", "jsav-exercise-model-init", "jsav-exercise-model-recorded"],
          ssEvents = ['jsav-forward', 'jsav-backward', 'jsav-begin', 'jsav-end', 'jsav-exercise-model-forward', 'jsav-exercise-model-backward', 'jsav-exercise-model-begin', 'jsav-exercise-model-end'];

      // Filter out events we aren't interested in
      if (discardEvents.indexOf(data.type) > -1) {
        return;
      }

      // Overwrite the av attribute with the correct value and append the uiid
      data.av = settings.AV_NAME;
      data.uiid = uiid;

      // If data.desc doesn't exist or is empty, initialize it
      if (!data.desc || data.desc === '') {
        data.desc = {};
      } else {
        // If it already exists, make sure its a JSON object
        data.desc = odsaUtils.getJSON(data.desc);
      }

      if (ssEvents.indexOf(data.type) > -1) {
        data.desc.currentStep = data.currentStep;
        data.desc.currentStep = data.totalSteps;

        // Flush event data when the end of a slideshow is reached
        if (data.currentStep === data.totalSteps) {
          flush = true;
        }
      } else if (data.type === "jsav-array-click") {
        data.desc.index = data.index;
        data.desc.arrayID = data.arrayid;
      } else if (data.type === "jsav-exercise-grade-change" || data.type === "jsav-exercise-grade" || data.type === "jsav-exercise-step-fixed") {
        // On grade change events, log the user's score and submit it
        data.desc.score = odsaUtils.roundPercent(data.score.correct / data.score.total);
        data.desc.complete = odsaUtils.roundPercent((data.score.correct + data.score.undo + data.score.fix) / data.score.total);

        // Prevent event data from being transmitted on every step
        // This makes better use of the buffering mechanism and overall reduces the network traffic (removed overhead of individual requests), but it takes a while to complete and while its sending the log data isn't saved in local storage, if the user closes the page before the request completes and it fails the data will be lost
        //if (data.desc.complete === 1) {
        flush = true;
        //}
      } else if (data.type === "jsav-exercise-model-open") {
        // If user looks at the model answer before they are done and
        // they haven't already lost credit, warn them they can no longer
        // receive credit and prevent them from getting credit for the exercise
        if (allowCredit && $('span.jsavamidone').html() !== "DONE") {
          allowCredit = false;

          alert("You can no longer receive credit for the current instance of this exercise.\nClick 'Reset' or refresh the page to get a new problem instance.");

          // Hide the score widget and display and appropriate message in its place
          $('span.jsavscore').hide();
          $('span.jsavscore').parent().append('Credit not given for this instance');
        }
      } else if (data.type === "jsav-exercise-reset") {
        flush = true;

        // If the student looked at the model answer for the previous
        // attempt, allow them to get credit for the new instance
        if (!allowCredit) {
          allowCredit = true;

          $('span.jsavscore').show();
          $('#credit_disabled_msg').remove();
        }
      }

      // Mark data as logged on the client side, then send message to the parent window
      data.totalTime = totalTime + (+new Date()) - focusTime;

      // Marks the data as logged if the AV itself will be in charge of logging the data
      if (selfLoggingEnabled) {
        data.logged = true;
      }

      if (settings.MODULE_ORIGIN) {
        parent.postMessage(data, settings.MODULE_ORIGIN);
      }

      // Save the event in localStorage
      if (odsaUtils.serverEnabled() && selfLoggingEnabled) {
        odsaUtils.logEvent(data);

        if (flush) {
          odsaUtils.sendEventData();
        }
      }
    });

    if (odsaUtils.serverEnabled()) {
      // Log the browser ready event
      odsaUtils.logUserAction('document-ready', 'User loaded the ' + settings.AV_NAME + ' AV');

      if (selfLoggingEnabled) {
        // Send any stored event data when the page loads
        odsaUtils.sendEventData();
      }

      $(window).focus(function (e) {
        odsaUtils.logUserAction('window-focus', 'User looking at ' + settings.AV_NAME + ' window');
        focusTime = +new Date();
      });

      $(window).blur(function (e) {
        odsaUtils.logUserAction('window-blur', 'User is no longer looking at ' + settings.AV_NAME + ' window');
        totalTime += (+new Date() - focusTime);
      });

      $(window).on('beforeunload', function () {
        // Log the browser unload event
        odsaUtils.logUserAction('window-unload', 'User closed or refreshed ' + settings.AV_NAME + ' window');
      });
    }
  });
}(jQuery));
<commit_msg>Edited setTitleAndInstructions so that it does not use fields beginning with 'av_' as jQuery selectors.<commit_after>"use strict";
/*global alert: true, console: true, ODSA, PARAMS */

/** This file should only be referenced by AVs (not modules) */

(function ($) {
  var settings = {},
      odsaUtils = {};

  // Eliminate dependence on OpenDSA framework by creating stubbed function definitions
  if (typeof ODSA === "undefined") {
    // If MODULE_ORIGIN is not specified, assume they are on the same domain
    if (!settings.MODULE_ORIGIN) {
      settings.MODULE_ORIGIN = location.protocol + '//' + location.host;
    }

    // Define the console object if it doesn't exist to support IE without developer tools
    if (!(window.console && console.log)) {
      console = {
        log: function () {},
        debug: function () {},
        info: function () {},
        warn: function () {},
        error: function () {}
      };
    }

    odsaUtils.serverEnabled = function () {return false; };
    odsaUtils.roundPercent = function (number) {
      return Math.round(number * 100) / 100;
    };
    odsaUtils.inDebugMode = function () {
      return localStorage.DEBUG_MODE === 'true';
    };

    odsaUtils.sendEventData = function () {};
    odsaUtils.logUserAction = function (type, desc, exerName, eventUiid) {};
    odsaUtils.logEvent = function (data) {};

    window.ODSA = {};
    window.ODSA.SETTINGS = settings;
    window.ODSA.UTILS = odsaUtils;

    console.warn('odsaUtils.js was not included, using fallback function definitions');
  } else {
    settings = ODSA.SETTINGS;
    odsaUtils = ODSA.UTILS;
  }

  /**
   * A timestamp when the user started looking at the page
   */
  var focusTime = +new Date();

  /**
   * The total amount of time the user has spent on the current exercise instance
   */
  var totalTime = 0;

  /**
   * Stores the empty contents of the avcontainer, used for reset
   */
  var emptyContent = '';

  /**
   * Set a flag indicating the user cannot receive credit for the
   * current exercise instance after viewing the model answer
   */
  var allowCredit = true;

  /**
   * A unique instance identifier, used to group interaction events from a single instance
   */
  var uiid = +new Date();

  //*****************************************************************************
  //*************                    AV FUNCTIONS                   *************
  //*****************************************************************************

  // Controls whether the AV submits its own event data or allows its parent page to handle event data
  var selfLoggingEnabled = PARAMS.selfLoggingEnabled === 'false' ? false : true;

  /**
   * Generates a JSAV event to log the initial state of an AV or exercise
   *   - initData - A JSON object that contains the initial state of an exercise
   *     Conventions:
   *       - The key for automatically generated data should have a prefix 'gen_'
   *         - Ex: an automatically generated array would be 'gen_array'
   *       - The key for user generated data should have a prefix 'user_'
   *         - Ex: Array data the user enters in the textbox should have a key 'user_array'
   */
  function logExerciseInit(initData) {
    // Reset the uiid (unique instance identifier)
    uiid = +new Date();
    totalTime = 0;

    var data = {av: settings.AV_NAME, type: 'odsa-exercise-init', desc: JSON.stringify(initData)};
    $("body").trigger("jsav-log-event", [data]);
  }

  /**
   * Generates a JSAV event which triggers the code to give a user credit for an exercise
   */
  function awardCompletionCredit() {
    var data = {av: settings.AV_NAME, type: 'odsa-award-credit'};
    $("body").trigger("jsav-log-event", [data]);
  }

  /**
   * Resets the AV to its initial state
   */
  function reset(flag) {
    // Replace the contents of the avcontainer with the save initial state
    $('.avcontainer').unbind().html(emptyContent);

    // Clear the array values field, when no params given and reset button hit
    if (flag !== true && !$('#arrayValues').prop("disabled")) {
      $('#arrayValues').val("");
    }
  }

  /**
   * Loads a JSON file that contains language and / or code translations
   */
  function getConfig(url) {
    var configurationFile;
    var result = {};

    // the selected language and code
    var lang = JSAV_EXERCISE_OPTIONS.lang || JSAV_OPTIONS.lang || "en";
    var code = JSAV_EXERCISE_OPTIONS.code || JSAV_OPTIONS.code;

    // download the entire configurationFileiguration file
    $.ajax({
      url: url,
      async: false,
      dataType: "json",
      success: function (data) { configurationFile = data; }
    });

    // URL where the language information can be downloaded from
    var langUrl = '';

    // set the language object for the result
    if (typeof configurationFile.translations === "string") {
      langUrl = configurationFile.translations.replace('{lang}', lang);
    } else if (typeof configurationFile.translations[lang] === "object") {
      // return the language object in the configurationFileiguration file
      result.language = configurationFile.translations[lang];
    } else if (typeof configurationFile.translations[lang] === "string") {
      langUrl = url.split("/");
      langUrl.pop();
      langUrl = langUrl.join("/");
      if (langUrl !== "") {
        langUrl += "/";
      }
      langUrl += configurationFile.translations[lang];
    }

    // If langUrl is initialized, download the language data
    if (langUrl !== '') {
      // download the language object from the url
      var langData;

      $.ajax({
        url: langUrl,
        async: false,
        dataType: "json",
        success: function (data) { langData = data; }
      });

      result.language = langData;
    }

    // set the code object for the result
    if (configurationFile.code) {
      result.code = configurationFile.code[code];
    }

    return result;
  }

  /**
   * Reset the text of the exercise title, instructions label and instructions to the specified language
   */
  function setTitleAndInstructions(container, translation) {
    if (typeof translation === 'undefined') {
      console.error('There is no translation support for language ' + JSAV_OPTIONS.lang);
    }

    container = $(container);
    var elem;
    for (var field in translation) {
      if (translation.hasOwnProperty(field) && field.indexOf("av_") !== 0) {
        elem = container.find(field);
        if (elem.size() > 0) {
          if (elem.is('input')) {
            elem.attr('value', translation[field]);
          } else {
            elem.html(translation[field]);
          }
        }
      }
    }

    //var $title = container.find(".exerciseTitle");
    //var $instructLabel = container.find(".instructLabel");
    //var $instructions = container.find(".instructions");

    //if ($title.length !== 0) {
    //  $title.html(translation.exerciseTitle);
    //}
    //if ($instructLabel.length !== 0) {
    //  $instructLabel.html(translation.instructLabel);
    //}
    //if ($instructions.length !== 0) {
    //  $instructions.html(translation.instructions);
    //}
  }

  // Add AV utility functions to the global ODSA object
  var av = {};
  av.logExerciseInit = logExerciseInit;
  av.awardCompletionCredit = awardCompletionCredit;
  av.reset = reset;
  av.getConfig = getConfig;
  av.setTitleAndInstructions = setTitleAndInstructions;
  window.ODSA.AV = av;

  //*****************************************************************************
  //*************                  JSAV Extensions                  *************
  //*****************************************************************************
  /**
   * Extends the JSAV AV array to have the slice functionality of JavaScript arrays
   */
  JSAV._types.ds.AVArray.prototype.slice = function (start, end) {
    var array = [];

    for (var i = 0; i < (end - start); i++) {
      array[i] = this.value(start + i);
    }

    return array;
  };

  /**
   * Convenience function for highlighting the pivot value in blue
   */
  JSAV._types.ds.AVArray.prototype.highlightBlue = function (index) {
    return this.addClass(index, "processing");
  };
  JSAV._types.ds.AVArray.prototype.unhighlightBlue = function (index) {
    return this.removeClass(index, "processing");
  };

  /**
   * Convenience function for highlighting sorted values
   */
  JSAV._types.ds.AVArray.prototype.markSorted = function (index) {
    this.removeClass(index, "processing");
    return this.addClass(index, "sorted");
  };

  /**
   * toString function for JSAV arrays, useful for debugging
   */
  JSAV._types.ds.AVArray.prototype.toString = function () {
    var size = this.size();
    var str = '[';
    for (var i = 0; i < size; i++) {
      str += this.value(i);

      if (i < size - 1) {
        str += ', ';
      }
    }
    str += ']';

    return str;
  };

  //*****************************************************************************
  //*************                      LOGGING                      *************
  //*****************************************************************************
  /**
   * Parses the name of the page from the URL
   */
  function getNameFromURL(url) {
    // If no URL is specified, uses the pathname of the current page
    url = (url) ? url : location.pathname;
    var start = url.lastIndexOf("/") + 1,
        end = url.lastIndexOf(".htm");

    // URL is a directory, redirecting to an index page
    if (start === url.length && end === -1) {
      return 'index';
    }

    return url.slice(start, end);
  }

  $(document).ready(function () {
    // Initialize settings.AV_NAME
    settings.AV_NAME = getNameFromURL();

    emptyContent = $('.avcontainer').html();

    // Listen for JSAV events and forward them to the parent page
    $("body").on("jsav-log-event", function (e, data) {
      var flush = false,
          discardEvents = ["jsav-init", "jsav-recorded", "jsav-exercise-model-init", "jsav-exercise-model-recorded"],
          ssEvents = ['jsav-forward', 'jsav-backward', 'jsav-begin', 'jsav-end', 'jsav-exercise-model-forward', 'jsav-exercise-model-backward', 'jsav-exercise-model-begin', 'jsav-exercise-model-end'];

      // Filter out events we aren't interested in
      if (discardEvents.indexOf(data.type) > -1) {
        return;
      }

      // Overwrite the av attribute with the correct value and append the uiid
      data.av = settings.AV_NAME;
      data.uiid = uiid;

      // If data.desc doesn't exist or is empty, initialize it
      if (!data.desc || data.desc === '') {
        data.desc = {};
      } else {
        // If it already exists, make sure its a JSON object
        data.desc = odsaUtils.getJSON(data.desc);
      }

      if (ssEvents.indexOf(data.type) > -1) {
        data.desc.currentStep = data.currentStep;
        data.desc.currentStep = data.totalSteps;

        // Flush event data when the end of a slideshow is reached
        if (data.currentStep === data.totalSteps) {
          flush = true;
        }
      } else if (data.type === "jsav-array-click") {
        data.desc.index = data.index;
        data.desc.arrayID = data.arrayid;
      } else if (data.type === "jsav-exercise-grade-change" || data.type === "jsav-exercise-grade" || data.type === "jsav-exercise-step-fixed") {
        // On grade change events, log the user's score and submit it
        data.desc.score = odsaUtils.roundPercent(data.score.correct / data.score.total);
        data.desc.complete = odsaUtils.roundPercent((data.score.correct + data.score.undo + data.score.fix) / data.score.total);

        // Prevent event data from being transmitted on every step
        // This makes better use of the buffering mechanism and overall reduces the network traffic (removed overhead of individual requests), but it takes a while to complete and while its sending the log data isn't saved in local storage, if the user closes the page before the request completes and it fails the data will be lost
        //if (data.desc.complete === 1) {
        flush = true;
        //}
      } else if (data.type === "jsav-exercise-model-open") {
        // If user looks at the model answer before they are done and
        // they haven't already lost credit, warn them they can no longer
        // receive credit and prevent them from getting credit for the exercise
        if (allowCredit && $('span.jsavamidone').html() !== "DONE") {
          allowCredit = false;

          alert("You can no longer receive credit for the current instance of this exercise.\nClick 'Reset' or refresh the page to get a new problem instance.");

          // Hide the score widget and display and appropriate message in its place
          $('span.jsavscore').hide();
          $('span.jsavscore').parent().append('Credit not given for this instance');
        }
      } else if (data.type === "jsav-exercise-reset") {
        flush = true;

        // If the student looked at the model answer for the previous
        // attempt, allow them to get credit for the new instance
        if (!allowCredit) {
          allowCredit = true;

          $('span.jsavscore').show();
          $('#credit_disabled_msg').remove();
        }
      }

      // Mark data as logged on the client side, then send message to the parent window
      data.totalTime = totalTime + (+new Date()) - focusTime;

      // Marks the data as logged if the AV itself will be in charge of logging the data
      if (selfLoggingEnabled) {
        data.logged = true;
      }

      if (settings.MODULE_ORIGIN) {
        parent.postMessage(data, settings.MODULE_ORIGIN);
      }

      // Save the event in localStorage
      if (odsaUtils.serverEnabled() && selfLoggingEnabled) {
        odsaUtils.logEvent(data);

        if (flush) {
          odsaUtils.sendEventData();
        }
      }
    });

    if (odsaUtils.serverEnabled()) {
      // Log the browser ready event
      odsaUtils.logUserAction('document-ready', 'User loaded the ' + settings.AV_NAME + ' AV');

      if (selfLoggingEnabled) {
        // Send any stored event data when the page loads
        odsaUtils.sendEventData();
      }

      $(window).focus(function (e) {
        odsaUtils.logUserAction('window-focus', 'User looking at ' + settings.AV_NAME + ' window');
        focusTime = +new Date();
      });

      $(window).blur(function (e) {
        odsaUtils.logUserAction('window-blur', 'User is no longer looking at ' + settings.AV_NAME + ' window');
        totalTime += (+new Date() - focusTime);
      });

      $(window).on('beforeunload', function () {
        // Log the browser unload event
        odsaUtils.logUserAction('window-unload', 'User closed or refreshed ' + settings.AV_NAME + ' window');
      });
    }
  });
}(jQuery));
<|endoftext|>


<commit_before>/*
 * Optics / option.js
 * copyright (c) 2016 Susisu
 */

/**
 * @module option
 */

"use strict";

function endModule() {
    module.exports = Object.freeze({
        OptionArgument,
        OptionalOptionArgument,
        Option
    });
}

/**
 * An instance of `OptionArgument` represents an argument of option.
 * @static
 */
class OptionArgument {
    /**
     * Creates a new `OptionArgument` instance.
     * @param {string} name A string used in placeholder.
     * @param {Function} reader A function which reads value given as argument.
     */
    constructor(name, reader) {
        this.name   = name;
        this.reader = reader;
    }
}

/**
 * An instance of `OptionalOptionArgument` represents an optional argument of option.
 * @static
 * @extends {module:option.OptionArgument}
 */
class OptionalOptionArgument extends OptionArgument {
    /**
     * Creates a new `OptionalOptionArgument` instance.
     * @param {string} name A string used in placeholder.
     * @param defaultVal The default value, which is used when the argument is not specified.
     * @param {Function} reader A function which reads value given as argument.
     */
    constructor(name, defaultVal, reader) {
        super(name, reader);
        this.defaultVal = defaultVal;
    }
}

/**
 * An instance of `Option` represents a command-line option of command.
 * @static
 */
class Option {
    /**
     * Creates an new instance of `Option`
     * @param {(string | undefined)} shortName Short name of the option.
     * @param {(string | undefined)} longName Long name of the option.
     * @param {(module:option.OptionArgument | null)} arg Argument of the option.
     * If `null` is specified, the option has no argument.
     * @param {string} desc Description of the option.
     */
    constructor(shortName, longName, arg, desc) {
        this.shortName = shortName;
        this.longName  = longName;
        this.arg       = arg;
        this.desc      = desc;
    }
}

endModule();
<commit_msg>Fix OptionArgument constructor does not throw TypeError<commit_after>/*
 * Optics / option.js
 * copyright (c) 2016 Susisu
 */

/**
 * @module option
 */

"use strict";

function endModule() {
    module.exports = Object.freeze({
        OptionArgument,
        OptionalOptionArgument,
        Option
    });
}

/**
 * An instance of `OptionArgument` represents an argument of option.
 * @static
 */
class OptionArgument {
    /**
     * Creates a new `OptionArgument` instance.
     * @param {string} name A string used in placeholder.
     * @param {Function} reader A function which reads value given as argument.
     * @throws {TypeError} `name` is not a string.
     * @throws {TypeError} `reader` is not a function.
     */
    constructor(name, reader) {
        if (typeof name !== "string") {
            throw new TypeError("name must be a string");
        }
        if (typeof reader !== "function") {
            throw new TypeError("reader must be a string");
        }
        this.name   = name;
        this.reader = reader;
    }
}

/**
 * An instance of `OptionalOptionArgument` represents an optional argument of option.
 * @static
 * @extends {module:option.OptionArgument}
 */
class OptionalOptionArgument extends OptionArgument {
    /**
     * Creates a new `OptionalOptionArgument` instance.
     * @param {string} name A string used in placeholder.
     * @param defaultVal The default value, which is used when the argument is not specified.
     * @param {Function} reader A function which reads value given as argument.
     */
    constructor(name, defaultVal, reader) {
        super(name, reader);
        this.defaultVal = defaultVal;
    }
}

/**
 * An instance of `Option` represents a command-line option of command.
 * @static
 */
class Option {
    /**
     * Creates an new instance of `Option`
     * @param {(string | undefined)} shortName Short name of the option.
     * @param {(string | undefined)} longName Long name of the option.
     * @param {(module:option.OptionArgument | null)} arg Argument of the option.
     * If `null` is specified, the option has no argument.
     * @param {string} desc Description of the option.
     */
    constructor(shortName, longName, arg, desc) {
        this.shortName = shortName;
        this.longName  = longName;
        this.arg       = arg;
        this.desc      = desc;
    }
}

endModule();
<|endoftext|>


<commit_before>// Copyright (c) 2012 Kuba Niegowski
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var util = require('util'),
    Stream = require('stream'),
    zlib = require('zlib'),
    Filter = require('./filter'),
    CrcStream = require('./crc'),
    constants = require('./constants');

var Packer = module.exports = function(options) {
    Stream.call(this);

    this._options = options;

    options.deflateChunkSize = options.deflateChunkSize || 32 * 1024;
    options.deflateLevel = options.deflateLevel || 9;

    this.readable = true;
};
util.inherits(Packer, Stream);

Packer.prototype.pack = function(data, width, height) {

    // Signature
    this.emit('data', new Buffer(constants.PNG_SIGNATURE));
    this.emit('data', this._packIHDR(width, height));

    // filter pixel data
    var filter = new Filter(width, height, 4, data, this._options);
    var data = filter.filter();

    // compress it
    var deflate = zlib.createDeflate({
            chunkSize: this._options.deflateChunkSize,
            level: this._options.deflateLevel
        });
    deflate.on('error', this.emit.bind(this, 'error'));

    deflate.on('data', function(data) {
        this.emit('data', this._packIDAT(data));
    }.bind(this));

    deflate.on('end', function() {
        this.emit('data', this._packIEND());
        this.emit('end');
    }.bind(this));

    deflate.end(data);
};

Packer.prototype._packChunk = function(type, data) {

    var len = (data ? data.length : 0),
        buf = new Buffer(len + 12);

    buf.writeUInt32BE(len, 0);
    buf.writeUInt32BE(type, 4);

    if (data) data.copy(buf, 8);

    buf.writeInt32BE(CrcStream.crc32(buf.slice(4, buf.length - 4)), buf.length - 4);
    return buf;
};

Packer.prototype._packIHDR = function(width, height) {

    var buf = new Buffer(13);
    buf.writeUInt32BE(width, 0);
    buf.writeUInt32BE(height, 4);
    buf[8] = 8;
    buf[9] = 6; // colorType
    buf[10] = 0; // compression
    buf[11] = 0; // filter
    buf[12] = 0; // interlace

    return this._packChunk(constants.TYPE_IHDR, buf);
};

Packer.prototype._packIDAT = function(data) {
    return this._packChunk(constants.TYPE_IDAT, data);
};

Packer.prototype._packIEND = function() {
    return this._packChunk(constants.TYPE_IEND, null);
};
<commit_msg>added zlib deflateStrategy option, default to Z_RLE<commit_after>// Copyright (c) 2012 Kuba Niegowski
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var util = require('util'),
    Stream = require('stream'),
    zlib = require('zlib'),
    Filter = require('./filter'),
    CrcStream = require('./crc'),
    constants = require('./constants');

var Packer = module.exports = function(options) {
    Stream.call(this);

    this._options = options;

    options.deflateChunkSize = options.deflateChunkSize || 32 * 1024;
    options.deflateLevel = options.deflateLevel || 9;
    options.deflateStrategy = options.deflateStrategy || 3;

    this.readable = true;
};
util.inherits(Packer, Stream);

Packer.prototype.pack = function(data, width, height) {

    // Signature
    this.emit('data', new Buffer(constants.PNG_SIGNATURE));
    this.emit('data', this._packIHDR(width, height));

    // filter pixel data
    var filter = new Filter(width, height, 4, data, this._options);
    var data = filter.filter();

    // compress it
    var deflate = zlib.createDeflate({
            chunkSize: this._options.deflateChunkSize,
            level: this._options.deflateLevel,
            strategy: this._options.deflateStrategy
        });
    deflate.on('error', this.emit.bind(this, 'error'));

    deflate.on('data', function(data) {
        this.emit('data', this._packIDAT(data));
    }.bind(this));

    deflate.on('end', function() {
        this.emit('data', this._packIEND());
        this.emit('end');
    }.bind(this));

    deflate.end(data);
};

Packer.prototype._packChunk = function(type, data) {

    var len = (data ? data.length : 0),
        buf = new Buffer(len + 12);

    buf.writeUInt32BE(len, 0);
    buf.writeUInt32BE(type, 4);

    if (data) data.copy(buf, 8);

    buf.writeInt32BE(CrcStream.crc32(buf.slice(4, buf.length - 4)), buf.length - 4);
    return buf;
};

Packer.prototype._packIHDR = function(width, height) {

    var buf = new Buffer(13);
    buf.writeUInt32BE(width, 0);
    buf.writeUInt32BE(height, 4);
    buf[8] = 8;
    buf[9] = 6; // colorType
    buf[10] = 0; // compression
    buf[11] = 0; // filter
    buf[12] = 0; // interlace

    return this._packChunk(constants.TYPE_IHDR, buf);
};

Packer.prototype._packIDAT = function(data) {
    return this._packChunk(constants.TYPE_IDAT, data);
};

Packer.prototype._packIEND = function() {
    return this._packChunk(constants.TYPE_IEND, null);
};
<|endoftext|>