<commit_before>#!/usr/bin/env node
var spawn = require('child_process').spawn;
var vimrl = require('vimrl');
var net = require('net');

var config = require(process.env.HOME + "/.jsiiConfig.js");

var nicks = {};

var chanNumber, server, chan;

var findChannel = function(searchString) {
    // exact match for server:chan format
    if(searchString.indexOf(':') !== -1) {
        // first try looking for a match in config
        for(var i = 0; i < config.favoriteChannels.length; i++) {
            if(searchString === (config.favoriteChannels[i].server + ':' +
                                config.favoriteChannels[i].chan)) {
                return {
                    chanNumber: i,
                    server: config.favoriteChannels[i].server,
                    chan: config.favoriteChannels[i].chan
                }
            }
        }
        // otherwise assume the user knows better, use provided names
        return {
            server: searchString.split(':')[0],
            chan: searchString.split(':')[1]
        }
    }

    // search for substring in shortName
    for(var i = 0; i < config.favoriteChannels.length; i++) {
        if(config.favoriteChannels[i].shortName.match(new RegExp(searchString))) {
            return {
                chanNumber: i,
                server: config.favoriteChannels[i].server,
                chan: config.favoriteChannels[i].chan
            }
        }
    }
};

/* find server & channels names from first argument */
// no args: pick first favorite chan
if(!process.argv[2]) {
    server = config.favoriteChannels[0].server;
    chan = config.favoriteChannels[0].chan;
    chanNumber = 0;
// by channel number
} else if (!isNaN(process.argv[2])) {
    chanNumber = parseInt(process.argv[2]);
    server = config.favoriteChannels[chanNumber].server;
    chan = config.favoriteChannels[chanNumber].chan;
// else search
} else {
    var results = findChannel(process.argv[2]);

    server = results.server;
    chan = results.chan;
    chanNumber = results.chanNumber;
}

// for prompt
var getChanName = function(server, chan, chanNumber) {
    if(!isNaN(chanNumber)) {
        return config.favoriteChannels[chanNumber].shortName;
    } else {
        return server + ':' + chan;
    }
};

var sendMsg = function(msg) {
    socket.write(JSON.stringify(msg) + '\n');
};

// reset cursor to lower left corner
var cursorReset = function() {
    process.stdout.write('\033[' + process.stdout.rows + ';0f');
};
// clear line where cursor currently is
var clearLine = function() {
    process.stdout.write('\033[K');
};

// prints line to lower left corner of terminal
var printLine = function(msg) {
    var i;

    var hilight = false;
    var action = false;
    var separator = config.nickSeparator;

    // move cursor to lower left
    cursorReset();
    // clear current line
    clearLine();

    var nickColor = 0;
    var textColor = 15;

    // support irc ACTION messages
    if(msg.cmd === 'action') {
        msg.message = msg.nick + ' ' + msg.message.substring(8);
        msg.nick = '*';
        nickColor = config.actionColor;
        textColor = config.actionColor;
    } else if (msg.cmd === 'join') {
        msg.message = config.joinMsg;
        textColor = config.joinColor;
    } else if (msg.cmd === 'part') {
        msg.message = config.partMsg;
        textColor = config.partColor;
    } else if (msg.cmd === 'nicklist') {
        msg.nick = '*';
        msg.message = 'Names: ' + msg.nicks.join(', ');
    } else if(msg.message.match(config.hilight_re)) {
        textColor = config.hilightColor;
    }

    if (msg.nick) {
        if(msg.nick === config.myNick) {
            nickColor = config.myNickColor;
            textColor = config.myNickColor;
        } else {
            // nick color, avoids dark colors
            for(i = 0; i < msg.nick.length; i++) {
                nickColor += msg.nick.charCodeAt(i);
            }
            nickColor = Math.pow(nickColor, 2) + nickColor * 2;
            nickColor = nickColor % 255;
            switch(nickColor) {
                case 18: case 22: case 23: case 24:
                    nickColor += 3; break;
                case 52: case 53: case 54: case 55: case 56: case 57: case 88: case 89:
                    nickColor += 6; break;
                case 232: case 233: case 234: case 235: case 236: case 237: case 238: case 239:
                    nickColor += 8; break;
                case 0: case 8: case 19: case 22:
                    nickColor++; break;
            }
        }
    }

    // limit nicklen
    msg.nick = msg.nick.substr(0, config.maxNickLen);
    // align nicks and print
    process.stdout.write(Array(config.maxNickLen - msg.nick.length + 1).join(' '));
    process.stdout.write('\033[38;5;' + nickColor + 'm' + msg.nick + // set nick color + nick
                         '\033[38;5;' + config.separatorColor + 'm' + config.nickSeparator + // set separator color + separator
                         '\033[000m'); // reset colors

    for (i = 0; i < config.findAndReplace.length; i++) {
        msg.message = msg.message.replace(config.findAndReplace[i][0],
                                          config.findAndReplace[i][1]);
    }

    var availWidth = process.stdout.columns - config.maxNickLen - config.nickSeparator.length;

    var wrappedChars = 0;
    i = 0;

    // terminal too small? don't print anything
    if(availWidth <= 5)
        return;

    while(i * availWidth - wrappedChars < msg.message.length) {
        var start = i * availWidth - wrappedChars;
        var curLine = msg.message.substr(start, availWidth);
        // remove leading space on next line
        curLine.replace(/^\s+/, '');
        // line wrap at word boundary only if there is whitespace on this line
        if(start + availWidth < msg.message.length && curLine.lastIndexOf(' ') !== -1) {
            curLine = curLine.slice(0, curLine.lastIndexOf(' '));
            // remove whitespace
            wrappedChars--;
        }

        wrappedChars += availWidth - curLine.length;

        // empty space on line wrap
        if (i > 0)
            process.stdout.write(Array(process.stdout.columns - availWidth + 1).join(' '));

        process.stdout.write('\033[38;5;' + textColor + 'm' + curLine + // set text color + text
                             '\033[000m' + '\n'); // reset colors + newline
        i++;
    }
};

// redraw screen
var redraw = function() {
    process.stdout.write('\u001B[2J\u001B[0;0f'); // clear terminal
    sendMsg({
        cmd: 'backlog',
        chan: chan,
        server: server
    });
    readline.redraw();
};

process.stdin.setRawMode(true);

// handle keyboard events
process.stdin.on('readable', function() {
    var input;
    while (null !== (input = process.stdin.read())) {
        // 'ctrl-c' = quit
        if(input == '\u0003') process.exit(0);

        var keyHex = input.toString('hex');

        // previous channel (alt + h)
        if(keyHex === '1b68') {
            if(isNaN(chanNumber)) chanNumber = 0;
            else chanNumber--;

            if(chanNumber < 0)
                chanNumber = config.favoriteChannels.length - 1;

            server = config.favoriteChannels[chanNumber].server;
            chan = config.favoriteChannels[chanNumber].chan;
            nicks = {};

            readline.changePrompt(config.getPrompt(
                        getChanName(server, chan, chanNumber), chanNumber));
            redraw();
        }
        // next channel (alt + l)
        else if(keyHex === '1b6c') {
            if(isNaN(chanNumber)) chanNumber = 0;
            else chanNumber++;

            if(chanNumber >= config.favoriteChannels.length)
                chanNumber = 0;

            server = config.favoriteChannels[chanNumber].server;
            chan = config.favoriteChannels[chanNumber].chan;
            nicks = {};

            readline.changePrompt(config.getPrompt(
                        getChanName(server, chan, chanNumber), chanNumber));
            redraw();
        }
        // jump to this channel (alt + 1-9)
        else if(keyHex.substring(0, 3) === '1b3' && keyHex.substring(3) !== '0') {
            chanNumber = parseInt(keyHex.substring(3));

            server = config.favoriteChannels[chanNumber].server;
            chan = config.favoriteChannels[chanNumber].chan;
            nicks = {};

            readline.changePrompt(config.getPrompt(
                        getChanName(server, chan, chanNumber), chanNumber));
            redraw();
        } else {
            readline.handleInput(input);
        }

        // DEBUG: uncomment this line to find the keycodes
        //console.log(key.toString('hex'));
    }
});

/*
// nickname completion from nicknames in file buffer
// TODO: get nicks from irc server?
completer: function(line) {
}
var word = line.substring(line.lastIndexOf(" ") + 1);
var hits = completions.filter(function(c) { return c.indexOf(word) == 0 })
for(var i = hits.length - 1; i >= 0; i--) {
    if(hits[i] === '!' || hits[i] === '***') { // skip status/info nicknames
        hits.splice(i, 1);
    } else if (word === line) { // typing at beginning of line? append colon
        hits[i] += ': ';
    } else { // else append comma
        hits[i] += ', ';
    }
}
return [hits, word]
*/

// watch file for changes
var socket = net.connect({port: config.port, host: config.addr});
var buffer = "";

socket.on('data', function(data) {
    buffer += data.toString('utf8');

    var lastNL = buffer.lastIndexOf('\n');
    if(lastNL !== -1) {
        var recvdLines = buffer.substr(0, lastNL).split('\n');
        buffer = buffer.substr(lastNL + 1);

        for(var i = 0; i < recvdLines.length; i++) {
            var msg = JSON.parse(recvdLines[i]);

            // is the message on the active channel?
            if(msg.server + ':' + msg.chan === server + ':' + chan) {
                // store nicklist
                if(msg.cmd === 'nicklist') {
                    for(var j = 0; j < msg.nicks.length; j++) {
                        nicks[msg.nicks[j]] = true;
                    }
                } else {
                    printLine(msg);
                    readline.redraw();

                    if(msg.cmd === 'join') {
                        nicks[msg.nick] = true;
                    } else if(msg.cmd === 'part') {
                        delete(nicks[msg.nick]);
                    }
                }
            }
        }
    }
});

// handle terminal resize
process.stdout.on('resize', function() {
    redraw();
});

var prompt = config.getPrompt(getChanName(server, chan, chanNumber), chanNumber);

