<commit_before>(function() {
    var defaultBindingAttributeName = "data-bind";

    ko.bindingProvider = function() {
        this.bindingCache = {};
    };

    ko.utils.extend(ko.bindingProvider.prototype, {
        'nodeHasBindings': function(node) {
            switch (node.nodeType) {
                case 1: return node.getAttribute(defaultBindingAttributeName) != null;   // Element
                case 8: return ko.virtualElements.virtualNodeBindingValue(node) != null; // Comment node
                default: return false;
            }
        },

        'getBindings': function(node, bindingContext) {
            var bindingsString = this['getBindingsString'](node, bindingContext);
            return bindingsString ? this['parseBindingsString'](bindingsString, bindingContext, node) : null;
        },

        // The following function is only used internally by this default provider.
        // It's not part of the interface definition for a general binding provider.
        'getBindingsString': function(node, bindingContext) {
            switch (node.nodeType) {
                case 1: return node.getAttribute(defaultBindingAttributeName);   // Element
                case 8: return ko.virtualElements.virtualNodeBindingValue(node); // Comment node
                default: return null;
            }
        },

        // The following function is only used internally by this default provider.
        // It's not part of the interface definition for a general binding provider.
        'parseBindingsString': function(bindingsString, bindingContext, node) {
            try {
                var bindingFunction = createBindingsStringEvaluatorViaCache(bindingsString, this.bindingCache);
                return bindingFunction(bindingContext, node);
            } catch (ex) {
                throw new Error("Unable to parse bindings.\nMessage: " + ex + ";\nBindings value: " + bindingsString);
            }
        }
    });

    ko.bindingProvider['instance'] = new ko.bindingProvider();

    function createBindingsStringEvaluatorViaCache(bindingsString, cache) {
        var cacheKey = bindingsString;
        return cache[cacheKey]
            || (cache[cacheKey] = createBindingsStringEvaluator(bindingsString));
    }

    function createBindingsStringEvaluator(bindingsString) {
        // Build the source for a function that evaluates "expression"
        // For each scope variable, add an extra level of "with" nesting
        // Example result: with(sc1) { with(sc0) { return (expression) } }
        var rewrittenBindings = ko.expressionRewriting.preProcessBindings(bindingsString),
            functionBody = "with($context){with($data||{}){return{" + rewrittenBindings + "}}}";
        return new Function("$context", "$element", functionBody);
    }
})();

ko.exportSymbol('bindingProvider', ko.bindingProvider);
<commit_msg>Make sure the original line location is shown<commit_after>(function() {
    var defaultBindingAttributeName = "data-bind";

    ko.bindingProvider = function() {
        this.bindingCache = {};
    };

    ko.utils.extend(ko.bindingProvider.prototype, {
        'nodeHasBindings': function(node) {
            switch (node.nodeType) {
                case 1: return node.getAttribute(defaultBindingAttributeName) != null;   // Element
                case 8: return ko.virtualElements.virtualNodeBindingValue(node) != null; // Comment node
                default: return false;
            }
        },

        'getBindings': function(node, bindingContext) {
            var bindingsString = this['getBindingsString'](node, bindingContext);
            return bindingsString ? this['parseBindingsString'](bindingsString, bindingContext, node) : null;
        },

        // The following function is only used internally by this default provider.
        // It's not part of the interface definition for a general binding provider.
        'getBindingsString': function(node, bindingContext) {
            switch (node.nodeType) {
                case 1: return node.getAttribute(defaultBindingAttributeName);   // Element
                case 8: return ko.virtualElements.virtualNodeBindingValue(node); // Comment node
                default: return null;
            }
        },

        // The following function is only used internally by this default provider.
        // It's not part of the interface definition for a general binding provider.
        'parseBindingsString': function(bindingsString, bindingContext, node) {
            try {
                var bindingFunction = createBindingsStringEvaluatorViaCache(bindingsString, this.bindingCache);
                return bindingFunction(bindingContext, node);
            } catch (ex) {
                ex.message = "Unable to parse bindings.\nBindings value: " + bindingsString + "\nMessage: " + ex.message;
            	throw ex;
            }
        }
    });

    ko.bindingProvider['instance'] = new ko.bindingProvider();

    function createBindingsStringEvaluatorViaCache(bindingsString, cache) {
        var cacheKey = bindingsString;
        return cache[cacheKey]
            || (cache[cacheKey] = createBindingsStringEvaluator(bindingsString));
    }

    function createBindingsStringEvaluator(bindingsString) {
        // Build the source for a function that evaluates "expression"
        // For each scope variable, add an extra level of "with" nesting
        // Example result: with(sc1) { with(sc0) { return (expression) } }
        var rewrittenBindings = ko.expressionRewriting.preProcessBindings(bindingsString),
            functionBody = "with($context){with($data||{}){return{" + rewrittenBindings + "}}}";
        return new Function("$context", "$element", functionBody);
    }
})();

ko.exportSymbol('bindingProvider', ko.bindingProvider);
<|endoftext|>


<commit_before>'use strict';

const utils = require('./utils');
const defaultPowerValues = process.env.defaultPowerValues.split(',');

/**
 * Initialize versions & aliases so we can execute everything in parallel.
 */
module.exports.handler = async(event, context) => {

    const {lambdaARN, num} = event;
    const powerValues = extractPowerValues(event);

    validateInput(lambdaARN, num); // may throw

    // fetch initial $LATEST value so we can reset it later
    const initialPower = await utils.getLambdaPower(lambdaARN);

    // reminder: configuration updates must run sequencially
    // (otherwise you get a ResourceConflictException)
    for (let value of powerValues){
        const alias = 'RAM' + value;
        await utils.createPowerConfiguration(lambdaARN, value, alias);
    }

    await utils.setLambdaPower(lambdaARN, initialPower);

    return powerValues;
};

const extractPowerValues = (event) => {
    var powerValues = event.powerValues; // could be undefined

    // auto-generate all possible values if ALL
    if (powerValues === 'ALL') {
        powerValues = utils.allPowerValues();
    }

    // use default list of values (defined at deploy-time) if not provided
    if (!powerValues || powerValues.length === 0) {
        powerValues = defaultPowerValues;
    }

    return powerValues;
};

const validateInput = (lambdaARN, num) => {
    if (!lambdaARN) {
        throw new Error('Missing or empty lambdaARN');
    }
    if (!num || num < 5) {
        throw new Error('Missing num or num below 5');
    }
};
<commit_msg>typo in comment<commit_after>'use strict';

const utils = require('./utils');
const defaultPowerValues = process.env.defaultPowerValues.split(',');

/**
 * Initialize versions & aliases so we can execute everything in parallel.
 */
module.exports.handler = async(event, context) => {

    const {lambdaARN, num} = event;
    const powerValues = extractPowerValues(event);

    validateInput(lambdaARN, num); // may throw

    // fetch initial $LATEST value so we can reset it later
    const initialPower = await utils.getLambdaPower(lambdaARN);

    // reminder: configuration updates must run sequentially
    // (otherwise you get a ResourceConflictException)
    for (let value of powerValues){
        const alias = 'RAM' + value;
        await utils.createPowerConfiguration(lambdaARN, value, alias);
    }

    await utils.setLambdaPower(lambdaARN, initialPower);

    return powerValues;
};

const extractPowerValues = (event) => {
    var powerValues = event.powerValues; // could be undefined

    // auto-generate all possible values if ALL
    if (powerValues === 'ALL') {
        powerValues = utils.allPowerValues();
    }

    // use default list of values (defined at deploy-time) if not provided
    if (!powerValues || powerValues.length === 0) {
        powerValues = defaultPowerValues;
    }

    return powerValues;
};

const validateInput = (lambdaARN, num) => {
    if (!lambdaARN) {
        throw new Error('Missing or empty lambdaARN');
    }
    if (!num || num < 5) {
        throw new Error('Missing num or num below 5');
    }
};
<|endoftext|>


<commit_before>"use strict";

window.Freya = window.Freya || {};
Freya.confs = {
    changeSubmitButtonHTML: true,
    submitButtonHTMLForAjaxInProcess: ''
};

// Get real width and height even for hidden element
Freya.getRealDimensions = function ($el, outer) {
  if ($el.length == 0) {
    return false;
  }
  var $clone = $el.clone()
    .show()
    .css("visibility","hidden")
    .appendTo("body");
  var result = {
    width:      (outer) ? $clone.outerWidth() : $clone.innerWidth(),
    height:     (outer) ? $clone.outerHeight() : $clone.innerHeight(),
    offsetTop:  $clone.offset().top,
    offsetLeft: $clone.offset().left
  };
  $clone.remove();
  return result;
};

// Clean MSWord tags
Freya.cleanWordHtml = function(input) {
    // 1. remove line breaks / Mso classes
    var stringStripper = /(\n|\r| class=(")?Mso[a-zA-Z]+(")?)/g;
    var output = input.replace(stringStripper, ' ');

    // 2. strip Word generated HTML comments
    var commentSripper = new RegExp('', 'g');
    var output = output.replace(commentSripper, '');

    // 3. remove
    var output = output.replace(/ /gi, '');
    return output;
};

