<commit_before>(function(root) {
    var get   = typeof require == 'function' && require('request')
    var hyve  = typeof exports != 'undefined'?  exports : root.hyve = { }

    // ECMA-262 compatible Array#forEach polyfills
    Array.prototype.forEach = Array.prototype.forEach
    || function(fn, ctx) {
           var len = this.length >>> 0
           for (var i = 0; i < len; ++i)
               if (i in this) fn.call(ctx, this[i], i, this)
       }

    // Fills a template with data from an object
    function format(string, data) {
        return string.replace( /{{(?:#(.+?)#)?\s*(.+?)\s*}}/g
                             , function(m, cond, id) {
                                 var rv = data[id]
                                 return rv? (cond || '') + rv
                                          :  cond? m : '' })
    }

    // Pulls data from several streams and handle all them with the given
    // callback
    function stream(query, callback, custom_services) {
        var services
        services = custom_services || Object.keys(hyve.feeds)
        services.forEach(function(service){
            var options = hyve.feeds[service.toLowerCase()]
            var feed_url = format( options.feed_url,
                                     { query:  query
                                     , apikey: options.api_key })
                fetch(feed_url, service, callback)
            setInterval(function(){
                var feed_url = format( options.feed_url,
                                     { query:  query
                                     , apikey: options.api_key })
                fetch(feed_url, service, callback)
            }, options.interval)
        })
    }

    // Fetches a JSON stream
    var fetch = function() {
        var counter   = 0
        var callbacks = { }
        var head      = !get && document.getElementsByTagName('head')[0]

        // Returns a qualified identifier pointing to a callback
        function get_callback() {
            return format('hyve.callbacks.f{{id}}', { id: ++counter })
        }

        // Requires an URI using JSONP
        function jsonp(url, callback) {
            hyve.callbacks['f' + counter] = callback
            var s = document.createElement('script')
            s.setAttribute('src', url)
            head.appendChild(s)
        }

        // Requires an URI using Node.js's request library
        function request(url, callback) {
            get({uri: url}, function(error, res, data) {
                try {
                    callback(JSON.parse(data))
                }
                catch(e){
                    callback({ }, e)
                }
            })
        }

        // Abstracts fetching URIs.
        function fetch(url, service, callback) {
            var fn = pass(service, callback)
            var cb = !get && get_callback()
            url    = format(url, { callback: cb })

            var fetcher = get? request : jsonp
            fetcher(url, fn)
        }

        // Higher-order function to process the fetched data
        function pass(service, callback) {
            return function(data) {
                hyve.feeds[service].parse(data, callback)
            }
        }

        // Export the `fetch` function
        return fetch
    }()

    // Exports data to the outside world
    hyve.stream    = stream
    hyve.callbacks = []
    hyve.feeds     = {
            twitter: {
                interval : 2000,
                feed_url :'http://search.twitter.com/search.json?lang=en&q={{query}}{{#&callback=#callback}}',
                parse : function(data,callback){
                    if (data.refresh_url != null){
                        this.feed_url = 'http://search.twitter.com/search.json'
                                      + data.refresh_url
                                      + '{{#&callback=#callback}}'
                    }
                    if (data.results != null){
                        data.results.forEach(function(item){
                            callback({
                                'service' : 'twitter',
                                'user' : {
                                    'id' : item.from_user_id_str,
                                    'name' : item.from_user,
                                    'avatar' : item.profile_image_url
                                },
                                'id' : item.id_str,
                                'date' : item.created_at, //TODO: normalize
                                'text' : item.text,
                                'source' : item.source
                            })
                        })
                    }
                }
            },
            identica: {
                interval : 6000,
                feed_url :'http://identi.ca/api/search.json?lang=en&q={{query}}{{#&callback=#callback}}',
                parse : function(data,callback){
                    if (data.refresh_url != null){
                        this.feed_url = 'http://identi.ca/api/search.json' + data.refresh_url+ '{{#&callback=#callback}}'
                    }
                    data.results.forEach(function(item){
                        callback({
                            'service' : 'identica',
                            'user' : {
                                'id' : item.from_user_id_str,
                                'name' : item.from_user,
                                'avatar' : item.profile_image_url
                            },
                            'id' : item.id_str,
                            'date' : item.created_at, //TODO: normalize
                            'text' : item.text,
                            'source' : item.source
                        })
                    })
                }
            },
            buzz: {
                interval : 5000,
                api_key: '',
                feed_url :'https://www.googleapis.com/buzz/v1/activities/search?q={{query}}&alt=json&orderby=published{{#&callback=#callback}}{{#&key=#apikey}}',
                parse : function(data,callback){
                    if (this.orig_url == null){
                        this.orig_url = this.feed_url
                    }
                    if (data.data != null){
                        var last_date = data.data.items[0].updated.split('.')[0]
                        this.feed_url = this.orig_url.replace('{{query}}','{{query}}%20AND%20date%3E' + last_date)
                        data.data.items.forEach(function(item){
                            if (item.title != '-'){
                                callback({
                                    'service' : 'buzz',
                                    'user' : {
                                        'id' : item.actor.name,
                                        'name' : item.actor.name,
                                        'avatar' : item.actor.thumbnailUrl,
                                        'source' : item.actor.profileUrl
                                    },
                                    'id' : item.id.split(':')[3],
                                    'date' : item.published, //TODO normalize
                                    'text' : item.title,
                                    'source' : item.object.links.alternate[0].href
                                })
                            }
                        })
                    }
                }
            },
            facebook: {
                interval : 3000,
                feed_url : 'https://graph.facebook.com/search?q={{query}}&type=post{{#&callback=#callback}}',
                parse : function(data,callback){
                    if (data.data != null){
                        if (data.paging != null) {
                            this.feed_url = data.paging.previous + '{{#&callback=#callback}}'
                        }
                        data.data.forEach(function(item){
                            if (item.message != null){
                                callback({
                                    'service' : 'facebook',
                                    'user' : {
                                        'id' : item.from.id,
                                        'name' : item.from.name,
                                        'avatar' : 'http://graph.facebook.com/'+item.from.id+'/picture'
                                    },
                                    'id' : item.id,
                                    'date' : item.created_time, //TODO: normalize
                                    'text' : item.message,
                                    'source' : 'http://facebook.com/'+item.from.id
                                })
                            }
                        })
                    }
                }
            },
            reddit: {
                interval : 5000,
                feed_url : 'http://www.reddit.com/search.json?q={{query}}&sort=new{{#&jsonp=#callback}}',
                parse : function(data,callback){
                    if (data.data.children[0]){
                        if (this.orig_url == null){
                            this.orig_url = this.feed_url
                        }
                        var before = data.data.children[0].data.name
                        if (before != null){
                            this.feed_url = this.orig_url + '&before=' + before
                        }
                        data.data.children.forEach(function(item){
                            item = item.data
                            callback({
                                'service' : 'reddit',
                                'user' : {
                                    'id' : '',
                                    'name' : '',
                                    'avatar' : ''
                                },
                                'id' : item.id,
                                'date' : item.created_utc, //TODO: normalize
                                'text' : item.title,
                                'source' : item.url,
                                'thumbnail':'http://reddit.com' + item.thumbnail
                            })
                        })
                    }
                }
            },
            flickr: {
                interval : 10000,
                api_key : '',
                feed_url : 'http://api.flickr.com/services/feeds/photos_public.gne?format=json&tagmode=all&tags={{query}}{{#&jsoncallback=#callback}}&extras=date_upload,date_taken,owner_name,geo,tags,views',
                parse : function(data,callback){
                    if (this.items_seen == null){
                        this.items_seen = {};
                    }
                    data.items && data.items.forEach(function(item){
                        if (this.items_seen[item.media.m] == null){
                            this.items_seen[item.media.m] = true
                            console.log(item)
                            callback({
                                'service' : 'flickr',
                                'user' : {
                                    'id' : item.author_id,
                                    'name' : item.author,
                                    'avatar' : ''
                                },
                                'id' : '',
                                'date' : item.published, //TODO: normalize
                                'text' : item.description,
                                'source' : item.link,
                                'thumbnail':item.media.m
                            })
                        }
                    }, this)
                }
            },
            youtube: {
                interval : 8000,
                feed_url : 'http://gdata.youtube.com/feeds/api/videos?q={{query}}&time=today&orderby=published&format=5&max-results=20&v=2&alt=jsonc{{#&callback=#callback}}',
                parse : function(data,callback){
                    if (this.items_seen == null){
                        this.items_seen = {};
                    }
                    if (data.data != null){
                        data.data.items.forEach(function(item){
                            if (this.items_seen[item.id] == null){
                                this.items_seen[item.id] = true
                                callback({
                                    'service' : 'youtube',
                                    'user' : {
                                        'id' : item.uploader,
                                        'name' : item.uploader,
                                        'profile' : 'http://youtube.com/' + item.uploader,
                                        'avatar' : ''
                                    },
                                    'id' : item.id,
                                    'date' : '', //TODO: normalize
                                    'text' : item.title,
                                    'source' : 'http://youtu.be/'+ item.id,
                                    'thumbnail':'http://i.ytimg.com/vi/' + item.id + '/hqdefault.jpg'
                                })
                            }
                        }, this)
                    }
                }
            },
            wordpress: {
                interval : 10000,
                feed_url : 'http://pipes.yahoo.com/pipes/pipe.run?_id=332d9216d8910ba39e6c2577fd321a6a&_render=json&u=http%3A%2F%2Fen.search.wordpress.com%2F%3Fq%3D{{query}}%26s%3Ddate%26f%3Djson{{#&_callback=#callback}}',
                parse : function(data,callback){
                    if (this.items_seen == null){
                        this.items_seen = {};
                    }
                    if (data != null){
                        data.value.items.forEach(function(item){
                            if (this.items_seen[item.guid] == null){
                                this.items_seen[item.guid] = true
                                callback({
                                    'service' : 'wordpress',
                                    'user' : {
                                        'id' : item.author,
                                        'name' : item.author,
                                        'profile' :'',
                                        'avatar' : ''
                                    },
                                    'id' : item.id,
                                    'date' : '', //TODO: normalize
                                    'text' : item.title,
                                    'description':item.content,
                                    'source' : item.guid,
                                })
                            }
                        }, this)
                    }
                }
            }
        }
})(this);

/*
$.getJSON('http://pipes.yahoo.com/pipes/pipe.run?_id=332d9216d8910ba39e6c2577fd321a6a&_render=json&u=http%3A%2F%2Fen.search.wordpress.com%2F%3Fq%3D' + query + '%26f%3Djson&_callback=?', function(data){                                  $('
Wordpress

').appendTo($('body')).show('slow')
    $.each(data.value.items, function(i,item) {
        $('
'+ item.author + ' : ' + item.title + '
').hide().appendTo($('body')).show('slow')
    });
});
});*/
<commit_msg>change flickrs text from title to description<commit_after>(function(root) {
    var get   = typeof require == 'function' && require('request')
    var hyve  = typeof exports != 'undefined'?  exports : root.hyve = { }

    // ECMA-262 compatible Array#forEach polyfills
    Array.prototype.forEach = Array.prototype.forEach
    || function(fn, ctx) {
           var len = this.length >>> 0
           for (var i = 0; i < len; ++i)
               if (i in this) fn.call(ctx, this[i], i, this)
       }

    // Fills a template with data from an object
    function format(string, data) {
        return string.replace( /{{(?:#(.+?)#)?\s*(.+?)\s*}}/g
                             , function(m, cond, id) {
                                 var rv = data[id]
                                 return rv? (cond || '') + rv
                                          :  cond? m : '' })
    }

    // Pulls data from several streams and handle all them with the given
    // callback
    function stream(query, callback, custom_services) {
        var services
        services = custom_services || Object.keys(hyve.feeds)
        services.forEach(function(service){
            var options = hyve.feeds[service.toLowerCase()]
            var feed_url = format( options.feed_url,
                                     { query:  query
                                     , apikey: options.api_key })
                fetch(feed_url, service, callback)
            setInterval(function(){
                var feed_url = format( options.feed_url,
                                     { query:  query
                                     , apikey: options.api_key })
                fetch(feed_url, service, callback)
            }, options.interval)
        })
    }

    // Fetches a JSON stream
    var fetch = function() {
        var counter   = 0
        var callbacks = { }
        var head      = !get && document.getElementsByTagName('head')[0]

        // Returns a qualified identifier pointing to a callback
        function get_callback() {
            return format('hyve.callbacks.f{{id}}', { id: ++counter })
        }

        // Requires an URI using JSONP
        function jsonp(url, callback) {
            hyve.callbacks['f' + counter] = callback
            var s = document.createElement('script')
            s.setAttribute('src', url)
            head.appendChild(s)
        }

        // Requires an URI using Node.js's request library
        function request(url, callback) {
            get({uri: url}, function(error, res, data) {
                try {
                    callback(JSON.parse(data))
                }
                catch(e){
                    callback({ }, e)
                }
            })
        }

        // Abstracts fetching URIs.
        function fetch(url, service, callback) {
            var fn = pass(service, callback)
            var cb = !get && get_callback()
            url    = format(url, { callback: cb })

            var fetcher = get? request : jsonp
            fetcher(url, fn)
        }

        // Higher-order function to process the fetched data
        function pass(service, callback) {
            return function(data) {
                hyve.feeds[service].parse(data, callback)
            }
        }

        // Export the `fetch` function
        return fetch
    }()

    // Exports data to the outside world
    hyve.stream    = stream
    hyve.callbacks = []
    hyve.feeds     = {
            twitter: {
                interval : 2000,
                feed_url :'http://search.twitter.com/search.json?lang=en&q={{query}}{{#&callback=#callback}}',
                parse : function(data,callback){
                    if (data.refresh_url != null){
                        this.feed_url = 'http://search.twitter.com/search.json'
                                      + data.refresh_url
                                      + '{{#&callback=#callback}}'
                    }
                    if (data.results != null){
                        data.results.forEach(function(item){
                            callback({
                                'service' : 'twitter',
                                'user' : {
                                    'id' : item.from_user_id_str,
                                    'name' : item.from_user,
                                    'avatar' : item.profile_image_url
                                },
                                'id' : item.id_str,
                                'date' : item.created_at, //TODO: normalize
                                'text' : item.text,
                                'source' : item.source
                            })
                        })
                    }
                }
            },
            identica: {
                interval : 6000,
                feed_url :'http://identi.ca/api/search.json?lang=en&q={{query}}{{#&callback=#callback}}',
                parse : function(data,callback){
                    if (data.refresh_url != null){
                        this.feed_url = 'http://identi.ca/api/search.json' + data.refresh_url+ '{{#&callback=#callback}}'
                    }
                    data.results.forEach(function(item){
                        callback({
                            'service' : 'identica',
                            'user' : {
                                'id' : item.from_user_id_str,
                                'name' : item.from_user,
                                'avatar' : item.profile_image_url
                            },
                            'id' : item.id_str,
                            'date' : item.created_at, //TODO: normalize
                            'text' : item.text,
                            'source' : item.source
                        })
                    })
                }
            },
            buzz: {
                interval : 5000,
                api_key: '',
                feed_url :'https://www.googleapis.com/buzz/v1/activities/search?q={{query}}&alt=json&orderby=published{{#&callback=#callback}}{{#&key=#apikey}}',
                parse : function(data,callback){
                    if (this.orig_url == null){
                        this.orig_url = this.feed_url
                    }
                    if (data.data != null){
                        var last_date = data.data.items[0].updated.split('.')[0]
                        this.feed_url = this.orig_url.replace('{{query}}','{{query}}%20AND%20date%3E' + last_date)
                        data.data.items.forEach(function(item){
                            if (item.title != '-'){
                                callback({
                                    'service' : 'buzz',
                                    'user' : {
                                        'id' : item.actor.name,
                                        'name' : item.actor.name,
                                        'avatar' : item.actor.thumbnailUrl,
                                        'source' : item.actor.profileUrl
                                    },
                                    'id' : item.id.split(':')[3],
                                    'date' : item.published, //TODO normalize
                                    'text' : item.title,
                                    'source' : item.object.links.alternate[0].href
                                })
                            }
                        })
                    }
                }
            },
            facebook: {
                interval : 3000,
                feed_url : 'https://graph.facebook.com/search?q={{query}}&type=post{{#&callback=#callback}}',
                parse : function(data,callback){
                    if (data.data != null){
                        if (data.paging != null) {
                            this.feed_url = data.paging.previous + '{{#&callback=#callback}}'
                        }
                        data.data.forEach(function(item){
                            if (item.message != null){
                                callback({
                                    'service' : 'facebook',
                                    'user' : {
                                        'id' : item.from.id,
                                        'name' : item.from.name,
                                        'avatar' : 'http://graph.facebook.com/'+item.from.id+'/picture'
                                    },
                                    'id' : item.id,
                                    'date' : item.created_time, //TODO: normalize
                                    'text' : item.message,
                                    'source' : 'http://facebook.com/'+item.from.id
                                })
                            }
                        })
                    }
                }
            },
            reddit: {
                interval : 5000,
                feed_url : 'http://www.reddit.com/search.json?q={{query}}&sort=new{{#&jsonp=#callback}}',
                parse : function(data,callback){
                    if (data.data.children[0]){
                        if (this.orig_url == null){
                            this.orig_url = this.feed_url
                        }
                        var before = data.data.children[0].data.name
                        if (before != null){
                            this.feed_url = this.orig_url + '&before=' + before
                        }
                        data.data.children.forEach(function(item){
                            item = item.data
                            callback({
                                'service' : 'reddit',
                                'user' : {
                                    'id' : '',
                                    'name' : '',
                                    'avatar' : ''
                                },
                                'id' : item.id,
                                'date' : item.created_utc, //TODO: normalize
                                'text' : item.title,
                                'source' : item.url,
                                'thumbnail':'http://reddit.com' + item.thumbnail
                            })
                        })
                    }
                }
            },
            flickr: {
                interval : 10000,
                api_key : '',
                feed_url : 'http://api.flickr.com/services/feeds/photos_public.gne?format=json&tagmode=all&tags={{query}}{{#&jsoncallback=#callback}}&extras=date_upload,date_taken,owner_name,geo,tags,views',
                parse : function(data,callback){
                    if (this.items_seen == null){
                        this.items_seen = {};
                    }
                    data.items && data.items.forEach(function(item){
                        if (this.items_seen[item.media.m] == null){
                            this.items_seen[item.media.m] = true
                            console.log(item)
                            callback({
                                'service' : 'flickr',
                                'user' : {
                                    'id' : item.author_id,
                                    'name' : item.author,
                                    'avatar' : ''
                                },
                                'id' : '',
                                'date' : item.published, //TODO: normalize
                                'text' : item.title,
                                'source' : item.link,
                                'thumbnail':item.media.m
                            })
                        }
                    }, this)
                }
            },
            youtube: {
                interval : 8000,
                feed_url : 'http://gdata.youtube.com/feeds/api/videos?q={{query}}&time=today&orderby=published&format=5&max-results=20&v=2&alt=jsonc{{#&callback=#callback}}',
                parse : function(data,callback){
                    if (this.items_seen == null){
                        this.items_seen = {};
                    }
                    if (data.data != null){
                        data.data.items.forEach(function(item){
                            if (this.items_seen[item.id] == null){
                                this.items_seen[item.id] = true
                                callback({
                                    'service' : 'youtube',
                                    'user' : {
                                        'id' : item.uploader,
                                        'name' : item.uploader,
                                        'profile' : 'http://youtube.com/' + item.uploader,
                                        'avatar' : ''
                                    },
                                    'id' : item.id,
                                    'date' : '', //TODO: normalize
                                    'text' : item.title,
                                    'source' : 'http://youtu.be/'+ item.id,
                                    'thumbnail':'http://i.ytimg.com/vi/' + item.id + '/hqdefault.jpg'
                                })
                            }
                        }, this)
                    }
                }
            },
            wordpress: {
                interval : 10000,
                feed_url : 'http://pipes.yahoo.com/pipes/pipe.run?_id=332d9216d8910ba39e6c2577fd321a6a&_render=json&u=http%3A%2F%2Fen.search.wordpress.com%2F%3Fq%3D{{query}}%26s%3Ddate%26f%3Djson{{#&_callback=#callback}}',
                parse : function(data,callback){
                    if (this.items_seen == null){
                        this.items_seen = {};
                    }
                    if (data != null){
                        data.value.items.forEach(function(item){
                            if (this.items_seen[item.guid] == null){
                                this.items_seen[item.guid] = true
                                callback({
                                    'service' : 'wordpress',
                                    'user' : {
                                        'id' : item.author,
                                        'name' : item.author,
                                        'profile' :'',
                                        'avatar' : ''
                                    },
                                    'id' : item.id,
                                    'date' : '', //TODO: normalize
                                    'text' : item.title,
                                    'description':item.content,
                                    'source' : item.guid,
                                })
                            }
                        }, this)
                    }
                }
            }
        }
})(this);

/*
$.getJSON('http://pipes.yahoo.com/pipes/pipe.run?_id=332d9216d8910ba39e6c2577fd321a6a&_render=json&u=http%3A%2F%2Fen.search.wordpress.com%2F%3Fq%3D' + query + '%26f%3Djson&_callback=?', function(data){                                  $('
Wordpress

').appendTo($('body')).show('slow')
    $.each(data.value.items, function(i,item) {
        $('
'+ item.author + ' : ' + item.title + '
').hide().appendTo($('body')).show('slow')
    });
});
});*/
<|endoftext|>


<commit_before>'use strict';
var async = require('async'),
    fs = require('fs'),
    path = require('path'),
    jsmin = require('jsmin').jsmin,
    cssmin = require('cssmin');
var config = require('./config.json'),
    database = require('./database'),
    graph = require('./graph'),
    util = require('./utility'),
    server = require('./server');

var formatRow = util.formattedRowGenerator(),
    checks = {};

function readall(dir, filter, callback) {
    fs.readdir(dir, function (err, files) {
        if (err) {
            callback(err);
        }
        var tpl = files.filter(function (file) {
            return filter.test(file);
        });
        async.map(tpl.map(function (file) {
            return path.join(dir, file);
        }), function (file, next) {
            fs.readFile(file, 'binary', next);
        }, function (errs, data) {
            if (errs) {
                return callback(errs);
            }
            callback(null, tpl.map(function (name, idx) {
                return {
                    name: name,
                    data: data[idx]
                };
            }));
        });

    });

}

function readTemplates(callback) {
    var suffix = /[.]html?$/;
    readall('templates', suffix, function (err, files) {
        if (err) {
            return callback(err);
        }
        var templates = {};
        files.forEach(function (file) {
            templates[file.name] = file.data;
            templates[file.name.replace(suffix, '')] = file.data;
        });
        callback(null, templates);
    });
}

function readScripts(callback) {
    readall('static/scripts', /[.]js/, function (err, files) {
        if (err) {
            return callback(err);
        }
        files.forEach(function (file) {
            if (/[.]min[.]js$/.test(file.name)) {
                return;
            }
            try {
                file.data = jsmin(file.data);
            } catch (e) {
                /*eslint-disable no-console */
                console.warn('Error minifying ' + file.name + ': ' + e);
                /*eslint-enable no-console */
            }
        });
        callback(null, files);
    });
}

function readStyles(callback) {
    readall('static/styles', /[.]css/, function (err, files) {
        if (err) {
            return callback(err);
        }
        files.forEach(function (file) {
            if (/[.]min[.]css$/.test(file.name)) {
                return;
            }
            try {
                file.data = cssmin(file.data);
            } catch (e) {
                /*eslint-disable no-console */
                console.warn('Error minifying ' + file.name + ': ' + e);
                /*eslint-enable no-console */
            }
        });
        callback(null, files);
    });
}

exports.buildCache = function buildCache(callback) {
    async.parallel({
        templates: readTemplates,
        scripts: readScripts,
        styles: readStyles
    }, function (err, results) {
        if (err) {
            return callback(err);
        }
        exports.templates = results.templates;
        exports.scripts = results.scripts;
        exports.styles = results.styles;
        callback();
    });
};

exports.templates = {};
exports.scripts = {};
exports.styles = {};

function setSummary(summary) {
    exports.summary = summary;
    exports.summary.getTimeChart = graph.getTimeChart;
    exports.summary.getScoreChart = graph.getScoreChart;
    updateClient();
}

function summarizeEndpoint(key, data, cutoff) {
    var counts = util.truncateData(data, function (d) {
            return d.checkedAt > cutoff;
        }, config.scoreEntries),
        score = util.roundAverage(counts, function (a) {
            return a.score;
        });
    return {
        name: key,
        response: util.getFlavor(score, config.scoreCode),
        responseCode: util.roundAverage(counts, function (a) {
            return a.responseCode;
        }, 2),
        responseTime: util.roundAverage(counts, function (a) {
            return a.responseTime;
        }, 2),
        responseScore: score,
        polledAt: counts[0].polledAt,
        checkIndex: counts[0].checkId,
        values: util.truncateData(data, config.historyEntries)
    };
}

function getOverallScores(data) {
    var res = [];
    for (var key in data) {
        if (key === 'overall') {
            continue;
        }
        res = res.concat(data[key].slice(0, 3));
    }
    return util.roundAverage(res, function (a) {
        return a.score;
    });
}

function summarize(data, extra, callback) {
    var cutoff = Date.now() - (config.scorePeriod * 1000),
        overall = summarizeEndpoint('overall', data.overall, cutoff),
        score = getOverallScores(data),
        result = {
            version: config.version,
            time: new Date().toISOString(),
            up: score >= 50,
            score: score,
            code: util.getFlavor(score, config.scoreCode),
            status: util.getFlavor(score, config.status),
            flavor: util.getFlavor(score, config.flavor)
        },
        keys = Object.keys(data);
    Object.keys(extra).forEach(function (key) {
        result[key] = extra[key];
    });
    keys.sort();
    result.summary = keys.map(function (key) {
        if (key === 'overall') {
            return null;
        }
        return summarizeEndpoint(key, data[key], cutoff);
    }).filter(function (d) {
        return d;
    });
    result.summary.unshift(overall);
    callback(null, result);
}

database.getChecks(config.historyPeriod, function (_, data) {
    util.parseData(data, formatRow, function (__, result) {
        checks = result;
        summarize(checks, {}, function (___, summary) {
            setSummary(summary);
        });
    });
});

database.registerListener(function (data) {
    formatRow(data, function (_, rows) {
        rows.forEach(function (r) {
            var check = checks[r.checkName] || [];
            check.unshift(r);
            check = util.truncateData(check, config.historyEntries);
            checks[r.checkName] = check;
            server.io.emit('data', null, r);
        });
        summarize(checks, {}, function (__, summary) {
            setSummary(summary);
        });
    });
});

exports.summary = {};

function updateClient() {
    var latest = {
        up: exports.summary.up,
        score: exports.summary.score,
        code: exports.summary.code,
        status: exports.summary.status,
        flavor: exports.summary.flavor,
        summary: exports.summary.summary.map(function (summary) {
            return {
                name: summary.name,
                response: summary.response,
                responseCode: summary.responseCode,
                responseScore: summary.responseScore,
                responseTime: summary.responseTime,
                polledAt: summary.polledAt,
                checkIndex: summary.checkIndex
            };
        })
    };
    server.io.emit('summary', latest);
}
<commit_msg>prevent break on stale cache<commit_after>'use strict';
var async = require('async'),
    fs = require('fs'),
    path = require('path'),
    jsmin = require('jsmin').jsmin,
    cssmin = require('cssmin');
var config = require('./config.json'),
    database = require('./database'),
    graph = require('./graph'),
    util = require('./utility'),
    server = require('./server');

var formatRow = util.formattedRowGenerator(),
    checks = {};

function readall(dir, filter, callback) {
    fs.readdir(dir, function (err, files) {
        if (err) {
            callback(err);
        }
        var tpl = files.filter(function (file) {
            return filter.test(file);
        });
        async.map(tpl.map(function (file) {
            return path.join(dir, file);
        }), function (file, next) {
            fs.readFile(file, 'binary', next);
        }, function (errs, data) {
            if (errs) {
                return callback(errs);
            }
            callback(null, tpl.map(function (name, idx) {
                return {
                    name: name,
                    data: data[idx]
                };
            }));
        });

    });

}

function readTemplates(callback) {
    var suffix = /[.]html?$/;
    readall('templates', suffix, function (err, files) {
        if (err) {
            return callback(err);
        }
        var templates = {};
        files.forEach(function (file) {
            templates[file.name] = file.data;
            templates[file.name.replace(suffix, '')] = file.data;
        });
        callback(null, templates);
    });
}

function readScripts(callback) {
    readall('static/scripts', /[.]js/, function (err, files) {
        if (err) {
            return callback(err);
        }
        files.forEach(function (file) {
            if (/[.]min[.]js$/.test(file.name)) {
                return;
            }
            try {
                file.data = jsmin(file.data);
            } catch (e) {
                /*eslint-disable no-console */
                console.warn('Error minifying ' + file.name + ': ' + e);
                /*eslint-enable no-console */
            }
        });
        callback(null, files);
    });
}

function readStyles(callback) {
    readall('static/styles', /[.]css/, function (err, files) {
        if (err) {
            return callback(err);
        }
        files.forEach(function (file) {
            if (/[.]min[.]css$/.test(file.name)) {
                return;
            }
            try {
                file.data = cssmin(file.data);
            } catch (e) {
                /*eslint-disable no-console */
                console.warn('Error minifying ' + file.name + ': ' + e);
                /*eslint-enable no-console */
            }
        });
        callback(null, files);
    });
}

exports.buildCache = function buildCache(callback) {
    async.parallel({
        templates: readTemplates,
        scripts: readScripts,
        styles: readStyles
    }, function (err, results) {
        if (err) {
            return callback(err);
        }
        exports.templates = results.templates;
        exports.scripts = results.scripts;
        exports.styles = results.styles;
        callback();
    });
};

exports.templates = {};
exports.scripts = {};
exports.styles = {};

function setSummary(summary) {
    exports.summary = summary;
    exports.summary.getTimeChart = graph.getTimeChart;
    exports.summary.getScoreChart = graph.getScoreChart;
    updateClient();
}

function summarizeEndpoint(key, data, cutoff) {
    var counts = util.truncateData(data, function (d) {
            return d.checkedAt > cutoff;
        }, config.scoreEntries),
        score = util.roundAverage(counts, function (a) {
            return a.score;
        });
    if (!counts) {
        return undefined;
    }
    return {
        name: key,
        response: util.getFlavor(score, config.scoreCode),
        responseCode: util.roundAverage(counts, function (a) {
            return a.responseCode;
        }, 2),
        responseTime: util.roundAverage(counts, function (a) {
            return a.responseTime;
        }, 2),
        responseScore: score,
        polledAt: counts[0].polledAt,
        checkIndex: counts[0].checkId,
        values: util.truncateData(data, config.historyEntries)
    };
}

function getOverallScores(data) {
    var res = [];
    for (var key in data) {
        if (key === 'overall') {
            continue;
        }
        res = res.concat(data[key].slice(0, 3));
    }
    return util.roundAverage(res, function (a) {
        return a.score;
    });
}

function summarize(data, extra, callback) {
    var cutoff = Date.now() - (config.scorePeriod * 1000),
        overall = summarizeEndpoint('overall', data.overall, cutoff),
        score = getOverallScores(data),
        result = {
            version: config.version,
            time: new Date().toISOString(),
            up: score >= 50,
            score: score,
            code: util.getFlavor(score, config.scoreCode),
            status: util.getFlavor(score, config.status),
            flavor: util.getFlavor(score, config.flavor)
        },
        keys = Object.keys(data);
    Object.keys(extra).forEach(function (key) {
        result[key] = extra[key];
    });
    keys.sort();
    result.summary = keys.map(function (key) {
        if (key === 'overall') {
            return null;
        }
        return summarizeEndpoint(key, data[key], cutoff);
    }).filter(function (d) {
        return d;
    });
    result.summary.unshift(overall);
    callback(null, result);
}

database.getChecks(config.historyPeriod, function (_, data) {
    util.parseData(data, formatRow, function (__, result) {
        checks = result;
        summarize(checks, {}, function (___, summary) {
            setSummary(summary);
        });
    });
});

database.registerListener(function (data) {
    formatRow(data, function (_, rows) {
        rows.forEach(function (r) {
            var check = checks[r.checkName] || [];
            check.unshift(r);
            check = util.truncateData(check, config.historyEntries);
            checks[r.checkName] = check;
            server.io.emit('data', null, r);
        });
        summarize(checks, {}, function (__, summary) {
            setSummary(summary);
        });
    });
});

exports.summary = {};

function updateClient() {
    var latest = {
        up: exports.summary.up,
        score: exports.summary.score,
        code: exports.summary.code,
        status: exports.summary.status,
        flavor: exports.summary.flavor,
        summary: exports.summary.summary.map(function (summary) {
            return {
                name: summary.name,
                response: summary.response,
                responseCode: summary.responseCode,
                responseScore: summary.responseScore,
                responseTime: summary.responseTime,
                polledAt: summary.polledAt,
                checkIndex: summary.checkIndex
            };
        })
    };
    server.io.emit('summary', latest);
}
<|endoftext|>


<commit_before>var keyboard = {
    size: 80,
    init: function() {
        keyboard.kbd = $('
').appendTo(document.body).css('font-family', 'sans-serif');
        keyboard.ui().appendTo(document.body);
        [
            '`1234567890-=|bksp|1.7|',
            '|tab|1.7|qwertyuiop[]\\',
            '|caps|1.88|asdfghjkl;\'|enter|2|',
            '|shift|2.53*|zxcvbnm,./|shift|2.53*|',
            '|ctrl|1.4*||super|1.4*||alt|1.4*||space|7.56||alt|1.4*||menu|1.4||ctrl|1.4*|'
        ].forEach(function(row) {
            keyboard.addKeys(row);
        });
        keyboard.updateSizes();
        keyboard.addRow();
        keyboard.addRow('shift');
        keyboard.addRow('ctrl');
        keyboard.addRow(['alt', 'shift']);
    },
    ui: function() {
        return $('
')
            .append($('
')
                .append($('<button>')
                    .text('+size')
                    .click(function() {
                        keyboard.updateSizes(keyboard.size + 5);
                        $('#size-input').val(keyboard.size);
                    })
                )
                .append($('<button>')
                    .text('-size')
                    .click(function() {
                        keyboard.updateSizes(keyboard.size - 5);
                        $('#size-input').val(keyboard.size);
                    })
                )
                .append($('<input>')
                    .attr('id', 'size-input')
                    .val(keyboard.size)
                    .keyup(function() {
                        keyboard.updateSizes(+$(this).val());
                    })
                )
            )
            .append($('
')
                .append($('<button>')
                    .text('add row')
                    .click(function() {
                        var modifiers = [].slice.call($('.addrow-chkbx')
                            .map(function() {
                                return this.checked ? this.id : '';
                            }))
                            .filter(function(x) {
                                return x !== '';
                            });
                        keyboard.addRow(modifiers);
                    })
                )
                .append($('').text('Shift: '))
                .append($('<input>')
                    .attr({ type: 'checkbox', id: 'shift' })
                    .addClass('addrow-chkbx')
                )
                .append($('').text(' Alt: '))
                .append($('<input>')
                    .attr({ type: 'checkbox', id: 'alt' })
                    .addClass('addrow-chkbx')
                )
                .append($('').text(' Ctrl: '))
                .append($('<input>')
                    .attr({ type: 'checkbox', id: 'ctrl' })
                    .addClass('addrow-chkbx')
                )
            )
            .append($('
')
                .append($('<button>')
                    .text('delete row')
                    .click(function() {
                        keyboard.deleteRow(+$('#delete-row').val());
                    })
                )
                .append($('<input>')
                    .attr('id', 'delete-row')
                    .val(1)
                )
            )
            .append($('
')
                .append($('<button>')
                    .text('import')
                    .click(function() {
                        var load = JSON.parse($('#import-export').val());
                        while (keyboard.rows.length !== 0) keyboard.deleteRow(1);
                        load.rows.forEach(function(x) { keyboard.addRow(x); });
                        var data = load.data;
                        $('.data').each(function() {
                            $(this).val(load.data.shift());
                            $(this).height(this.scrollHeight);
                        });
                    })
                )
                .append($('<button>')
                    .text('export')
                    .click(function() {
                        $('#import-export').val(JSON.stringify({
                            rows: keyboard.rows,
                            data: [].slice.call($('.data').map(function() {
                                return $(this).val();
                            }))
                        }));
                    })
                )
                .append($('<textarea>')
                    .attr('id', 'import-export')
                    .focus(function() {
                        $(this).select();
                        // WebKit bugfix
                        setTimeout(function() { $(this).select(); }, 1);
                        // another WebKit bugfix
                        $(this).mouseup(function() {
                            $(this).off('mouseup');
                            return false;
                        });
                    })
                )
            );
    },
    addKey: function(k, w, mod) {
        return $('
')
            .addClass('key' + (mod ? ' mod' : ''))
            .css({
                float: 'left'
            })
            .data({
                key: k,
                width: +w || 1
            })
            .append($('
')
                .css('border', '1px solid black')
                .append($('
')
                    .css({
                        padding: '0px',
                        margin: '0px',
                        borderCollapse: 'collapse'
                    })
                )
            );
    },
    addKeys: function(keys, fmtChar) {
        if (fmtChar === undefined) fmtChar = '|';
        var f = '\\' + fmtChar,
            rgx = f + '[^' + f + ']*' + f + '[^' + f + ']*' + f + '|.';
        keys.match(new RegExp(rgx, 'g')).forEach(function(k) {
            if (k[0] === fmtChar) {
                var key = k.split(fmtChar)[1], width = k.split(fmtChar)[2];
                keyboard.kbd.append(keyboard.addKey(key, width.replace('*', ''),
                    width.indexOf('*') !== -1));
            } else {
                keyboard.kbd.append(keyboard.addKey(k));
            }
        });
        keyboard.kbd.append($('
').css('clear', 'both'));
    },
    updateSizes: function(newSize) {
        if (newSize) keyboard.size = newSize;
        $('.key').each(function() {
            $('>div', this).css({
                width: (keyboard.size * $(this).data('width')) + 'px',
                height: keyboard.size + 'px',
                margin: (keyboard.size / 25) + 'px',
                padding: (keyboard.size / 25) + 'px',
                fontSize: Math.floor(keyboard.size * 0.1) + 'pt'
            });
        });
        keyboard.updateRowSizes();
        $('.data').each(function() {
            $(this).height(0).height(this.scrollHeight);
        });
    },
    updateRowSizes: function() {
        $('.row').css({ height: (keyboard.size / $('.key:first tr').length) + 'px' })
        $('.row2').css({ height: keyboard.size + 'px' })
    },
    rows: [],
    addRow: function(modifiers) {
        if (typeof modifiers === 'undefined') modifiers = [];
        if (typeof modifiers === 'string') modifiers = [modifiers];
        var funcs = [];
        if (modifiers.indexOf('shift') !== -1) {
            funcs.push(function(k) {
                var idx;
                if ('abcdefghijklmnopqrstuvwxyz'.split('').indexOf(k) !== -1) {
                    return k.toUpperCase();
                } else if ((idx = '`1234567890-=[]\\;\',./'.split('').indexOf(k)) !== -1) {
                    return '~!@#$%^&*()_+{}|:"<>?'[idx];
                } else {
                    return 'S-' + k;
                }
            });
        }
        if (modifiers.indexOf('alt') !== -1) {
            funcs.push(function(k) { return 'A-' + k; });
        }
        if (modifiers.indexOf('ctrl') !== -1) {
            funcs.push(function(k) { return 'C-' + k; });
        }
        var transFunc = function(k) {
            funcs.forEach(function(f) { k = f(k); });
            return k;
        };
        keyboard.rows.push(modifiers);

        $('.key:not(.mod)').each(function() {
            $('table', this).append($('
')
                .addClass('row')
                .append($('')
                    .text(transFunc($(this).data('key')))
                    .css({
                        fontWeight: 'bold',
                        padding: '0px 5px 0px 0px',
                        whiteSpace: 'nowrap'
                    })
                    .click(function() {
                        $(this).parent().find('textarea').focus().select();
                    })
                ).append($('')
                    .append($('<textarea>')
                        .val('-')
                        .addClass('data toFix')
                        .css({
                            border: 'none',
                            padding: '0px',
                            margin: '0px',
                            font: 'inherit',
                            width: '100%',
                            height: '0px',
                            overflow: 'hidden',
                            outline: 'none',
                            resize: 'none'
                        })
                        .attr('spellcheck', false)
                        .on('input', function() {
                            $(this).height(0).height(this.scrollHeight);
                        })
                    )
                )
            );
            $('.toFix').removeClass('toFix').each(function() {
                $(this).height(this.scrollHeight);
            });
        });
        $('.key.mod').each(function() {
            var tbl = $('table', this);
            if (tbl.children().length === 0) {
                tbl.append($('
')
                    .addClass('row2')
                    .css('font-weight', 'bold')
                    .append($('')
                        .text($(this).data('key'))
                    )
                );
            }
        });
        keyboard.updateRowSizes();
    },
    deleteRow: function(n) {
        $('.key:not(.mod) .row:nth-child(' + n + ')').remove();
        keyboard.rows.splice(n-1, 1);
        keyboard.updateRowSizes();
    }
};

$(keyboard.init);
<commit_msg>visual improvements, even smaller default size<commit_after>var keyboard = {
    size: 60,
    init: function() {
        keyboard.kbd = $('
').appendTo(document.body).css('font-family', 'sans-serif');
        keyboard.ui().appendTo(document.body);
        [
            '`1234567890-=|bksp|1.7|',
            '|tab|1.7|qwertyuiop[]\\',
            '|caps|1.88|asdfghjkl;\'|enter|2|',
            '|shift|2.53*|zxcvbnm,./|shift|2.53*|',
            '|ctrl|1.4*||super|1.4*||alt|1.4*||space|7.56||alt|1.4*||menu|1.4||ctrl|1.4*|'
        ].forEach(function(row) {
            keyboard.addKeys(row);
        });
        keyboard.updateSizes();
        keyboard.addRow();
        keyboard.addRow('ctrl');
        keyboard.addRow(['alt', 'shift']);
    },
    ui: function() {
        return $('
')
            .append($('
')
                .append($('<button>')
                    .text('+size')
                    .click(function() {
                        keyboard.updateSizes(keyboard.size + 5);
                        $('#size-input').val(keyboard.size);
                    })
                )
                .append($('<button>')
                    .text('-size')
                    .click(function() {
                        keyboard.updateSizes(keyboard.size - 5);
                        $('#size-input').val(keyboard.size);
                    })
                )
                .append($('<input>')
                    .attr('id', 'size-input')
                    .val(keyboard.size)
                    .keyup(function() {
                        keyboard.updateSizes(+$(this).val());
                    })
                )
            )
            .append($('
')
                .append($('<button>')
                    .text('add row')
                    .click(function() {
                        var modifiers = [].slice.call($('.addrow-chkbx')
                            .map(function() {
                                return this.checked ? this.id : '';
                            }))
                            .filter(function(x) {
                                return x !== '';
                            });
                        keyboard.addRow(modifiers);
                    })
                )
                .append($('').text('Shift: '))
                .append($('<input>')
                    .attr({ type: 'checkbox', id: 'shift' })
                    .addClass('addrow-chkbx')
                )
                .append($('').text(' Alt: '))
                .append($('<input>')
                    .attr({ type: 'checkbox', id: 'alt' })
                    .addClass('addrow-chkbx')
                )
                .append($('').text(' Ctrl: '))
                .append($('<input>')
                    .attr({ type: 'checkbox', id: 'ctrl' })
                    .addClass('addrow-chkbx')
                )
            )
            .append($('
')
                .append($('<button>')
                    .text('delete row')
                    .click(function() {
                        keyboard.deleteRow(+$('#delete-row').val());
                    })
                )
                .append($('<input>')
                    .attr('id', 'delete-row')
                    .val(1)
                )
            )
            .append($('
')
                .append($('<button>')
                    .text('import')
                    .click(function() {
                        var load = JSON.parse($('#import-export').val());
                        while (keyboard.rows.length !== 0) keyboard.deleteRow(1);
                        load.rows.forEach(function(x) { keyboard.addRow(x); });
                        var data = load.data;
                        $('.data').each(function() {
                            $(this).val(load.data.shift());
                            $(this).height(this.scrollHeight);
                        });
                    })
                )
                .append($('<button>')
                    .text('export')
                    .click(function() {
                        $('#import-export').val(JSON.stringify({
                            rows: keyboard.rows,
                            data: [].slice.call($('.data').map(function() {
                                return $(this).val();
                            }))
                        }));
                    })
                )
                .append($('<textarea>')
                    .attr('id', 'import-export')
                    .focus(function() {
                        $(this).select();
                        // WebKit bugfix
                        setTimeout(function() { $(this).select(); }, 1);
                        // another WebKit bugfix
                        $(this).mouseup(function() {
                            $(this).off('mouseup');
                            return false;
                        });
                    })
                )
            );
    },
    addKey: function(k, w, mod) {
        return $('
')
            .addClass('key' + (mod ? ' mod' : ''))
            .css({
                float: 'left'
            })
            .data({
                key: k,
                width: +w || 1
            })
            .append($('
')
                .css('border', '1px solid black')
                .append($('
')
                    .css({
                        padding: '0px',
                        margin: '0px',
                        borderCollapse: 'collapse'
                    })
                )
            );
    },
    addKeys: function(keys, fmtChar) {
        if (fmtChar === undefined) fmtChar = '|';
        var f = '\\' + fmtChar,
            rgx = f + '[^' + f + ']*' + f + '[^' + f + ']*' + f + '|.';
        keys.match(new RegExp(rgx, 'g')).forEach(function(k) {
            if (k[0] === fmtChar) {
                var key = k.split(fmtChar)[1], width = k.split(fmtChar)[2];
                keyboard.kbd.append(keyboard.addKey(key, width.replace('*', ''),
                    width.indexOf('*') !== -1));
            } else {
                keyboard.kbd.append(keyboard.addKey(k));
            }
        });
        keyboard.kbd.append($('
').css('clear', 'both'));
    },
    updateSizes: function(newSize) {
        if (newSize) keyboard.size = newSize;
        $('.key').each(function() {
            $('>div', this).css({
                width: (keyboard.size * $(this).data('width')) + 'px',
                height: keyboard.size + 'px',
                margin: (keyboard.size / 25) + 'px',
                padding: (keyboard.size / 25) + 'px',
                fontSize: Math.floor(keyboard.size * 0.1) + 'pt'
            });
        });
        keyboard.updateRowSizes();
        $('.data').each(function() {
            $(this).height(0).height(this.scrollHeight);
        });
        // an ugly hack, which fixes a visual bug due to rounding
        var endPx = undefined;
        keyboard.kbd.find('>br').each(function() {
            var key = $(this).prev().find('>div');
            var thisPx = key.width() + key.offset().left;
            if (endPx !== undefined) {
                key.width(key.width() - (thisPx - endPx));
            } else {
                endPx = thisPx;
            }
        });
    },
    updateRowSizes: function() {
        $('.row').css({ height: (keyboard.size / $('.key:first tr').length) + 'px' })
        $('.row2').css({ height: keyboard.size + 'px' })
    },
    rows: [],
    addRow: function(modifiers) {
        if (typeof modifiers === 'undefined') modifiers = [];
        if (typeof modifiers === 'string') modifiers = [modifiers];
        var funcs = [];
        if (modifiers.indexOf('shift') !== -1) {
            funcs.push(function(k) {
                var idx;
                if ('abcdefghijklmnopqrstuvwxyz'.split('').indexOf(k) !== -1) {
                    return k.toUpperCase();
                } else if ((idx = '`1234567890-=[]\\;\',./'.split('').indexOf(k)) !== -1) {
                    return '~!@#$%^&*()_+{}|:"<>?'[idx];
                } else {
                    return 'S-' + k;
                }
            });
        }
        if (modifiers.indexOf('alt') !== -1) {
            funcs.push(function(k) { return 'A-' + k; });
        }
        if (modifiers.indexOf('ctrl') !== -1) {
            funcs.push(function(k) { return 'C-' + k; });
        }
        var transFunc = function(k) {
            funcs.forEach(function(f) { k = f(k); });
            return k;
        };
        keyboard.rows.push(modifiers);

        $('.key:not(.mod)').each(function() {
            $('table', this).append($('
')
                .addClass('row')
                .append($('')
                    .text(transFunc($(this).data('key')))
                    .css({
                        fontWeight: 'bold',
                        padding: '0px 5px 0px 0px',
                        whiteSpace: 'nowrap'
                    })
                    .click(function() {
                        $(this).parent().find('textarea').focus().select();
                    })
                ).append($('')
                    .append($('<textarea>')
                        .val('-')
                        .addClass('data toFix')
                        .css({
                            border: 'none',
                            padding: '0px',
                            margin: '0px',
                            font: 'inherit',
                            width: '100%',
                            height: '0px',
                            overflow: 'hidden',
                            outline: 'none',
                            resize: 'none'
                        })
                        .attr('spellcheck', false)
                        .on('input', function() {
                            $(this).height(0).height(this.scrollHeight);
                        })
                    )
                )
            );
            $('.toFix').removeClass('toFix').each(function() {
                $(this).height(this.scrollHeight);
            });
        });
        $('.key.mod').each(function() {
            var tbl = $('table', this);
            if (tbl.children().length === 0) {
                tbl.append($('
')
                    .addClass('row2')
                    .css('font-weight', 'bold')
                    .append($('')
                        .text($(this).data('key'))
                    )
                );
            }
        });
        keyboard.updateRowSizes();
    },
    deleteRow: function(n) {
        $('.key:not(.mod) .row:nth-child(' + n + ')').remove();
        keyboard.rows.splice(n-1, 1);
        keyboard.updateRowSizes();
    }
};

$(keyboard.init);
<|endoftext|>


<commit_before>'use strict';

/**
takes in string value from move command from message,
converts it to value that can be used for accessing
the board array
**/
function getCoord(val){
	const convert = {
		"a": 0, "b": 1, "c": 2, "d": 3, "e": 4, "f": 5, "g": 6, "h": 7,
		"8": 0, "7": 1, "6": 2, "5": 3, "4": 4, "3": 5, "2": 6, "1": 7
	};

	//check for bad coords
	// tolowercase will not do anything to numbers
	if(convert[val.toLowerCase()] !== undefined){
		return convert[val];
	}else{
		throw new Error('invalid string to convert to coordinate');
	}
}

/*return the first letter of the unit on that board coord
i.e. the color of the piece*/
function getColor(x, y, board){
	if(x > 7 || x < 0 || y > 7 || y < 0){
		throw new Error('trying to get color of something out of bounds');
	}else{
		return board[y][x].charAt(0);
	}
}

function getPiece(x, y, board){
	if(x > 7 || x < 0 || y > 7 || y < 0){
		throw new Error('trying to get piece type of something out of bounds');
	}else{
		return board[y][x].charAt(1);
	}
}

function isInBoard(coord){
	return (coord > 0 && coord < 7) ? true : false;
}

function nothingBetweenDiag(startX, startY, destX, destY, board){
	var upperBoundX;
	var lowerBoundX;
	var lowerBoundY;
	/*don't need to do upperBoundY because we
	use upperBoundX to get the number of spaces that the bishop
	moves vertically*/
	if(destX > startX){
		upperBoundX = destX;
		lowerBoundX = startX;
	}else{
		upperBoundX = startX;
		lowerBoundX = destX;
	}

	if(destY > startY){
		lowerBoundY = startY;
	}else{
		lowerBoundY = destY;
	}

	//chose upperBoundX and lowerBoundX because only displacement amount matters
	//completely arbitrary, could use the other too
	for(var i = 1; i < upperBoundX - lowerBoundX - 1; i++){
		if(board[lowerBoundY + i][lowerBoundX + i]){
			return false;
		}
	}
	return true;
}

function nothingBetweenLateral(start, startX, startY, dest, board, isVertical){
	//check that nothing is inbetween
	var upperBound;
	var lowerBound;
	if(dest > start){
		upperBound = dest;
		lowerBound = start;
	}else{
		upperBound = start;
		lowerBound = dest;
	}

	/* since rook can't jump over piece, return false if trying that
	   lowerBound + 1 since its not looking at either end points
	*/
	for(var i = 1; i < upperBound - lowerBound - 1; i++){
		if((isVertical && (board[lowerBound + i][startX] === 0))
		||(!isVertical && (board[startY][lowerBound + i] === 0))){
			return false;
		}

	}
	return true;
}

function findNextPiece(adjustX, adjustY, startX, startY, board){
	console.log(isInBoard(startY));
	if(!isInBoard(startX) || !isInBoard(startY) || !(Math.abs(adjustX) in [0,1]) || !(Math.abs(adjustY) in [0,1])){
		return 0;
	}else if(board[startY][startX] !== 0){
		return board[startY][startX];
	}else{
		return findNextPiece(adjustX, adjustY, startX+adjustX, startY+adjustY, board);
	}
}

function pawn(color, startX, startY, destX, destY, board){
	if(color === "w"){
		//move forward 1
		if(destY - startY === -1){
			//moving diagonal 1, so killing a piece
			if((Math.abs(destX - startX) === 1)
			||(destX === startX && board[destY][destX] === 0)){
			//or, just moving forward(not diagonal) and empty space there
				return true;
			}
		}else if(Math.abs(destY - startY) === 2 && (startY === 6) && (board[destY][destX] === 0)){
		//first move for pawn, move two, also empty space
			return true;
		}
	/*same thing as white, only Y is reversed and we kill white pieces now
		also, can move two if start at y === 1
	*/
	}else if(color === "b"){
		if(destY - startY === 1){
			if((Math.abs(destX - startX) === 1)
			||(destX === startX && board[destY][destX] === 0)){
				return true;
			}
		}else if(Math.abs(destY - startY) === 2 && (startY === 1) && (board[destY][destX] === 0)){
			return true;
		}
	}
	return false;
}

function rook(startX, startY, destX, destY, board){
	//move vertically
	if(((destX === startX) && nothingBetweenLateral(startY, startX, startY, destY, board, true))
	||((destY === startY) && nothingBetweenLateral(startX, startX, startY, destX, board, false))){
	//move horizontally, destY is now destX, startY is now startX
		return true;
	}
	return false;
}

function knight(startX, startY, destX, destY){
	if((Math.abs(destX - startX) === 2 && Math.abs(destY - startY) === 1)
	||(Math.abs(destX - startX) === 1 && Math.abs(destY - startY) === 2)){
		return true;
	}
	return false;
}

function bishop(startX, startY, destX, destY, board){
	if((Math.abs(destX-startX) === Math.abs(destY-startY)) && nothingBetweenDiag(startX, startY, destX, destY, board)){
		return true;
	}
	return false;
}

function queen(startX, startY, destX, destY, board){
	if(Math.abs(destX - startX) === 0 && nothingBetweenLateral(startY, startX, startY, destY, board, true)){
	//move vertical
		return true;
	}else if(Math.abs(destY - startY) === 0 && nothingBetweenLateral(startX, startX, startY, destY, board, false)){
	//move horizontal
		return true;
	}else if((Math.abs(destY - startY) === Math.abs(destX - startX)) && nothingBetweenDiag(startX, startY, destX, destY, board)){
	//move diagonal
		return true;
	}
	return false;
}

// castling is done elsewhere
function king(startX, startY, destX, destY){
	//since eating its own piece is already checked for, nothing to really check
	if((Math.abs(startX - destX) === 1) && (Math.abs(startY - destY) === 1)){
		return true;
	}
	return false;
}

var self = module.exports = {
	/**
	returns 2d array that represents chess board
	N is knight
	b is black
	w is white
	**/
	/*accessing this board
	will be board[Y coord][X coord] because it is row then term*/
	initBoard: function(){
		var board = [["bR", "bN", "bB", "bQ", "bK", "bB", "bN", "bR"],
				 ["bP", "bP", "bP", "bP", "bP", "bP", "bP", "bP"],
				 [0, 0, 0, 0, 0, 0, 0, 0],
				 [0, 0, 0, 0, 0, 0, 0, 0],
				 [0, 0, 0, 0, 0, 0, 0, 0],
				 [0, 0, 0, 0, 0, 0, 0, 0],
				 ["wP", "wP", "wP", "wP", "wP", "wP", "wP", "wP"],
				 ["wR", "wN", "wB", "wQ", "wK", "wB", "wN", "wR"]];
		return board;
	},

	getMoveInfo: function(movePhrase, board){
		var startX = getCoord(movePhrase.charAt(0));
		var startY = getCoord(movePhrase.charAt(1));
		var destX = getCoord(movePhrase.charAt(2));
		var destY = getCoord(movePhrase.charAt(3));
		console.log(board[startY][startX]);
		var piece = getPiece(startX, startY, board);
		var pieceColor = getColor(startX, startY, board);

		return {"startX": startX,
				"startY": startY,
				"destX": destX,
				"destY": destY,
				"piece": piece,
				"pieceColor": pieceColor
		};
	},

	/**checks if moving side's king is put in check
	but knight case checks if its putitng opponent king into check

	this should really check if a king is put in check from old position(your king is in check)
	and from new position (enemy king is in check)
	**/
	isCheck: function(color, board){
		var findKing = function(color, board){
			var ownKing = color+"K";
			var ownKingCoords;
			for(var y = 0; y < 8; y++){
				for(var x = 0; x < 8; x++){
					if(board[y][x] === ownKing){
						ownKingCoords = [x, y];
						break;
					}
				}
			}
			console.log(ownKingCoords);
			if(ownKingCoords !== undefined){
				return ownKingCoords;
			}else{
				throw new Error("king of ${color} color was not found.");
			}
		}

		var horseCheck = function(color, ownKingX, ownKingY, board){
			var oppositeColor = color === "w" ? "b" : "w";

			//possible places that a knight could attack from
			let dirArray = [[-1, -2], [-1, 2], [1, -2], [1, 2],
							[-2, -1], [-2, 1], [2, -1], [2, 1]];

			for(var i = 0; i < dirArray.length; i++){
				/**if both coordinates are in the board and they are knights
				then the king is in check**/
				if(isInBoard(ownKingY+dirArray[i][0]) && isInBoard(ownKingX+dirArray[i][1])){
					if(board[ownKingY+dirArray[i][0]][ownKingX+dirArray[i][1]] === oppositeColor+"N"){
						return true;
					}
				}
			}
			return false;
		}

		var kingCheck = function(color, ownKingX, ownKingY, board){
			var oppositeColor = color === "w" ? "b" : "w";

			//all around the king(possible places king could attack from)
			let dirArray = [[0, 1], [0, -1], [1, 0], [-1, 0],
							[1, -1], [-1, 1], [1, 1], [-1, -1]];

			for(var i = 0; i < 8; i++){
				if(isInBoard(ownKingY+dirArray[i][0]) && isInBoard(ownKingX+dirArray[i][1])){
					if(board[ownKingY+dirArray[i][0]][ownKingX+dirArray[i][1]] === oppositeColor+"K"){
						return true;
					}
				}
			}
			return false;
		}

		var ownKingCoords = findKing(color, board);
		var oppositeColor = color === "w" ? "b" : "w";
		var vertSet = [oppositeColor+"Q", oppositeColor+"R"];
		var diagSet = [oppositeColor+"Q", oppositeColor+"B"];

		if(findNextPiece(0, 1, ownKingCoords[0], ownKingCoords[1] + 1, board) in vertSet){
		//up
			return true;
		}else if(findNextPiece(0, -1, ownKingCoords[0], ownKingCoords[1] - 1, board) in vertSet){
		//down
			return true;
		}else if(findNextPiece(1, 0, ownKingCoords[0] + 1, ownKingCoords[1], board) in vertSet){
		//right
			return true;
		}else if(findNextPiece(-1, 0, ownKingCoords[0] - 1, ownKingCoords[1], board) in vertSet){
		//left
			return true;
		}else if(findNextPiece(1, 1, ownKingCoords[0] + 1, ownKingCoords[1] + 1, board) in diagSet){
		//up right
			return true;
		}else if(findNextPiece(1, -1, ownKingCoords[0] + 1, ownKingCoords[1] - 1, board) in diagSet){
		//up left
			return true;
		}else if(findNextPiece(-1, 1, ownKingCoords[0] - 1, ownKingCoords[1] + 1, board) in diagSet){
		//down right
			return true;
		}else if(findNextPiece(-1, -1, ownKingCoords[0] - 1, ownKingCoords[1] - 1, board) in diagSet){
		//down left
			return true;
		}else if(color === "w" && ((board[ownKingCoords[1] + 1][ownKingCoords[0] + 1] === "P") || (board[ownKingCoords[1] + 1][ownKingCoords[0] - 1] === "P"))){
		//white and pawn above in either diagonal, 1 space apart
			return true;
		}else if(color === "b" && ((board[ownKingCoords[1] - 1][ownKingCoords[0] - 1] === "P") || (board[ownKingCoords[1] - 1][ownKingCoords[0] + 1] === "P"))){
		//black and pawn below in either diagonal, 1 space apart
			return true;
		}else if(horseCheck(color, ownKingCoords[0], ownKingCoords[1], board)){
		//check if horses can kill king from this position
			return true;
		}else if(kingCheck(color, ownKingCoords[0], ownKingCoords[1], board)){
		//check if opponent king can kill king from this position
			return true;
		}
		return false;

	},

	/* *** need to check if move is out of range of board - done
		check if it puts own king into check
		check if invalid move command(letter and numbers are out of order or something) - done
		check if move is not putting piece in the same place its already in - done
		check if move tries to kill piece of own colour -done
		check if it is indeed moving its own color piece - done
	*/

	//color is taken in as "w" or "b"
	//move phrase is [PIECE][START][DEST]
	isValidMove: function(movePhrase, color, checkStatus, board){
		var isValid = false;
		console.log("hello 1");
		var moveInfo = self.getMoveInfo(movePhrase, board);
		var startX = moveInfo["startX"];
		var startY = moveInfo["startY"];
		var destX = moveInfo["destX"];
		var destY = moveInfo["destY"];
		var piece = moveInfo["piece"];
		var pieceColor = moveInfo["pieceColor"];
		console.log("hello 2");
		//moving to same space
		if(((startX === destX) && (startY === destY))
		//moving piece of not own color
		|| (pieceColor !== color)
		//killing piece of own color
		|| ((board[destY][destX] !== 0) && getColor(destX, destY, board) === color)){
			return false;
		}
		console.log("hello 3");
		switch(piece){
			case "P":
				isValid = pawn(color, startX, startY, destX, destY, board);
				break;
			case "R":
				isValid = rook(startX, startY, destX, destY, board);
				break;
			case "N":
				isValid = knight(startX, startY, destX, destY);
				break;
			case "B":
				isValid = bishop(startX, startY, destX, destY, board);
				break;
			case "Q":
				isValid = queen(startX, startY, destX, destY, board);
				break;
			case "K":
				isValid = king(startX, startY, destX, destY);
				break;
		}
		console.log("hello 4");
		console.log("isValid: " , isValid, "checkStatus: ", checkStatus);
		//if the move is valid but mover's king is in check before move
		if(isValid && checkStatus){
			var afterBoard = board.map(function(arr) {
			    return arr.slice();
			});
			afterBoard[startY][startX] = 0
			afterBoard[destY][destX] = piece;
			if(self.isCheck(color, afterBoard)){
				return false;
			}
		}
		console.log("hello 5");
		return isValid;
	}

}
<commit_msg>rewrote ischeck<commit_after>'use strict';

/**
takes in string value from move command from message,
converts it to value that can be used for accessing
the board array
**/
function getCoord(val){
	const convert = {
		"a": 0, "b": 1, "c": 2, "d": 3, "e": 4, "f": 5, "g": 6, "h": 7,
		"8": 0, "7": 1, "6": 2, "5": 3, "4": 4, "3": 5, "2": 6, "1": 7
	};

	//check for bad coords
	// tolowercase will not do anything to numbers
	if(convert[val.toLowerCase()] !== undefined){
		return convert[val];
	}else{
		throw new Error('invalid string to convert to coordinate');
	}
}

/*return the first letter of the unit on that board coord
i.e. the color of the piece*/
function getColor(x, y, board){
	if(x > 7 || x < 0 || y > 7 || y < 0){
		throw new Error('trying to get color of something out of bounds');
	}else{
		return board[y][x].charAt(0);
	}
}

function getPiece(x, y, board){
	if(x > 7 || x < 0 || y > 7 || y < 0){
		throw new Error('trying to get piece type of something out of bounds');
	}else{
		return board[y][x].charAt(1);
	}
}

function isInBoard(coord){
	return (coord > 0 && coord < 7) ? true : false;
}

function nothingBetweenDiag(startX, startY, destX, destY, board){
	var upperBoundX;
	var lowerBoundX;
	var lowerBoundY;
	/*don't need to do upperBoundY because we
	use upperBoundX to get the number of spaces that the bishop
	moves vertically*/
	if(destX > startX){
		upperBoundX = destX;
		lowerBoundX = startX;
	}else{
		upperBoundX = startX;
		lowerBoundX = destX;
	}

	if(destY > startY){
		lowerBoundY = startY;
	}else{
		lowerBoundY = destY;
	}

	//chose upperBoundX and lowerBoundX because only displacement amount matters
	//completely arbitrary, could use the other too
	for(var i = 1; i < upperBoundX - lowerBoundX - 1; i++){
		if(board[lowerBoundY + i][lowerBoundX + i]){
			return false;
		}
	}
	return true;
}

function nothingBetweenLateral(start, startX, startY, dest, board, isVertical){
	//check that nothing is inbetween
	var upperBound;
	var lowerBound;
	if(dest > start){
		upperBound = dest;
		lowerBound = start;
	}else{
		upperBound = start;
		lowerBound = dest;
	}

	/* since rook can't jump over piece, return false if trying that
	   lowerBound + 1 since its not looking at either end points
	*/
	for(var i = 1; i < upperBound - lowerBound - 1; i++){
		if((isVertical && (board[lowerBound + i][startX] === 0))
		||(!isVertical && (board[startY][lowerBound + i] === 0))){
			return false;
		}

	}
	return true;
}

function pawn(color, startX, startY, destX, destY, board){
	if(color === "w"){
		//move forward 1
		if(destY - startY === -1){
			//moving diagonal 1, so killing a piece
			if((Math.abs(destX - startX) === 1)
			||(destX === startX && board[destY][destX] === 0)){
			//or, just moving forward(not diagonal) and empty space there
				return true;
			}
		}else if(Math.abs(destY - startY) === 2 && (startY === 6) && (board[destY][destX] === 0)){
		//first move for pawn, move two, also empty space
			return true;
		}
	/*same thing as white, only Y is reversed and we kill white pieces now
		also, can move two if start at y === 1
	*/
	}else if(color === "b"){
		if(destY - startY === 1){
			if((Math.abs(destX - startX) === 1)
			||(destX === startX && board[destY][destX] === 0)){
				return true;
			}
		}else if(Math.abs(destY - startY) === 2 && (startY === 1) && (board[destY][destX] === 0)){
			return true;
		}
	}
	return false;
}

function rook(startX, startY, destX, destY, board){
	//move vertically
	if(((destX === startX) && nothingBetweenLateral(startY, startX, startY, destY, board, true))
	||((destY === startY) && nothingBetweenLateral(startX, startX, startY, destX, board, false))){
	//move horizontally, destY is now destX, startY is now startX
		return true;
	}
	return false;
}

function knight(startX, startY, destX, destY){
	if((Math.abs(destX - startX) === 2 && Math.abs(destY - startY) === 1)
	||(Math.abs(destX - startX) === 1 && Math.abs(destY - startY) === 2)){
		return true;
	}
	return false;
}

function bishop(startX, startY, destX, destY, board){
	if((Math.abs(destX-startX) === Math.abs(destY-startY)) && nothingBetweenDiag(startX, startY, destX, destY, board)){
		return true;
	}
	return false;
}

function queen(startX, startY, destX, destY, board){
	if(Math.abs(destX - startX) === 0 && nothingBetweenLateral(startY, startX, startY, destY, board, true)){
	//move vertical
		return true;
	}else if(Math.abs(destY - startY) === 0 && nothingBetweenLateral(startX, startX, startY, destY, board, false)){
	//move horizontal
		return true;
	}else if((Math.abs(destY - startY) === Math.abs(destX - startX)) && nothingBetweenDiag(startX, startY, destX, destY, board)){
	//move diagonal
		return true;
	}
	return false;
}

// castling is done elsewhere
function king(startX, startY, destX, destY){
	//since eating its own piece is already checked for, nothing to really check
	if((Math.abs(startX - destX) === 1) && (Math.abs(startY - destY) === 1)){
		return true;
	}
	return false;
}

var self = module.exports = {
	/**
	returns 2d array that represents chess board
	N is knight
	b is black
	w is white
	**/
	/*accessing this board
	will be board[Y coord][X coord] because it is row then term*/
	initBoard: function(){
		var board = [["bR", "bN", "bB", "bQ", "bK", "bB", "bN", "bR"],
				 ["bP", "bP", "bP", "bP", "bP", "bP", "bP", "bP"],
				 [0, 0, 0, 0, 0, 0, 0, 0],
				 [0, 0, 0, 0, 0, 0, 0, 0],
				 [0, 0, 0, 0, 0, 0, 0, 0],
				 [0, 0, 0, 0, 0, 0, 0, 0],
				 ["wP", "wP", "wP", "wP", "wP", "wP", "wP", "wP"],
				 ["wR", "wN", "wB", "wQ", "wK", "wB", "wN", "wR"]];
		return board;
	},

	getMoveInfo: function(movePhrase, board){
		var startX = getCoord(movePhrase.charAt(0));
		var startY = getCoord(movePhrase.charAt(1));
		var destX = getCoord(movePhrase.charAt(2));
		var destY = getCoord(movePhrase.charAt(3));
		console.log(board[startY][startX]);
		var piece = getPiece(startX, startY, board);
		var pieceColor = getColor(startX, startY, board);

		return {"startX": startX,
				"startY": startY,
				"destX": destX,
				"destY": destY,
				"piece": piece,
				"pieceColor": pieceColor
		};
	},

	/**checks if moving side's king is put in check
	but knight case checks if its putitng opponent king into check

	this should really check if a king is put in check from old position(your king is in check)
	and from new position (enemy king is in check)
	**/
	isCheck: function(color, board){
		var king = (() => {
			var kingPhrase = color + "K";
			for(var y = 0; y < 8; y++){
				for(var x = 0; x < 8; x++){
					if(board[y][x] === kingPhrase){
						return {x:x, y:y};
					}
				}
			}
		})();
		var opponentColor = (color == "w") ? "b" : "w";
		const diagKillers = [{piece:"Q", range: 8}, {piece:"P", range: 1}, {piece:"K", range: 1}, {piece:"B", range: 8}];
		const horiKillers = [{piece:"Q", range: 8}, {piece:"R", range: 8}, {piece:"K", range: 1}];
		const cardinalDirections = {up:[0, 1], down:[0, -1], right:[1, 0], left:[-1, 0],
							se:[1, -1], nw:[-1, 1], ne:[1, 1], sw:[-1, -1]};
		const knightDirections = [[-1, -2], [-1, 2], [1, -2], [1, 2],
								[-2, -1], [-2, 1], [2, -1], [2, 1]];
		for(var i in cardinalDirections){
			var pieceInfo = findPieceWithInfo(cardinalDirections[i], king.x, king.y, board);
			if(isKiller(i, pieceInfo.name, pieceInfo.dist)) {return true};
		}
		for(var i in knightDirections){
			if(board[i.y][i.x] == opponentColor + "K"){
				return true;
			}
		}
		return false;
		function findPieceWithInfo(direction, x, y, board){
			var dist = 0;
			while (0 <= x + direction.x <= 7 && 0 <= y + direction+y <= 7){
				dist++;
				x = x + direction.x;
				y = y + direction.y;
				if(board[y][x] != 0){
					return {name:board[y][x], dist:dist}
				}
			}
			return {name:0, dist:0}
		}

		function isKiller(direction, piece, dist){
			if(piece[0] == color){
				return false;
			}
			if (direction in ["up", "down", "left", "right"]){
				return (diagKillerRange[i] > dist);
			}
			if (direction in ["nw", "ne", "se", "sw"]){
				return (diagKillerRange[i] > dist);
			}
			return false;
		}
		//find all cardinal pieces
		//check if any of them are offensive
		//check for jumper
	},

	/* *** need to check if move is out of range of board - done
		check if it puts own king into check
		check if invalid move command(letter and numbers are out of order or something) - done
		check if move is not putting piece in the same place its already in - done
		check if move tries to kill piece of own colour -done
		check if it is indeed moving its own color piece - done
	*/

	//color is taken in as "w" or "b"
	//move phrase is [PIECE][START][DEST]
	isValidMove: function(movePhrase, color, checkStatus, board){
		var isValid = false;
		console.log("hello 1");
		var moveInfo = self.getMoveInfo(movePhrase, board);
		var startX = moveInfo["startX"];
		var startY = moveInfo["startY"];
		var destX = moveInfo["destX"];
		var destY = moveInfo["destY"];
		var piece = moveInfo["piece"];
		var pieceColor = moveInfo["pieceColor"];
		console.log("hello 2");
		//moving to same space
		if(((startX === destX) && (startY === destY))
		//moving piece of not own color
		|| (pieceColor !== color)
		//killing piece of own color
		|| ((board[destY][destX] !== 0) && getColor(destX, destY, board) === color)){
			return false;
		}
		console.log("hello 3");
		switch(piece){
			case "P":
				isValid = pawn(color, startX, startY, destX, destY, board);
				break;
			case "R":
				isValid = rook(startX, startY, destX, destY, board);
				break;
			case "N":
				isValid = knight(startX, startY, destX, destY);
				break;
			case "B":
				isValid = bishop(startX, startY, destX, destY, board);
				break;
			case "Q":
				isValid = queen(startX, startY, destX, destY, board);
				break;
			case "K":
				isValid = king(startX, startY, destX, destY);
				break;
		}
		console.log("hello 4");
		console.log("isValid: " , isValid, "checkStatus: ", checkStatus);
		//if the move is valid but mover's king is in check before move
		if(isValid && checkStatus){
			var afterBoard = board.map(function(arr) {
			    return arr.slice();
			});
			afterBoard[startY][startX] = 0
			afterBoard[destY][destX] = piece;
			if(self.isCheck(color, afterBoard)){
				return false;
			}
		}
		console.log("hello 5");
		return isValid;
	}

}
<|endoftext|>