// parse some select commands from input line
readline = vimrl(prompt, function(line) {
    if(line === '/bl' || line.substring(0, 4) === '/bl ') { // request backlog
        line = "/privmsg *backlog " + chan + ' ' + line.substring(4);
    } else if(line.substring(0, 4) === '/me ') { // irc ACTION message
        line = "\001ACTION " + line.substring(4);
    } else if(line === '/names') { // request nick list
        printLine({
            cmd: 'nicklist',
            nicks: Object.keys(nicks)
        });
        return;
    } else if (line === '/ul') { // list urls in buffer
        var current = 0;
        var splitFile = backlog.split('\n');

        var url;
        for (var i = splitFile.length - 1; i >= 0; i--) {
            // TODO: put this mess into a function
            // remove timestamps
            splitFile[i] = splitFile[i].replace(/^([^ ]+ ){2}/, '');
            var words = splitFile[i].split(' ');
            // nick is first whitespace separated word after timestamp
            var nick = words[0].substring(1, words[0].length - 1);
            // ignore select bots
            if(config.url_ignore_nicks.indexOf(nick) !== -1)
                continue;

            for (var j = words.length - 1; j >= 0; j--) {
                var res = config.url_re.exec(words[j]);
                if(res) {
                    url = res[0];
                    printLine({
                        nick: '***',
                        msg: 'URL ' + current + ': ' + url
                    });
                    readline.redraw();
                    current++;
                }
            }
        }

        return;
    } else if (line === '/u' || line.substring(0, 3) === '/u ') { // open url
        var skip = parseInt(line.substring(3)) | 0;
        var splitFile = backlog.split('\n');

        var url;
        for (var i = splitFile.length - 1; i >= 0; i--) {
            // TODO: put this mess into a function
            // remove timestamps
            splitFile[i] = splitFile[i].replace(/^([^ ]+ ){2}/, '');
            var words = splitFile[i].split(' ');
            // nick is first whitespace separated word after timestamp
            var nick = words[0].substring(1, words[0].length - 1);
            // ignore select bots
            if(config.url_ignore_nicks.indexOf(nick) !== -1)
                continue;

            for (var j = words.length - 1; j >= 0; j--) {
                var res = config.url_re.exec(words[j]);
                if(res) {
                    if(skip > 0) {
                        skip--;
                        continue;
                    }
                    url = res[0];
                    break;
                }
            }

            if(url)
                break;
        }

        if(url) {
            var child = spawn('chromium', [url], {
                detached: true,
                stdio: [ 'ignore', 'ignore' , 'ignore' ]
            });
            child.unref();
            printLine({
                nick: '***',
                msg: 'URL ' + (parseInt(line.substring(3)) | 0) +  " opened: " + url
            });
            readline.redraw();
        } else {
            printLine({
                nick: '***',
                msg: 'No URL found'
            });
            readline.redraw();
        }

        return;
    }

    // send input line to jsiid
    var msg = {
        cmd: 'message',
        chan: chan,
        server: server,
        message: line,
        nick: config.myNick
    };
    sendMsg(msg);

    msg.nick = config.myNick;
    printLine(msg);
});

readline.gotoInsertMode();

redraw();
<commit_msg>custom separator in join/part msgs<commit_after>#!/usr/bin/env node
var spawn = require('child_process').spawn;
var vimrl = require('vimrl');
var net = require('net');

var config = require(process.env.HOME + "/.jsiiConfig.js");

var nicks = {};

var chanNumber, server, chan;

var findChannel = function(searchString) {
    // exact match for server:chan format
    if(searchString.indexOf(':') !== -1) {
        // first try looking for a match in config
        for(var i = 0; i < config.favoriteChannels.length; i++) {
            if(searchString === (config.favoriteChannels[i].server + ':' +
                                config.favoriteChannels[i].chan)) {
                return {
                    chanNumber: i,
                    server: config.favoriteChannels[i].server,
                    chan: config.favoriteChannels[i].chan
                }
            }
        }
        // otherwise assume the user knows better, use provided names
        return {
            server: searchString.split(':')[0],
            chan: searchString.split(':')[1]
        }
    }

    // search for substring in shortName
    for(var i = 0; i < config.favoriteChannels.length; i++) {
        if(config.favoriteChannels[i].shortName.match(new RegExp(searchString))) {
            return {
                chanNumber: i,
                server: config.favoriteChannels[i].server,
                chan: config.favoriteChannels[i].chan
            }
        }
    }
};

/* find server & channels names from first argument */
// no args: pick first favorite chan
if(!process.argv[2]) {
    server = config.favoriteChannels[0].server;
    chan = config.favoriteChannels[0].chan;
    chanNumber = 0;
// by channel number
} else if (!isNaN(process.argv[2])) {
    chanNumber = parseInt(process.argv[2]);
    server = config.favoriteChannels[chanNumber].server;
    chan = config.favoriteChannels[chanNumber].chan;
// else search
} else {
    var results = findChannel(process.argv[2]);

    server = results.server;
    chan = results.chan;
    chanNumber = results.chanNumber;
}

// for prompt
var getChanName = function(server, chan, chanNumber) {
    if(!isNaN(chanNumber)) {
        return config.favoriteChannels[chanNumber].shortName;
    } else {
        return server + ':' + chan;
    }
};

var sendMsg = function(msg) {
    socket.write(JSON.stringify(msg) + '\n');
};

// reset cursor to lower left corner
var cursorReset = function() {
    process.stdout.write('\033[' + process.stdout.rows + ';0f');
};
// clear line where cursor currently is
var clearLine = function() {
    process.stdout.write('\033[K');
};

// prints line to lower left corner of terminal
var printLine = function(msg) {
    var i;

    var hilight = false;
    var action = false;
    var separator = config.nickSeparator;

    // move cursor to lower left
    cursorReset();
    // clear current line
    clearLine();

    var nickColor = 0;
    var textColor = 15;

    // support irc ACTION messages
    if(msg.cmd === 'action') {
        msg.message = msg.nick + ' ' + msg.message.substring(8);
        msg.nick = '*';
        nickColor = config.actionColor;
        textColor = config.actionColor;
    } else if (msg.cmd === 'join') {
        separator = '';
        msg.message = config.joinMsg;
        textColor = config.joinColor;
    } else if (msg.cmd === 'part') {
        separator = '';
        msg.message = config.partMsg;
        textColor = config.partColor;
    } else if (msg.cmd === 'nicklist') {
        msg.nick = '*';
        msg.message = 'Names: ' + msg.nicks.join(', ');
    } else if(msg.message.match(config.hilight_re)) {
        textColor = config.hilightColor;
    }

    if (msg.nick) {
        if(msg.nick === config.myNick) {
            nickColor = config.myNickColor;
            textColor = config.myNickColor;
        } else {
            // nick color, avoids dark colors
            for(i = 0; i < msg.nick.length; i++) {
                nickColor += msg.nick.charCodeAt(i);
            }
            nickColor = Math.pow(nickColor, 2) + nickColor * 2;
            nickColor = nickColor % 255;
            switch(nickColor) {
                case 18: case 22: case 23: case 24:
                    nickColor += 3; break;
                case 52: case 53: case 54: case 55: case 56: case 57: case 88: case 89:
                    nickColor += 6; break;
                case 232: case 233: case 234: case 235: case 236: case 237: case 238: case 239:
                    nickColor += 8; break;
                case 0: case 8: case 19: case 22:
                    nickColor++; break;
            }
        }
    }

    // limit nicklen
    msg.nick = msg.nick.substr(0, config.maxNickLen);
    // align nicks and print
    process.stdout.write(Array(config.maxNickLen - msg.nick.length + 1).join(' '));
    process.stdout.write('\033[38;5;' + nickColor + 'm' + msg.nick + // set nick color + nick
                         '\033[38;5;' + config.separatorColor + 'm' + separator + // set separator color + separator
                         '\033[000m'); // reset colors

    for (i = 0; i < config.findAndReplace.length; i++) {
        msg.message = msg.message.replace(config.findAndReplace[i][0],
                                          config.findAndReplace[i][1]);
    }

    var availWidth = process.stdout.columns - config.maxNickLen - separator.length;

    var wrappedChars = 0;
    i = 0;

    // terminal too small? don't print anything
    if(availWidth <= 5)
        return;

    while(i * availWidth - wrappedChars < msg.message.length) {
        var start = i * availWidth - wrappedChars;
        var curLine = msg.message.substr(start, availWidth);
        // remove leading space on next line
        curLine.replace(/^\s+/, '');
        // line wrap at word boundary only if there is whitespace on this line
        if(start + availWidth < msg.message.length && curLine.lastIndexOf(' ') !== -1) {
            curLine = curLine.slice(0, curLine.lastIndexOf(' '));
            // remove whitespace
            wrappedChars--;
        }

        wrappedChars += availWidth - curLine.length;

        // empty space on line wrap
        if (i > 0)
            process.stdout.write(Array(process.stdout.columns - availWidth + 1).join(' '));

        process.stdout.write('\033[38;5;' + textColor + 'm' + curLine + // set text color + text
                             '\033[000m' + '\n'); // reset colors + newline
        i++;
    }
};

// redraw screen
var redraw = function() {
    process.stdout.write('\u001B[2J\u001B[0;0f'); // clear terminal
    sendMsg({
        cmd: 'backlog',
        chan: chan,
        server: server
    });
    readline.redraw();
};

process.stdin.setRawMode(true);

// handle keyboard events
process.stdin.on('readable', function() {
    var input;
    while (null !== (input = process.stdin.read())) {
        // 'ctrl-c' = quit
        if(input == '\u0003') process.exit(0);

        var keyHex = input.toString('hex');

        // previous channel (alt + h)
        if(keyHex === '1b68') {
            if(isNaN(chanNumber)) chanNumber = 0;
            else chanNumber--;

            if(chanNumber < 0)
                chanNumber = config.favoriteChannels.length - 1;

            server = config.favoriteChannels[chanNumber].server;
            chan = config.favoriteChannels[chanNumber].chan;
            nicks = {};

            readline.changePrompt(config.getPrompt(
                        getChanName(server, chan, chanNumber), chanNumber));
            redraw();
        }
        // next channel (alt + l)
        else if(keyHex === '1b6c') {
            if(isNaN(chanNumber)) chanNumber = 0;
            else chanNumber++;

            if(chanNumber >= config.favoriteChannels.length)
                chanNumber = 0;

            server = config.favoriteChannels[chanNumber].server;
            chan = config.favoriteChannels[chanNumber].chan;
            nicks = {};

            readline.changePrompt(config.getPrompt(
                        getChanName(server, chan, chanNumber), chanNumber));
            redraw();
        }
        // jump to this channel (alt + 1-9)
        else if(keyHex.substring(0, 3) === '1b3' && keyHex.substring(3) !== '0') {
            chanNumber = parseInt(keyHex.substring(3));

            server = config.favoriteChannels[chanNumber].server;
            chan = config.favoriteChannels[chanNumber].chan;
            nicks = {};

            readline.changePrompt(config.getPrompt(
                        getChanName(server, chan, chanNumber), chanNumber));
            redraw();
        } else {
            readline.handleInput(input);
        }

        // DEBUG: uncomment this line to find the keycodes
        //console.log(key.toString('hex'));
    }
});

/*
// nickname completion from nicknames in file buffer
// TODO: get nicks from irc server?
completer: function(line) {
}
var word = line.substring(line.lastIndexOf(" ") + 1);
var hits = completions.filter(function(c) { return c.indexOf(word) == 0 })
for(var i = hits.length - 1; i >= 0; i--) {
    if(hits[i] === '!' || hits[i] === '***') { // skip status/info nicknames
        hits.splice(i, 1);
    } else if (word === line) { // typing at beginning of line? append colon
        hits[i] += ': ';
    } else { // else append comma
        hits[i] += ', ';
    }
}
return [hits, word]
*/