Freya.removeAttributes = function (value) {
    var content = $('
').html(value);
    $(content).find('*').each(function(){
        $(this).removeAttributes();
    });
    return $(content)[0].outerHTML;
};

jQuery.fn.removeAttributes = function() {
    return this.each(function() {
        var attributes = $.map(this.attributes, function(item) {
            return item.name;
        });
        var tag = $(this);
        $.each(attributes, function(i, item) {
            if (item !== 'href'){
                tag.removeAttr(item);
            }
        });
    });
};

// Left only valid tags in the string
Freya.sanitize_html = function(value, allowedTags) {
    value = Freya.cleanWordHtml(value);
    value = Freya.removeAttributes(value);
    if (allowedTags.indexOf("br")) {
      allowedTags.push("br /");
      allowedTags.push("br/");
    }
    var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi;
    return value.replace(tags, function($0, $1) {
        return allowedTags.indexOf($1.toLowerCase()) > -1 ? $0 : '';
    });
};

Freya.disableSubmitBtn = function($btn) {
    $btn.prop("disabled", true);
    if(Freya.confs.changeSubmitButtonHTML) {
        var currentHTML = $btn.html();
        $btn.html(Freya.confs.submitButtonHTMLForAjaxInProcess);
        return currentHTML;
    }
};

Freya.enableSubmitBtn = function($btn, previousHTML) {
    $btn.prop("disabled", false);
    if(Freya.confs.changeSubmitButtonHTML) {
        $btn.html(previousHTML);
    }
};

Freya.getGETparams = function() {
  var params = decodeURIComponent(window.location.search.substring(1)).split("&");
  return params.map(function(item) {
    // to support similar keys, for example for multicheckbox elements
    return {"name": item.match(/[^=]*/i)[0], "value": item.match(/=(.*)/i)[1]}
  });
}

Freya.GETparamsArrayToURL = function(params) {
  return params.map(function(item) {
    return item.name + "=" + item.value;
  }).join("&");
}

// Tests for sanitize_html
//var str, newstr;
//var allowedTags = ['em', 'p', 'ul', 'ol', 'li', 'br', 'a', 'span', 'div', 'strong'];
//
//str = 'Valid tags: em,
p
,

 ' +
//      'span
div
 strong ';
//newstr = Freya.sanitize_html(str, allowedTags);
//console.log('str:', str);
//console.log('new:', newstr);
//if (newstr == str) console.log('pass');
//else console.log('error');
//
//str = '

';
//newstr = Freya.sanitize_html(str, allowedTags);
//console.log('str:', str);
//console.log('new:', newstr);
//if (newstr == '

') console.log('pass');
//else console.log('error');
//
//str = 'Test links: Here is the link';
//newstr = Freya.sanitize_html(str, allowedTags);
//console.log('str:', str);
//console.log('new:', newstr);
//if (newstr == str) console.log('pass');
//else console.log('error');
//
//str = 'Test links with attr: Here is the link';
//newstr = Freya.sanitize_html(str, allowedTags);
//console.log('str:', str);
//console.log('new:', newstr);
//if (newstr != str) console.log('pass');
//else console.log('error');
//
//str = 'Images are not valid: ';
//newstr = Freya.sanitize_html(str, allowedTags);
//console.log('str:', str);
//console.log('new:', newstr);
//if (newstr != str) console.log('pass');
//else console.log('error');
//
//str = 'As well as js scripts and frames:  iframe';
//newstr = Freya.sanitize_html(str, allowedTags);
//console.log('str:', str);
//console.log('newstr:', newstr);
//newstr = Freya.sanitize_html(str, allowedTags);
//if (newstr != str) console.log('pass');
//else console.log('error');
<commit_msg>Bugfix in GETparamsArrayToURL helper<commit_after>"use strict";

window.Freya = window.Freya || {};
Freya.confs = {
    changeSubmitButtonHTML: true,
    submitButtonHTMLForAjaxInProcess: ''
};

// Get real width and height even for hidden element
Freya.getRealDimensions = function ($el, outer) {
  if ($el.length == 0) {
    return false;
  }
  var $clone = $el.clone()
    .show()
    .css("visibility","hidden")
    .appendTo("body");
  var result = {
    width:      (outer) ? $clone.outerWidth() : $clone.innerWidth(),
    height:     (outer) ? $clone.outerHeight() : $clone.innerHeight(),
    offsetTop:  $clone.offset().top,
    offsetLeft: $clone.offset().left
  };
  $clone.remove();
  return result;
};

// Clean MSWord tags
Freya.cleanWordHtml = function(input) {
    // 1. remove line breaks / Mso classes
    var stringStripper = /(\n|\r| class=(")?Mso[a-zA-Z]+(")?)/g;
    var output = input.replace(stringStripper, ' ');

    // 2. strip Word generated HTML comments
    var commentSripper = new RegExp('', 'g');
    var output = output.replace(commentSripper, '');

    // 3. remove
    var output = output.replace(/ /gi, '');
    return output;
};

Freya.removeAttributes = function (value) {
    var content = $('
').html(value);
    $(content).find('*').each(function(){
        $(this).removeAttributes();
    });
    return $(content)[0].outerHTML;
};

jQuery.fn.removeAttributes = function() {
    return this.each(function() {
        var attributes = $.map(this.attributes, function(item) {
            return item.name;
        });
        var tag = $(this);
        $.each(attributes, function(i, item) {
            if (item !== 'href'){
                tag.removeAttr(item);
            }
        });
    });
};

// Left only valid tags in the string
Freya.sanitize_html = function(value, allowedTags) {
    value = Freya.cleanWordHtml(value);
    value = Freya.removeAttributes(value);
    if (allowedTags.indexOf("br")) {
      allowedTags.push("br /");
      allowedTags.push("br/");
    }
    var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi;
    return value.replace(tags, function($0, $1) {
        return allowedTags.indexOf($1.toLowerCase()) > -1 ? $0 : '';
    });
};

Freya.disableSubmitBtn = function($btn) {
    $btn.prop("disabled", true);
    if(Freya.confs.changeSubmitButtonHTML) {
        var currentHTML = $btn.html();
        $btn.html(Freya.confs.submitButtonHTMLForAjaxInProcess);
        return currentHTML;
    }
};

Freya.enableSubmitBtn = function($btn, previousHTML) {
    $btn.prop("disabled", false);
    if(Freya.confs.changeSubmitButtonHTML) {
        $btn.html(previousHTML);
    }
};

Freya.getGETparams = function() {
  var params = decodeURIComponent(window.location.search.substring(1)).split("&");
  return params.map(function(item) {
    // to support similar keys, for example for multicheckbox elements
    return {"name": item.match(/[^=]*/i)[0], "value": item.match(/=(.*)/i)[1]}
  });
}

Freya.GETparamsArrayToURL = function(params) {
  return params.map(function(item) {
    return item.name + "=" + encodeURIComponent(item.value);
  }).join("&");
}

// Tests for sanitize_html
//var str, newstr;
//var allowedTags = ['em', 'p', 'ul', 'ol', 'li', 'br', 'a', 'span', 'div', 'strong'];
//
//str = 'Valid tags: em,
p
,

 ' +
//      'span
div
 strong ';
//newstr = Freya.sanitize_html(str, allowedTags);
//console.log('str:', str);
//console.log('new:', newstr);
//if (newstr == str) console.log('pass');
//else console.log('error');
//
//str = '

';
//newstr = Freya.sanitize_html(str, allowedTags);
//console.log('str:', str);
//console.log('new:', newstr);
//if (newstr == '

') console.log('pass');
//else console.log('error');
//
//str = 'Test links: Here is the link';
//newstr = Freya.sanitize_html(str, allowedTags);
//console.log('str:', str);
//console.log('new:', newstr);
//if (newstr == str) console.log('pass');
//else console.log('error');
//
//str = 'Test links with attr: Here is the link';
//newstr = Freya.sanitize_html(str, allowedTags);
//console.log('str:', str);
//console.log('new:', newstr);
//if (newstr != str) console.log('pass');
//else console.log('error');
//
//str = 'Images are not valid: ';
//newstr = Freya.sanitize_html(str, allowedTags);
//console.log('str:', str);
//console.log('new:', newstr);
//if (newstr != str) console.log('pass');
//else console.log('error');
//
//str = 'As well as js scripts and frames:  iframe';
//newstr = Freya.sanitize_html(str, allowedTags);
//console.log('str:', str);
//console.log('newstr:', newstr);
//newstr = Freya.sanitize_html(str, allowedTags);
//if (newstr != str) console.log('pass');
//else console.log('error');
<|endoftext|>


<commit_before>var tape = require('tape');
var crypto = require('crypto');
var fs = require('fs');
var http = require('http');
var TileJSON = require('..');

var fixtures = {
    'world-bright': JSON.parse(fs.readFileSync(__dirname + '/fixtures/world-bright.tilejson')),
    'grid': JSON.parse(fs.readFileSync(__dirname + '/fixtures/grid.tilejson'))
};

function md5(str) {
    return crypto.createHash('md5').update(str).digest('hex');
}

var world_bright;
var world_bright_ssl;
var grid_source;

tape('setup', function(assert) {
    new TileJSON('tilejson://' + __dirname + '/fixtures/world-bright.tilejson', function(err, source) {
        world_bright = source;
        assert.ifError(err);
        assert.end();
    });
});
tape('setup', function(assert) {
    new TileJSON('tilejson://' + __dirname + '/fixtures/world-bright-ssl.tilejson', function(err, source) {
        world_bright_ssl = source;
        assert.ifError(err);
        assert.end();
    });
});
tape('setup', function(assert) {
    new TileJSON('tilejson://' + __dirname + '/fixtures/grid.tilejson', function(err, source) {
        grid_source = source;
        assert.ifError(err);
        assert.end();
    });
});

(function() {
    function checkTile(source, assert) {
        source.getTile(0, 0, 0, function(err, data, headers) {
            assert.ifError(err);
            assert.ok(!isNaN(Date.parse(headers['Last-Modified'])));
            assert.equal('image/png', headers['Content-Type']);
            assert.equal('string', typeof headers['ETag']);
            assert.equal('string', typeof headers['Cache-Control']);
            assert.equal('943ca1495e3b6e8d84dab88227904190', md5(data));
            assert.end();
        });
    }

    tape('loads directly from data', function(assert) {
        new TileJSON({ data: fixtures['world-bright'] }, function(err, source) {
            assert.ifError(err);
            assert.ok(source.data);
            checkTile(source, assert);
        });
    });

    tape('loads a tilejson file', function(assert) {
        new TileJSON('http://a.tiles.mapbox.com/v3/mapbox.world-bright.json', function(err, source) {
            assert.ifError(err);
            assert.ok(source.data);
            source.getTile(0, 0, 0, function(err, data, headers) {
                assert.ifError(err);
                assert.ok('Cache-Control' in headers);
                assert.equal('943ca1495e3b6e8d84dab88227904190', md5(data));
                assert.end();
            });
        });
    });

    tape('loads a tilejson file with tilejson+http:', function(assert) {
        new TileJSON('tilejson+http://a.tiles.mapbox.com/v3/mapbox.world-bright.json', function(err, source) {
            assert.ifError(err);
            assert.ok(source.data);
            checkTile(source, assert);
            assert.end();
        });
    });

    tape('errors on 404', function(assert) {
        new TileJSON('http://a.tiles.mapbox.com/v3/mapbox.doesnotexist.json', function(err, source) {
            assert.equal('Tileset does not exist', err.message);
            assert.end();
        });
    });

    tape('errors on bad JSON', function(assert) {
        new TileJSON('http://a.tiles.mapbox.com/v3/mapbox.world-bright.jsonp', function(err, source) {
            assert.equal('Unexpected token g', err.message);
            assert.end();
        });
    });

    tape('should load a tilejson file', function(assert) {
        new TileJSON('tilejson://' + __dirname + '/fixtures/world-bright.tilejson', function(err, source) {
            assert.ifError(err);
            assert.ok(source.data);
            checkTile(source, assert);
        });
    });

    tape('should load a tilejson file with tilejson+file:', function(assert) {
        new TileJSON('tilejson+file://' + __dirname + '/fixtures/world-bright.tilejson', function(err, source) {
            assert.ifError(err);
            assert.ok(source.data);
            checkTile(source, assert);
        });
    });

    tape('should return ENOENT for missing file', function(assert) {
         new TileJSON('tilejson://' + __dirname + '/fixtures/enoent.tilejson', function(err, source) {
            assert.ok(err);
            assert.equal(err.code, 'ENOENT');
            assert.end();
        });
    });

    tape('should return parser error for invalid JSON', function(assert) {
         new TileJSON('tilejson://' + __dirname + '/fixtures/bad.tilejson', function(err, source) {
            assert.ok(err);
            assert.equal(err.type, 'unexpected_token');
            assert.end();
        });
    });

    tape('should not attempt to load source from cache', function(assert) {
         new TileJSON('tilejson://' + __dirname + '/fixtures/bad.tilejson', function(err, source) {
            assert.ok(err);
            assert.equal(err.type, 'unexpected_token');
            assert.end();
        });
    });
})();

(function() {
    tape('avoids multiple IO calls', function(assert) {
        var stats = {
            a: { once: 0, many: 0 },
            b: { once: 0, many: 0 }
        };
        var remaining = 10;
        for (var i = 0; i < 4; i++) {
            var lock = TileJSON.Locking('a', function(err, data) {
                stats.a.many++;
                assert.ifError(err);
                assert.ok(data);
                if (--remaining === 0) {
                    assert.equal(1, stats.a.once);
                    assert.equal(4, stats.a.many);
                    assert.end();
                }
            });
            lock(function(callback) {
                stats.a.once++;
                fs.readFile(__dirname + '/fixtures/world-bright.tilejson', 'utf8', callback);
            });
        }
        for (var i = 0; i < 6; i++) {
            var lock = TileJSON.Locking('b', function(err, data) {
                stats.b.many++;
                assert.ifError(err);
                assert.ok(data);
                if (--remaining === 0) {
                    assert.equal(1, stats.b.once);
                    assert.equal(6, stats.b.many);
                    assert.end();
                }
            });
            lock(function(callback) {
                stats.b.once++;
                fs.readFile(__dirname + '/fixtures/world-bright.tilejson', 'utf8', callback);
            });
        }
    });
    tape('completes multiple callbacks', function(assert) {
        var url = __dirname + '/fixtures/world-bright.tilejson';
        var stats = { once: 0, many: 0 };
        var remaining = 4;
        for (var i = 0; i < 4; i++) {
            var callback = function(err, data) {
                stats.many++;
                assert.ifError(err);
                if (--remaining === 0) {
                    assert.equal(4, stats.many);
                    assert.end();
                }
            };
            var lock = TileJSON.Locking(url, function(err, data) {
                assert.ifError(err);
                assert.ok(data);
                return callback(null, data);
            });
            lock(function(callback) {
                stats.once++;
                fs.readFile(url, 'utf8', function(err, buffer) {
                    if (err) return callback(err);
                    try { var data = JSON.parse(buffer); }
                    catch(err) { return callback(err); }
                    callback(null, data)
                });
            });
        }
    });
    tape('completes multiple callbacks asynchronously', function(assert) {
        var url = __dirname + '/fixtures/world-bright.tilejson';
        var stats = { once: 0, many: 0 };
        var once = function(callback) {
            stats.once++;
            fs.readFile(url, 'utf8', function(err, buffer) {
                if (err) return callback(err);
                try { var data = JSON.parse(buffer); }
                catch(err) { return callback(err); }
                callback(null, data);
            });
        };
        var lock = function(callback) {
            return TileJSON.Locking(url, function(err, data) {
                stats.many++;
                assert.ifError(err);
                assert.ok(data);
                callback();
            })(once);
        };
        lock(function() {
            lock(function() {
                assert.equal(2, stats.many);
                assert.end();
            });
        });
    });
})();

(function() {
    tape('should load tile 0/0/0', function(assert) {
        world_bright.getTile(0, 0, 0, function(err, data, headers) {
            if (err) throw err;
            assert.ok(!isNaN(Date.parse(headers['Last-Modified'])));
            assert.equal('image/png', headers['Content-Type']);
            assert.equal('string', typeof headers['ETag']);
            assert.equal('string', typeof headers['Cache-Control']);
            assert.equal('943ca1495e3b6e8d84dab88227904190', md5(data));
            assert.end();
        });
    });

    tape('should load tile 2/2/2', function(assert) {
        world_bright.getTile(2, 2, 2, function(err, data, headers) {
            if (err) throw err;
            assert.ok(!isNaN(Date.parse(headers['Last-Modified'])));
            assert.equal('image/png', headers['Content-Type']);
            assert.equal('string', typeof headers['ETag']);
            assert.equal('string', typeof headers['Cache-Control']);
            assert.equal('84044cc921ee458cd1ece905e2682db0', md5(data));
            assert.end();
        });
    });

    tape('https should load tile 0/0/0', function(assert) {
        world_bright_ssl.getTile(0, 0, 0, function(err, data, headers) {
            if (err) throw err;
            assert.ok(!isNaN(Date.parse(headers['Last-Modified'])));
            assert.equal('image/png', headers['Content-Type']);
            assert.equal('string', typeof headers['ETag']);
            assert.equal('string', typeof headers['Cache-Control']);
            assert.equal('943ca1495e3b6e8d84dab88227904190', md5(data));
            assert.end();
        });
    });

    tape('https should load tile 2/2/2', function(assert) {
        world_bright_ssl.getTile(2, 2, 2, function(err, data, headers) {
            if (err) throw err;
            assert.ok(!isNaN(Date.parse(headers['Last-Modified'])));
            assert.equal('image/png', headers['Content-Type']);
            assert.equal('string', typeof headers['ETag']);
            assert.equal('string', typeof headers['Cache-Control']);
            assert.equal('84044cc921ee458cd1ece905e2682db0', md5(data));
            assert.end();
        });
    });
})();

(function() {
    tape('should fail for non-existent grid', function(assert) {
        world_bright.getGrid(0, 0, 0, function(err, data) {
            assert.ok(err);
            assert.equal(err.message, 'Grid does not exist');
            assert.end();
        });
    });

    tape('should load grid 6/29/30', function(assert) {
        grid_source.getGrid(6, 29, 30, function(err, data, headers) {
            if (err) throw err;
            assert.ok(!isNaN(Date.parse(headers['Last-Modified'])));
            assert.equal('application/json', headers['Content-Type']);
            assert.equal('string', typeof headers['ETag']);
            assert.equal('string', typeof headers['Cache-Control']);
            assert.equal('4f8790dc72e204132531f1e12dea20a1', md5(JSON.stringify(data)));
            assert.end();
        });
    });
})();

(function() {
    var tilejson;
    var server;

    tape('setup', function(assert) {
        new TileJSON('tilejson://' + __dirname + '/fixtures/invalid.tilejson?timeout=200', function(err, source) {
            tilejson = source;
            assert.ifError(err);
            assert.end();
        });
    });

    tape('setup', function(assert) {
        server = http.createServer(function (req, res) {
            if (req.url === '/tiles/1/0/0.png') {
                // Wait forever.
            } else  {
                res.writeHead(500);
                res.end();
            }
        }).listen(38923, assert.end);
    });

    tape('should load a tile from the specified tilejson source', function(assert) {
        tilejson.getTile(0, 0, 0, function(err, data, headers) {
            assert.ok(err);
            assert.equal(err.message, 'Server returned HTTP 500');
            assert.end();
        });
    });

    tape('should abort when the server takes too long', function(assert) {
        tilejson.getTile(1, 0, 0, function(err, data, headers) {
            assert.ok(err);
            assert.equal(err.message, 'Timed out after 200ms');
            assert.end();
        });
    });

    tape('cleanup', function(assert) {
        server.close(assert.end);
    });
})();

(function() {
    var tilejson;
    var server;

    tape('setup', function(assert) {
        new TileJSON('tilejson://' + __dirname + '/fixtures/invalid.tilejson?timeout=200', function(err, source) {
            assert.ifError(err);
            tilejson = source;
            assert.end();
        });
    });

    tape('should retry on socket hangup', function(assert) {
        var connectionCount = 0;

        function setupServer(callback) {
            server = http.createServer(function (req, res) {});
            server.on('connection', function(socket) {
                connectionCount++;
                socket.destroy();
            });
            server.listen(38923);
            callback();
        }

        (setupServer(function() {
            tilejson.getTile(2, 2, 2, function(err, data, headers) {
                assert.equal(err.code, 'ECONNRESET');
                assert.equal(connectionCount, 2);
                server.close(assert.end);
            });
        }));
    });

    tape('500 should retry', function(assert) {
        var connectionCount = 0;

        function setupServer(callback) {
            server = http.createServer(function (req, res) {
                connectionCount++;
                if (req.url === '/tiles/5/0/0.png') {
                    res.writeHead(500);
                } else  {
                    res.writeHead(400);
                }
                res.end();
            }).listen(38923, callback);
        }
        setupServer(function() {
            tilejson.getTile(5, 0, 0, function(err, data, headers) {
                assert.equal(err.status, 500);
                assert.equal(connectionCount, 2);
                server.close(assert.end);
            });
        });
    });

    tape('400 should not retry', function(assert) {
        var connectionCount = 0;

        function setupServer(callback) {
            server = http.createServer(function (req, res) {
                connectionCount++;
                if (req.url === '/tiles/5/0/0.png') {
                    res.writeHead(500);
                } else  {
                    res.writeHead(400);
                }
                res.end();
            }).listen(38923, callback);
        }
        setupServer(function() {
            tilejson.getTile(4, 0, 0, function(err, data, headers) {
                assert.equal(err.status, 400);
                assert.equal(connectionCount, 1);
                server.close(assert.end);
            });
        });
    });
})();

<commit_msg>Add tests for templated URI sources.<commit_after>var tape = require('tape');
var crypto = require('crypto');
var fs = require('fs');
var http = require('http');
var TileJSON = require('..');

var fixtures = {
    'world-bright': JSON.parse(fs.readFileSync(__dirname + '/fixtures/world-bright.tilejson')),
    'grid': JSON.parse(fs.readFileSync(__dirname + '/fixtures/grid.tilejson'))
};

function md5(str) {
    return crypto.createHash('md5').update(str).digest('hex');
}

var world_bright;
var world_bright_ssl;
var grid_source;

tape('setup', function(assert) {
    new TileJSON('tilejson://' + __dirname + '/fixtures/world-bright.tilejson', function(err, source) {
        world_bright = source;
        assert.ifError(err);
        assert.end();
    });
});
tape('setup', function(assert) {
    new TileJSON('tilejson://' + __dirname + '/fixtures/world-bright-ssl.tilejson', function(err, source) {
        world_bright_ssl = source;
        assert.ifError(err);
        assert.end();
    });
});
tape('setup', function(assert) {
    new TileJSON('tilejson://' + __dirname + '/fixtures/grid.tilejson', function(err, source) {
        grid_source = source;
        assert.ifError(err);
        assert.end();
    });
});

(function() {
    function checkTile(source, assert) {
        source.getTile(0, 0, 0, function(err, data, headers) {
            assert.ifError(err);
            assert.ok(!isNaN(Date.parse(headers['Last-Modified'])));
            assert.equal('image/png', headers['Content-Type']);
            assert.equal('string', typeof headers['ETag']);
            assert.equal('string', typeof headers['Cache-Control']);
            assert.equal('943ca1495e3b6e8d84dab88227904190', md5(data));
            assert.end();
        });
    }

    tape('loads directly from data', function(assert) {
        new TileJSON({ data: fixtures['world-bright'] }, function(err, source) {
            assert.ifError(err);
            assert.ok(source.data);
            checkTile(source, assert);
        });
    });

    tape('loads a tilejson file', function(assert) {
        new TileJSON('http://a.tiles.mapbox.com/v3/mapbox.world-bright.json', function(err, source) {
            assert.ifError(err);
            assert.ok(source.data);
            source.getTile(0, 0, 0, function(err, data, headers) {
                assert.ifError(err);
                assert.ok('Cache-Control' in headers);
                assert.equal('943ca1495e3b6e8d84dab88227904190', md5(data));
                assert.end();
            });
        });
    });

    tape('loads a tilejson file with tilejson+http:', function(assert) {
        new TileJSON('tilejson+http://a.tiles.mapbox.com/v3/mapbox.world-bright.json', function(err, source) {
            assert.ifError(err);
            assert.ok(source.data);
            checkTile(source, assert);
            assert.end();
        });
    });

    tape('errors on 404', function(assert) {
        new TileJSON('http://a.tiles.mapbox.com/v3/mapbox.doesnotexist.json', function(err, source) {
            assert.equal('Tileset does not exist', err.message);
            assert.end();
        });
    });

    tape('errors on bad JSON', function(assert) {
        new TileJSON('http://a.tiles.mapbox.com/v3/mapbox.world-bright.jsonp', function(err, source) {
            assert.equal('Unexpected token g', err.message);
            assert.end();
        });
    });

    tape('should load a tilejson file', function(assert) {
        new TileJSON('tilejson://' + __dirname + '/fixtures/world-bright.tilejson', function(err, source) {
            assert.ifError(err);
            assert.ok(source.data);
            checkTile(source, assert);
        });
    });

    tape('should load a tilejson file with tilejson+file:', function(assert) {
        new TileJSON('tilejson+file://' + __dirname + '/fixtures/world-bright.tilejson', function(err, source) {
            assert.ifError(err);
            assert.ok(source.data);
            checkTile(source, assert);
        });
    });

    tape('should return ENOENT for missing file', function(assert) {
         new TileJSON('tilejson://' + __dirname + '/fixtures/enoent.tilejson', function(err, source) {
            assert.ok(err);
            assert.equal(err.code, 'ENOENT');
            assert.end();
        });
    });

    tape('should return parser error for invalid JSON', function(assert) {
         new TileJSON('tilejson://' + __dirname + '/fixtures/bad.tilejson', function(err, source) {
            assert.ok(err);
            assert.equal(err.type, 'unexpected_token');
            assert.end();
        });
    });

    tape('should not attempt to load source from cache', function(assert) {
         new TileJSON('tilejson://' + __dirname + '/fixtures/bad.tilejson', function(err, source) {
            assert.ok(err);
            assert.equal(err.type, 'unexpected_token');
            assert.end();
        });
    });

    tape('should load a templated source from an http tiles url', function(assert) {
        new TileJSON('http://a.tiles.mapbox.com/v3/mapbox.world-bright/{z}/{x}/{y}.png', function(err, source) {
            assert.ifError(err);
            assert.ok(source.data);
            assert.deepEqual(source.data.tiles, [
                'http://a.tiles.mapbox.com/v3/mapbox.world-bright/{z}/{x}/{y}.png'
            ]);
            checkTile(source, assert);
        });
    });

    tape('should load a templated source from an tilejson+http tiles url', function(assert) {
        new TileJSON('tilejson+http://a.tiles.mapbox.com/v3/mapbox.world-bright/{z}/{x}/{y}.png', function(err, source) {
            assert.ifError(err);
            assert.ok(source.data);
            assert.deepEqual(source.data.tiles, [
                'http://a.tiles.mapbox.com/v3/mapbox.world-bright/{z}/{x}/{y}.png'
            ]);
            checkTile(source, assert);
        });
    });
})();

(function() {
    tape('avoids multiple IO calls', function(assert) {
        var stats = {
            a: { once: 0, many: 0 },
            b: { once: 0, many: 0 }
        };
        var remaining = 10;
        for (var i = 0; i < 4; i++) {
            var lock = TileJSON.Locking('a', function(err, data) {
                stats.a.many++;
                assert.ifError(err);
                assert.ok(data);
                if (--remaining === 0) {
                    assert.equal(1, stats.a.once);
                    assert.equal(4, stats.a.many);
                    assert.end();
                }
            });
            lock(function(callback) {
                stats.a.once++;
                fs.readFile(__dirname + '/fixtures/world-bright.tilejson', 'utf8', callback);
            });
        }
        for (var i = 0; i < 6; i++) {
            var lock = TileJSON.Locking('b', function(err, data) {
                stats.b.many++;
                assert.ifError(err);
                assert.ok(data);
                if (--remaining === 0) {
                    assert.equal(1, stats.b.once);
                    assert.equal(6, stats.b.many);
                    assert.end();
                }
            });
            lock(function(callback) {
                stats.b.once++;
                fs.readFile(__dirname + '/fixtures/world-bright.tilejson', 'utf8', callback);
            });
        }
    });
    tape('completes multiple callbacks', function(assert) {
        var url = __dirname + '/fixtures/world-bright.tilejson';
        var stats = { once: 0, many: 0 };
        var remaining = 4;
        for (var i = 0; i < 4; i++) {
            var callback = function(err, data) {
                stats.many++;
                assert.ifError(err);
                if (--remaining === 0) {
                    assert.equal(4, stats.many);
                    assert.end();
                }
            };
            var lock = TileJSON.Locking(url, function(err, data) {
                assert.ifError(err);
                assert.ok(data);
                return callback(null, data);
            });
            lock(function(callback) {
                stats.once++;
                fs.readFile(url, 'utf8', function(err, buffer) {
                    if (err) return callback(err);
                    try { var data = JSON.parse(buffer); }
                    catch(err) { return callback(err); }
                    callback(null, data)
                });
            });
        }
    });
    tape('completes multiple callbacks asynchronously', function(assert) {
        var url = __dirname + '/fixtures/world-bright.tilejson';
        var stats = { once: 0, many: 0 };
        var once = function(callback) {
            stats.once++;
            fs.readFile(url, 'utf8', function(err, buffer) {
                if (err) return callback(err);
                try { var data = JSON.parse(buffer); }
                catch(err) { return callback(err); }
                callback(null, data);
            });
        };
        var lock = function(callback) {
            return TileJSON.Locking(url, function(err, data) {
                stats.many++;
                assert.ifError(err);
                assert.ok(data);
                callback();
            })(once);
        };
        lock(function() {
            lock(function() {
                assert.equal(2, stats.many);
                assert.end();
            });
        });
    });
})();

(function() {
    tape('should load tile 0/0/0', function(assert) {
        world_bright.getTile(0, 0, 0, function(err, data, headers) {
            if (err) throw err;
            assert.ok(!isNaN(Date.parse(headers['Last-Modified'])));
            assert.equal('image/png', headers['Content-Type']);
            assert.equal('string', typeof headers['ETag']);
            assert.equal('string', typeof headers['Cache-Control']);
            assert.equal('943ca1495e3b6e8d84dab88227904190', md5(data));
            assert.end();
        });
    });

    tape('should load tile 2/2/2', function(assert) {
        world_bright.getTile(2, 2, 2, function(err, data, headers) {
            if (err) throw err;
            assert.ok(!isNaN(Date.parse(headers['Last-Modified'])));
            assert.equal('image/png', headers['Content-Type']);
            assert.equal('string', typeof headers['ETag']);
            assert.equal('string', typeof headers['Cache-Control']);
            assert.equal('84044cc921ee458cd1ece905e2682db0', md5(data));
            assert.end();
        });
    });

    tape('https should load tile 0/0/0', function(assert) {
        world_bright_ssl.getTile(0, 0, 0, function(err, data, headers) {
            if (err) throw err;
            assert.ok(!isNaN(Date.parse(headers['Last-Modified'])));
            assert.equal('image/png', headers['Content-Type']);
            assert.equal('string', typeof headers['ETag']);
            assert.equal('string', typeof headers['Cache-Control']);
            assert.equal('943ca1495e3b6e8d84dab88227904190', md5(data));
            assert.end();
        });
    });

    tape('https should load tile 2/2/2', function(assert) {
        world_bright_ssl.getTile(2, 2, 2, function(err, data, headers) {
            if (err) throw err;
            assert.ok(!isNaN(Date.parse(headers['Last-Modified'])));
            assert.equal('image/png', headers['Content-Type']);
            assert.equal('string', typeof headers['ETag']);
            assert.equal('string', typeof headers['Cache-Control']);
            assert.equal('84044cc921ee458cd1ece905e2682db0', md5(data));
            assert.end();
        });
    });
})();

(function() {
    tape('should fail for non-existent grid', function(assert) {
        world_bright.getGrid(0, 0, 0, function(err, data) {
            assert.ok(err);
            assert.equal(err.message, 'Grid does not exist');
            assert.end();
        });
    });

    tape('should load grid 6/29/30', function(assert) {
        grid_source.getGrid(6, 29, 30, function(err, data, headers) {
            if (err) throw err;
            assert.ok(!isNaN(Date.parse(headers['Last-Modified'])));
            assert.equal('application/json', headers['Content-Type']);
            assert.equal('string', typeof headers['ETag']);
            assert.equal('string', typeof headers['Cache-Control']);
            assert.equal('4f8790dc72e204132531f1e12dea20a1', md5(JSON.stringify(data)));
            assert.end();
        });
    });
})();

(function() {
    var tilejson;
    var server;

    tape('setup', function(assert) {
        new TileJSON('tilejson://' + __dirname + '/fixtures/invalid.tilejson?timeout=200', function(err, source) {
            tilejson = source;
            assert.ifError(err);
            assert.end();
        });
    });

    tape('setup', function(assert) {
        server = http.createServer(function (req, res) {
            if (req.url === '/tiles/1/0/0.png') {
                // Wait forever.
            } else  {
                res.writeHead(500);
                res.end();
            }
        }).listen(38923, assert.end);
    });

    tape('should load a tile from the specified tilejson source', function(assert) {
        tilejson.getTile(0, 0, 0, function(err, data, headers) {
            assert.ok(err);
            assert.equal(err.message, 'Server returned HTTP 500');
            assert.end();
        });
    });

    tape('should abort when the server takes too long', function(assert) {
        tilejson.getTile(1, 0, 0, function(err, data, headers) {
            assert.ok(err);
            assert.equal(err.message, 'Timed out after 200ms');
            assert.end();
        });
    });

    tape('cleanup', function(assert) {
        server.close(assert.end);
    });
})();

(function() {
    var tilejson;
    var server;

    tape('setup', function(assert) {
        new TileJSON('tilejson://' + __dirname + '/fixtures/invalid.tilejson?timeout=200', function(err, source) {
            assert.ifError(err);
            tilejson = source;
            assert.end();
        });
    });

    tape('should retry on socket hangup', function(assert) {
        var connectionCount = 0;

        function setupServer(callback) {
            server = http.createServer(function (req, res) {});
            server.on('connection', function(socket) {
                connectionCount++;
                socket.destroy();
            });
            server.listen(38923);
            callback();
        }

        (setupServer(function() {
            tilejson.getTile(2, 2, 2, function(err, data, headers) {
                assert.equal(err.code, 'ECONNRESET');
                assert.equal(connectionCount, 2);
                server.close(assert.end);
            });
        }));
    });

    tape('500 should retry', function(assert) {
        var connectionCount = 0;

        function setupServer(callback) {
            server = http.createServer(function (req, res) {
                connectionCount++;
                if (req.url === '/tiles/5/0/0.png') {
                    res.writeHead(500);
                } else  {
                    res.writeHead(400);
                }
                res.end();
            }).listen(38923, callback);
        }
        setupServer(function() {
            tilejson.getTile(5, 0, 0, function(err, data, headers) {
                assert.equal(err.status, 500);
                assert.equal(connectionCount, 2);
                server.close(assert.end);
            });
        });
    });

    tape('400 should not retry', function(assert) {
        var connectionCount = 0;

        function setupServer(callback) {
            server = http.createServer(function (req, res) {
                connectionCount++;
                if (req.url === '/tiles/5/0/0.png') {
                    res.writeHead(500);
                } else  {
                    res.writeHead(400);
                }
                res.end();
            }).listen(38923, callback);
        }
        setupServer(function() {
            tilejson.getTile(4, 0, 0, function(err, data, headers) {
                assert.equal(err.status, 400);
                assert.equal(connectionCount, 1);
                server.close(assert.end);
            });
        });
    });
})();

<|endoftext|>


<commit_before>/**
 * # ChannelRegistry
 *
 * Container for clients, rooms and aliases
 *
 * ---
 */
(function(exports, node) {

// ## Global scope
exports.ChannelRegistry = ChannelRegistry;

var ngc = require('nodegame-client');

var JSUS       = ngc.JSUS;
var PlayerList = ngc.PlayerList;
var Player     = ngc.Player;

/**
 * ## ChannelRegistry constructor
 *
 * Creates a new instance of ChannelRegistry
 */
function ChannelRegistry() {
    /**
     * ### ChannelRegistry.clients
     *
     * List of registered clients
     *
     * @see PlayerList
     */
    this.clients = new PlayerList({
        I: {
            admin:        function(p) { return p.admin ? p.id : undefined; },
            player:       function(p) { return !p.admin ? p.id : undefined; },
            disconnected: function(p) { return p.disconnected ? p.id : undefined; },
            connected:    function(p) { return !p.disconnected ? p.id : undefined; }
        }});

    /**
     * ### ChannelRegistry.roomStacks
     *
     * Room membership of players
     *
     * key: clientID,  value: array of roomIDs, last ID is current room
     */
    this.roomStacks = {};

    /**
     * ### ChannelRegistry.roomAliases
     *
     * Per-room client aliases
     *
     * key: roomID,  value: map from aliases to aliases/clientIDs
     */
    this.roomAliases = {};

    /**
     * ### ChannelRegistry.gameAliases
     *
     * Game-global client aliases
     *
     * key: alias,  value: alias/clientID
     */
    this.gameAliases = {};
}

// ## ChannelRegistry methods

/**
 * ### ChannelRegistry.generateClientId
 *
 * Generates a unique id for newly connecting clients
 *
 * @return {string} The ID of the client
 *
 * @see ChannelRegistry.addClient
 */
ChannelRegistry.prototype.generateClientId = function() {
    return JSUS.uniqueKey(this.clients.id);
};

/**
 * ### ChannelRegistry.addClient
 *
 * Registers a client
 *
 * @param {object} playerObj An object constituting a valid Player object
 *   except the `id` field
 *
 * @return {string} The ID of the client.
 *
 * @see Player
 */
ChannelRegistry.prototype.addClient = function(playerObj) {
    var clientId, errStr, res;
    if ('undefined' === typeof playerObj) {
        throw TypeError('ChannelRegistry.addClient: playerObj cannot be undefined');
    }
    clientId = this.generateClientId();

    if ('string' !== typeof clientId) {
        throw TypeError('ChannelRegistry.addClient: clientId must be a string');
    }

    playerObj.id = clientId;
    playerObj = new Player(playerObj);

    // TODO this must be changed to support different types of DB
    res = this.clients.add(playerObj)

    if (!res) {
        errStr = "ChannelRegistry.addClient: an error occurred when adding player with id " + clientId;
        this.sysLogger.log('error', errStr);
        return null;
    }

    this.roomStacks[clientId] = [];
    return clientId;
};

/**
 * ### ChannelRegistry.removeClient
 *
 * Deregisters a client
 *
 * All room and game aliases to the client's ID are removed.
 *
 * @param {string} client The client's alias or ID
 *
 * @return {boolean} TRUE on success, FALSE on error
 */
ChannelRegistry.prototype.removeClient = function(client) {
    var clientId = this.lookupClient(client);
    var alias;

    if (clientId === null) return false;

    // Remove client from all the rooms to trigger removing
    // room aliases to client ID:
    while (this.moveClientBack(clientId));

    // Remove game aliases to ID:
    for (alias in this.gameAliases) {
        if (this.gameAliases.hasOwnProperty(alias) &&
                this.gameAliases[alias] === clientId) {
            delete this.gameAliases[alias];
        }
    }

    // Remove room stack:
    delete this.roomStacks[clientId];

    return this.clients.remove(clientId);
};

/**
 * ### ChannelRegistry.moveClient
 *
 * Moves client to a new game room
 *
 * @param {string} client The client's alias or ID
 * @param {string} newRoom The new room ID of the client
 *
 * @return {boolean} TRUE on success, FALSE on error
 */
ChannelRegistry.prototype.moveClient = function(client, newRoom) {
    var clientId = this.lookupClient(client);

    if (clientId === null) return false;
    if ('string' !== typeof newRoom) return false;

    this.roomStacks[clientId].push(newRoom);

    return true;
};

/**
 * ### ChannelRegistry.moveClientBack
 *
 * Moves client to previous game room
 *
 * All local room aliases pointing directly to the client's ID are removed.
 *
 * @param {string} client The client's alias or ID
 *
 * @return {boolean} TRUE on success, FALSE on error
 */
ChannelRegistry.prototype.moveClientBack = function(client) {
    var clientId = this.lookupClient(client);
    var roomStack;
    var oldRoom, oldRoomAlises;
    var alias;

    if (clientId === null) return false;

    roomStack = this.roomStacks[clientId];

    // Remove all room aliases to the client's ID:
    if (roomStack.length > 0) {
        oldRoom = roomStack[roomStack.length - 1];
        oldRoomAliases = this.roomAliases[oldRoom];
        for (alias in oldRoomAliases) {
            if (oldRoomAliases.hasOwnProperty(alias) &&
                    oldRoomAliases[alias] === clientId) {
                // Delete that alias:
                this.registerRoomAlias(oldRoom, alias);
            }
        }
    }

    // Try to pop the client's stack:
    if ('undefined' === typeof roomStack.pop()) return false;

    return true;
};

/**
 * ### ChannelRegistry.getIds
 *
 * Returns an array of all IDs
 */
ChannelRegistry.prototype.getIds = function() {
    // TODO: Check with new NDDB
    return this.clients.id.getAllKeys();
};

/**
 * ### ChannelRegistry.getClients
 *
 * Returns a map of all clients
 */
ChannelRegistry.prototype.getClients = function() {
    // TODO: Check with new NDDB
    return this.clients.id.getAllKeyElements();
};

/**
 * ### ChannelRegistry.getPlayers
 *
 * Returns a map of all players
 */
ChannelRegistry.prototype.getPlayers = function() {
    // TODO: Check with new NDDB
    return this.clients.player;
};

/**
 * ### ChannelRegistry.getAdmins
 *
 * Returns a map of all admins
 */
ChannelRegistry.prototype.getAdmins = function() {
    // TODO: Check with new NDDB
    return this.clients.admin;
};

/**
 * ### ChannelRegistry.getConnected
 *
 * Returns a map of all connected clients
 */
ChannelRegistry.prototype.getConnected = function() {
    // TODO: Check with new NDDB
    return this.clients.connected;
};

/**
 * ### ChannelRegistry.getDisconnected
 *
 * Returns a map of all disconnected clients
 */
ChannelRegistry.prototype.getDisconnected = function() {
    // TODO: Check with new NDDB
    return this.clients.disconnected;
};

/**
 * ### ChannelRegistry.getClientRoom
 *
 * Returns the current room ID of a client
 *
 * @param {string} client The client's alias or ID
 *
 * @return {string|null} The room ID. NULL on error.
 */
ChannelRegistry.prototype.getClientRoom = function(client) {
    var stack = this.getClientRoomStack(client);

    if (!stack || !stack.length) return null;

    return stack[stack.length - 1];
};

/**
 * ### ChannelRegistry.getClientRoomStack
 *
 * Returns the room stack of a client
 *
 * @param {string} client The client's alias or ID
 *
 * @return {array|null} The room IDs of the client in the order of joining.
 *   NULL on error.
 */
ChannelRegistry.prototype.getClientRoomStack = function(client) {
    var clientId = this.lookupClient(client);

    if (clientId === null) return null;

    return this.roomStacks[clientId];
};

/**
 * ### ChannelRegistry.getRoomIds
 *
 * Returns an array of all known room IDs
 */
ChannelRegistry.prototype.getRoomIds = function(room) {
    var ids = [];
    var clientId;

    for (clientId in this.roomStacks) {
        if (this.roomStacks.hasOwnProperty(clientId) &&
                this.getClientRoom(clientId) === room) {
            ids.push(clientId);
        }
    }

    return ids;
};

/**
 * ### ChannelRegistry.getRoomAliases
 *
 * Returns the room alias mappings
 *
 * @see ChannelRegistry.roomAliases
 */
ChannelRegistry.prototype.getRoomAliases = function(room) {
    return this.roomAliases[room];
};

/**
 * ### ChannelRegistry.registerRoomAlias
 *
 * Registers a per-room client alias
 *
 * The alias can point to a client ID, a game alias or
 * a different room alias in the same room.
 *
 * Aliases can be registered even if the target doesn't exist, but lookups on
 * the alias will fail then.
 *
 * @param {string} room The room ID
 * @param {string} alias The alias to register
 * @param {string} client The target of the alias
 *
 * @return {boolean} TRUE on success, FALSE on error
 */
ChannelRegistry.prototype.registerRoomAlias = function(room, alias, client) {
    if ('string' !== typeof room ||
        'string' !== typeof alias ||
        'string' !== typeof client) return false;

    if (!this.roomAliases.hasOwnProperty(room)) {
        this.roomAliases[room] = {};
    }

    this.roomAliases[room][alias] = client;
    return true;
};

/**
 * ### ChannelRegistry.deregisterRoomAlias
 *
 * Deregisters a per-room client alias
 *
 * @param {string} room The room ID
 * @param {string} alias The alias to deregister
 *
 * @return {boolean} TRUE on success, FALSE on error
 */
ChannelRegistry.prototype.deregisterRoomAlias = function(room, alias) {
    if (!this.roomAliases.hasOwnProperty(room)) return false;
    if (!this.roomAliases[room].hasOwnProperty(alias)) return false;

    delete this.roomAliases[room][alias];

    if (JSUS.isEmpty(this.roomAliases[room])) {
        delete this.roomAliases[room];
    }

    return true;
};

/**
 * ### ChannelRegistry.registerGameAlias
 *
 * Registers a game-global client alias
 *
 * The alias can point to a client ID, a game alias or a room alias.
 *
 * Aliases can be registered even if the target doesn't exist, but lookups on
 * the alias will fail then.
 *
 * @param {string} alias The alias to register
 * @param {string} client The target of the alias
 *
 * @return {boolean} TRUE on success, FALSE on error
 */
ChannelRegistry.prototype.registerGameAlias = function(alias, client) {
    if ('string' !== typeof alias ||
        'string' !== typeof client) return false;

    this.gameAliases[alias] = client;
    return true;
};

/**
 * ### ChannelRegistry.deregisterGameAlias
 *
 * Deregisters a game-global client alias
 *
 * @param {string} alias The alias to deregister
 *
 * @return {boolean} TRUE on success, FALSE on error
 */
ChannelRegistry.prototype.deregisterGameAlias = function(alias) {
    if (!this.gameAliases.hasOwnProperty(alias)) return false;

    delete this.gameAliases[alias];

    return true;
};

/**
 * ### ChannelRegistry.lookupClient
 *
 * Gets client ID from an alias and optional game room
 *
 * May return ID even if that client is not in the given room.
 * Returns NULL if not found or infinite loop detected.
 *
 * @param {string} client The client's alias or ID
 * @param {string} room Optional. The room ID.
 *
 * @return {string|null} The client ID. NULL on error.
 */
ChannelRegistry.prototype.lookupClient = function(client, room) {
    var aliasTrace = [];

    while (true) {
        // Avoid alias loop:
        if (aliasTrace.indexOf(client) !== -1) return null;
        aliasTrace.push(client);

        // Check aliases in given room:
        if ('undefined' !== (typeof room) &&
                this.roomAliases.hasOwnProperty(room) &&
                this.roomAliases[room].hasOwnProperty(client)) {
            client = this.roomAliases[room][client];
            continue;
        }

        // Check if ID exists:
        if (this.clients.exist(client)) {
            return client;
        }

        // Check game aliases:
        if (this.gameAliases.hasOwnProperty(client)) {
            client = this.gameAliases[client];
            continue;
        }

        // Client wasn't found:
        return null;
    }
};

// ## Closure
})(
    'undefined' != typeof node ? node : module.exports,
    'undefined' != typeof node ? node : module.parent.exports
);
<commit_msg>removed unnecessary closure from ChannelRegistry<commit_after>/**
 * # ChannelRegistry
 *
 * Container for clients, rooms and aliases
 *
 * ---
 */

// ## Global scope
module.exports = ChannelRegistry;

var ngc = require('nodegame-client');

var JSUS       = ngc.JSUS;
var PlayerList = ngc.PlayerList;
var Player     = ngc.Player;

/**
 * ## ChannelRegistry constructor
 *
 * Creates a new instance of ChannelRegistry
 */
function ChannelRegistry() {
    /**
     * ### ChannelRegistry.clients
     *
     * List of registered clients
     *
     * @see PlayerList
     */
    this.clients = new PlayerList({
        I: {
            admin:        function(p) { return p.admin ? p.id : undefined; },
            player:       function(p) { return !p.admin ? p.id : undefined; },
            disconnected: function(p) { return p.disconnected ? p.id : undefined; },
            connected:    function(p) { return !p.disconnected ? p.id : undefined; }
        }});

    /**
     * ### ChannelRegistry.roomStacks
     *
     * Room membership of players
     *
     * key: clientID,  value: array of roomIDs, last ID is current room
     */
    this.roomStacks = {};

    /**
     * ### ChannelRegistry.roomAliases
     *
     * Per-room client aliases
     *
     * key: roomID,  value: map from aliases to aliases/clientIDs
     */
    this.roomAliases = {};

    /**
     * ### ChannelRegistry.gameAliases
     *
     * Game-global client aliases
     *
     * key: alias,  value: alias/clientID
     */
    this.gameAliases = {};
}

// ## ChannelRegistry methods

/**
 * ### ChannelRegistry.generateClientId
 *
 * Generates a unique id for newly connecting clients
 *
 * @return {string} The ID of the client
 *
 * @see ChannelRegistry.addClient
 */
ChannelRegistry.prototype.generateClientId = function() {
    return JSUS.uniqueKey(this.clients.id);
};

/**
 * ### ChannelRegistry.addClient
 *
 * Registers a client
 *
 * @param {object} playerObj An object constituting a valid Player object
 *   except the `id` field
 *
 * @return {string} The ID of the client.
 *
 * @see Player
 */
ChannelRegistry.prototype.addClient = function(playerObj) {
    var clientId, errStr, res;
    if ('undefined' === typeof playerObj) {
        throw TypeError('ChannelRegistry.addClient: playerObj cannot be undefined');
    }
    clientId = this.generateClientId();

    if ('string' !== typeof clientId) {
        throw TypeError('ChannelRegistry.addClient: clientId must be a string');
    }

    playerObj.id = clientId;
    playerObj = new Player(playerObj);

    // TODO this must be changed to support different types of DB
    res = this.clients.add(playerObj)

    if (!res) {
        errStr = "ChannelRegistry.addClient: an error occurred when adding player with id " + clientId;
        this.sysLogger.log('error', errStr);
        return null;
    }

    this.roomStacks[clientId] = [];
    return clientId;
};

/**
 * ### ChannelRegistry.removeClient
 *
 * Deregisters a client
 *
 * All room and game aliases to the client's ID are removed.
 *
 * @param {string} client The client's alias or ID
 *
 * @return {boolean} TRUE on success, FALSE on error
 */
ChannelRegistry.prototype.removeClient = function(client) {
    var clientId = this.lookupClient(client);
    var alias;

    if (clientId === null) return false;

    // Remove client from all the rooms to trigger removing
    // room aliases to client ID:
    while (this.moveClientBack(clientId));

    // Remove game aliases to ID:
    for (alias in this.gameAliases) {
        if (this.gameAliases.hasOwnProperty(alias) &&
                this.gameAliases[alias] === clientId) {
            delete this.gameAliases[alias];
        }
    }

    // Remove room stack:
    delete this.roomStacks[clientId];

    return this.clients.remove(clientId);
};

/**
 * ### ChannelRegistry.moveClient
 *
 * Moves client to a new game room
 *
 * @param {string} client The client's alias or ID
 * @param {string} newRoom The new room ID of the client
 *
 * @return {boolean} TRUE on success, FALSE on error
 */
ChannelRegistry.prototype.moveClient = function(client, newRoom) {
    var clientId = this.lookupClient(client);

    if (clientId === null) return false;
    if ('string' !== typeof newRoom) return false;

    this.roomStacks[clientId].push(newRoom);

    return true;
};

/**
 * ### ChannelRegistry.moveClientBack
 *
 * Moves client to previous game room
 *
 * All local room aliases pointing directly to the client's ID are removed.
 *
 * @param {string} client The client's alias or ID
 *
 * @return {boolean} TRUE on success, FALSE on error
 */
ChannelRegistry.prototype.moveClientBack = function(client) {
    var clientId = this.lookupClient(client);
    var roomStack;
    var oldRoom, oldRoomAlises;
    var alias;

    if (clientId === null) return false;

    roomStack = this.roomStacks[clientId];

    // Remove all room aliases to the client's ID:
    if (roomStack.length > 0) {
        oldRoom = roomStack[roomStack.length - 1];
        oldRoomAliases = this.roomAliases[oldRoom];
        for (alias in oldRoomAliases) {
            if (oldRoomAliases.hasOwnProperty(alias) &&
                    oldRoomAliases[alias] === clientId) {
                // Delete that alias:
                this.registerRoomAlias(oldRoom, alias);
            }
        }
    }

    // Try to pop the client's stack:
    if ('undefined' === typeof roomStack.pop()) return false;

    return true;
};

/**
 * ### ChannelRegistry.getIds
 *
 * Returns an array of all IDs
 */
ChannelRegistry.prototype.getIds = function() {
    // TODO: Check with new NDDB
    return this.clients.id.getAllKeys();
};

/**
 * ### ChannelRegistry.getClients
 *
 * Returns a map of all clients
 */
ChannelRegistry.prototype.getClients = function() {
    // TODO: Check with new NDDB
    return this.clients.id.getAllKeyElements();
};

/**
 * ### ChannelRegistry.getPlayers
 *
 * Returns a map of all players
 */
ChannelRegistry.prototype.getPlayers = function() {
    // TODO: Check with new NDDB
    return this.clients.player;
};

/**
 * ### ChannelRegistry.getAdmins
 *
 * Returns a map of all admins
 */
ChannelRegistry.prototype.getAdmins = function() {
    // TODO: Check with new NDDB
    return this.clients.admin;
};

/**
 * ### ChannelRegistry.getConnected
 *
 * Returns a map of all connected clients
 */
ChannelRegistry.prototype.getConnected = function() {
    // TODO: Check with new NDDB
    return this.clients.connected;
};

/**
 * ### ChannelRegistry.getDisconnected
 *
 * Returns a map of all disconnected clients
 */
ChannelRegistry.prototype.getDisconnected = function() {
    // TODO: Check with new NDDB
    return this.clients.disconnected;
};

/**
 * ### ChannelRegistry.getClientRoom
 *
 * Returns the current room ID of a client
 *
 * @param {string} client The client's alias or ID
 *
 * @return {string|null} The room ID. NULL on error.
 */
ChannelRegistry.prototype.getClientRoom = function(client) {
    var stack = this.getClientRoomStack(client);

    if (!stack || !stack.length) return null;

    return stack[stack.length - 1];
};

/**
 * ### ChannelRegistry.getClientRoomStack
 *
 * Returns the room stack of a client
 *
 * @param {string} client The client's alias or ID
 *
 * @return {array|null} The room IDs of the client in the order of joining.
 *   NULL on error.
 */
ChannelRegistry.prototype.getClientRoomStack = function(client) {
    var clientId = this.lookupClient(client);

    if (clientId === null) return null;

    return this.roomStacks[clientId];
};

/**
 * ### ChannelRegistry.getRoomIds
 *
 * Returns an array of all known room IDs
 */
ChannelRegistry.prototype.getRoomIds = function(room) {
    var ids = [];
    var clientId;

    for (clientId in this.roomStacks) {
        if (this.roomStacks.hasOwnProperty(clientId) &&
                this.getClientRoom(clientId) === room) {
            ids.push(clientId);
        }
    }

    return ids;
};

/**
 * ### ChannelRegistry.getRoomAliases
 *
 * Returns the room alias mappings
 *
 * @see ChannelRegistry.roomAliases
 */
ChannelRegistry.prototype.getRoomAliases = function(room) {
    return this.roomAliases[room];
};

/**
 * ### ChannelRegistry.registerRoomAlias
 *
 * Registers a per-room client alias
 *
 * The alias can point to a client ID, a game alias or
 * a different room alias in the same room.
 *
 * Aliases can be registered even if the target doesn't exist, but lookups on
 * the alias will fail then.
 *
 * @param {string} room The room ID
 * @param {string} alias The alias to register
 * @param {string} client The target of the alias
 *
 * @return {boolean} TRUE on success, FALSE on error
 */
ChannelRegistry.prototype.registerRoomAlias = function(room, alias, client) {
    if ('string' !== typeof room ||
        'string' !== typeof alias ||
        'string' !== typeof client) return false;

    if (!this.roomAliases.hasOwnProperty(room)) {
        this.roomAliases[room] = {};
    }

    this.roomAliases[room][alias] = client;
    return true;
};

/**
 * ### ChannelRegistry.deregisterRoomAlias
 *
 * Deregisters a per-room client alias
 *
 * @param {string} room The room ID
 * @param {string} alias The alias to deregister
 *
 * @return {boolean} TRUE on success, FALSE on error
 */
ChannelRegistry.prototype.deregisterRoomAlias = function(room, alias) {
    if (!this.roomAliases.hasOwnProperty(room)) return false;
    if (!this.roomAliases[room].hasOwnProperty(alias)) return false;

    delete this.roomAliases[room][alias];

    if (JSUS.isEmpty(this.roomAliases[room])) {
        delete this.roomAliases[room];
    }

    return true;
};

/**
 * ### ChannelRegistry.registerGameAlias
 *
 * Registers a game-global client alias
 *
 * The alias can point to a client ID, a game alias or a room alias.
 *
 * Aliases can be registered even if the target doesn't exist, but lookups on
 * the alias will fail then.
 *
 * @param {string} alias The alias to register
 * @param {string} client The target of the alias
 *
 * @return {boolean} TRUE on success, FALSE on error
 */
ChannelRegistry.prototype.registerGameAlias = function(alias, client) {
    if ('string' !== typeof alias ||
        'string' !== typeof client) return false;

    this.gameAliases[alias] = client;
    return true;
};

/**
 * ### ChannelRegistry.deregisterGameAlias
 *
 * Deregisters a game-global client alias
 *
 * @param {string} alias The alias to deregister
 *
 * @return {boolean} TRUE on success, FALSE on error
 */
ChannelRegistry.prototype.deregisterGameAlias = function(alias) {
    if (!this.gameAliases.hasOwnProperty(alias)) return false;

    delete this.gameAliases[alias];

    return true;
};

/**
 * ### ChannelRegistry.lookupClient
 *
 * Gets client ID from an alias and optional game room
 *
 * May return ID even if that client is not in the given room.
 * Returns NULL if not found or infinite loop detected.
 *
 * @param {string} client The client's alias or ID
 * @param {string} room Optional. The room ID.
 *
 * @return {string|null} The client ID. NULL on error.
 */
ChannelRegistry.prototype.lookupClient = function(client, room) {
    var aliasTrace = [];

    while (true) {
        // Avoid alias loop:
        if (aliasTrace.indexOf(client) !== -1) return null;
        aliasTrace.push(client);

        // Check aliases in given room:
        if ('undefined' !== (typeof room) &&
                this.roomAliases.hasOwnProperty(room) &&
                this.roomAliases[room].hasOwnProperty(client)) {
            client = this.roomAliases[room][client];
            continue;
        }

        // Check if ID exists:
        if (this.clients.exist(client)) {
            return client;
        }

        // Check game aliases:
        if (this.gameAliases.hasOwnProperty(client)) {
            client = this.gameAliases[client];
            continue;
        }

        // Client wasn't found:
        return null;
    }
};
<|endoftext|>


<commit_before>"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var redux_actions_1 = require("redux-actions");
;
/**
 * Action creator's and reducer's factory. It contains current store brunch's type and action payload's type.
 * @template TState store's current brunch type.
 * @template TPayload action's payload type.
 */
var GuardedFactory = (function () {
    function GuardedFactory(type) {
        this._type = type;
        this._actionCreator = redux_actions_1.createAction(this._type.toString());
    }
    Object.defineProperty(GuardedFactory.prototype, "type", {
        get: function () {
            return this._type.toString();
        },
        enumerable: true,
        configurable: true
    });
    /**
     * Creates an action creator.
     * @param payload payload for an action.
     */
    GuardedFactory.prototype.createAction = function (payload) {
        return this._actionCreator(payload);
    };
    /**
     * Creates a reducer, bounded to concrete GuardedActionType.
     * @param reducer reducer.
     */
    GuardedFactory.prototype.createReducer = function (reducer, initialState) {
        var _this = this;
        var actionReducer = function (state, action) {
            if (is(action, _this._type))
                return reducer(state, action);
            return state || initialState;
        };
        return new GuardedReducer(this._type, actionReducer);
    };
    return GuardedFactory;
}());
exports.GuardedFactory = GuardedFactory;
var GuardedReducer = (function () {
    function GuardedReducer(type, reducer) {
        this._type = (type).toString();
        this._reducer = reducer;
    }
    Object.defineProperty(GuardedReducer.prototype, "reducer", {
        get: function () {
            return this._reducer;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(GuardedReducer.prototype, "type", {
        get: function () {
            return this._type;
        },
        enumerable: true,
        configurable: true
    });
    return GuardedReducer;
}());
/**
* Join a set of reducers that work with same state's branch
* (unlike combine where each reducer work with different branches).
*
* You can register a defaultReducer that will be triggered even if an action
* was already processed. Please avoid such registrations.
* @param initialState initial state.
* @param actionReducers a set of reducers with GuardedActionType.
* @param defaultReducer default reducer.
*/
var joinReducers = function (initialState, actionReducers, defaultReducer) {
    var actionReducerMap = {};
    for (var _i = 0, actionReducers_1 = actionReducers; _i < actionReducers_1.length; _i++) {
        var actionReducer = actionReducers_1[_i];
        if (actionReducerMap[actionReducer.type] != null)
            throw new Error("Reducer with type \"" + actionReducer.type + "\" had already been registered.");
        actionReducerMap[actionReducer.type] = actionReducer.reducer;
    }
    var reducer = function (currentState, action) {
        if (currentState === void 0) { currentState = initialState; }
        var actionReducer = actionReducerMap[action.type];
        var nextState = currentState;
        if (actionReducer != null)
            nextState = actionReducer(nextState, action);
        if (defaultReducer != null)
            nextState = defaultReducer(nextState, action);
        return nextState;
    };
    return reducer;
};
exports.joinReducers = joinReducers;
/**
 * Check that FSA (Flux Standard Action) has required type.
 * @param action action to check.
 * @param type required action type.
 * @template TPayload action's payload type.
 */
var is = function (action, type) { return action.type === type; };
/**
 * Create action creator's and action reducer's factory.
 * @param type action's type.
 * @template TPayload action's payload type.
 */
var createFactory = function (type) { return new GuardedFactory(type); };
exports.createFactory = createFactory;
<commit_msg>Delete js folder<commit_after><|endoftext|>


<commit_before>const findAll = require('../').findAll;

const expect = require('chai').expect;

describe('findAll', () => {
  it('retrieves a collection of kernel specs', () => {
    return findAll().then(dirs => {
      expect(dirs).to.have.property('python3');
    });
  });
});
<commit_msg>Problem: Travis defaults to Python 2<commit_after>const findAll = require('../').findAll;

const expect = require('chai').expect;

describe('findAll', () => {
  it('retrieves a collection of kernel specs', () => {
    return findAll().then(dirs => {
      expect(dirs).to.have.any.keys('python3', 'python2');
    });
  });
});
<|endoftext|>


<commit_before>/**
* used to story shared functions used in story and comments pages
*/
HN.util = {};
/**
		* returns object of info about a story based on integer id
		* id		The item's unique id.
		* deleted	true if the item is deleted.
		* type		The type of item. One of "job", "story", "comment", "poll", or "pollopt".
		* by		The username of the item's author.
		* time		Creation date of the item, in Unix Time.
		* text		The comment, story or poll text. HTML.
		* dead		true if the item is dead.
		* parent	The item's parent. For comments, either another comment or the relevant story. For pollopts, the relevant poll.
		* kids		The ids of the item's comments, in ranked display order.
		* url		The URL of the story.
		* score		The story's score, or the votes for a pollopt.
		* title		The title of the story, poll or job.
		* parts		A list of related pollopts, in display order.
		* descendants	In the case of stories or polls, the total comment count.
		*/
HN.util.getItemInfoUrlFromId = function(id){
		return 'https://hacker-news.firebaseio.com/v0/item/' + id + '.json';
	};
/**
* Takes HN.Story object as a parameter and returns the html to display the title and link
*/
HN.util.getStoryTitleHTML = function(story){
	return "
" + story.title() + " "+ story.urlRoot() + "
";
};

/**
* Used for ask pages to display without the link
* Even though code only used on ask pages, is kept here because it should have the same style as getStoryTitleHTML()
*/
HN.util.getAskStoryTitle = function(story){
	return "
" + story.title() + "
";
};

/**
* Used for comment HTML text to smarten quotes
* takes html string and returns html string with dumb quotes replaced with smart quotes
* preserves dumbquotes in html attributes
*/
HN.util.smartenQuotesHTML = function(dumbString){
	return HN.util.replaceSmartQuoteEntities(HN.util.transformTextNodes(dumbString, function(text){
		return HN.util.replaceDumbQuotes(text);
	}));
};

/**
* Used to transform just the textNodes in html
* used for smarten quotes
* @element first argument is either the result of document.getElementById()
* or a string of html such as 'text' or 'text
more text
' or '
text
'
* @textTransformFunc is a function used to transform the text nodes
* it should be of the format: function(text){return text.toUpperCase();} or however the text is to be transformed
* @returns either the transformed string if was originally a string or transformed element if was originally an element
*/
HN.util.transformTextNodes = function (element, textTransformFunc) {
    var elementType = typeof element;

    if(elementType === 'string'){
        var div = document.createElement('div');
        div.innerHTML = element;
        element = div;
    }

    var walker = document.createTreeWalker(
        element,
        NodeFilter.SHOW_TEXT,
        null,
        false
    );

    var textNode;

    while(textNode = walker.nextNode()) {
        textNode.nodeValue = textTransformFunc(textNode.nodeValue);
    }
    if(elementType === 'string'){
        return element.innerHTML;
    }
    return element;
};

/**
* Used to replace dumb quotes in a text string
* uses a second replace in the function passed to replace to preserve the non word characters before the quote, such as spaces or parens
*/
HN.util.replaceDumbQuotes = function(dumbString){
	var rightSingleSmartQuote = "’";
	var leftSingleSmartQuote = "‘";
	var rightDoubleSmartQuote = "”";
	var leftDoubleSmartQuote = "“";
	var emDash = "—";
	var ellipsis = "…";
	return dumbString
			   		 .replace(/\b'|[\.?,-\/#!$%\^&\*;:{}<>=|\-_`~()\[\]]'/g, function(match, str, offset){return match.replace(/'/g, rightSingleSmartQuote);})
		       		 .replace(/'/g, leftSingleSmartQuote)
		       		 .replace(/\b"|[\.?,-\/#!$%\^&\*;:{}<>|=\-_`~()\[\]]"/g, function(match, str, offset){return match.replace(/"/g, rightDoubleSmartQuote);})
		       		 .replace(/"/g, leftDoubleSmartQuote)
		       		 .replace(/\D\s*-+\s*\D|\s*[-]{2,}\s*/, function(match, str, offset){return match.replace(/\s*-+\s*/g, emDash);})
		       		 .replace(/\.\s?\.\s?\.\s?/g, ellipsis);
};

/**
* Required because the treeWalker automatically escapes ampersands
*/
HN.util.replaceSmartQuoteEntities = function(string){
	return string.replace(/&#[\d]{4};/g, function(match, str, offset){
		return match.replace('&', '&');
	});
};

<commit_msg>fix bug where emdash was replacing hyphen in compound words<commit_after>/**
* used to story shared functions used in story and comments pages
*/
HN.util = {};
/**
		* returns object of info about a story based on integer id
		* id		The item's unique id.
		* deleted	true if the item is deleted.
		* type		The type of item. One of "job", "story", "comment", "poll", or "pollopt".
		* by		The username of the item's author.
		* time		Creation date of the item, in Unix Time.
		* text		The comment, story or poll text. HTML.
		* dead		true if the item is dead.
		* parent	The item's parent. For comments, either another comment or the relevant story. For pollopts, the relevant poll.
		* kids		The ids of the item's comments, in ranked display order.
		* url		The URL of the story.
		* score		The story's score, or the votes for a pollopt.
		* title		The title of the story, poll or job.
		* parts		A list of related pollopts, in display order.
		* descendants	In the case of stories or polls, the total comment count.
		*/
HN.util.getItemInfoUrlFromId = function(id){
		return 'https://hacker-news.firebaseio.com/v0/item/' + id + '.json';
	};
/**
* Takes HN.Story object as a parameter and returns the html to display the title and link
*/
HN.util.getStoryTitleHTML = function(story){
	return "
" + story.title() + " "+ story.urlRoot() + "
";
};

/**
* Used for ask pages to display without the link
* Even though code only used on ask pages, is kept here because it should have the same style as getStoryTitleHTML()
*/
HN.util.getAskStoryTitle = function(story){
	return "
" + story.title() + "
";
};

/**
* Used for comment HTML text to smarten quotes
* takes html string and returns html string with dumb quotes replaced with smart quotes
* preserves dumbquotes in html attributes
*/
HN.util.smartenQuotesHTML = function(dumbString){
	return HN.util.replaceSmartQuoteEntities(HN.util.transformTextNodes(dumbString, function(text){
		return HN.util.replaceDumbQuotes(text);
	}));
};

/**
* Used to transform just the textNodes in html
* used for smarten quotes
* @element first argument is either the result of document.getElementById()
* or a string of html such as 'text' or 'text
more text
' or '
text
'
* @textTransformFunc is a function used to transform the text nodes
* it should be of the format: function(text){return text.toUpperCase();} or however the text is to be transformed
* @returns either the transformed string if was originally a string or transformed element if was originally an element
*/
HN.util.transformTextNodes = function (element, textTransformFunc) {
    var elementType = typeof element;

    if(elementType === 'string'){
        var div = document.createElement('div');
        div.innerHTML = element;
        element = div;
    }

    var walker = document.createTreeWalker(
        element,
        NodeFilter.SHOW_TEXT,
        null,
        false
    );

    var textNode;

    while(textNode = walker.nextNode()) {
        textNode.nodeValue = textTransformFunc(textNode.nodeValue);
    }
    if(elementType === 'string'){
        return element.innerHTML;
    }
    return element;
};

/**
* Used to replace dumb quotes in a text string
* uses a second replace in the function passed to replace to preserve the non word characters before the quote, such as spaces or parens
*/
HN.util.replaceDumbQuotes = function(dumbString){
	var rightSingleSmartQuote = "’";
	var leftSingleSmartQuote = "‘";
	var rightDoubleSmartQuote = "”";
	var leftDoubleSmartQuote = "“";
	var emDash = "—";
	var ellipsis = "…";
	return dumbString
			   		 .replace(/\b'|[\.?,-\/#!$%\^&\*;:{}<>=|\-_`~()\[\]]'/g, function(match, str, offset){return match.replace(/'/g, rightSingleSmartQuote);})
		       		 .replace(/'/g, leftSingleSmartQuote)
		       		 .replace(/\b"|[\.?,-\/#!$%\^&\*;:{}<>|=\-_`~()\[\]]"/g, function(match, str, offset){return match.replace(/"/g, rightDoubleSmartQuote);})
		       		 .replace(/"/g, leftDoubleSmartQuote)
		       		 .replace(/\D\s+-+\s*\D|\D\s*-+\s+\D|\s*--+\s*/, function(match, str, offset){return match.replace(/\s*-+\s*/g, emDash);})
		       		 .replace(/\.\s?\.\s?\.\s?/g, ellipsis);
};

/**
* Required because the treeWalker automatically escapes ampersands
*/
HN.util.replaceSmartQuoteEntities = function(string){
	return string.replace(/&#[\d]{4};/g, function(match, str, offset){
		return match.replace('&', '&');
	});
};

<|endoftext|>