<commit_before>$(function () {

  function get_params() {
    var params = [], hash;
    var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
    for(var i = 0; i < hashes.length; i++)
    {
      hash = hashes[i].split('=');
      params.push(hash[0]);
      params[hash[0]] = hash[1];
    }
    return params;
  }

  var params = get_params();
  var origin_page_url = params["origin_page_url"];

    var WidgetState = function(){
        var cookieName = "widgetCookie" + origin_page_url;
        function _get(){
            try{
                var cookieValue = JSON.parse( $.cookie(cookieName) );
                return cookieValue == null ? {} : cookieValue; // never return null
            }catch(e){ console.log(["error parsing cookie. deleting the cookie ",e]);$.cookie( cookieName,null ); return {}; }
        }

        function _set( obj ){
            var newObj = $.extend(_get(), obj);
            $.cookie( cookieName, JSON.stringify(newObj) );
        }

        function get_or_set( key, val )
        {
            if ( !val || typeof(val) == "undefined" ) {
                return _get()[key];
            } else {
                var toSet={};
                toSet[key]= val;
                _set( toSet );
                return this;
            }
        }

        // should make sure cookie is not empty and not removed ( we support logical remove )
        this.isValid = function ()
        {
            var currentCookie = _get();
            return currentCookie != null && typeof(currentCookie) != "undefined" && !$.isEmptyObject( currentCookie ) && !currentCookie.removed
        };

        function generateFieldFunction( key ){ return function( val ){ return get_or_set.call(this,key,val) };}
        this.instanceId = generateFieldFunction("instanceId");
        this.publicIp = generateFieldFunction("publicIp");
        this.customLink = generateFieldFunction("customLink");

        this.remove = function ( remove )
        {
            _set( { removed: remove } ); // logic remove just in case removing the cookie does not work
            if ( remove ) {  // try removing the cookie. see issue #40

                try {
                    $.removeCookie( cookieName );
                    $.cookie( cookieName, null );
                } catch ( e ) {
                    console.log( ["error removing cookie", e] )
                }
                var currentCookie = _get();
                if ( $.isEmptyObject( currentCookie ) ) {
                    console.log( ["cookie is not removed for some reason, using logic delete instead"] )
                }
            }
        }
    };

    var WidgetLog = function(){
        var myLog = [];
        var $dom = $("#log");

        function clear(){
            $dom.empty();
        }

        function write_log(message, class_name) {
          $dom.append($("
", {html: message}).addClass(class_name));
          $dom.scrollTop($dom[0].scrollHeight);
        }

        // array of output strings.
        // this function will append new lines of log if new log is longer than current log.
        // otherwise it will clear the current log and rewrite it.
        this.appendOrOverride = function (aOutput) {
            if (myLog.length!= aOutput.length || myLog[0] != aOutput[0]) { // print only the difference

                var index = myLog.length;
                var logLength = aOutput.length;

                if (logLength <= index) {
                    clear();
                    index = 0;
                }

                for (; index < logLength; index = index + 1) {
                    write_log(aOutput[index]);
                }
                myLog = aOutput;
            }
        };

        this.clear= function(){clear()};
        this.error = function (message){ write_log(message, "error"); };
        this.important = function(message){ write_log(message,"important");};

    };

    var widgetState = new WidgetState();
    var widgetLog = new WidgetLog();

    function setTimeoutForUpdateStatus( myTimeout )
    {
        myTimeout = ( typeof(myTimeout) == "undefined" || myTimeout == null ) ? 10000 : myTimeout;
        setTimeout( update_status, myTimeout );
    }

    function handleUpdateStatusSuccess( data )
    {

        if ( data.status.timeleft ) {
            $("#time_left").show();
            $("#time_left_counter").text(data.status.timeleft + " minutes");
        }

        if ( data.status.output ){
            widgetLog.appendOrOverride(data.status.output);
        }

        if ( data.status.publicIp ) {
            widgetState.publicIp(data.status.publicIp);
        }

        if ( data.status.cloudifyUiIsAvailable ){ // must be after publicIp
            show_cloudify_ui_link( ui );
        }

        if ( data.status.consoleLink ){
            var link_info = data.status.consoleLink;
            var custom_link = $("

",{"id":"custom_link"}).append($("", {"href": link_info.url, "target":"_blank", "text":link_info.title}).text(link_info.title));
            widgetState.customLink(custom_link);
            show_custom_link( true );
       }

        var state = data.status.state.toLocaleLowerCase();
        if ( state == "stopped") {
            $("#start_btn,#stop_btn").toggle();
            if ( data.status.message ){
                widgetLog.error( data.status.message );
            }
            stop_instance();
        } else {// status == running
            setTimeoutForUpdateStatus();
        }
    }

  function update_status() {
    $.ajax({
        type:'get',
        url: "/widget/"+ widgetState.instanceId() + "/status?apiKey=" + apiKey,
        success:function( data ){ handleUpdateStatusSuccess(data); },
        error:function(){ setTimeoutForUpdateStatus() }
    });
  }

    function stop_instance() {
        if ($("#log").find(".successfully_completed_msg").length == 0) { // make sure this appears only once.. we might be firing an Ajax request after the first stop.
            widgetLog.important("Test drive successfully completed!
Download Cloudify here or read the documentation.");
        }
        show_cloudify_ui_link(false);
        show_custom_link(false);
        $("#time_left").hide();
        widgetState.remove(true);

    }

    function start_instance_btn_handler()
    {
        $( "#start_btn,#stop_btn" ).toggle();
        if ( !widgetState.isValid() ) {
			var playData = { apiKey : params["apiKey"], "hpcsKey" : $("#advanced [name=hpcs_key]").val(), "hpcsSecretKey":$("#advanced [name=hpcs_secret_key]").val() };
            $.ajax(
                { type:"POST",
                  url : "/widget/start?" + $.param(playData),
                    success: function ( data, textStatus, jqXHR ){

                                    if ( data.status.state == "error" ) {
                                        $( "#start_btn,#stop_btn" ).toggle();
                                        widgetLog.error( data.status.message );
                                        return;
                                    }
                                    if ( data.status.instanceId ){
                                        widgetState.instanceId( data.status.instanceId );
                                    }

                                    setTimeoutForUpdateStatus(1);
                            },
                    error: function( data ){
                        var displayMessage = data.getResponseHeader("display-message");
                        if ( displayMessage ){
                            var displayMessageObj = JSON.parse( displayMessage );
                            widgetLog.error( displayMessageObj.msg);
                        }
                    }
                }

            );
        }
    }

    function stop_instance_btn_handler() {
    if (!confirm("Are you sure you want to stop the instance?")) {
      return;
    }
    $("#start_btn,#stop_btn").toggle();
    if ( widgetState.instanceId()) {
      $.post("/widget/"+ widgetState.instanceId() + "/stop?apiKey=" + params["apiKey"], {}, function (data) {
        if (data.status.state == "error") {
          $("#start_btn,#stop_btn").toggle();
          widgetLog.error(data.status.message );
          return;
        }
        stop_instance();
      });
    }
  }

  function show_cloudify_ui_link( show ){
      if ( show ){
        $("#links").find("li").show();
        $("#cloudify_dashboard_link").attr("href", "http://" + widgetState.publicIp() + ":8099/");
      }else{
          $("#links").find("li").hide();
      }
  }

    function show_custom_link( show ) {
        if ( show ){
            var custom_link = widgetState.customLink();
            if ($("#custom_link").get(0)){
                $("#custom_link").replaceWith(custom_link);
            }
            else{
                $("#links").append($(custom_link));
            }
        }else{
            $("#custom_link").remove();
        }
    }

    $("#title").text(decodeURIComponent(params["title"]));

  var apiKey = params["apiKey"];
  var shareUrl= encodeURI("http://launch.cloudifysource.org/admin/signin");

  var msg =  encodeURI("Launch on the cloud in a single click using the Cloudify widget");
    var twitterMsg = encodeURIComponent( $("#title" ).text() + " on any cloud with a single click with #cloudifysource");
  $("#facebook_share_link").attr("href", "http://www.facebook.com/sharer/sharer.php?u=" + shareUrl);
  $("#google_plus_share_link").attr("href", "https://plus.google.com/share?url=" + shareUrl);
  $("#twitter_share_link").attr("href", "https://twitter.com/share?url=" + encodeURIComponent(origin_page_url) + "&text=" + twitterMsg);

  if (params["video_url"]) {
    $("#video_container").append($(""));
    $("#youtube_iframe").attr("src", decodeURIComponent(params["video_url"]));
  }

  if (widgetState.instanceId()) {
    $("#start_btn,#stop_btn").toggle();

      // lets update these just in case the update status fails.
      if (widgetState.publicIp()) {
          show_cloudify_ui_link( true );
      }

      if (widgetState.customLink()) {
          show_custom_link( true );
      }
    setTimeoutForUpdateStatus( 1 );
  }

  $("#start_btn").click(start_instance_btn_handler);
  $("#stop_btn").click(stop_instance_btn_handler);

  $("#show_advanced, #hide_advanced").click(function () {
    $("#advanced").toggle();
  });

        $(".download_link" ).live("click",function(){
            mixpanel.track("Download Button Clicks",{'page name' : $("#title" ).text(), 'url' : origin_page_url});
        });

        $(".documentation_link" ).live("click",function(){
            mixpanel.track("Documentation Button Clicks",{'page name' : $("#title" ).text(), 'url' : origin_page_url});
        });

        mixpanel.track("Widget Impression");

  $(".share_link").click(function (e) {
    e.preventDefault();
    var leftvar = (screen.width-600)/2;
    var topvar = (screen.height-600)/2;

    mixpanel.track("Social Button Clicks",{'page name' : $("#title" ).text(), 'url' : origin_page_url, 'button': $(this ).attr("id")});

    window.open($(e.target).parents("a").attr("href"), '', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=600,width=600,left='+ leftvar +',top=' + topvar);

  });
});
<commit_msg>fixing serverNode deletion.<commit_after>$(function () {

  function get_params() {
    var params = [], hash;
    var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
    for(var i = 0; i < hashes.length; i++)
    {
      hash = hashes[i].split('=');
      params.push(hash[0]);
      params[hash[0]] = hash[1];
    }
    return params;
  }

  var params = get_params();
  var origin_page_url = params["origin_page_url"];

    var WidgetState = function(){
        var cookieName = "widgetCookie" + origin_page_url;
        function _get(){
            try{
                var cookieValue = JSON.parse( $.cookie(cookieName) );
                return cookieValue == null ? {} : cookieValue; // never return null
            }catch(e){ console.log(["error parsing cookie. deleting the cookie ",e]);$.cookie( cookieName,null ); return {}; }
        }

        function _set( obj ){
            var newObj = $.extend(_get(), obj);
            $.cookie( cookieName, JSON.stringify(newObj) );
        }

        function get_or_set( key, val )
        {
            if ( !val || typeof(val) == "undefined" ) {
                return _get()[key];
            } else {
                var toSet={};
                toSet[key]= val;
                _set( toSet );
                return this;
            }
        }

        // should make sure cookie is not empty and not removed ( we support logical remove )
        this.isValid = function ()
        {
            var currentCookie = _get();
            return currentCookie != null && typeof(currentCookie) != "undefined" && !$.isEmptyObject( currentCookie ) && !currentCookie.removed
        };

        function generateFieldFunction( key ){ return function( val ){ return get_or_set.call(this,key,val) };}
        this.instanceId = generateFieldFunction("instanceId");
        this.publicIp = generateFieldFunction("publicIp");
        this.customLink = generateFieldFunction("customLink");

        this.remove = function ( remove )
        {
            _set( { removed: remove } ); // logic remove just in case removing the cookie does not work
            if ( remove ) {  // try removing the cookie. see issue #40

                try {
                    $.removeCookie( cookieName );
                    $.cookie( cookieName, null );
                } catch ( e ) {
                    console.log( ["error removing cookie", e] )
                }
                var currentCookie = _get();
                if ( $.isEmptyObject( currentCookie ) ) {
                    console.log( ["cookie is not removed for some reason, using logic delete instead"] )
                }
            }
        }
    };

    var WidgetLog = function(){
        var myLog = [];
        var $dom = $("#log");

        function clear(){
            $dom.empty();
        }

        function write_log(message, class_name) {
          $dom.append($("
", {html: message}).addClass(class_name));
          $dom.scrollTop($dom[0].scrollHeight);
        }

        // array of output strings.
        // this function will append new lines of log if new log is longer than current log.
        // otherwise it will clear the current log and rewrite it.
        this.appendOrOverride = function (aOutput) {
            if (myLog.length!= aOutput.length || myLog[0] != aOutput[0]) { // print only the difference

                var index = myLog.length;
                var logLength = aOutput.length;

                if (logLength <= index) {
                    clear();
                    index = 0;
                }

                for (; index < logLength; index = index + 1) {
                    write_log(aOutput[index]);
                }
                myLog = aOutput;
            }
        };

        this.clear= function(){clear()};
        this.error = function (message){ write_log(message, "error"); };
        this.important = function(message){ write_log(message,"important");};

    };

    var widgetState = new WidgetState();
    var widgetLog = new WidgetLog();

    function setTimeoutForUpdateStatus( myTimeout )
    {
        myTimeout = ( typeof(myTimeout) == "undefined" || myTimeout == null ) ? 10000 : myTimeout;
        setTimeout( update_status, myTimeout );
    }

    function handleUpdateStatusSuccess( data )
    {

        if ( data.status.timeleft ) {
            $("#time_left").show();
            $("#time_left_counter").text(data.status.timeleft + " minutes");
        }

        if ( data.status.output ){
            widgetLog.appendOrOverride(data.status.output);
        }

        if ( data.status.publicIp ) {
            widgetState.publicIp(data.status.publicIp);
        }

        if ( data.status.cloudifyUiIsAvailable ){ // must be after publicIp
            show_cloudify_ui_link( ui );
        }

        if ( data.status.consoleLink ){
            var link_info = data.status.consoleLink;
            var custom_link = $("

",{"id":"custom_link"}).append($("", {"href": link_info.url, "target":"_blank", "text":link_info.title}).text(link_info.title));
            widgetState.customLink(custom_link);
            show_custom_link( true );
       }

        var state = data.status.state.toLocaleLowerCase();
        if ( state == "stopped") {
            $("#start_btn,#stop_btn").toggle();
            if ( data.status.message ){
                widgetLog.error( data.status.message );
            }
            stop_instance();
        } else {// status == running
            setTimeoutForUpdateStatus();
        }
    }

  function update_status() {
      if ( widgetState.instanceId() ){  // guy - double check. saw that sometimes it misses.
            $.ajax({
                type:'get',
                url: "/widget/"+ widgetState.instanceId() + "/status?apiKey=" + apiKey,
                success:function( data ){ handleUpdateStatusSuccess(data); },
                error:function(){ setTimeoutForUpdateStatus() }
            });
      }else{
          stop_instance();
      }
  }

    function stop_instance() {
        if ($("#log").find(".successfully_completed_msg").length == 0) { // make sure this appears only once.. we might be firing an Ajax request after the first stop.
            widgetLog.important("Test drive successfully completed!
Download Cloudify here or read the documentation.");
        }
        show_cloudify_ui_link(false);
        show_custom_link(false);
        $("#time_left").hide();
        widgetState.remove(true);

    }

    function start_instance_btn_handler()
    {
        $( "#start_btn,#stop_btn" ).toggle();
        if ( !widgetState.isValid() ) {
			var playData = { apiKey : params["apiKey"], "hpcsKey" : $("#advanced [name=hpcs_key]").val(), "hpcsSecretKey":$("#advanced [name=hpcs_secret_key]").val() };
            $.ajax(
                { type:"POST",
                  url : "/widget/start?" + $.param(playData),
                    success: function ( data, textStatus, jqXHR ){

                                    if ( data.status.state == "error" ) {
                                        $( "#start_btn,#stop_btn" ).toggle();
                                        widgetLog.error( data.status.message );
                                        return;
                                    }
                                    if ( data.status.instanceId ){
                                        widgetState.instanceId( data.status.instanceId );
                                    }

                                    setTimeoutForUpdateStatus(1);
                            },
                    error: function( data ){
                        var displayMessage = data.getResponseHeader("display-message");
                        if ( displayMessage ){
                            var displayMessageObj = JSON.parse( displayMessage );
                            widgetLog.error( displayMessageObj.msg);
                        }
                    }
                }

            );
        }
    }

    function stop_instance_btn_handler() {
    if (!confirm("Are you sure you want to stop the instance?")) {
      return;
    }
    $("#start_btn,#stop_btn").toggle();
    if ( widgetState.instanceId()) {
      $.post("/widget/"+ widgetState.instanceId() + "/stop?apiKey=" + params["apiKey"], {}, function (data) {
        if (data.status.state == "error") {
          $("#start_btn,#stop_btn").toggle();
          widgetLog.error(data.status.message );
          return;
        }
        stop_instance();
      });
    }
  }

  function show_cloudify_ui_link( show ){
      if ( show ){
        $("#links").find("li").show();
        $("#cloudify_dashboard_link").attr("href", "http://" + widgetState.publicIp() + ":8099/");
      }else{
          $("#links").find("li").hide();
      }
  }

    function show_custom_link( show ) {
        if ( show ){
            var custom_link = widgetState.customLink();
            if ($("#custom_link").get(0)){
                $("#custom_link").replaceWith(custom_link);
            }
            else{
                $("#links").append($(custom_link));
            }
        }else{
            $("#custom_link").remove();
        }
    }

    $("#title").text(decodeURIComponent(params["title"]));

  var apiKey = params["apiKey"];
  var shareUrl= encodeURI("http://launch.cloudifysource.org/admin/signin");

  var msg =  encodeURI("Launch on the cloud in a single click using the Cloudify widget");
    var twitterMsg = encodeURIComponent( $("#title" ).text() + " on any cloud with a single click with #cloudifysource");
  $("#facebook_share_link").attr("href", "http://www.facebook.com/sharer/sharer.php?u=" + shareUrl);
  $("#google_plus_share_link").attr("href", "https://plus.google.com/share?url=" + shareUrl);
  $("#twitter_share_link").attr("href", "https://twitter.com/share?url=" + encodeURIComponent(origin_page_url) + "&text=" + twitterMsg);

  if (params["video_url"]) {
    $("#video_container").append($(""));
    $("#youtube_iframe").attr("src", decodeURIComponent(params["video_url"]));
  }

  if (widgetState.instanceId()) {
    $("#start_btn,#stop_btn").toggle();

      // lets update these just in case the update status fails.
      if (widgetState.publicIp()) {
          show_cloudify_ui_link( true );
      }

      if (widgetState.customLink()) {
          show_custom_link( true );
      }
    setTimeoutForUpdateStatus( 1 );
  }

  $("#start_btn").click(start_instance_btn_handler);
  $("#stop_btn").click(stop_instance_btn_handler);

  $("#show_advanced, #hide_advanced").click(function () {
    $("#advanced").toggle();
  });

        $(".download_link" ).live("click",function(){
            mixpanel.track("Download Button Clicks",{'page name' : $("#title" ).text(), 'url' : origin_page_url});
        });

        $(".documentation_link" ).live("click",function(){
            mixpanel.track("Documentation Button Clicks",{'page name' : $("#title" ).text(), 'url' : origin_page_url});
        });

        mixpanel.track("Widget Impression");

  $(".share_link").click(function (e) {
    e.preventDefault();
    var leftvar = (screen.width-600)/2;
    var topvar = (screen.height-600)/2;

    mixpanel.track("Social Button Clicks",{'page name' : $("#title" ).text(), 'url' : origin_page_url, 'button': $(this ).attr("id")});

    window.open($(e.target).parents("a").attr("href"), '', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=600,width=600,left='+ leftvar +',top=' + topvar);

  });
});
<|endoftext|>


<commit_before>/* global define */

(function (root, pluralize) {
  /* istanbul ignore else */
  if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
    // Node.
    module.exports = pluralize();
  } else if (typeof define === 'function' && define.amd) {
    // AMD, registers as an anonymous module.
    define(function () {
      return pluralize();
    });
  } else {
    // Browser global.
    root.pluralize = pluralize();
  }
})(this, function () {
  // Rule storage - pluralize and singularize need to be run sequentially,
  // while other rules can be optimized using an object for instant lookups.
  var pluralRules = [];
  var singularRules = [];
  var uncountables = {};
  var irregularPlurals = {};
  var irregularSingles = {};

  /**
   * Title case a string.
   *
   * @param  {string} str
   * @return {string}
   */
  function toTitleCase (str) {
    return str.charAt(0).toUpperCase() + str.substr(1).toLowerCase();
  }

  /**
   * Sanitize a pluralization rule to a usable regular expression.
   *
   * @param  {(RegExp|string)} rule
   * @return {RegExp}
   */
  function sanitizeRule (rule) {
    if (typeof rule === 'string') {
      return new RegExp('^' + rule + '$', 'i');
    }

    return rule;
  }

  /**
   * Pass in a word token to produce a function that can replicate the case on
   * another word.
   *
   * @param  {string}   word
   * @param  {string}   token
   * @return {Function}
   */
  function restoreCase (word, token) {
    // Upper cased words. E.g. "HELLO".
    if (word === word.toUpperCase()) {
      return token.toUpperCase();
    }

    // Title cased words. E.g. "Title".
    if (word[0] === word[0].toUpperCase()) {
      return toTitleCase(token);
    }

    // Lower cased words. E.g. "test".
    return token.toLowerCase();
  }

  /**
   * Interpolate a regexp string.
   *
   * @param  {string} str
   * @param  {Array}  args
   * @return {string}
   */
  function interpolate (str, args) {
    return str.replace(/\$(\d{1,2})/g, function (match, index) {
      return args[index] || '';
    });
  }

  /**
   * Sanitize a word by passing in the word and sanitization rules.
   *
   * @param  {String}   token
   * @param  {String}   word
   * @param  {Array}    collection
   * @return {String}
   */
  function sanitizeWord (token, word, collection) {
    // Empty string or doesn't need fixing.
    if (!token.length || uncountables.hasOwnProperty(token)) {
      return word;
    }

    var len = collection.length;

    // Iterate over the sanitization rules and use the first one to match.
    while (len--) {
      var rule = collection[len];

      // If the rule passes, return the replacement.
      if (rule[0].test(word)) {
        return word.replace(rule[0], function (match, index, word) {
          var result = interpolate(rule[1], arguments);

          if (match === '') {
            return restoreCase(word[index - 1], result);
          }

          return restoreCase(match, result);
        });
      }
    }

    return word;
  }

  /**
   * Replace a word with the updated word.
   *
   * @param  {Object}   replaceMap
   * @param  {Object}   keepMap
   * @param  {Array}    rules
   * @return {Function}
   */
  function replaceWord (replaceMap, keepMap, rules) {
    return function (word) {
      // Get the correct token and case restoration functions.
      var token = word.toLowerCase();

      // Check against the keep object map.
      if (keepMap.hasOwnProperty(token)) {
        return restoreCase(word, token);
      }

      // Check against the replacement map for a direct word replacement.
      if (replaceMap.hasOwnProperty(token)) {
        return restoreCase(word, replaceMap[token]);
      }

      // Run all the rules against the word.
      return sanitizeWord(token, word, rules);
    };
  }

  /**
   * Pluralize or singularize a word based on the passed in count.
   *
   * @param  {String}  word
   * @param  {Number}  count
   * @param  {Boolean} inclusive
   * @return {String}
   */
  function pluralize (word, count, inclusive) {
    var pluralized = count === 1
      ? pluralize.singular(word) : pluralize.plural(word);

    return (inclusive ? count + ' ' : '') + pluralized;
  }

  /**
   * Pluralize a word.
   *
   * @type {Function}
   */
  pluralize.plural = replaceWord(
    irregularSingles, irregularPlurals, pluralRules
  );

  /**
   * Singularize a word.
   *
   * @type {Function}
   */
  pluralize.singular = replaceWord(
    irregularPlurals, irregularSingles, singularRules
  );

  /**
   * Add a pluralization rule to the collection.
   *
   * @param {(string|RegExp)} rule
   * @param {string}          replacement
   */
  pluralize.addPluralRule = function (rule, replacement) {
    pluralRules.push([sanitizeRule(rule), replacement]);
  };

  /**
   * Add a singularization rule to the collection.
   *
   * @param {(string|RegExp)} rule
   * @param {string}          replacement
   */
  pluralize.addSingularRule = function (rule, replacement) {
    singularRules.push([sanitizeRule(rule), replacement]);
  };

  /**
   * Add an uncountable word rule.
   *
   * @param {(string|RegExp)} word
   */
  pluralize.addUncountableRule = function (word) {
    if (typeof word === 'string') {
      uncountables[word.toLowerCase()] = true;
      return;
    }

    // Set singular and plural references for the word.
    pluralize.addPluralRule(word, '$0');
    pluralize.addSingularRule(word, '$0');
  };

  /**
   * Add an irregular word definition.
   *
   * @param {String} single
   * @param {String} plural
   */
  pluralize.addIrregularRule = function (single, plural) {
    plural = plural.toLowerCase();
    single = single.toLowerCase();

    irregularSingles[single] = plural;
    irregularPlurals[plural] = single;
  };

  /**
   * Irregular rules.
   */
  [
    // Pronouns.
    ['I', 'we'],
    ['me', 'us'],
    ['he', 'they'],
    ['she', 'they'],
    ['them', 'them'],
    ['myself', 'ourselves'],
    ['yourself', 'yourselves'],
    ['itself', 'themselves'],
    ['herself', 'themselves'],
    ['himself', 'themselves'],
    ['themself', 'themselves'],
    ['is', 'are'],
    ['this', 'these'],
    ['that', 'those'],
    // Words ending in with a consonant and `o`.
    ['echo', 'echoes'],
    ['dingo', 'dingoes'],
    ['volcano', 'volcanoes'],
    ['tornado', 'tornadoes'],
    ['torpedo', 'torpedoes'],
    // Ends with `us`.
    ['genus', 'genera'],
    ['viscus', 'viscera'],
    // Ends with `ma`.
    ['stigma', 'stigmata'],
    ['stoma', 'stomata'],
    ['dogma', 'dogmata'],
    ['lemma', 'lemmata'],
    ['schema', 'schemata'],
    ['anathema', 'anathemata'],
    // Other irregular rules.
    ['ox', 'oxen'],
    ['axe', 'axes'],
    ['die', 'dice'],
    ['yes', 'yeses'],
    ['foot', 'feet'],
    ['eave', 'eaves'],
    ['goose', 'geese'],
    ['tooth', 'teeth'],
    ['quiz', 'quizzes'],
    ['human', 'humans'],
    ['proof', 'proofs'],
    ['carve', 'carves'],
    ['valve', 'valves'],
    ['thief', 'thieves'],
    ['genie', 'genies'],
    ['groove', 'grooves'],
    ['pickaxe', 'pickaxes'],
    ['whiskey', 'whiskies']
  ].forEach(function (rule) {
    return pluralize.addIrregularRule(rule[0], rule[1]);
  });

  /**
   * Pluralization rules.
   */
  [
    [/s?$/i, 's'],
    [/([^aeiou]ese)$/i, '$1'],
    [/(ax|test)is$/i, '$1es'],
    [/(alias|[^aou]us|tlas|gas|ris)$/i, '$1es'],
    [/(e[mn]u)s?$/i, '$1s'],
    [/([^l]ias|[aeiou]las|[emjzr]as|[iu]am)$/i, '$1'],
    [/(alumn|syllab|octop|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i, '$1i'],
    [/(alumn|alg|vertebr)(?:a|ae)$/i, '$1ae'],
    [/(seraph|cherub)(?:im)?$/i, '$1im'],
    [/(her|at|gr)o$/i, '$1oes'],
    [/(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|automat|quor)(?:a|um)$/i, '$1a'],
    [/(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)(?:a|on)$/i, '$1a'],
    [/sis$/i, 'ses'],
    [/(?:(kni|wi|li)fe|(ar|l|ea|eo|oa|hoo)f)$/i, '$1$2ves'],
    [/([^aeiouy]|qu)y$/i, '$1ies'],
    [/([^ch][ieo][ln])ey$/i, '$1ies'],
    [/(x|ch|ss|sh|zz)$/i, '$1es'],
    [/(matr|cod|mur|sil|vert|ind|append)(?:ix|ex)$/i, '$1ices'],
    [/(m|l)(?:ice|ouse)$/i, '$1ice'],
    [/(pe)(?:rson|ople)$/i, '$1ople'],
    [/(child)(?:ren)?$/i, '$1ren'],
    [/eaux$/i, '$0'],
    [/m[ae]n$/i, 'men'],
    ['thou', 'you']
  ].forEach(function (rule) {
    return pluralize.addPluralRule(rule[0], rule[1]);
  });

  /**
   * Singularization rules.
   */
  [
    [/s$/i, ''],
    [/(ss)$/i, '$1'],
    [/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(?:sis|ses)$/i, '$1sis'],
    [/(^analy)(?:sis|ses)$/i, '$1sis'],
    [/(wi|kni|(?:after|half|high|low|mid|non|night|[^\w]|^)li)ves$/i, '$1fe'],
    [/(ar|(?:wo|[ae])l|[eo][ao])ves$/i, '$1f'],
    [/([^aeiouy]|qu)ies$/i, '$1y'],
    [/(^[pl]|zomb|^(?:neck)?t|[aeo][lt]|cut)ies$/i, '$1ie'],
    [/(\b(?:mon|smil))ies$/i, '$1ey'],
    [/(m|l)ice$/i, '$1ouse'],
    [/(seraph|cherub)im$/i, '$1'],
    [/(x|ch|ss|sh|zz|tto|go|cho|alias|[^aou]us|tlas|gas|(?:her|at|gr)o|ris)(?:es)?$/i, '$1'],
    [/(e[mn]u)s?$/i, '$1'],
    [/(movie|twelve)s$/i, '$1'],
    [/(cris|test|diagnos)(?:is|es)$/i, '$1is'],
    [/(alumn|syllab|octop|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i, '$1us'],
    [/(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|quor)a$/i, '$1um'],
    [/(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)a$/i, '$1on'],
    [/(alumn|alg|vertebr)ae$/i, '$1a'],
    [/(cod|mur|sil|vert|ind)ices$/i, '$1ex'],
    [/(matr|append)ices$/i, '$1ix'],
    [/(pe)(rson|ople)$/i, '$1rson'],
    [/(child)ren$/i, '$1'],
    [/(eau)x?$/i, '$1'],
    [/men$/i, 'man']
  ].forEach(function (rule) {
    return pluralize.addSingularRule(rule[0], rule[1]);
  });

  /**
   * Uncountable rules.
   */
  [
    // Singular words with no plurals.
    'advice',
    'adulthood',
    'agenda',
    'aid',
    'alcohol',
    'ammo',
    'athletics',
    'bison',
    'blood',
    'bream',
    'buffalo',
    'butter',
    'carp',
    'cash',
    'chassis',
    'chess',
    'clothing',
    'commerce',
    'cod',
    'cooperation',
    'corps',
    'digestion',
    'debris',
    'diabetes',
    'energy',
    'equipment',
    'elk',
    'excretion',
    'expertise',
    'flounder',
    'fun',
    'gallows',
    'garbage',
    'graffiti',
    'headquarters',
    'health',
    'herpes',
    'highjinks',
    'homework',
    'housework',
    'information',
    'jeans',
    'justice',
    'kudos',
    'labour',
    'literature',
    'machinery',
    'mackerel',
    'media',
    'mews',
    'moose',
    'music',
    'news',
    'pike',
    'plankton',
    'pliers',
    'pollution',
    'premises',
    'rain',
    'research',
    'rice',
    'salmon',
    'scissors',
    'series',
    'sewage',
    'shambles',
    'shrimp',
    'species',
    'staff',
    'swine',
    'trout',
    'traffic',
    'transporation',
    'tuna',
    'wealth',
    'welfare',
    'whiting',
    'wildebeest',
    'wildlife',
    'you',
    // Regexes.
    /pox$/i, // "chickpox", "smallpox"
    /ois$/i,
    /deer$/i, // "deer", "reindeer"
    /fish$/i, // "fish", "blowfish", "angelfish"
    /sheep$/i,
    /measles$/i,
    /[^aeiou]ese$/i // "chinese", "japanese"
  ].forEach(pluralize.addUncountableRule);

  return pluralize;
});
<commit_msg>Add "mail" to the singular-only word list (#38)<commit_after>/* global define */

(function (root, pluralize) {
  /* istanbul ignore else */
  if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
    // Node.
    module.exports = pluralize();
  } else if (typeof define === 'function' && define.amd) {
    // AMD, registers as an anonymous module.
    define(function () {
      return pluralize();
    });
  } else {
    // Browser global.
    root.pluralize = pluralize();
  }
})(this, function () {
  // Rule storage - pluralize and singularize need to be run sequentially,
  // while other rules can be optimized using an object for instant lookups.
  var pluralRules = [];
  var singularRules = [];
  var uncountables = {};
  var irregularPlurals = {};
  var irregularSingles = {};

  /**
   * Title case a string.
   *
   * @param  {string} str
   * @return {string}
   */
  function toTitleCase (str) {
    return str.charAt(0).toUpperCase() + str.substr(1).toLowerCase();
  }

  /**
   * Sanitize a pluralization rule to a usable regular expression.
   *
   * @param  {(RegExp|string)} rule
   * @return {RegExp}
   */
  function sanitizeRule (rule) {
    if (typeof rule === 'string') {
      return new RegExp('^' + rule + '$', 'i');
    }

    return rule;
  }

  /**
   * Pass in a word token to produce a function that can replicate the case on
   * another word.
   *
   * @param  {string}   word
   * @param  {string}   token
   * @return {Function}
   */
  function restoreCase (word, token) {
    // Upper cased words. E.g. "HELLO".
    if (word === word.toUpperCase()) {
      return token.toUpperCase();
    }

    // Title cased words. E.g. "Title".
    if (word[0] === word[0].toUpperCase()) {
      return toTitleCase(token);
    }

    // Lower cased words. E.g. "test".
    return token.toLowerCase();
  }

  /**
   * Interpolate a regexp string.
   *
   * @param  {string} str
   * @param  {Array}  args
   * @return {string}
   */
  function interpolate (str, args) {
    return str.replace(/\$(\d{1,2})/g, function (match, index) {
      return args[index] || '';
    });
  }

  /**
   * Sanitize a word by passing in the word and sanitization rules.
   *
   * @param  {String}   token
   * @param  {String}   word
   * @param  {Array}    collection
   * @return {String}
   */
  function sanitizeWord (token, word, collection) {
    // Empty string or doesn't need fixing.
    if (!token.length || uncountables.hasOwnProperty(token)) {
      return word;
    }

    var len = collection.length;

    // Iterate over the sanitization rules and use the first one to match.
    while (len--) {
      var rule = collection[len];

      // If the rule passes, return the replacement.
      if (rule[0].test(word)) {
        return word.replace(rule[0], function (match, index, word) {
          var result = interpolate(rule[1], arguments);

          if (match === '') {
            return restoreCase(word[index - 1], result);
          }

          return restoreCase(match, result);
        });
      }
    }

    return word;
  }

  /**
   * Replace a word with the updated word.
   *
   * @param  {Object}   replaceMap
   * @param  {Object}   keepMap
   * @param  {Array}    rules
   * @return {Function}
   */
  function replaceWord (replaceMap, keepMap, rules) {
    return function (word) {
      // Get the correct token and case restoration functions.
      var token = word.toLowerCase();

      // Check against the keep object map.
      if (keepMap.hasOwnProperty(token)) {
        return restoreCase(word, token);
      }

      // Check against the replacement map for a direct word replacement.
      if (replaceMap.hasOwnProperty(token)) {
        return restoreCase(word, replaceMap[token]);
      }

      // Run all the rules against the word.
      return sanitizeWord(token, word, rules);
    };
  }

  /**
   * Pluralize or singularize a word based on the passed in count.
   *
   * @param  {String}  word
   * @param  {Number}  count
   * @param  {Boolean} inclusive
   * @return {String}
   */
  function pluralize (word, count, inclusive) {
    var pluralized = count === 1
      ? pluralize.singular(word) : pluralize.plural(word);

    return (inclusive ? count + ' ' : '') + pluralized;
  }

  /**
   * Pluralize a word.
   *
   * @type {Function}
   */
  pluralize.plural = replaceWord(
    irregularSingles, irregularPlurals, pluralRules
  );

  /**
   * Singularize a word.
   *
   * @type {Function}
   */
  pluralize.singular = replaceWord(
    irregularPlurals, irregularSingles, singularRules
  );

  /**
   * Add a pluralization rule to the collection.
   *
   * @param {(string|RegExp)} rule
   * @param {string}          replacement
   */
  pluralize.addPluralRule = function (rule, replacement) {
    pluralRules.push([sanitizeRule(rule), replacement]);
  };

  /**
   * Add a singularization rule to the collection.
   *
   * @param {(string|RegExp)} rule
   * @param {string}          replacement
   */
  pluralize.addSingularRule = function (rule, replacement) {
    singularRules.push([sanitizeRule(rule), replacement]);
  };

  /**
   * Add an uncountable word rule.
   *
   * @param {(string|RegExp)} word
   */
  pluralize.addUncountableRule = function (word) {
    if (typeof word === 'string') {
      uncountables[word.toLowerCase()] = true;
      return;
    }

    // Set singular and plural references for the word.
    pluralize.addPluralRule(word, '$0');
    pluralize.addSingularRule(word, '$0');
  };

  /**
   * Add an irregular word definition.
   *
   * @param {String} single
   * @param {String} plural
   */
  pluralize.addIrregularRule = function (single, plural) {
    plural = plural.toLowerCase();
    single = single.toLowerCase();

    irregularSingles[single] = plural;
    irregularPlurals[plural] = single;
  };

  /**
   * Irregular rules.
   */
  [
    // Pronouns.
    ['I', 'we'],
    ['me', 'us'],
    ['he', 'they'],
    ['she', 'they'],
    ['them', 'them'],
    ['myself', 'ourselves'],
    ['yourself', 'yourselves'],
    ['itself', 'themselves'],
    ['herself', 'themselves'],
    ['himself', 'themselves'],
    ['themself', 'themselves'],
    ['is', 'are'],
    ['this', 'these'],
    ['that', 'those'],
    // Words ending in with a consonant and `o`.
    ['echo', 'echoes'],
    ['dingo', 'dingoes'],
    ['volcano', 'volcanoes'],
    ['tornado', 'tornadoes'],
    ['torpedo', 'torpedoes'],
    // Ends with `us`.
    ['genus', 'genera'],
    ['viscus', 'viscera'],
    // Ends with `ma`.
    ['stigma', 'stigmata'],
    ['stoma', 'stomata'],
    ['dogma', 'dogmata'],
    ['lemma', 'lemmata'],
    ['schema', 'schemata'],
    ['anathema', 'anathemata'],
    // Other irregular rules.
    ['ox', 'oxen'],
    ['axe', 'axes'],
    ['die', 'dice'],
    ['yes', 'yeses'],
    ['foot', 'feet'],
    ['eave', 'eaves'],
    ['goose', 'geese'],
    ['tooth', 'teeth'],
    ['quiz', 'quizzes'],
    ['human', 'humans'],
    ['proof', 'proofs'],
    ['carve', 'carves'],
    ['valve', 'valves'],
    ['thief', 'thieves'],
    ['genie', 'genies'],
    ['groove', 'grooves'],
    ['pickaxe', 'pickaxes'],
    ['whiskey', 'whiskies']
  ].forEach(function (rule) {
    return pluralize.addIrregularRule(rule[0], rule[1]);
  });

  /**
   * Pluralization rules.
   */
  [
    [/s?$/i, 's'],
    [/([^aeiou]ese)$/i, '$1'],
    [/(ax|test)is$/i, '$1es'],
    [/(alias|[^aou]us|tlas|gas|ris)$/i, '$1es'],
    [/(e[mn]u)s?$/i, '$1s'],
    [/([^l]ias|[aeiou]las|[emjzr]as|[iu]am)$/i, '$1'],
    [/(alumn|syllab|octop|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i, '$1i'],
    [/(alumn|alg|vertebr)(?:a|ae)$/i, '$1ae'],
    [/(seraph|cherub)(?:im)?$/i, '$1im'],
    [/(her|at|gr)o$/i, '$1oes'],
    [/(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|automat|quor)(?:a|um)$/i, '$1a'],
    [/(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)(?:a|on)$/i, '$1a'],
    [/sis$/i, 'ses'],
    [/(?:(kni|wi|li)fe|(ar|l|ea|eo|oa|hoo)f)$/i, '$1$2ves'],
    [/([^aeiouy]|qu)y$/i, '$1ies'],
    [/([^ch][ieo][ln])ey$/i, '$1ies'],
    [/(x|ch|ss|sh|zz)$/i, '$1es'],
    [/(matr|cod|mur|sil|vert|ind|append)(?:ix|ex)$/i, '$1ices'],
    [/(m|l)(?:ice|ouse)$/i, '$1ice'],
    [/(pe)(?:rson|ople)$/i, '$1ople'],
    [/(child)(?:ren)?$/i, '$1ren'],
    [/eaux$/i, '$0'],
    [/m[ae]n$/i, 'men'],
    ['thou', 'you']
  ].forEach(function (rule) {
    return pluralize.addPluralRule(rule[0], rule[1]);
  });

  /**
   * Singularization rules.
   */
  [
    [/s$/i, ''],
    [/(ss)$/i, '$1'],
    [/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(?:sis|ses)$/i, '$1sis'],
    [/(^analy)(?:sis|ses)$/i, '$1sis'],
    [/(wi|kni|(?:after|half|high|low|mid|non|night|[^\w]|^)li)ves$/i, '$1fe'],
    [/(ar|(?:wo|[ae])l|[eo][ao])ves$/i, '$1f'],
    [/([^aeiouy]|qu)ies$/i, '$1y'],
    [/(^[pl]|zomb|^(?:neck)?t|[aeo][lt]|cut)ies$/i, '$1ie'],
    [/(\b(?:mon|smil))ies$/i, '$1ey'],
    [/(m|l)ice$/i, '$1ouse'],
    [/(seraph|cherub)im$/i, '$1'],
    [/(x|ch|ss|sh|zz|tto|go|cho|alias|[^aou]us|tlas|gas|(?:her|at|gr)o|ris)(?:es)?$/i, '$1'],
    [/(e[mn]u)s?$/i, '$1'],
    [/(movie|twelve)s$/i, '$1'],
    [/(cris|test|diagnos)(?:is|es)$/i, '$1is'],
    [/(alumn|syllab|octop|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i, '$1us'],
    [/(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|quor)a$/i, '$1um'],
    [/(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)a$/i, '$1on'],
    [/(alumn|alg|vertebr)ae$/i, '$1a'],
    [/(cod|mur|sil|vert|ind)ices$/i, '$1ex'],
    [/(matr|append)ices$/i, '$1ix'],
    [/(pe)(rson|ople)$/i, '$1rson'],
    [/(child)ren$/i, '$1'],
    [/(eau)x?$/i, '$1'],
    [/men$/i, 'man']
  ].forEach(function (rule) {
    return pluralize.addSingularRule(rule[0], rule[1]);
  });

  /**
   * Uncountable rules.
   */
  [
    // Singular words with no plurals.
    'advice',
    'adulthood',
    'agenda',
    'aid',
    'alcohol',
    'ammo',
    'athletics',
    'bison',
    'blood',
    'bream',
    'buffalo',
    'butter',
    'carp',
    'cash',
    'chassis',
    'chess',
    'clothing',
    'commerce',
    'cod',
    'cooperation',
    'corps',
    'digestion',
    'debris',
    'diabetes',
    'energy',
    'equipment',
    'elk',
    'excretion',
    'expertise',
    'flounder',
    'fun',
    'gallows',
    'garbage',
    'graffiti',
    'headquarters',
    'health',
    'herpes',
    'highjinks',
    'homework',
    'housework',
    'information',
    'jeans',
    'justice',
    'kudos',
    'labour',
    'literature',
    'machinery',
    'mackerel',
    'mail',
    'media',
    'mews',
    'moose',
    'music',
    'news',
    'pike',
    'plankton',
    'pliers',
    'pollution',
    'premises',
    'rain',
    'research',
    'rice',
    'salmon',
    'scissors',
    'series',
    'sewage',
    'shambles',
    'shrimp',
    'species',
    'staff',
    'swine',
    'trout',
    'traffic',
    'transporation',
    'tuna',
    'wealth',
    'welfare',
    'whiting',
    'wildebeest',
    'wildlife',
    'you',
    // Regexes.
    /pox$/i, // "chickpox", "smallpox"
    /ois$/i,
    /deer$/i, // "deer", "reindeer"
    /fish$/i, // "fish", "blowfish", "angelfish"
    /sheep$/i,
    /measles$/i,
    /[^aeiou]ese$/i // "chinese", "japanese"
  ].forEach(pluralize.addUncountableRule);

  return pluralize;
});
<|endoftext|>


<commit_before>var Stroke = (function() {
  var predefined_patterns = {
    'dotted': [1, 1],
    'dashed': [2, 2]
  };

  return _class("Stroke", {
    props: {
      color: null,
      width: null,
      pattern: null
    },

    class_methods: {
      parseDashString: function Stroke_parseDashString(str) {
        var retval;
        if ((retval = predefined_patterns[str]))
          return retval;

        retval = [];
        var last = '-';
        var segment_length = 0;
        for (var i in str) {
          var c = str[i];
          if (c != '-' && c != '_')
            throw new ValueError("Invalid character: '" + c + "' occurred in " + str);
          if (last != str[i]) {
            retval.push(segment_length);
            segment_length = 0;
            last = c;
          }
          segment_length++;
        }
        retval.push(segment_length);
        return retval;
      }
    },

    methods: {
      init: function Stroke_init() {
        switch (arguments.length) {
        case 0:
          break;
        case 1:
          if (typeof arguments[0] == 'string' || arguments[0] instanceof String) {
            this.initWithString.apply(this, arguments);
            break;
          }
          /* fall throigh */
        case 2:
        case 3:
          this.initWithArguments.apply(this, arguments);
          break;
        default:
          throw new ArgumentError("constructor expects 0, 1, 2 or 3 arguments, got " + arguments.length);
        }
      },

      initWithArguments: function Stroke_initWithArguments(color, width, pattern) {
        this.color = color;
        this.width = width === void(0) ? null: width;
        this.pattern = pattern === void(0) ? null: pattern;
      },

      initWithString: function Stroke_initWithString(str) {
        var rt = null;

        var tokens = str.split(/\s+/);
        if (tokens[0] == '')
          tokens.shift();
        if (tokens[tokens.length - 1] == '')
          tokens.pop();
        if (tokens.length == 0 || tokens.length > 3)
          throw new ValueError("Invalid number of tokens: " + str);

        this.initWithArguments(
          new Color(tokens[0]),
          tokens.length >= 2 ? xparseInt(tokens[1]): null,
          tokens.length >= 3 ? this.constructor.parseDashString(tokens[2]): null
        );
      }
    }
  });
})();

/*
 * vim: sts=2 sw=2 ts=2 et
 */
<commit_msg>add solid and double StrokeStyle.<commit_after>var Stroke = (function() {
  var predefined_patterns = {
    'solid':  [],
    'dotted': [1, 1],
    'dashed': [2, 2],
    'double': []
  };

  return _class("Stroke", {
    props: {
      color: null,
      width: null,
      pattern: null
    },

    class_methods: {
      parseDashString: function Stroke_parseDashString(str) {
        var retval;
        if ((retval = predefined_patterns[str]))
          return retval;

        retval = [];
        var last = '-';
        var segment_length = 0;
        for (var i in str) {
          var c = str[i];
          if (c != '-' && c != '_')
            throw new ValueError("Invalid character: '" + c + "' occurred in " + str);
          if (last != str[i]) {
            retval.push(segment_length);
            segment_length = 0;
            last = c;
          }
          segment_length++;
        }
        retval.push(segment_length);
        return retval;
      }
    },

    methods: {
      init: function Stroke_init() {
        switch (arguments.length) {
        case 0:
          break;
        case 1:
          if (typeof arguments[0] == 'string' || arguments[0] instanceof String) {
            this.initWithString.apply(this, arguments);
            break;
          }
          /* fall throigh */
        case 2:
        case 3:
          this.initWithArguments.apply(this, arguments);
          break;
        default:
          throw new ArgumentError("constructor expects 0, 1, 2 or 3 arguments, got " + arguments.length);
        }
      },

      initWithArguments: function Stroke_initWithArguments(color, width, pattern) {
        this.color = color;
        this.width = width === void(0) ? null: width;
        this.pattern = pattern === void(0) ? null: pattern;
      },

      initWithString: function Stroke_initWithString(str) {
        var rt = null;

        var tokens = str.split(/\s+/);
        if (tokens[0] == '')
          tokens.shift();
        if (tokens[tokens.length - 1] == '')
          tokens.pop();
        if (tokens.length == 0 || tokens.length > 3)
          throw new ValueError("Invalid number of tokens: " + str);

        this.initWithArguments(
          new Color(tokens[0]),
          tokens.length >= 2 ? xparseInt(tokens[1]): null,
          tokens.length >= 3 ? this.constructor.parseDashString(tokens[2]): null
        );
      }
    }
  });
})();

/*
 * vim: sts=2 sw=2 ts=2 et
 */
<|endoftext|>


<commit_before>(function($) {

	var MainMenuRoom = this.MainMenuRoom = this.Room.extend({
		tinyWidth: 340,
		bestWidth: 628,
		events: {
			'click .button': 'clickButton',
			'keydown textarea': 'keyPress',
			'click .username': 'clickUsername',
			'click .closebutton': 'closePM',
			'click .pm-window': 'clickPMBackground',
			'focus textarea': 'onFocusPM',
			'blur textarea': 'onBlurPM'
		},
		initialize: function() {
			// left menu
			var buf = '

';
			buf += '

<button class="button big" value="search">Look for a battle</button>

';
			buf += '

<button class="button" value="teambuilder">Teambuilder</button>

<button class="button" value="ladder">Ladder</button>

';

			// right menu
			buf += '

<button class="button" value="lobby">Join lobby chat</button>

';
			this.$el.html(buf);

			this.$activityMenu = $('.activitymenu');
			this.$pmBox = this.$activityMenu.find('.pmbox');

			app.on('init:formats', this.updateSearch, this);
			this.updateSearch();
		},
		addPM: function(name, message, target) {
			var oName = name;
			if (toId(name) === app.user.get('userid')) {
				oName = target;
			}

			var $pmWindow = this.openPM(oName);

			var $chatFrame = $pmWindow.find('.pm-log');
			var $chat = $pmWindow.find('.inner');
			if ($chatFrame.scrollTop() + 60 >= $chat.height() - $chatFrame.height()) {
				autoscroll = true;
			}

			var timestamp = ChatRoom.getTimestamp('pms');
			var color = hashColor(toId(name));
			var clickableName = '' + Tools.escapeHTML(name.substr(1)) + '';
			if (name.substr(0, 1) !== ' ') clickableName = '' + Tools.escapeHTML(name.substr(0, 1)) + ''+clickableName;
			$chat.append('
' + timestamp + '' + clickableName + ': ' + messageSanitize(message) + '
');

			if (autoscroll) {
				$chatFrame.scrollTop($chat.height());
			}
		},
		openPM: function(name) {
			var userid = toId(name);
			var $pmWindow = this.$pmBox.find('.pm-window-'+userid);
			if (!$pmWindow.length) {
				group = name.charAt(0);
				if (group === ' ') {
					group = '';
				} else {
					group = ''+Tools.escapeHTML(group)+'';
				}
				var buf = '

<button class="closebutton" href="'+app.root+'teambuilder"></button>'+group+Tools.escapeHTML(name.substr(1))+'

';
				buf += '
<form class="chatbox nolabel"><textarea class="textbox" type="text" size="70" autocomplete="off" name="message"></textarea></form>

';
				$pmWindow = $(buf).prependTo(this.$pmBox);
				$pmWindow.find('textarea').autoResize({
					animate: false,
					extraSpace: 0
				});
			} else {
				$pmWindow.show();
				var $chatFrame = $pmWindow.find('.pm-log');
				var $chat = $pmWindow.find('.inner');
				$chatFrame.scrollTop($chat.height());
			}
			this.$el.scrollTop(0);
			return $pmWindow;
		},
		closePM: function(e) {
			var userid;
			if (e.currentTarget) {
				e.preventDefault();
				e.stopPropagation();
				userid = $(e.currentTarget).closest('.pm-window').data('userid');
			} else {
				userid = toId(e);
			}
			this.$pmBox.find('.pm-window-'+userid).hide();
		},
		focusPM: function(name) {
			this.openPM(name).prependTo(this.$pmBox).find('textarea[name=message]').focus();
		},
		onFocusPM: function(e) {
			$(e.currentTarget).closest('.pm-window').addClass('focused');
		},
		onBlurPM: function(e) {
			$(e.currentTarget).closest('.pm-window').removeClass('focused');
		},
		keyPress: function(e) {
			if (e.keyCode === 13 && !e.shiftKey) { // Enter
				var $target = $(e.currentTarget);
				e.preventDefault();
				e.stopPropagation();
				var text;
				if ((text = $target.val())) {
					// this.tabComplete.reset();
					// this.chatHistory.push(text);
					var userid = $target.closest('.pm-window').data('userid');
					text = ('\n'+text).replace(/\n/g, '\n/pm '+userid+', ').substr(1);
					this.send(text);
					$(e.currentTarget).val('');
				}
			} else if (e.keyCode === 27) { // Esc
				this.closePM(e);
			}
		},
		clickUsername: function(e) {
			e.stopPropagation();
			var name = $(e.currentTarget).data('name');
			app.addPopup('user', UserPopup, {name: name, sourceEl: e.currentTarget});
		},
		clickPMBackground: function(e) {
			if (!e.shiftKey && !e.cmdKey && !e.ctrlKey) {
				if (window.getSelection && !window.getSelection().isCollapsed) {
					return;
				}
				app.dismissPopups();
				$(e.currentTarget).find('textarea[name=message]').focus();
			}
		},
		updateSearch: function() {
			if (window.BattleFormats) {
				this.$('.mainmenu button.big').html('Look for a battle').removeClass('disabled');
			} else {
				this.$('.mainmenu button.big').html('Connecting...').addClass('disabled');
			}
		},
		updateRightMenu: function() {
			if (app.sideRoom) {
				this.$('.rightmenu').hide();
			} else {
				this.$('.rightmenu').show();
			}
		},
		clickButton: function(e) {
			e.preventDefault();
			var $target = $(e.currentTarget);
			switch ($target.val()) {
			case 'search':
				alert('we don\'t support battles yet :(');
				break;
			case 'lobby':
				app.joinRoom('lobby');
				break;
			case 'teambuilder':
				app.joinRoom('teambuilder');
				break;
			case 'ladder':
				app.joinRoom('ladder');
				break;
			}
		}
	});

}).call(this, jQuery);
<commit_msg>Don't tab PM close buttons (Esc is already available as a keyboard shortcut to close PMs)<commit_after>(function($) {

	var MainMenuRoom = this.MainMenuRoom = this.Room.extend({
		tinyWidth: 340,
		bestWidth: 628,
		events: {
			'click .button': 'clickButton',
			'keydown textarea': 'keyPress',
			'click .username': 'clickUsername',
			'click .closebutton': 'closePM',
			'click .pm-window': 'clickPMBackground',
			'focus textarea': 'onFocusPM',
			'blur textarea': 'onBlurPM'
		},
		initialize: function() {
			// left menu
			var buf = '

';
			buf += '

<button class="button big" value="search">Look for a battle</button>

';
			buf += '

<button class="button" value="teambuilder">Teambuilder</button>

<button class="button" value="ladder">Ladder</button>

';

			// right menu
			buf += '

<button class="button" value="lobby">Join lobby chat</button>

';
			this.$el.html(buf);

			this.$activityMenu = $('.activitymenu');
			this.$pmBox = this.$activityMenu.find('.pmbox');

			app.on('init:formats', this.updateSearch, this);
			this.updateSearch();
		},
		addPM: function(name, message, target) {
			var oName = name;
			if (toId(name) === app.user.get('userid')) {
				oName = target;
			}

			var $pmWindow = this.openPM(oName);

			var $chatFrame = $pmWindow.find('.pm-log');
			var $chat = $pmWindow.find('.inner');
			if ($chatFrame.scrollTop() + 60 >= $chat.height() - $chatFrame.height()) {
				autoscroll = true;
			}

			var timestamp = ChatRoom.getTimestamp('pms');
			var color = hashColor(toId(name));
			var clickableName = '' + Tools.escapeHTML(name.substr(1)) + '';
			if (name.substr(0, 1) !== ' ') clickableName = '' + Tools.escapeHTML(name.substr(0, 1)) + ''+clickableName;
			$chat.append('
' + timestamp + '' + clickableName + ': ' + messageSanitize(message) + '
');

			if (autoscroll) {
				$chatFrame.scrollTop($chat.height());
			}
		},
		openPM: function(name) {
			var userid = toId(name);
			var $pmWindow = this.$pmBox.find('.pm-window-'+userid);
			if (!$pmWindow.length) {
				group = name.charAt(0);
				if (group === ' ') {
					group = '';
				} else {
					group = ''+Tools.escapeHTML(group)+'';
				}
				var buf = '

<button class="closebutton" href="'+app.root+'teambuilder" tabindex="-1"></button>'+group+Tools.escapeHTML(name.substr(1))+'

';
				buf += '
<form class="chatbox nolabel"><textarea class="textbox" type="text" size="70" autocomplete="off" name="message"></textarea></form>

';
				$pmWindow = $(buf).prependTo(this.$pmBox);
				$pmWindow.find('textarea').autoResize({
					animate: false,
					extraSpace: 0
				});
			} else {
				$pmWindow.show();
				var $chatFrame = $pmWindow.find('.pm-log');
				var $chat = $pmWindow.find('.inner');
				$chatFrame.scrollTop($chat.height());
			}
			this.$el.scrollTop(0);
			return $pmWindow;
		},
		closePM: function(e) {
			var userid;
			if (e.currentTarget) {
				e.preventDefault();
				e.stopPropagation();
				userid = $(e.currentTarget).closest('.pm-window').data('userid');
			} else {
				userid = toId(e);
			}
			this.$pmBox.find('.pm-window-'+userid).hide();
		},
		focusPM: function(name) {
			this.openPM(name).prependTo(this.$pmBox).find('textarea[name=message]').focus();
		},
		onFocusPM: function(e) {
			$(e.currentTarget).closest('.pm-window').addClass('focused');
		},
		onBlurPM: function(e) {
			$(e.currentTarget).closest('.pm-window').removeClass('focused');
		},
		keyPress: function(e) {
			if (e.keyCode === 13 && !e.shiftKey) { // Enter
				var $target = $(e.currentTarget);
				e.preventDefault();
				e.stopPropagation();
				var text;
				if ((text = $target.val())) {
					// this.tabComplete.reset();
					// this.chatHistory.push(text);
					var userid = $target.closest('.pm-window').data('userid');
					text = ('\n'+text).replace(/\n/g, '\n/pm '+userid+', ').substr(1);
					this.send(text);
					$(e.currentTarget).val('');
				}
			} else if (e.keyCode === 27) { // Esc
				this.closePM(e);
			}
		},
		clickUsername: function(e) {
			e.stopPropagation();
			var name = $(e.currentTarget).data('name');
			app.addPopup('user', UserPopup, {name: name, sourceEl: e.currentTarget});
		},
		clickPMBackground: function(e) {
			if (!e.shiftKey && !e.cmdKey && !e.ctrlKey) {
				if (window.getSelection && !window.getSelection().isCollapsed) {
					return;
				}
				app.dismissPopups();
				$(e.currentTarget).find('textarea[name=message]').focus();
			}
		},
		updateSearch: function() {
			if (window.BattleFormats) {
				this.$('.mainmenu button.big').html('Look for a battle').removeClass('disabled');
			} else {
				this.$('.mainmenu button.big').html('Connecting...').addClass('disabled');
			}
		},
		updateRightMenu: function() {
			if (app.sideRoom) {
				this.$('.rightmenu').hide();
			} else {
				this.$('.rightmenu').show();
			}
		},
		clickButton: function(e) {
			e.preventDefault();
			var $target = $(e.currentTarget);
			switch ($target.val()) {
			case 'search':
				alert('we don\'t support battles yet :(');
				break;
			case 'lobby':
				app.joinRoom('lobby');
				break;
			case 'teambuilder':
				app.joinRoom('teambuilder');
				break;
			case 'ladder':
				app.joinRoom('ladder');
				break;
			}
		}
	});

}).call(this, jQuery);
<|endoftext|>


<commit_before>Vue.component('status', {
    props: ['status_item', 'poster_data', 'users'],
    data: function() {
        return {
            localComments: Object.assign({}, this.status_item.comments),
            comment_message: '',
            notice: null,
            place_holder: 'leave a comment',
            post_comment: {
                posterId: smf_member_id,
                statusOwnerId: this.status_item.status_poster_id,
                profileOwnerId: this.status_item.status_owner_id,
                statusId: this.status_item.status_id,
                body: '',
            },
        }
    },
    template: `

                {{status_item.status_time | formatDate}}

                {{status_item.status_body}}

            <comment
                v-for='comment in localComments'
                v-bind:comment='comment'
                v-bind:comment_poster_data='getUserData(comment.comments_poster_id)'
                v-bind:key='comment.comments_id'
                class='windowbg'>
            </comment>
            <message-box
                v-if="notice !== null"
                 @close="clearNotice()"
                 v-bind:type="notice.type">
                {{notice.message}}
            </message-box>

                <textarea
                    v-model="post_comment.body"
                    class="post_comment"
                    :placeholder="place_holder"
                    @focus="clearNotice()"></textarea>

                <input type="submit" @click="postComment()" class="button">

`,
    filters: {
        formatDate: function(unixTime) {
            return moment.unix(unixTime).format('LLLL')
        },
    },
    methods: {
        avatarImage: function (posterImageHref) {
            return { backgroundImage: 'url(' + posterImageHref + ')' }
        },
        getUserData: function (userId) {
            return this.users[userId];
        },
        setLocalComment: function(comments){
            this.localComments = Object.assign({}, this.localComments, comments)
        },
        clearPostComment: function(){
            this.post_comment.body = '';

            setTimeout(this.clearNotice,5000)
        },
        postComment: function () {
            this.clearNotice();

            if (!this.isValidComment())
                return false;

            axios.post(smf_scripturl + '?action=breezeComment;sa=postComment;'+ smf_session_var +'='+ smf_session_id,
                this.post_comment).then(response => {

                    this.setNotice(response.data.message, response.data.type);

                    if (response.data.content){
                        this.$root.setUserData(response.data.content.users)
                        this.setLocalComment(response.data.content.comments);
                    }

                    this.clearPostComment();
            });
        },
        isValidComment: function () {
            if (this.post_comment.body === '' || typeof(this.post_comment.body) !== 'string' )
            {
                this.setNotice('el body esta vacio');

                return false;
            }

            if (this.post_comment.body === 'about:Suki')
            {
                alert('Back against the wall and odds\n' +
                    'With the strength of a will and a cause\n' +
                    'Your pursuits are called outstanding\n' +
                    'You\'re emotionally complex');

                return false;
            }

            return true;
        },
        setNotice: function(message, type){
            type = type || 'error';

            this.notice = {
                'message': message,
                'type': type + 'box',
            };
        },
        clearNotice: function(){
            this.notice = null;
        }
    }
})
<commit_msg>fix: use db column names<commit_after>Vue.component('status', {
    props: ['status_item', 'poster_data', 'users'],
    data: function() {
        return {
            localComments: Object.assign({}, this.status_item.comments),
            comment_message: '',
            notice: null,
            place_holder: 'leave a comment',
            post_comment: {
                comments_poster_id: smf_member_id,
                comments_status_owner_id: this.status_item.status_poster_id,
                comments_profile_id: this.status_item.status_owner_id,
                comments_status_id: this.status_item.status_id,
                body: '',
            },
        }
    },
    template: `

                {{status_item.status_time | formatDate}}

                {{status_item.status_body}}

            <comment
                v-for='comment in localComments'
                v-bind:comment='comment'
                v-bind:comment_poster_data='getUserData(comment.comments_poster_id)'
                v-bind:key='comment.comments_id'
                class='windowbg'>
            </comment>
            <message-box
                v-if="notice !== null"
                 @close="clearNotice()"
                 v-bind:type="notice.type">
                {{notice.message}}
            </message-box>

                <textarea
                    v-model="post_comment.body"
                    class="post_comment"
                    :placeholder="place_holder"
                    @focus="clearNotice()"></textarea>

                <input type="submit" @click="postComment()" class="button">

`,
    filters: {
        formatDate: function(unixTime) {
            return moment.unix(unixTime).format('LLLL')
        },
    },
    methods: {
        avatarImage: function (posterImageHref) {
            return { backgroundImage: 'url(' + posterImageHref + ')' }
        },
        getUserData: function (userId) {
            return this.users[userId];
        },
        setLocalComment: function(comments){
            this.localComments = Object.assign({}, this.localComments, comments)
        },
        clearPostComment: function(){
            this.post_comment.body = '';

            setTimeout(this.clearNotice,5000)
        },
        postComment: function () {
            this.clearNotice();

            if (!this.isValidComment())
                return false;

            axios.post(smf_scripturl + '?action=breezeComment;sa=postComment;'+ smf_session_var +'='+ smf_session_id,
                this.post_comment).then(response => {

                    this.setNotice(response.data.message, response.data.type);

                    if (response.data.content){
                        this.$root.setUserData(response.data.content.users)
                        this.setLocalComment(response.data.content.comments);
                    }

                    this.clearPostComment();
            });
        },
        isValidComment: function () {
            if (this.post_comment.body === '' || typeof(this.post_comment.body) !== 'string' )
            {
                this.setNotice('el body esta vacio');

                return false;
            }

            if (this.post_comment.body === 'about:Suki')
            {
                alert('Back against the wall and odds\n' +
                    'With the strength of a will and a cause\n' +
                    'Your pursuits are called outstanding\n' +
                    'You\'re emotionally complex');

                return false;
            }

            return true;
        },
        setNotice: function(message, type){
            type = type || 'error';

            this.notice = {
                'message': message,
                'type': type + 'box',
            };
        },
        clearNotice: function(){
            this.notice = null;
        }
    }
})
<|endoftext|>


<commit_before>// Copyright 2015-2018, University of Colorado Boulder

/**
 * Lightweight event & listener abstraction for a single event type.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
define( require => {
  'use strict';

  // modules
  const axon = require( 'AXON/axon' );
  const EmitterIO = require( 'AXON/EmitterIO' );
  const PhetioObject = require( 'TANDEM/PhetioObject' );
  const Tandem = require( 'TANDEM/Tandem' );
  const Validator = require( 'AXON/Validator' );

  // constants
  const EmitterIOWithNoArgs = EmitterIO( [] );

  // Simulations have thousands of Emitters, so we re-use objects where possible.
  const EMPTY_ARRAY = [];
  assert && Object.freeze( EMPTY_ARRAY );

  /**
   * @param {Object} [options]
   */
  class Emitter extends PhetioObject {
    constructor( options ) {

      options = _.extend( {

        // {Array.<Object>|null} - array of "Validator Options" Objects that hold options for how to validate each
        // argument, see Validator.js for details.
        argumentTypes: EMPTY_ARRAY,

        tandem: Tandem.optional,
        phetioState: false,
        phetioType: EmitterIOWithNoArgs, // subtypes can override with EmitterIO([...]), see EmitterIO.js
        before: null, // {function} [before] optional function to call before listeners
        after: null // {function} [before] optional function to call after listeners
      }, options );

      assert && assert( !options.hasOwnProperty( 'listener' ), 'listener option no longer supported, please use before' );

      // phetioPlayback events need to know the order the arguments occur in order to call EmitterIO.emit()
      // Indicate whether the event is for playback, but leave this "sparse"--only indicate when this happens to be true
      if ( options.phetioPlayback ) {
        options.phetioEventMetadata = options.phetioEventMetadata || {};
        assert && assert( !options.phetioEventMetadata.hasOwnProperty( 'dataKeys' ), 'dataKeys should be supplied by Emitter, not elsewhere' );
        options.phetioEventMetadata.dataKeys = options.phetioType.elements.map( element => element.name );
      }

      super( options );

      Validator.validate( options.argumentTypes, { valueType: Array } );

      if ( assert ) {

        // Iterate through all argumentType validator options and make sure that they won't validate options on validating value
        options.argumentTypes.forEach( validatorOptions => {
          assert && assert(
            validatorOptions.validateOptionsOnValidateValue === undefined,
            'emitter sets its own validateOptionsOnValidateValue for each argument type'
          );
          validatorOptions.validateOptionsOnValidateValue = false;

          // Changing the validator options after construction indicates a logic error
          assert && Object.freeze( validatorOptions );

          // validate the options passed in to validate each emitter argument
          Validator.validateOptions( validatorOptions );
        } );

        // Changing after construction indicates a logic error
        assert && Object.freeze( options.argumentTypes );
      }

      // @private {function|false}
      this.validate = assert && function() {
        assert(
          arguments.length === options.argumentTypes.length,
          `Emitted unexpected number of args. Expected: ${options.argumentTypes.length} and received ${arguments.length}`
        );
        for ( let i = 0; i < options.argumentTypes.length; i++ ) {
          Validator.validate( arguments[ i ], options.argumentTypes[ i ] );
        }
      };

      // @private {function[]} - the listeners that will be called on emit
      this.listeners = [];

      // @private {function[][]} - during emit() keep track of which listeners should receive events in order to manage
      //                         - removal of listeners during emit()
      this.activeListenersStack = [];

      // @private {function|undefined} if defined, called before listeners are notified
      this.before = options.before;

      // @private {function|undefined} if defined, called after listeners are notified
      this.after = options.after;
    }

    /**
     * Dispose an Emitter that is no longer used.  Like Property.dispose, this method checks that there are no leaked
     * listeners.
     */
    dispose() {

      // See https://github.com/phetsims/axon/issues/124
      this.listeners.length = 0;

      PhetioObject.prototype.dispose.call( this );
    }

    /**
     * Adds a listener which will be called during emit.
     * @param {function} listener
     * @public
     */
    addListener( listener ) {

      assert && assert( this.listeners.indexOf( listener ) === -1, 'Cannot add the same listener twice' );

      // If a listener is added during an emit(), we must make a copy of the current list of listeners--the newly added
      // listener will be available for the next emit() but not the one in progress.  This is to match behavior with
      // removeListener.
      this.defendListeners();

      this.listeners.push( listener );
    }

    /**
     * Removes a listener
     * @param {function} listener
     * @public
     */
    removeListener( listener ) {

      const index = this.listeners.indexOf( listener );

      // Throw an error when removing a non-listener (except when the Emitter has already been disposed, see
      // https://github.com/phetsims/sun/issues/394#issuecomment-419998231
      if ( assert && !this.isDisposed ) {
        assert && assert( index >= 0, 'tried to removeListener on something that wasn\'t a listener' );
      }

      // If an emit is in progress, make a copy of the current list of listeners--the removed listener will remain in
      // the list and be called for this emit call, see #72
      this.defendListeners();

      this.listeners.splice( index, 1 );
    }

    /**
     * Removes all the listeners
     * @public
     */
    removeAllListeners() {
      while ( this.listeners.length > 0 ) {
        this.removeListener( this.listeners[ 0 ] );
      }
    }

    /**
     * If addListener/removeListener is called while emit() is in progress, we must make a defensive copy of the array
     * of listeners before changing the array, and use it for the rest of the notifications until the emit call has
     * completed.
     * @private
     */
    defendListeners() {

      for ( let i = this.activeListenersStack.length - 1; i >= 0; i-- ) {

        // Once we meet a level that was already defended, we can stop, since all previous levels are also defended
        if ( this.activeListenersStack[ i ].defended ) {
          break;
        }
        else {
          const defendedListeners = this.listeners.slice();

          // Mark copies as 'defended' so that it will use the original listeners when emit started and not the modified
          // list.
          defendedListeners.defended = true;
          this.activeListenersStack[ i ] = defendedListeners;
        }
      }
    }

    /**
     * Emits a single event.  This method is called many times in a simulation and must be well-optimized.  Listeners
     * are notified in the order they were added via addListener.
     * @params - expected parameters are based on options.argumentTypes, see constructor
     * @public
     */
    emit() {

      // validate the args
      this.validate && this.validate.apply( this, arguments );

      this.before && this.before.apply( null, arguments );

      // handle phet-io data stream for the emitted event
      if ( this.isPhetioInstrumented() ) {

        // null if there are no arguments.  dataStream.js omits null values for data
        let data = null;
        if ( this.phetioType.elements.length > 0 ) {

          // Enumerate named argsObject for the data stream.
          data = {};
          for ( let i = 0; i < this.phetioType.elements.length; i++ ) {
            const element = this.phetioType.elements[ i ];
            data[ element.name ] = element.type.toStateObject( arguments[ i ] );
          }
        }

        this.phetioStartEvent( 'emitted', data );
      }

      // Notify wired-up listeners, if any
      if ( this.listeners.length > 0 ) {

        this.activeListenersStack.push( this.listeners );
        const lastEntry = this.activeListenersStack.length - 1;

        // Notify listeners
        for ( let i = 0; i < this.activeListenersStack[ lastEntry ].length; i++ ) {
          this.activeListenersStack[ lastEntry ][ i ].apply( null, arguments );
        }

        this.activeListenersStack.pop();
      }
      this.isPhetioInstrumented() && this.phetioEndEvent();

      this.after && this.after.apply( null, arguments );
    }

    /**
     * Emits a single event with one argument.  This is a copy-paste of emit() for performance reasons.
     * @param {*} arg0
     * @public
     * @deprecated - please use emit()
     */
    emit1( arg0 ) {

      this.before && this.before( arg0 );

      this.isPhetioInstrumented() && this.phetioStartEvent( 'emitted', {
        args: [ this.phetioType.parameterTypes[ 0 ].toStateObject( arg0 ) ]
      } );

      if ( this.listeners.length > 0 ) {
        this.activeListenersStack.push( this.listeners );
        const lastEntry = this.activeListenersStack.length - 1;

        for ( let i = 0; i < this.activeListenersStack[ lastEntry ].length; i++ ) {
          this.activeListenersStack[ lastEntry ][ i ]( arg0 );
        }

        this.activeListenersStack.pop();
      }
      this.isPhetioInstrumented() && this.phetioEndEvent();

      this.after && this.after( arg0 );
    }

    /**
     * Emits a single event with two arguments.  This is a copy-paste of emit() for performance reasons.
     * @param {*} arg0
     * @param {*} arg1
     * @public
     * @deprecated - please use emit()
     */
    emit2( arg0, arg1 ) {
      this.before && this.before( arg0, arg1 );
      this.isPhetioInstrumented() && this.phetioStartEvent( 'emitted', {
        args: [
          this.phetioType.parameterTypes[ 0 ].toStateObject( arg0 ),
          this.phetioType.parameterTypes[ 1 ].toStateObject( arg1 )
        ]
      } );

      if ( this.listeners.length > 0 ) {
        this.activeListenersStack.push( this.listeners );
        const lastEntry = this.activeListenersStack.length - 1;

        for ( let i = 0; i < this.activeListenersStack[ lastEntry ].length; i++ ) {
          this.activeListenersStack[ lastEntry ][ i ]( arg0, arg1 );
        }
        this.activeListenersStack.pop();
      }
      this.isPhetioInstrumented() && this.phetioEndEvent();
      this.after && this.after( arg0, arg1 );
    }

    /**
     * Emits a single event with three arguments.  This is a copy-paste of emit() for performance reasons.
     * @param {*} arg0
     * @param {*} arg1
     * @param {*} arg2
     * @public
     * @deprecated - please use emit()
     */
    emit3( arg0, arg1, arg2 ) {
      this.before && this.before( arg0, arg1, arg2 );
      this.isPhetioInstrumented() && this.phetioStartEvent( 'emitted', {
        args: [
          this.phetioType.parameterTypes[ 0 ].toStateObject( arg0 ),
          this.phetioType.parameterTypes[ 1 ].toStateObject( arg1 ),
          this.phetioType.parameterTypes[ 2 ].toStateObject( arg2 )
        ]
      } );

      if ( this.listeners.length > 0 ) {
        this.activeListenersStack.push( this.listeners );
        const lastEntry = this.activeListenersStack.length - 1;

        for ( let i = 0; i < this.activeListenersStack[ lastEntry ].length; i++ ) {
          this.activeListenersStack[ lastEntry ][ i ]( arg0, arg1, arg2 );
        }

        this.activeListenersStack.pop();
      }
      this.isPhetioInstrumented() && this.phetioEndEvent();
      this.after && this.after( arg0, arg1, arg2 );
    }

    /**
     * Checks whether a listener is registered with this Emitter
     * @param {function} listener
     * @returns {boolean}
     * @public
     */
    hasListener( listener ) {
      assert && assert( arguments.length === 1, 'Emitter.hasListener should be called with 1 argument' );
      return this.listeners.indexOf( listener ) >= 0;
    }

    /**
     * Returns true if there are any listeners.
     * @returns {boolean}
     * @public
     */
    hasListeners() {
      assert && assert( arguments.length === 0, 'Emitter.hasListeners should be called without arguments' );
      return this.listeners.length > 0;
    }

    /**
     * Returns the number of listeners.
     * @returns {number}
     * @public
     */
    getListenerCount() {
      return this.listeners.length;
    }
  }

  return axon.register( 'Emitter', Emitter );
} );<commit_msg>Use null instead of this for validation function, see https://github.com/phetsims/axon/issues/216<commit_after>// Copyright 2015-2018, University of Colorado Boulder

/**
 * Lightweight event & listener abstraction for a single event type.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
define( require => {
  'use strict';

  // modules
  const axon = require( 'AXON/axon' );
  const EmitterIO = require( 'AXON/EmitterIO' );
  const PhetioObject = require( 'TANDEM/PhetioObject' );
  const Tandem = require( 'TANDEM/Tandem' );
  const Validator = require( 'AXON/Validator' );

  // constants
  const EmitterIOWithNoArgs = EmitterIO( [] );

  // Simulations have thousands of Emitters, so we re-use objects where possible.
  const EMPTY_ARRAY = [];
  assert && Object.freeze( EMPTY_ARRAY );

  /**
   * @param {Object} [options]
   */
  class Emitter extends PhetioObject {
    constructor( options ) {

      options = _.extend( {

        // {Array.<Object>|null} - array of "Validator Options" Objects that hold options for how to validate each
        // argument, see Validator.js for details.
        argumentTypes: EMPTY_ARRAY,

        tandem: Tandem.optional,
        phetioState: false,
        phetioType: EmitterIOWithNoArgs, // subtypes can override with EmitterIO([...]), see EmitterIO.js
        before: null, // {function} [before] optional function to call before listeners
        after: null // {function} [before] optional function to call after listeners
      }, options );

      assert && assert( !options.hasOwnProperty( 'listener' ), 'listener option no longer supported, please use before' );

      // phetioPlayback events need to know the order the arguments occur in order to call EmitterIO.emit()
      // Indicate whether the event is for playback, but leave this "sparse"--only indicate when this happens to be true
      if ( options.phetioPlayback ) {
        options.phetioEventMetadata = options.phetioEventMetadata || {};
        assert && assert( !options.phetioEventMetadata.hasOwnProperty( 'dataKeys' ), 'dataKeys should be supplied by Emitter, not elsewhere' );
        options.phetioEventMetadata.dataKeys = options.phetioType.elements.map( element => element.name );
      }

      super( options );

      Validator.validate( options.argumentTypes, { valueType: Array } );

      if ( assert ) {

        // Iterate through all argumentType validator options and make sure that they won't validate options on validating value
        options.argumentTypes.forEach( validatorOptions => {
          assert && assert(
            validatorOptions.validateOptionsOnValidateValue === undefined,
            'emitter sets its own validateOptionsOnValidateValue for each argument type'
          );
          validatorOptions.validateOptionsOnValidateValue = false;

          // Changing the validator options after construction indicates a logic error
          assert && Object.freeze( validatorOptions );

          // validate the options passed in to validate each emitter argument
          Validator.validateOptions( validatorOptions );
        } );

        // Changing after construction indicates a logic error
        assert && Object.freeze( options.argumentTypes );
      }

      // @private {function|false}
      this.validate = assert && function() {
        assert(
          arguments.length === options.argumentTypes.length,
          `Emitted unexpected number of args. Expected: ${options.argumentTypes.length} and received ${arguments.length}`
        );
        for ( let i = 0; i < options.argumentTypes.length; i++ ) {
          Validator.validate( arguments[ i ], options.argumentTypes[ i ] );
        }
      };

      // @private {function[]} - the listeners that will be called on emit
      this.listeners = [];

      // @private {function[][]} - during emit() keep track of which listeners should receive events in order to manage
      //                         - removal of listeners during emit()
      this.activeListenersStack = [];

      // @private {function|undefined} if defined, called before listeners are notified
      this.before = options.before;

      // @private {function|undefined} if defined, called after listeners are notified
      this.after = options.after;
    }

    /**
     * Dispose an Emitter that is no longer used.  Like Property.dispose, this method checks that there are no leaked
     * listeners.
     */
    dispose() {

      // See https://github.com/phetsims/axon/issues/124
      this.listeners.length = 0;

      PhetioObject.prototype.dispose.call( this );
    }

    /**
     * Adds a listener which will be called during emit.
     * @param {function} listener
     * @public
     */
    addListener( listener ) {

      assert && assert( this.listeners.indexOf( listener ) === -1, 'Cannot add the same listener twice' );

      // If a listener is added during an emit(), we must make a copy of the current list of listeners--the newly added
      // listener will be available for the next emit() but not the one in progress.  This is to match behavior with
      // removeListener.
      this.defendListeners();

      this.listeners.push( listener );
    }

    /**
     * Removes a listener
     * @param {function} listener
     * @public
     */
    removeListener( listener ) {

      const index = this.listeners.indexOf( listener );

      // Throw an error when removing a non-listener (except when the Emitter has already been disposed, see
      // https://github.com/phetsims/sun/issues/394#issuecomment-419998231
      if ( assert && !this.isDisposed ) {
        assert && assert( index >= 0, 'tried to removeListener on something that wasn\'t a listener' );
      }

      // If an emit is in progress, make a copy of the current list of listeners--the removed listener will remain in
      // the list and be called for this emit call, see #72
      this.defendListeners();

      this.listeners.splice( index, 1 );
    }

    /**
     * Removes all the listeners
     * @public
     */
    removeAllListeners() {
      while ( this.listeners.length > 0 ) {
        this.removeListener( this.listeners[ 0 ] );
      }
    }

    /**
     * If addListener/removeListener is called while emit() is in progress, we must make a defensive copy of the array
     * of listeners before changing the array, and use it for the rest of the notifications until the emit call has
     * completed.
     * @private
     */
    defendListeners() {

      for ( let i = this.activeListenersStack.length - 1; i >= 0; i-- ) {

        // Once we meet a level that was already defended, we can stop, since all previous levels are also defended
        if ( this.activeListenersStack[ i ].defended ) {
          break;
        }
        else {
          const defendedListeners = this.listeners.slice();

          // Mark copies as 'defended' so that it will use the original listeners when emit started and not the modified
          // list.
          defendedListeners.defended = true;
          this.activeListenersStack[ i ] = defendedListeners;
        }
      }
    }

    /**
     * Emits a single event.  This method is called many times in a simulation and must be well-optimized.  Listeners
     * are notified in the order they were added via addListener.
     * @params - expected parameters are based on options.argumentTypes, see constructor
     * @public
     */
    emit() {

      // validate the args
      this.validate && this.validate.apply( null, arguments );

      this.before && this.before.apply( null, arguments );

      // handle phet-io data stream for the emitted event
      if ( this.isPhetioInstrumented() ) {

        // null if there are no arguments.  dataStream.js omits null values for data
        let data = null;
        if ( this.phetioType.elements.length > 0 ) {

          // Enumerate named argsObject for the data stream.
          data = {};
          for ( let i = 0; i < this.phetioType.elements.length; i++ ) {
            const element = this.phetioType.elements[ i ];
            data[ element.name ] = element.type.toStateObject( arguments[ i ] );
          }
        }

        this.phetioStartEvent( 'emitted', data );
      }

      // Notify wired-up listeners, if any
      if ( this.listeners.length > 0 ) {

        this.activeListenersStack.push( this.listeners );
        const lastEntry = this.activeListenersStack.length - 1;

        // Notify listeners
        for ( let i = 0; i < this.activeListenersStack[ lastEntry ].length; i++ ) {
          this.activeListenersStack[ lastEntry ][ i ].apply( null, arguments );
        }

        this.activeListenersStack.pop();
      }
      this.isPhetioInstrumented() && this.phetioEndEvent();

      this.after && this.after.apply( null, arguments );
    }

    /**
     * Emits a single event with one argument.  This is a copy-paste of emit() for performance reasons.
     * @param {*} arg0
     * @public
     * @deprecated - please use emit()
     */
    emit1( arg0 ) {

      this.before && this.before( arg0 );

      this.isPhetioInstrumented() && this.phetioStartEvent( 'emitted', {
        args: [ this.phetioType.parameterTypes[ 0 ].toStateObject( arg0 ) ]
      } );

      if ( this.listeners.length > 0 ) {
        this.activeListenersStack.push( this.listeners );
        const lastEntry = this.activeListenersStack.length - 1;

        for ( let i = 0; i < this.activeListenersStack[ lastEntry ].length; i++ ) {
          this.activeListenersStack[ lastEntry ][ i ]( arg0 );
        }

        this.activeListenersStack.pop();
      }
      this.isPhetioInstrumented() && this.phetioEndEvent();

      this.after && this.after( arg0 );
    }

    /**
     * Emits a single event with two arguments.  This is a copy-paste of emit() for performance reasons.
     * @param {*} arg0
     * @param {*} arg1
     * @public
     * @deprecated - please use emit()
     */
    emit2( arg0, arg1 ) {
      this.before && this.before( arg0, arg1 );
      this.isPhetioInstrumented() && this.phetioStartEvent( 'emitted', {
        args: [
          this.phetioType.parameterTypes[ 0 ].toStateObject( arg0 ),
          this.phetioType.parameterTypes[ 1 ].toStateObject( arg1 )
        ]
      } );

      if ( this.listeners.length > 0 ) {
        this.activeListenersStack.push( this.listeners );
        const lastEntry = this.activeListenersStack.length - 1;

        for ( let i = 0; i < this.activeListenersStack[ lastEntry ].length; i++ ) {
          this.activeListenersStack[ lastEntry ][ i ]( arg0, arg1 );
        }
        this.activeListenersStack.pop();
      }
      this.isPhetioInstrumented() && this.phetioEndEvent();
      this.after && this.after( arg0, arg1 );
    }

    /**
     * Emits a single event with three arguments.  This is a copy-paste of emit() for performance reasons.
     * @param {*} arg0
     * @param {*} arg1
     * @param {*} arg2
     * @public
     * @deprecated - please use emit()
     */
    emit3( arg0, arg1, arg2 ) {
      this.before && this.before( arg0, arg1, arg2 );
      this.isPhetioInstrumented() && this.phetioStartEvent( 'emitted', {
        args: [
          this.phetioType.parameterTypes[ 0 ].toStateObject( arg0 ),
          this.phetioType.parameterTypes[ 1 ].toStateObject( arg1 ),
          this.phetioType.parameterTypes[ 2 ].toStateObject( arg2 )
        ]
      } );

      if ( this.listeners.length > 0 ) {
        this.activeListenersStack.push( this.listeners );
        const lastEntry = this.activeListenersStack.length - 1;

        for ( let i = 0; i < this.activeListenersStack[ lastEntry ].length; i++ ) {
          this.activeListenersStack[ lastEntry ][ i ]( arg0, arg1, arg2 );
        }

        this.activeListenersStack.pop();
      }
      this.isPhetioInstrumented() && this.phetioEndEvent();
      this.after && this.after( arg0, arg1, arg2 );
    }

    /**
     * Checks whether a listener is registered with this Emitter
     * @param {function} listener
     * @returns {boolean}
     * @public
     */
    hasListener( listener ) {
      assert && assert( arguments.length === 1, 'Emitter.hasListener should be called with 1 argument' );
      return this.listeners.indexOf( listener ) >= 0;
    }

    /**
     * Returns true if there are any listeners.
     * @returns {boolean}
     * @public
     */
    hasListeners() {
      assert && assert( arguments.length === 0, 'Emitter.hasListeners should be called without arguments' );
      return this.listeners.length > 0;
    }

    /**
     * Returns the number of listeners.
     * @returns {number}
     * @public
     */
    getListenerCount() {
      return this.listeners.length;
    }
  }

  return axon.register( 'Emitter', Emitter );
} );<|endoftext|>


<commit_before>var express = require('express'),
    path = require('path'),
    bodyParser = require('body-parser'),
    errors = require('common-errors'),
    morgan = require('morgan'),
    services = require('./services'),
    logger = services.logger,
    PORT_NUM = process.env.PORT || '8080',
    app = express();

// Middleware
app.use(morgan('combined'));
app.use(bodyParser.json());

// Routes
app.use(express.static(path.join(__dirname, 'public')));
app.use('/spectate', require('./routes/spectate'));

// Error Handler
app.use(errors.middleware.errorHandler);

// Since its included in the gulp file
if (!module.parent) {
  app.listen(PORT_NUM);
  logger.info('Server listening on port: %s', PORT_NUM );
}
<commit_msg>Exported app<commit_after>var express = require('express'),
    path = require('path'),
    bodyParser = require('body-parser'),
    errors = require('common-errors'),
    morgan = require('morgan'),
    services = require('./services'),
    logger = services.logger,
    PORT_NUM = process.env.PORT || '8080',
    app = express();

module.exports = app;

// Middleware
app.use(morgan('combined'));
app.use(bodyParser.json());

// Routes
app.use(express.static(path.join(__dirname, 'public')));
app.use('/spectate', require('./routes/spectate'));

// Error Handler
app.use(errors.middleware.errorHandler);

// Since its included in the gulp file
if (!module.parent) {
  app.listen(PORT_NUM);
  logger.info('Server listening on port: %s', PORT_NUM );
}
<|endoftext|>