// watch file for changes
var socket = net.connect({port: config.port, host: config.addr});
var buffer = "";

socket.on('data', function(data) {
    buffer += data.toString('utf8');

    var lastNL = buffer.lastIndexOf('\n');
    if(lastNL !== -1) {
        var recvdLines = buffer.substr(0, lastNL).split('\n');
        buffer = buffer.substr(lastNL + 1);

        for(var i = 0; i < recvdLines.length; i++) {
            var msg = JSON.parse(recvdLines[i]);

            // is the message on the active channel?
            if(msg.server + ':' + msg.chan === server + ':' + chan) {
                // store nicklist
                if(msg.cmd === 'nicklist') {
                    for(var j = 0; j < msg.nicks.length; j++) {
                        nicks[msg.nicks[j]] = true;
                    }
                } else {
                    printLine(msg);
                    readline.redraw();

                    if(msg.cmd === 'join') {
                        nicks[msg.nick] = true;
                    } else if(msg.cmd === 'part') {
                        delete(nicks[msg.nick]);
                    }
                }
            }
        }
    }
});

// handle terminal resize
process.stdout.on('resize', function() {
    redraw();
});

var prompt = config.getPrompt(getChanName(server, chan, chanNumber), chanNumber);

// parse some select commands from input line
readline = vimrl(prompt, function(line) {
    if(line === '/bl' || line.substring(0, 4) === '/bl ') { // request backlog
        line = "/privmsg *backlog " + chan + ' ' + line.substring(4);
    } else if(line.substring(0, 4) === '/me ') { // irc ACTION message
        line = "\001ACTION " + line.substring(4);
    } else if(line === '/names') { // request nick list
        printLine({
            cmd: 'nicklist',
            nicks: Object.keys(nicks)
        });
        return;
    } else if (line === '/ul') { // list urls in buffer
        var current = 0;
        var splitFile = backlog.split('\n');

        var url;
        for (var i = splitFile.length - 1; i >= 0; i--) {
            // TODO: put this mess into a function
            // remove timestamps
            splitFile[i] = splitFile[i].replace(/^([^ ]+ ){2}/, '');
            var words = splitFile[i].split(' ');
            // nick is first whitespace separated word after timestamp
            var nick = words[0].substring(1, words[0].length - 1);
            // ignore select bots
            if(config.url_ignore_nicks.indexOf(nick) !== -1)
                continue;

            for (var j = words.length - 1; j >= 0; j--) {
                var res = config.url_re.exec(words[j]);
                if(res) {
                    url = res[0];
                    printLine({
                        nick: '***',
                        msg: 'URL ' + current + ': ' + url
                    });
                    readline.redraw();
                    current++;
                }
            }
        }

        return;
    } else if (line === '/u' || line.substring(0, 3) === '/u ') { // open url
        var skip = parseInt(line.substring(3)) | 0;
        var splitFile = backlog.split('\n');

        var url;
        for (var i = splitFile.length - 1; i >= 0; i--) {
            // TODO: put this mess into a function
            // remove timestamps
            splitFile[i] = splitFile[i].replace(/^([^ ]+ ){2}/, '');
            var words = splitFile[i].split(' ');
            // nick is first whitespace separated word after timestamp
            var nick = words[0].substring(1, words[0].length - 1);
            // ignore select bots
            if(config.url_ignore_nicks.indexOf(nick) !== -1)
                continue;

            for (var j = words.length - 1; j >= 0; j--) {
                var res = config.url_re.exec(words[j]);
                if(res) {
                    if(skip > 0) {
                        skip--;
                        continue;
                    }
                    url = res[0];
                    break;
                }
            }

            if(url)
                break;
        }

        if(url) {
            var child = spawn('chromium', [url], {
                detached: true,
                stdio: [ 'ignore', 'ignore' , 'ignore' ]
            });
            child.unref();
            printLine({
                nick: '***',
                msg: 'URL ' + (parseInt(line.substring(3)) | 0) +  " opened: " + url
            });
            readline.redraw();
        } else {
            printLine({
                nick: '***',
                msg: 'No URL found'
            });
            readline.redraw();
        }

        return;
    }

    // send input line to jsiid
    var msg = {
        cmd: 'message',
        chan: chan,
        server: server,
        message: line,
        nick: config.myNick
    };
    sendMsg(msg);

    msg.nick = config.myNick;
    printLine(msg);
});

readline.gotoInsertMode();

redraw();
<|endoftext|>


<commit_before>'use strict';

var assign               = require('es5-ext/object/assign')
  , normalizeOptions     = require('es5-ext/object/normalize-options')
  , capitalize           = require('es5-ext/string/#/capitalize')
  , uncapitalize         = require('es5-ext/string/#/uncapitalize')
  , memoize              = require('memoizee')
  , ObservableArray      = require('observable-array')
  , location             = require('mano/lib/client/location')
  , _                    = require('mano').i18n.bind('View: Statistics')
  , db                   = require('mano').db
  , getData              = require('mano/lib/client/xhr-driver').get
  , getQueryHandlerConf  = require('../apps/statistics/get-query-conf')
  , setupQueryHandler    = require('../utils/setup-client-query-handler')
  , resolveFullStepPath  = require('../utils/resolve-processing-step-full-path')
  , getDurationDaysHours = require('./utils/get-duration-days-hours-fine-grain')
  , dateFromToBlock      = require('./components/filter-bar/select-date-range-safe-fallback')
  , getDynamicUrl        = require('./utils/get-dynamic-url')
  , processingStepsMetaWithoutFrontDesk
	= require('./utils/processing-steps-meta-without-front-desk');

exports._parent = require('./statistics-time');
exports._customFilters = Function.prototype;

exports['time-nav']     = { class: { 'submitted-menu-item-active': true } };
exports['per-role-nav'] = { class: { 'pills-nav-active': true } };

var queryServer = memoize(function (query) {
	return getData('/get-time-per-role/', query);
}, {
	normalizer: function (args) { return JSON.stringify(args[0]); }
});

exports['statistics-main'] = function () {
	var stepsMeta = processingStepsMetaWithoutFrontDesk(),
		mainData, queryHandler, params, queryResult;
	mainData = new ObservableArray();
	queryHandler = setupQueryHandler(getQueryHandlerConf({
		processingStepsMeta: stepsMeta
	}), location, '/time/');
	params = queryHandler._handlers.map(function (handler) {
		return handler.name;
	});

	queryHandler.on('query', function (query) {
		if (query.dateFrom) {
			query.dateFrom = query.dateFrom.toJSON();
		}
		if (query.dateTo) {
			query.dateTo = query.dateTo.toJSON();
		}
		queryServer(query)(function (result) {
			queryResult = result;
			var totalWithoutCorrections, total, totalCorrections, perRoleTotal;
			mainData.splice(0, mainData.length);

			totalWithoutCorrections = result.businessProcesses.processing;
			totalWithoutCorrections.label = _("Total process without corrections");

			totalCorrections = result.businessProcesses.correction;
			totalCorrections.label = _("Total correcting time");

			total = result.businessProcesses.processing;
			total.label = _("Total process");

			Object.keys(stepsMeta).forEach(function (key) {
				perRoleTotal = result.steps.byStep[key].processing;
				perRoleTotal.key = key;
				perRoleTotal.label   = db['BusinessProcess' +
					capitalize.call(stepsMeta[key]._services[0])].prototype
					.processingSteps.map.getBySKeyPath(resolveFullStepPath(key)).label;
				mainData.push(perRoleTotal);
			});

			mainData.push(totalWithoutCorrections);
			// Below line was explicitly requested, though doesn't give anything interesting right now
			mainData.push(assign(normalizeOptions(totalCorrections),
				{ label: _("Corrections by the users") }));
			mainData.push(totalCorrections);
			mainData.push(total);
		}).done();
	});

	div({ class: 'block-pull-up' }, form({ action: '/time/', autoSubmit: true },
		section({ class: 'date-period-selector-positioned-on-submenu' },
			dateFromToBlock()),
		section({ class: 'entities-overview-info' },
			_("As processing time is properly recorded since 25th of October." +
				" Below table only exposes data for files submitted after that day.")),
		br(),
		section({ class: 'section-primary users-table-filter-bar' },
			div(
				{ class: 'users-table-filter-bar-status' },
				label({ for: 'service-select' }, _("Service"), ":"),
				select({ id: 'service-select', name: 'service' },
					option(
						{ value: '', selected: location.query.get('service').map(function (value) {
							return (value == null);
						})
							},
						_("All")
					),
					list(db.BusinessProcess.extensions, function (service) {
						var serviceName = uncapitalize.call(service.__id__.slice('BusinessProcess'.length));
						return option({ value: serviceName, selected:
								location.query.get('service').map(function (value) {
								var selected = (serviceName ? (value === serviceName) : (value == null));
								return selected ? 'selected' : null;
							}) },
							service.prototype.label);
					}, null))
			),
			div(
				{ class: 'users-table-filter-bar-status' },
				exports._customFilters.call(this)
			),
			div(
				a({ class: 'users-table-filter-bar-print', href: getDynamicUrl('/time-per-role.csv',
					{ only: params }),
					target: '_blank' }, span({ class: 'fa fa-print' }), " ", _("Print csv"))
			),
			div(
				a({ class: 'users-table-filter-bar-print', href: getDynamicUrl('/time-per-role.pdf',
					{ only: params }),
					target: '_blank' }, span({ class: 'fa fa-print' }), " ", _("Print pdf"))
			))),
		br(),
		section(table({ class: 'statistics-table' },
			thead(
				th(),
				th({ class: 'statistics-table-number' }, _("Files processed")),
				th({ class: 'statistics-table-number' }, _("Average time")),
				th({ class: 'statistics-table-number' }, _("Min time")),
				th({ class: 'statistics-table-number' }, _("Max time"))
			),
			tbody({
				onEmpty: tr(td({ class: 'empty', colspan: 5 },
						_("There is no data to display")))
			}, mainData, function (row) {
				return tr({ class: 'cursor-pointer', onclick: function () {
					var jQuery = window.jQuery;
					var currentRow = jQuery(this);
						var detailRow = currentRow.next('.detail');

						if (detailRow.length === 0) {
						var rows = queryResult.steps.byStep[row.key].businessProcesses.map(function (record, index) {

							var user = db.User.getById(record.processor),
								userName = user === null ? record.processor : user.fullName;

							var style = { class: 'background-secondary' };
							var lastTdContent = index === 0 ? span({
								onclick: function () {
									detailRow.hide()
								},
								class:'cursor-pointer'},'x') : '';
							return tr(
								td(style, record.businessName),
								td(style, userName),
								td(style, getDurationDaysHours(record.processingTime)),
								td(style, new db.DateTime(record.processingStart)),
								td(style, new db.DateTime(record.processingEnd)),
								td(style, lastTdContent)
							);
						});

						detailRow = jQuery(tr({
								class: 'detail',
								style: 'display:none'
							},
							td({ colspan: 5 }, table(rows))
						));

						detailRow.insertAfter(currentRow);
					}
					detailRow.toggle();
				} },
					td(row.label),
					td({ class: 'statistics-table-number' }, row.timedCount),
					td({ class: 'statistics-table-number' },
						row.timedCount ? getDurationDaysHours(row.avgTime) : "-"),
					td({ class: 'statistics-table-number' },
						row.timedCount ? getDurationDaysHours(row.minTime) : "-"),
					td({ class: 'statistics-table-number' },
						row.timedCount ? getDurationDaysHours(row.maxTime) : "-"));
			}))
			));
};
<commit_msg>fix lint<commit_after>'use strict';

var assign               = require('es5-ext/object/assign')
  , normalizeOptions     = require('es5-ext/object/normalize-options')
  , capitalize           = require('es5-ext/string/#/capitalize')
  , uncapitalize         = require('es5-ext/string/#/uncapitalize')
  , memoize              = require('memoizee')
  , ObservableArray      = require('observable-array')
  , location             = require('mano/lib/client/location')
  , _                    = require('mano').i18n.bind('View: Statistics')
  , db                   = require('mano').db
  , getData              = require('mano/lib/client/xhr-driver').get
  , getQueryHandlerConf  = require('../apps/statistics/get-query-conf')
  , setupQueryHandler    = require('../utils/setup-client-query-handler')
  , resolveFullStepPath  = require('../utils/resolve-processing-step-full-path')
  , getDurationDaysHours = require('./utils/get-duration-days-hours-fine-grain')
  , dateFromToBlock      = require('./components/filter-bar/select-date-range-safe-fallback')
  , getDynamicUrl        = require('./utils/get-dynamic-url')
  , processingStepsMetaWithoutFrontDesk
	= require('./utils/processing-steps-meta-without-front-desk');

exports._parent = require('./statistics-time');
exports._customFilters = Function.prototype;

exports['time-nav']     = { class: { 'submitted-menu-item-active': true } };
exports['per-role-nav'] = { class: { 'pills-nav-active': true } };

var queryServer = memoize(function (query) {
	return getData('/get-time-per-role/', query);
}, {
	normalizer: function (args) { return JSON.stringify(args[0]); }
});

exports['statistics-main'] = function () {
	var stepsMeta = processingStepsMetaWithoutFrontDesk(),
		mainData, queryHandler, params, queryResult;
	mainData = new ObservableArray();
	queryHandler = setupQueryHandler(getQueryHandlerConf({
		processingStepsMeta: stepsMeta
	}), location, '/time/');
	params = queryHandler._handlers.map(function (handler) {
		return handler.name;
	});

	queryHandler.on('query', function (query) {
		if (query.dateFrom) {
			query.dateFrom = query.dateFrom.toJSON();
		}
		if (query.dateTo) {
			query.dateTo = query.dateTo.toJSON();
		}
		queryServer(query)(function (result) {
			queryResult = result;
			var totalWithoutCorrections, total, totalCorrections, perRoleTotal;
			mainData.splice(0, mainData.length);

			totalWithoutCorrections = result.businessProcesses.processing;
			totalWithoutCorrections.label = _("Total process without corrections");

			totalCorrections = result.businessProcesses.correction;
			totalCorrections.label = _("Total correcting time");

			total = result.businessProcesses.processing;
			total.label = _("Total process");

			Object.keys(stepsMeta).forEach(function (key) {
				perRoleTotal = result.steps.byStep[key].processing;
				perRoleTotal.key = key;
				perRoleTotal.label   = db['BusinessProcess' +
					capitalize.call(stepsMeta[key]._services[0])].prototype
					.processingSteps.map.getBySKeyPath(resolveFullStepPath(key)).label;
				mainData.push(perRoleTotal);
			});

			mainData.push(totalWithoutCorrections);
			// Below line was explicitly requested, though doesn't give anything interesting right now
			mainData.push(assign(normalizeOptions(totalCorrections),
				{ label: _("Corrections by the users") }));
			mainData.push(totalCorrections);
			mainData.push(total);
		}).done();
	});

	div({ class: 'block-pull-up' }, form({ action: '/time/', autoSubmit: true },
		section({ class: 'date-period-selector-positioned-on-submenu' },
			dateFromToBlock()),
		section({ class: 'entities-overview-info' },
			_("As processing time is properly recorded since 25th of October." +
				" Below table only exposes data for files submitted after that day.")),
		br(),
		section({ class: 'section-primary users-table-filter-bar' },
			div(
				{ class: 'users-table-filter-bar-status' },
				label({ for: 'service-select' }, _("Service"), ":"),
				select({ id: 'service-select', name: 'service' },
					option(
						{ value: '', selected: location.query.get('service').map(function (value) {
							return (value == null);
						})
							},
						_("All")
					),
					list(db.BusinessProcess.extensions, function (service) {
						var serviceName = uncapitalize.call(service.__id__.slice('BusinessProcess'.length));
						return option({ value: serviceName, selected:
								location.query.get('service').map(function (value) {
								var selected = (serviceName ? (value === serviceName) : (value == null));
								return selected ? 'selected' : null;
							}) },
							service.prototype.label);
					}, null))
			),
			div(
				{ class: 'users-table-filter-bar-status' },
				exports._customFilters.call(this)
			),
			div(
				a({ class: 'users-table-filter-bar-print', href: getDynamicUrl('/time-per-role.csv',
					{ only: params }),
					target: '_blank' }, span({ class: 'fa fa-print' }), " ", _("Print csv"))
			),
			div(
				a({ class: 'users-table-filter-bar-print', href: getDynamicUrl('/time-per-role.pdf',
					{ only: params }),
					target: '_blank' }, span({ class: 'fa fa-print' }), " ", _("Print pdf"))
			))),
		br(),
		section(table({ class: 'statistics-table' },
			thead(
				th(),
				th({ class: 'statistics-table-number' }, _("Files processed")),
				th({ class: 'statistics-table-number' }, _("Average time")),
				th({ class: 'statistics-table-number' }, _("Min time")),
				th({ class: 'statistics-table-number' }, _("Max time"))
			),
			tbody({
				onEmpty: tr(td({ class: 'empty', colspan: 5 },
						_("There is no data to display")))
			}, mainData, function (row) {
				return tr({ class: 'cursor-pointer', onclick: function () {
					var jQuery = window.jQuery,
						currentRow = jQuery(this),
						detailRow = currentRow.next('.detail');

					if (detailRow.length === 0) {
						var rows = queryResult.steps
							.byStep[row.key]
							.businessProcesses
							.map(function (record, index) {

								var user = db.User.getById(record.processor),
								userName = user === null ? record.processor : user.fullName,
								style = { class: 'background-secondary' },
								lastTdContent = index === 0 ? span({
										onclick: function () {
											detailRow.hide();
										},
										class: 'cursor-pointer'
									}, 'x') : '';

								return tr(
									td(style, record.businessName),
									td(style, userName),
									td(style, getDurationDaysHours(record.processingTime)),
									td(style, new db.DateTime(record.processingStart)),
									td(style, new db.DateTime(record.processingEnd)),
									td(style, lastTdContent)
								);
							});

						detailRow = jQuery(tr({
							class: 'detail',
							style: 'display:none'
						}, td({ colspan: 5 }, table(rows))));

						detailRow.insertAfter(currentRow);
					}
					detailRow.toggle();
				} },
					td(row.label),
					td({ class: 'statistics-table-number' }, row.timedCount),
					td({ class: 'statistics-table-number' },
						row.timedCount ? getDurationDaysHours(row.avgTime) : "-"),
					td({ class: 'statistics-table-number' },
						row.timedCount ? getDurationDaysHours(row.minTime) : "-"),
					td({ class: 'statistics-table-number' },
						row.timedCount ? getDurationDaysHours(row.maxTime) : "-"));
			}))
			));
};
<|endoftext|>


<commit_before>require('./src/contra.shim.js');
<commit_msg>fix broken require in shim.js<commit_after>require('./contra.shim.js');
<|endoftext|>


<commit_before><commit_msg>bugfix in dropdown-menu<commit_after><|endoftext|>


<commit_before>var dgram = require('dgram');
var net = require('net');
var util = require('util');
var events = require('events');
var clone = require('clone');

var port = 56700;

var debug = false;

function init() {
	var l = new Lifx();
	l.startDiscovery();
	return l;
}

function Lifx() {
	events.EventEmitter.call(this);
	this.gateways = [];
	this.bulbs = [];
	this._intervalID = null;
}
Lifx.prototype.__proto__ = events.EventEmitter.prototype;

Lifx.prototype.foundGateway = function(gw) {
	var found = false;
	for (var i in this.gateways) {
		if (this.gateways[i].ipAddress.ip == gw.ipAddress.ip) {
			found = true;
		}
	}
	if (!found) {
		this.gateways.push(gw);
		// Look for bulbs on this gateway
		gw.connect();
		gw.on('_packet', this._getPacketHandler());
		gw.findBulbs();
		this.emit("gateway", gw);
	}
};

Lifx.prototype._getPacketHandler = function() {
	var self = this;
	return function(p, gw) {self._gotPacket(p, gw);};
};

Lifx.prototype._gotPacket = function(data, gw) {
	if (debug) console.log(" T- " + data.toString("hex"));

	switch (data[32]) {

		case 0x6b:
			this.foundBulb(data, gw);
			break;

		case 0x16:
			var bulb = this.getBulbByLifxAddress(data.slice(8,14));
			if (data[37] == 0xff) {
				this.emit('bulbonoff', {bulb:clone(bulb),on:true});
				if (debug) console.log(" * Light is on");
			} else {
				this.emit('bulbonoff', {bulb:clone(bulb),on:false});
				if (debug) console.log(" * Light is off");
			}
			break;
	}
};

Lifx.prototype.foundBulb = function(data, gw) {
	// Find the end of the name
	var endPos = 49;
	for (var i=48; i<data.length; i++) {
		if (data[i] == 0) {
			endPos = i;
			break;
		}
	}

	var bulbName = data.slice(48, endPos).toString('ascii').replace(/\0/g, '');
	var lifxAddress = data.slice(8,14);
	if (debug) console.log(" * Found a bulb: " + bulbName + " (address " + util.inspect(lifxAddress) + ")");

	var found = false;
	for (var i in this.bulbs) {
		if (this.bulbs[i].lifxAddress.toString("hex") == lifxAddress.toString("hex")) {
			found = true;
		}
	}

	if (!found) {
		var newBulb = new Bulb(lifxAddress, bulbName);
		if (debug) console.log("*** New bulb found (" + newBulb.name + ") by gateway " + gw.ipAddress.ip + " ***");
		this.bulbs.push(newBulb);
		this.emit('bulb', clone(newBulb));
	}

	var bulb = this.getBulbByLifxAddress(lifxAddress);
	var hue = (data[37] << 8) + data[36];
	var sat = (data[39] << 8) + data[38];
	var lum = (data[41] << 8) + data[40];
	var whi = (data[43] << 8) + data[42];
	var onoff = (data[46] > 0);
	var state = {hue:hue, saturation:sat, luminance:lum, colourTemp:whi, on:onoff, bulb:bulb};
	this.emit('bulbstate', clone(state));
};

Lifx.prototype.startDiscovery = function() {
	var self = this;
	var UDPClient = dgram.createSocket("udp4");
	UDPClient.unref(); // Stop this from preventing Node from ending

	UDPClient.on("error", function (err) {
		console.log("UDP error " + err);
	});

	UDPClient.on("message", function (msg, rinfo) {
		if (debug) console.log(" U- " + msg.toString("hex"));
		var gwBulb = msg.slice(16, 22);
		if (msg.length > 4 && msg[3] == 0x54 && msg[32] == 0x03) {
			self.foundGateway(new Gateway({address:rinfo.address, port:rinfo.port, family:rinfo.family, gwBulb:gwBulb}));
		}
	});

	UDPClient.bind(port, "0.0.0.0", function() {
		UDPClient.setBroadcast(true);
		var intervalID;
		// Now send the discovery packets
		self._intervalID = setInterval(function() {
			var message = new Buffer([0x24, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00]);
			if (debug) console.log(" U+ " + message.toString("hex"));
			UDPClient.send(message, 0, message.length, port, "255.255.255.255", function(err, bytes) {
			});
		}, 1000);
	});

};

// This represents each individual bulb
function Bulb(_lifxAddress, _name) {
	this.lifxAddress = _lifxAddress;
	this.name        = _name;
}

// This represents the gateway, and its respective functions (eg discovery, send-to-all etc)
function Gateway(addr) {
	// TODO: validation...
	this.ipAddress = {ip:addr.address, port:addr.port, family:addr.family};
	this.lifxAddress = addr.gwBulb;
	this.tcpClient = null;
	this.reconnect = true;
	events.EventEmitter.call(this);
}

// Make the Gateway into an event emitter
Gateway.prototype.__proto__ = events.EventEmitter.prototype;

Lifx.prototype.getBulbByLifxAddress = function(lifxAddress) {
	var addrToSearch = lifxAddress;
	if (typeof lifxAddress != 'string') {
		addrToSearch = lifxAddress.toString('hex');
	}
	for (var i in this.bulbs) {
		if (this.bulbs[i].lifxAddress.toString('hex') == addrToSearch) {
			return this.bulbs[i];
		}
	}
	return false;
};

// Open a control connection (over TCP) to the gateway node
Gateway.prototype.connect = function(cb) {
	var self = this;
	this.tcpClient = net.connect(this.ipAddress.port, this.ipAddress.ip, function() { //'connect' listener
		if (typeof cb == 'function') {
			cb();
		}
	});
	this.tcpClient.on('data', function(data) {
		self.emit('_packet', data, self);
	});
	this.tcpClient.on('end', function() {
		console.log('TCP client disconnected');
		self.tcpClient.destroy();
		if (self.reconnect) {
			self.connect();
		}
	});
};

Lifx.prototype.findBulbs = function() {
	this.gateways.forEach(function(g) {
		g.findBulbs();
	});
};

// This method requests that the gateway tells about all of its bulbs
Gateway.prototype.findBulbs = function() {
	this.sendToAll(new Buffer([0x65, 0x00, 0x00, 0x00]));
};

// Send a raw command - targetAddr should be a buffer containing an 8-byte address.  Use zeroes to send to all bulbs.
Gateway.prototype.send = function(message, targetAddr) {
	var standardPrefix = new Buffer([0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
	// Splice in the address of our discovered gateway bulb to replace these bytes --------------------------------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
	// Splice in the address of this bulb to replace these bytes --------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
	this.lifxAddress.copy(standardPrefix, 15);
	targetAddr.copy(standardPrefix, 7);

	var bLen = new Buffer([message.length + standardPrefix.length + 1]);
	var sendBuf = Buffer.concat([
		bLen,
		standardPrefix,
		message
	]);
	if (debug) console.log(" T+ " + sendBuf.toString("hex"));
	this.tcpClient.write(sendBuf);
};

// Send a raw command to an individual bulb
Gateway.prototype.sendToOne = function(message, bulb) {
	var targetAddr;
	if (typeof bulb.lifxAddress == 'undefined') {
		// No idea what we've been passed as a bulb.  Erm.
		throw "Unknown thing been passed instead of a bulb: " + util.inspect(bulb);
	}
	targetAddr = bulb.lifxAddress;
	this.send(message, targetAddr);
};

// Send a raw command to all bulbs attached to this gateway
Gateway.prototype.sendToAll = function(message) {
	var targetAddr = new Buffer(8);
	targetAddr.fill(0);
	this.send(message, targetAddr);
};

// Close the connection to this gateway
Gateway.prototype.close = function() {
	this.reconnect = false;
	this.tcpClient.end();
};

Lifx.prototype.close = function() {
	clearInterval(this._intervalID);
	this.gateways.forEach(function(g) {
		g.close();
	});
};

Lifx.prototype.sendToAll = function(command) {
	this._sendToOneOrAll(command);
};

Lifx.prototype.sendToOne = function(command, bulb) {
	this._sendToOneOrAll(command, bulb);
};

Lifx.prototype._sendToOneOrAll = function(command, bulb) {
	this.gateways.forEach(function(g) {
		if (typeof bulb == 'undefined') {
			g.sendToAll(command);
		} else {
			g.sendToOne(command, bulb);
		}
	});
};

/////// Fun methods ////////

// Turn all lights on
Lifx.prototype.lightsOn = function(bulb) {
	this._sendToOneOrAll(new Buffer([0x15, 0x00, 0x00, 0x00, 0x01, 0x00]), bulb);
};

// Turn all lights off
Lifx.prototype.lightsOff = function(bulb) {
	this._sendToOneOrAll(new Buffer([0x15, 0x00, 0x00, 0x00, 0x00, 0x00]), bulb);
};

// Set all bulbs to a particular colour
// Pass in 16-bit numbers for each param - they will be byte shuffled as appropriate
Lifx.prototype.lightsColour = function(hue, sat, lum, whitecol, timing, bulb) {
	var message = new Buffer([0x66, 0x00, 0x00, 0x00, 0x00, 0x9e, 0xd4, 0xff, 0xff, 0x30, 0x00, 0xac, 0x0d, 0x13, 0x05, 0x00, 0x00]);
	//                                                      ^^^^^^^^^^  ^^^^^^^^^^  ^^^^^^^^^^  ^^^^^^^^^^  ^^^^^^^^^^
	//                                                         hue      saturation  luminance  white colour   timing
	message[5]  = (hue & 0x00ff);
	message[6]  = (hue & 0xff00) >> 8;
	message[7]  = (sat & 0x00ff);
	message[8]  = (sat & 0xff00) >> 8;
	message[9]  = (lum & 0x00ff);
	message[10] = (lum & 0xff00) >> 8;
	message[11] = (whitecol & 0x00ff);
	message[12] = (whitecol & 0xff00) >> 8;
	message[13] = (timing & 0x00ff);
	message[14] = (timing & 0xff00) >> 8;

	this._sendToOneOrAll(message, bulb);
};

module.exports = {
	init:init,
	setDebug:function(d){debug=d;}
};

<commit_msg>equality and reused identifier<commit_after>var dgram = require('dgram');
var net = require('net');
var util = require('util');
var events = require('events');
var clone = require('clone');

var port = 56700;

var debug = false;

function init() {
	var l = new Lifx();
	l.startDiscovery();
	return l;
}

function Lifx() {
	events.EventEmitter.call(this);
	this.gateways = [];
	this.bulbs = [];
	this._intervalID = null;
}
Lifx.prototype.__proto__ = events.EventEmitter.prototype;

Lifx.prototype.foundGateway = function(gw) {
	var found = false;
	for (var i in this.gateways) {
		if (this.gateways[i].ipAddress.ip == gw.ipAddress.ip) {
			found = true;
		}
	}
	if (!found) {
		this.gateways.push(gw);
		// Look for bulbs on this gateway
		gw.connect();
		gw.on('_packet', this._getPacketHandler());
		gw.findBulbs();
		this.emit("gateway", gw);
	}
};

Lifx.prototype._getPacketHandler = function() {
	var self = this;
	return function(p, gw) {self._gotPacket(p, gw);};
};

Lifx.prototype._gotPacket = function(data, gw) {
	if (debug) console.log(" T- " + data.toString("hex"));

	switch (data[32]) {

		case 0x6b:
			this.foundBulb(data, gw);
			break;

		case 0x16:
			var bulb = this.getBulbByLifxAddress(data.slice(8,14));
			if (data[37] == 0xff) {
				this.emit('bulbonoff', {bulb:clone(bulb),on:true});
				if (debug) console.log(" * Light is on");
			} else {
				this.emit('bulbonoff', {bulb:clone(bulb),on:false});
				if (debug) console.log(" * Light is off");
			}
			break;
	}
};

Lifx.prototype.foundBulb = function(data, gw) {
	// Find the end of the name
	var endPos = 49;
	for (var i=48; i<data.length; i++) {
		if (data[i] === 0) {
			endPos = i;
			break;
		}
	}

	var bulbName = data.slice(48, endPos).toString('ascii').replace(/\0/g, '');
	var lifxAddress = data.slice(8,14);
	if (debug) console.log(" * Found a bulb: " + bulbName + " (address " + util.inspect(lifxAddress) + ")");

	var found = false;
	for (var bulbId in this.bulbs) {
		if (this.bulbs[bulbId].lifxAddress.toString("hex") == lifxAddress.toString("hex")) {
			found = true;
		}
	}

	if (!found) {
		var newBulb = new Bulb(lifxAddress, bulbName);
		if (debug) console.log("*** New bulb found (" + newBulb.name + ") by gateway " + gw.ipAddress.ip + " ***");
		this.bulbs.push(newBulb);
		this.emit('bulb', clone(newBulb));
	}

	var bulb = this.getBulbByLifxAddress(lifxAddress);
	var hue = (data[37] << 8) + data[36];
	var sat = (data[39] << 8) + data[38];
	var lum = (data[41] << 8) + data[40];
	var whi = (data[43] << 8) + data[42];
	var onoff = (data[46] > 0);
	var state = {hue:hue, saturation:sat, luminance:lum, colourTemp:whi, on:onoff, bulb:bulb};
	this.emit('bulbstate', clone(state));
};

Lifx.prototype.startDiscovery = function() {
	var self = this;
	var UDPClient = dgram.createSocket("udp4");
	UDPClient.unref(); // Stop this from preventing Node from ending

	UDPClient.on("error", function (err) {
		console.log("UDP error " + err);
	});

	UDPClient.on("message", function (msg, rinfo) {
		if (debug) console.log(" U- " + msg.toString("hex"));
		var gwBulb = msg.slice(16, 22);
		if (msg.length > 4 && msg[3] == 0x54 && msg[32] == 0x03) {
			self.foundGateway(new Gateway({address:rinfo.address, port:rinfo.port, family:rinfo.family, gwBulb:gwBulb}));
		}
	});

	UDPClient.bind(port, "0.0.0.0", function() {
		UDPClient.setBroadcast(true);
		var intervalID;
		// Now send the discovery packets
		self._intervalID = setInterval(function() {
			var message = new Buffer([0x24, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00]);
			if (debug) console.log(" U+ " + message.toString("hex"));
			UDPClient.send(message, 0, message.length, port, "255.255.255.255", function(err, bytes) {
			});
		}, 1000);
	});

};

// This represents each individual bulb
function Bulb(_lifxAddress, _name) {
	this.lifxAddress = _lifxAddress;
	this.name        = _name;
}

// This represents the gateway, and its respective functions (eg discovery, send-to-all etc)
function Gateway(addr) {
	// TODO: validation...
	this.ipAddress = {ip:addr.address, port:addr.port, family:addr.family};
	this.lifxAddress = addr.gwBulb;
	this.tcpClient = null;
	this.reconnect = true;
	events.EventEmitter.call(this);
}

// Make the Gateway into an event emitter
Gateway.prototype.__proto__ = events.EventEmitter.prototype;

Lifx.prototype.getBulbByLifxAddress = function(lifxAddress) {
	var addrToSearch = lifxAddress;
	if (typeof lifxAddress != 'string') {
		addrToSearch = lifxAddress.toString('hex');
	}
	for (var i in this.bulbs) {
		if (this.bulbs[i].lifxAddress.toString('hex') == addrToSearch) {
			return this.bulbs[i];
		}
	}
	return false;
};

// Open a control connection (over TCP) to the gateway node
Gateway.prototype.connect = function(cb) {
	var self = this;
	this.tcpClient = net.connect(this.ipAddress.port, this.ipAddress.ip, function() { //'connect' listener
		if (typeof cb == 'function') {
			cb();
		}
	});
	this.tcpClient.on('data', function(data) {
		self.emit('_packet', data, self);
	});
	this.tcpClient.on('end', function() {
		console.log('TCP client disconnected');
		self.tcpClient.destroy();
		if (self.reconnect) {
			self.connect();
		}
	});
};

Lifx.prototype.findBulbs = function() {
	this.gateways.forEach(function(g) {
		g.findBulbs();
	});
};

// This method requests that the gateway tells about all of its bulbs
Gateway.prototype.findBulbs = function() {
	this.sendToAll(new Buffer([0x65, 0x00, 0x00, 0x00]));
};

// Send a raw command - targetAddr should be a buffer containing an 8-byte address.  Use zeroes to send to all bulbs.
Gateway.prototype.send = function(message, targetAddr) {
	var standardPrefix = new Buffer([0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
	// Splice in the address of our discovered gateway bulb to replace these bytes --------------------------------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
	// Splice in the address of this bulb to replace these bytes --------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
	this.lifxAddress.copy(standardPrefix, 15);
	targetAddr.copy(standardPrefix, 7);

	var bLen = new Buffer([message.length + standardPrefix.length + 1]);
	var sendBuf = Buffer.concat([
		bLen,
		standardPrefix,
		message
	]);
	if (debug) console.log(" T+ " + sendBuf.toString("hex"));
	this.tcpClient.write(sendBuf);
};

// Send a raw command to an individual bulb
Gateway.prototype.sendToOne = function(message, bulb) {
	var targetAddr;
	if (typeof bulb.lifxAddress == 'undefined') {
		// No idea what we've been passed as a bulb.  Erm.
		throw "Unknown thing been passed instead of a bulb: " + util.inspect(bulb);
	}
	targetAddr = bulb.lifxAddress;
	this.send(message, targetAddr);
};

// Send a raw command to all bulbs attached to this gateway
Gateway.prototype.sendToAll = function(message) {
	var targetAddr = new Buffer(8);
	targetAddr.fill(0);
	this.send(message, targetAddr);
};

// Close the connection to this gateway
Gateway.prototype.close = function() {
	this.reconnect = false;
	this.tcpClient.end();
};

Lifx.prototype.close = function() {
	clearInterval(this._intervalID);
	this.gateways.forEach(function(g) {
		g.close();
	});
};

Lifx.prototype.sendToAll = function(command) {
	this._sendToOneOrAll(command);
};

Lifx.prototype.sendToOne = function(command, bulb) {
	this._sendToOneOrAll(command, bulb);
};

Lifx.prototype._sendToOneOrAll = function(command, bulb) {
	this.gateways.forEach(function(g) {
		if (typeof bulb == 'undefined') {
			g.sendToAll(command);
		} else {
			g.sendToOne(command, bulb);
		}
	});
};

/////// Fun methods ////////

// Turn all lights on
Lifx.prototype.lightsOn = function(bulb) {
	this._sendToOneOrAll(new Buffer([0x15, 0x00, 0x00, 0x00, 0x01, 0x00]), bulb);
};

// Turn all lights off
Lifx.prototype.lightsOff = function(bulb) {
	this._sendToOneOrAll(new Buffer([0x15, 0x00, 0x00, 0x00, 0x00, 0x00]), bulb);
};

// Set all bulbs to a particular colour
// Pass in 16-bit numbers for each param - they will be byte shuffled as appropriate
Lifx.prototype.lightsColour = function(hue, sat, lum, whitecol, timing, bulb) {
	var message = new Buffer([0x66, 0x00, 0x00, 0x00, 0x00, 0x9e, 0xd4, 0xff, 0xff, 0x30, 0x00, 0xac, 0x0d, 0x13, 0x05, 0x00, 0x00]);
	//                                                      ^^^^^^^^^^  ^^^^^^^^^^  ^^^^^^^^^^  ^^^^^^^^^^  ^^^^^^^^^^
	//                                                         hue      saturation  luminance  white colour   timing
	message[5]  = (hue & 0x00ff);
	message[6]  = (hue & 0xff00) >> 8;
	message[7]  = (sat & 0x00ff);
	message[8]  = (sat & 0xff00) >> 8;
	message[9]  = (lum & 0x00ff);
	message[10] = (lum & 0xff00) >> 8;
	message[11] = (whitecol & 0x00ff);
	message[12] = (whitecol & 0xff00) >> 8;
	message[13] = (timing & 0x00ff);
	message[14] = (timing & 0xff00) >> 8;

	this._sendToOneOrAll(message, bulb);
};

module.exports = {
	init:init,
	setDebug:function(d){debug=d;}
};

<|endoftext|>


<commit_before>/**
 * Created by Alexander Lex on 3/4/14.
 */

var SET_SIZE_GROUP_PREFIX = 'SetSizeG_';
var EMPTY_GROUP_ID = 'EmptyGroup';
var SET_BASED_GROUPING_PREFIX = "SetG_";

var handleLogicGroups= function(subsets,dataRows,level){
    var addGroups = [];
    var oldGroupIDs = {};
    if (previousState!=false) previousState.logicGroups.forEach(function(d){oldGroupIDs[d.id]=1})
    UpSetState.logicGroups.forEach(function(d){
        if (d.id in oldGroupIDs){}
        else {
            var group = new Group(d.id, d.groupName, level);
            var compareList= []
            d.orClauses.forEach(function(orClause){

                var compareObject = []
                for (key in orClause){
                    compareObject.push(orClause[key].state);
                }
                compareList.push(compareObject)
            })

            var isAhit = true;
            subsets.forEach(function(subset){

                isAhit = true;
                var combinedSets = subset.combinedSets;
                compareList.forEach(function(compare){
                    var csLength = combinedSets.length;
                    if (isAhit && csLength==compare.length ){
                        for (var i =0; i<csLength;i++){
                            isAhit &= (
                                (combinedSets[i]==compare[i])
                                || compare[i]==2 );
                        }

                    }
                })

                if (isAhit) group.addSubSet(subset);

            })

            addGroups.push(group)
        };
    })
//    console.log(addGroups);

    if (addGroups.length>0){
        addGroups.forEach(function(addGroup){
            dataRows.unshift(addGroup);
        })
    }

}

var groupByDeviation = function (subSets, level) {
    var newGroups = [];
    newGroups.push(new Group('GROUP_POS_DEV', 'Positive Expected Value', level));
    newGroups.push(new Group('GROUP_POS_NEG', 'Negative Expected Value', level));
    newGroups.push(new Group(EMPTY_GROUP_ID, 'Empty Subset', level));
    for (var i = 0; i < subSets.length; i++) {
        var index = 0
        if (subSets[i].expectedValueDeviation > 0) {
            index = 0;
        }
        else if (subSets[i].expectedValueDeviation < 0) {
            index = 1;
        }
        else {
            index = 2;
        }
        newGroups[index].addSubSet(subSets[i])
    }
    return newGroups;
}

var groupBySetSize = function (subSets, level) {
    var newGroups = [];
    newGroups.push(new Group(EMPTY_GROUP_ID, 'Empty Subset', level));
    var maxSetSize = Math.min(usedSets.length, UpSetState.maxCardinality);
    for (var i = UpSetState.minCardinality; i < maxSetSize; i++) {
        newGroups.push(new Group(SET_SIZE_GROUP_PREFIX + (i + 1), (i + 1) + '-Set Subsets'));
    }
    subSets.forEach(function (subSet) {
        var group = newGroups[subSet.nrCombinedSets];
        if (group != null)
            group.addSubSet(subSet);
        else
            console.log('Fail ' + group + subSet.nrCombinedSets);
    })
    return newGroups;
}

/**
 * Creates groups for all sets containing all subsets of this set
 */
var groupBySet = function (subSets, level) {

    // TODO add empty subset

    var newGroups = [];
    newGroups.push(new Group(EMPTY_GROUP_ID, 'Empty Subset', level));
    for (var i = 0; i < usedSets.length; i++) {
        var group = new Group(SET_BASED_GROUPING_PREFIX + (i + 1), usedSets[i].elementName);

        newGroups.push(group);

        subSets.forEach(function (subSet) {
            if (subSet.combinedSets[i] !== 0) {
                group.addSubSet(subSet);
            }
        });
    }
    return newGroups;
};

/** Collapse or uncollapse group */
var collapseGroup = function (group) {
    group.isCollapsed = !group.isCollapsed;
    UpSetState.collapseChanged = true;
    updateState();
};

var toggleCollapseAll = function () {
    if (UpSetState.collapseAll) {
        UpSetState.unCollapseAll = true;
    }
    UpSetState.collapseAll = !UpSetState.collapseAll;
    UpSetState.collapseChanged = true;
    updateState();
};

var collapseAggregate = function (aggregate) {
    aggregate.isCollapsed = !aggregate.isCollapsed;
    updateState();
};

// ----------------------- Sort Functions ----------------------------

function getFilteredSubSets() {
    if (!UpSetState.hideEmpties) {
        return subSets.slice(0);
    }
    var filteredSubSets = []
    for (var i = 0; i < subSets.length; i++) {
        if (subSets[i].items.length > 0) {
            filteredSubSets.push(subSets[i]);
        }
    }
    return filteredSubSets;
}

function sortBySetItem(subSets, set) {
    if (!set) {
        set = usedSets[0];
    }
    var dataRows = getFilteredSubSets();
    var setIndex = usedSets.indexOf(set);
    dataRows.sort(function (a, b) {
        // move all elements that contain the clicked set to the top
        if (b.combinedSets[setIndex] !== a.combinedSets[setIndex]) {
            return b.combinedSets[setIndex] - a.combinedSets[setIndex];
        }
        // move all elements with fewer intersections to the top
        if (a.nrCombinedSets !== b.nrCombinedSets) {
            return a.nrCombinedSets - b.nrCombinedSets;
        }
        // if the number of combined sets is identical, we can pick the largest one
        return b.id - a.id;
    });
    return dataRows;
}

function sortByCombinationSize(subsets) {
    var dataRows = getFilteredSubSets();

// sort by number of combinations
    dataRows.sort(function (a, b) {
        if (a.nrCombinedSets !== b.nrCombinedSets) {
            return a.nrCombinedSets - b.nrCombinedSets;
        }
        // if the number of combined sets is identical, we can pick the largest one
        return b.id - a.id;
    });
    return dataRows;
}

/** sort by size of set overlap */
function sortBySubSetSize(subsets) {
    var dataRows = getFilteredSubSets();
    dataRows.sort(function (a, b) {
        return b.setSize - a.setSize;
    });
    return dataRows;
}

/** sort by size of set overlap */
function sortByExpectedValue(subSets) {
    var dataRows = getFilteredSubSets();

    dataRows.sort(function (a, b) {
        return Math.abs(b.expectedValueDeviation) - Math.abs(a.expectedValueDeviation);
    });
    return dataRows;
}

/**
 * Takes a list of groups and writes them into an array, according to the nesting & collapse rules
 * @param groupList
 * @returns {*}
 */
var unwrapGroups = function (groupList) {
    var dataRows = []
    for (var i = 0; i < groupList.length; i++) {
        var group = groupList[i];
        // ignoring an empty empty group
        if (group.id === EMPTY_GROUP_ID && group.setSize === 0 || (group.visibleSets.length === 0 && UpSetState.hideEmpties)) {
            continue;
        }
        dataRows.push(group);
        if (UpSetState.collapseAll) {
            group.isCollapsed = true;
        }
        if (UpSetState.unCollapseAll) {
            group.isCollapsed = false;
        }
        if (!group.isCollapsed) {

            if (UpSetState.levelTwoGrouping && group.nestedGroups) {
                dataRows = dataRows.concat(unwrapGroups(group.nestedGroups, []));
            }
            else {
                for (var j = 0; j < group.visibleSets.length; j++) {
                    dataRows.push(group.visibleSets[j]);
                }
                if (group.aggregate.subSets.length > 0 && !UpSetState.hideEmpties) {
                    dataRows.push(group.aggregate);
                    if (!group.aggregate.isCollapsed) {
                        for (var k = 0; k < group.aggregate.subSets.length; k++) {
                            dataRows.push(group.aggregate.subSets[k]);
                        }
                    }
                }
            }
        }
    }
    UpSetState.unCollapseAll = false;
    return dataRows;
};

var StateMap = {
    groupBySetSize: groupBySetSize,
    groupBySet: groupBySet,
    groupByDeviation: groupByDeviation,

    sortByCombinationSize: sortByCombinationSize,
    sortBySubSetSize: sortBySubSetSize,
    sortByExpectedValue: sortByExpectedValue,
    sortBySubSetSize: sortBySubSetSize,
    sortBySetItem: sortBySetItem
};

var StateOpt = {
    groupBySetSize: 'groupBySetSize',
    groupBySet: 'groupBySet',
    groupByDeviation: 'groupByDeviation',

    sortByCombinationSize: 'sortByCombinationSize',
    sortBySubSetSize: 'sortBySubSetSize',
    sortByExpectedValue: 'sortByExpectedValue',
    sortBySubSetSize: 'sortBySubSetSize',
    sortBySetItem: 'sortBySetItem'
};

var UpSetState = {
    collapseAll: false,
    unCollapseAll: false,
    collapseChanged: false,
    grouping: StateOpt.groupBySet,
    levelTwoGrouping: StateOpt.groupBySetSize,
    sorting: undefined,

    /** hide empty subsets, groups and aggregates */
    hideEmpties: true,

    /** Sets the upper threshold of cardinality of subsets */
    maxCardinality: undefined,
    /** Sets the lower threshold of cardinality of subsets */
    minCardinality: undefined,

    forceUpdate: false,

    /** user defined logic groups **/
    logicGroups:[],
    logicGroupChanged:false

};

var previousState = false;

var updateState = function (parameter) {

    var forceUpdate = !previousState || UpSetState.forceUpdate || (UpSetState.hideEmpties != previousState.hideEmpties);

    if(UpSetState.logicGroupChanged ){
        handleLogicGroups(subSets,levelOneGroups,1);
        UpSetState.logicGroupChanged = false;
        console.log("datarows:",levelOneGroups[0]);
    }

    // true if pure sorting - no grouping
    if ((UpSetState.sorting && !UpSetState.grouping) && (forceUpdate || (previousState && previousState.sorting !== UpSetState.sorting))) {
        dataRows = StateMap[StateOpt[UpSetState.sorting]](subSets, parameter);
    }
    else if (UpSetState.grouping && (forceUpdate || (previousState && previousState.grouping !== UpSetState.grouping || previousState.levelTwoGrouping !== UpSetState.levelTwoGrouping))) {
        levelOneGroups = StateMap[StateOpt[UpSetState.grouping]](subSets);

        if (UpSetState.levelTwoGrouping) {
            levelOneGroups.forEach(function (group) {
                group.nestedGroups = StateMap[StateOpt[UpSetState.levelTwoGrouping]](group.subSets, 2);
            });
        }
        dataRows = unwrapGroups(levelOneGroups);

    }
    else if (UpSetState.collapseChanged && UpSetState.grouping) {
        dataRows = unwrapGroups(levelOneGroups);
    }

    // unwrapGroups deals with collapse, so we can reset it
    UpSetState.collapseChanged = false;
    UpSetState.forceUpdate = false;

    renderRows.length = 0;

    var registry = {};
    dataRows.forEach(function (element) {
        var count = 1;
        if (registry.hasOwnProperty(element.id)) {
            count = registry[element.id];
            count += 1;
            registry[element.id] = count;
        }
        else {
            registry[element.id] = 1;
        }
        var wrapper = {};
        wrapper.id = element.id + '_' + count;
        wrapper.data = element;

        renderRows.push(wrapper);

    });
    previousState = JSON.parse(JSON.stringify(UpSetState));
};
<commit_msg>logic: rendering of rows -- just a hack to see something<commit_after>/**
 * Created by Alexander Lex on 3/4/14.
 */

var SET_SIZE_GROUP_PREFIX = 'SetSizeG_';
var EMPTY_GROUP_ID = 'EmptyGroup';
var SET_BASED_GROUPING_PREFIX = "SetG_";

var handleLogicGroups= function(subsets,dataRows,level){
    var addGroups = [];
    var oldGroupIDs = {};
    if (previousState!=false) previousState.logicGroups.forEach(function(d){oldGroupIDs[d.id]=1})
    UpSetState.logicGroups.forEach(function(d){
        if (d.id in oldGroupIDs){}
        else {
            var group = new Group(d.id, d.groupName, level);
            var compareList= []
            d.orClauses.forEach(function(orClause){

                var compareObject = []
                for (key in orClause){
                    compareObject.push(orClause[key].state);
                }
                compareList.push(compareObject)
            })

            var isAhit = true;
            subsets.forEach(function(subset){

                isAhit = true;
                var combinedSets = subset.combinedSets;
                compareList.forEach(function(compare){
                    var csLength = combinedSets.length;
                    if (isAhit && csLength==compare.length ){
                        for (var i =0; i<csLength;i++){
                            isAhit &= (
                                (combinedSets[i]==compare[i])
                                || compare[i]==2 );
                        }

                    }
                })

                if (isAhit) group.addSubSet(subset);

            })

            addGroups.push(group)
        };
    })
//    console.log(addGroups);

    if (addGroups.length>0){
        var groupElements= unwrapGroups(addGroups)
        console.log(groupElements
        );

        groupElements.reverse()
        groupElements.forEach(function(addGroup){
            dataRows.unshift(addGroup)
        })
    }

}

var groupByDeviation = function (subSets, level) {
    var newGroups = [];
    newGroups.push(new Group('GROUP_POS_DEV', 'Positive Expected Value', level));
    newGroups.push(new Group('GROUP_POS_NEG', 'Negative Expected Value', level));
    newGroups.push(new Group(EMPTY_GROUP_ID, 'Empty Subset', level));
    for (var i = 0; i < subSets.length; i++) {
        var index = 0
        if (subSets[i].expectedValueDeviation > 0) {
            index = 0;
        }
        else if (subSets[i].expectedValueDeviation < 0) {
            index = 1;
        }
        else {
            index = 2;
        }
        newGroups[index].addSubSet(subSets[i])
    }
    return newGroups;
}

var groupBySetSize = function (subSets, level) {
    var newGroups = [];
    newGroups.push(new Group(EMPTY_GROUP_ID, 'Empty Subset', level));
    var maxSetSize = Math.min(usedSets.length, UpSetState.maxCardinality);
    for (var i = UpSetState.minCardinality; i < maxSetSize; i++) {
        newGroups.push(new Group(SET_SIZE_GROUP_PREFIX + (i + 1), (i + 1) + '-Set Subsets'));
    }
    subSets.forEach(function (subSet) {
        var group = newGroups[subSet.nrCombinedSets];
        if (group != null)
            group.addSubSet(subSet);
        else
            console.log('Fail ' + group + subSet.nrCombinedSets);
    })
    return newGroups;
}

/**
 * Creates groups for all sets containing all subsets of this set
 */
var groupBySet = function (subSets, level) {

    // TODO add empty subset

    var newGroups = [];
    newGroups.push(new Group(EMPTY_GROUP_ID, 'Empty Subset', level));
    for (var i = 0; i < usedSets.length; i++) {
        var group = new Group(SET_BASED_GROUPING_PREFIX + (i + 1), usedSets[i].elementName);

        newGroups.push(group);

        subSets.forEach(function (subSet) {
            if (subSet.combinedSets[i] !== 0) {
                group.addSubSet(subSet);
            }
        });
    }
    return newGroups;
};

/** Collapse or uncollapse group */
var collapseGroup = function (group) {
    group.isCollapsed = !group.isCollapsed;
    UpSetState.collapseChanged = true;
    updateState();
};

var toggleCollapseAll = function () {
    if (UpSetState.collapseAll) {
        UpSetState.unCollapseAll = true;
    }
    UpSetState.collapseAll = !UpSetState.collapseAll;
    UpSetState.collapseChanged = true;
    updateState();
};

var collapseAggregate = function (aggregate) {
    aggregate.isCollapsed = !aggregate.isCollapsed;
    updateState();
};

// ----------------------- Sort Functions ----------------------------

function getFilteredSubSets() {
    if (!UpSetState.hideEmpties) {
        return subSets.slice(0);
    }
    var filteredSubSets = []
    for (var i = 0; i < subSets.length; i++) {
        if (subSets[i].items.length > 0) {
            filteredSubSets.push(subSets[i]);
        }
    }
    return filteredSubSets;
}

function sortBySetItem(subSets, set) {
    if (!set) {
        set = usedSets[0];
    }
    var dataRows = getFilteredSubSets();
    var setIndex = usedSets.indexOf(set);
    dataRows.sort(function (a, b) {
        // move all elements that contain the clicked set to the top
        if (b.combinedSets[setIndex] !== a.combinedSets[setIndex]) {
            return b.combinedSets[setIndex] - a.combinedSets[setIndex];
        }
        // move all elements with fewer intersections to the top
        if (a.nrCombinedSets !== b.nrCombinedSets) {
            return a.nrCombinedSets - b.nrCombinedSets;
        }
        // if the number of combined sets is identical, we can pick the largest one
        return b.id - a.id;
    });
    return dataRows;
}

function sortByCombinationSize(subsets) {
    var dataRows = getFilteredSubSets();

// sort by number of combinations
    dataRows.sort(function (a, b) {
        if (a.nrCombinedSets !== b.nrCombinedSets) {
            return a.nrCombinedSets - b.nrCombinedSets;
        }
        // if the number of combined sets is identical, we can pick the largest one
        return b.id - a.id;
    });
    return dataRows;
}

/** sort by size of set overlap */
function sortBySubSetSize(subsets) {
    var dataRows = getFilteredSubSets();
    dataRows.sort(function (a, b) {
        return b.setSize - a.setSize;
    });
    return dataRows;
}

/** sort by size of set overlap */
function sortByExpectedValue(subSets) {
    var dataRows = getFilteredSubSets();

    dataRows.sort(function (a, b) {
        return Math.abs(b.expectedValueDeviation) - Math.abs(a.expectedValueDeviation);
    });
    return dataRows;
}

/**
 * Takes a list of groups and writes them into an array, according to the nesting & collapse rules
 * @param groupList
 * @returns {*}
 */
var unwrapGroups = function (groupList) {
    var dataRows = []
    for (var i = 0; i < groupList.length; i++) {
        var group = groupList[i];
        // ignoring an empty empty group
        if (group.id === EMPTY_GROUP_ID && group.setSize === 0 || (group.visibleSets.length === 0 && UpSetState.hideEmpties)) {
            continue;
        }
        dataRows.push(group);
        if (UpSetState.collapseAll) {
            group.isCollapsed = true;
        }
        if (UpSetState.unCollapseAll) {
            group.isCollapsed = false;
        }
        if (!group.isCollapsed) {

            if (UpSetState.levelTwoGrouping && group.nestedGroups) {
                dataRows = dataRows.concat(unwrapGroups(group.nestedGroups, []));
            }
            else {
                for (var j = 0; j < group.visibleSets.length; j++) {
                    dataRows.push(group.visibleSets[j]);
                }
                if (group.aggregate.subSets.length > 0 && !UpSetState.hideEmpties) {
                    dataRows.push(group.aggregate);
                    if (!group.aggregate.isCollapsed) {
                        for (var k = 0; k < group.aggregate.subSets.length; k++) {
                            dataRows.push(group.aggregate.subSets[k]);
                        }
                    }
                }
            }
        }
    }
    UpSetState.unCollapseAll = false;
    return dataRows;
};

var StateMap = {
    groupBySetSize: groupBySetSize,
    groupBySet: groupBySet,
    groupByDeviation: groupByDeviation,

    sortByCombinationSize: sortByCombinationSize,
    sortBySubSetSize: sortBySubSetSize,
    sortByExpectedValue: sortByExpectedValue,
    sortBySubSetSize: sortBySubSetSize,
    sortBySetItem: sortBySetItem
};

var StateOpt = {
    groupBySetSize: 'groupBySetSize',
    groupBySet: 'groupBySet',
    groupByDeviation: 'groupByDeviation',

    sortByCombinationSize: 'sortByCombinationSize',
    sortBySubSetSize: 'sortBySubSetSize',
    sortByExpectedValue: 'sortByExpectedValue',
    sortBySubSetSize: 'sortBySubSetSize',
    sortBySetItem: 'sortBySetItem'
};

var UpSetState = {
    collapseAll: false,
    unCollapseAll: false,
    collapseChanged: false,
    grouping: StateOpt.groupBySet,
    levelTwoGrouping: StateOpt.groupBySetSize,
    sorting: undefined,

    /** hide empty subsets, groups and aggregates */
    hideEmpties: true,

    /** Sets the upper threshold of cardinality of subsets */
    maxCardinality: undefined,
    /** Sets the lower threshold of cardinality of subsets */
    minCardinality: undefined,

    forceUpdate: false,

    /** user defined logic groups **/
    logicGroups:[],
    logicGroupChanged:false

};

var previousState = false;

var updateState = function (parameter) {

    var forceUpdate = !previousState || UpSetState.forceUpdate || (UpSetState.hideEmpties != previousState.hideEmpties);

    // true if pure sorting - no grouping
    if ((UpSetState.sorting && !UpSetState.grouping) && (forceUpdate || (previousState && previousState.sorting !== UpSetState.sorting))) {
        dataRows = StateMap[StateOpt[UpSetState.sorting]](subSets, parameter);
    }
    else if (UpSetState.grouping && (forceUpdate || (previousState && previousState.grouping !== UpSetState.grouping || previousState.levelTwoGrouping !== UpSetState.levelTwoGrouping))) {
        levelOneGroups = StateMap[StateOpt[UpSetState.grouping]](subSets);

        if (UpSetState.levelTwoGrouping) {
            levelOneGroups.forEach(function (group) {
                group.nestedGroups = StateMap[StateOpt[UpSetState.levelTwoGrouping]](group.subSets, 2);
            });
        }
        dataRows = unwrapGroups(levelOneGroups);

    }
    else if (UpSetState.collapseChanged && UpSetState.grouping) {
        dataRows = unwrapGroups(levelOneGroups);
    }

    if(UpSetState.logicGroupChanged ){
        handleLogicGroups(subSets,dataRows,1);
        UpSetState.logicGroupChanged = false;
//        console.log("datarows:",levelOneGroups[0]);
    }

    // unwrapGroups deals with collapse, so we can reset it
    UpSetState.collapseChanged = false;
    UpSetState.forceUpdate = false;

    renderRows.length = 0;

    var registry = {};
    dataRows.forEach(function (element) {
        var count = 1;
        if (registry.hasOwnProperty(element.id)) {
            count = registry[element.id];
            count += 1;
            registry[element.id] = count;
        }
        else {
            registry[element.id] = 1;
        }
        var wrapper = {};
        wrapper.id = element.id + '_' + count;
        wrapper.data = element;

        renderRows.push(wrapper);

    });
    previousState = JSON.parse(JSON.stringify(UpSetState));
};
<|endoftext|>


<commit_before>var Twitter = require('twitter');

var importTwitter = require('./keys.js');
var keysTwitter = importTwitter.keysTwitter;

var client = new Twitter(keysTwitter);

client.get('statuses/user_timeline', {screen_name: '@willbeard4food', count: 10}, function(error, tweets, response) {
   console.log(tweets);
});

// client.post('statuses/update', {status: 'Posting from node.js'}, function(error, tweet, response){
//   if(error) throw error;
//   console.log(tweet);
//   // console.log(response);
// });

var spotify = require('spotify');

spotify.search({type: 'track', query: 'dancing in the moonlight'}, function(error, data){
  if(error) throw error;
  // console.log(data.tracks.items[0])
});

var request = require('request');

var url = 'http://www.omdbapi.com/?t=gladiator&y=&plot=short&r=json';

request(url,function(error, response, body){
  if(error) throw error;
  // console.log(body)
})<commit_msg>:art: Changed export style for twitter keys<commit_after>var command = process.argv[2];
var mediaName = process.argv.splice(3).join(' ');

console.log('command', command);
console.log('mediaName', mediaName);

var Twitter = require('twitter');

var keysTwitter = require('./keys.js');

var client = new Twitter(keysTwitter);

client.get('statuses/user_timeline', {screen_name: '@willbeard4food', count: 10}, function(error, tweets, response) {
  //  console.log(tweets);
});

// client.post('statuses/update', {status: 'Posting from node.js'}, function(error, tweet, response){
//   if(error) throw error;
//   console.log(tweet);
//   // console.log(response);
// });

var spotify = require('spotify');

spotify.search({type: 'track', query: 'dancing in the moonlight'}, function(error, data){
  if(error) throw error;
  // console.log(data.tracks.items[0])
});

var request = require('request');

var url = 'http://www.omdbapi.com/?t=gladiator&y=&plot=short&r=json';

request(url,function(error, response, body){
  if(error) throw error;
  // console.log(body)
})<|endoftext|>


<commit_before>/**
 * @author Nicolas CARPi <nicolas.carpi@curie.fr>
 * @copyright 2012 Nicolas CARPi
 * @see https://www.elabftw.net Official website
 * @license AGPL-3.0
 * @package elabftw
 */
window.MathJax = {
  tex: {
    inlineMath: [ ['$','$'], ['\\(','\\)'] ],
    displayMath: [ ['$$','$$'], ['\\[','\\]'] ],
    processEscapes: true,
    packages: ['base', 'ams', 'autoload']
  },
  startup: {
    ready: () => {
      MathJax.startup.defaultReady();
    }
  }
};
<commit_msg>use workaround for mathjax/MathJax#2327<commit_after>/**
 * @author Nicolas CARPi <nicolas.carpi@curie.fr>
 * @copyright 2012 Nicolas CARPi
 * @see https://www.elabftw.net Official website
 * @license AGPL-3.0
 * @package elabftw
 */
window.MathJax = {
  tex: {
    inlineMath: [ ['$','$'], ['\\(','\\)'] ],
    displayMath: [ ['$$','$$'], ['\\[','\\]'] ],
    processEscapes: true,
    packages: ['base', 'ams', 'autoload']
  },
  startup: {
    ready: () => {
      MathJax.startup.defaultReady();
    },
    pageReady() {
      const options = MathJax.startup.document.options;
      const BaseMathItem = options.MathItem;
      options.MathItem = class FixedMathItem extends BaseMathItem {
        assistiveMml(document) {
          if (this.display !== null) super.assistiveMml(document);
        }
      };
      return MathJax.startup.defaultPageReady();
    }
  }
};
<|endoftext|>