<commit_before>const { dialog } = nodeRequire('electron').remote;

const readline = nodeRequire('readline');
const fs = nodeRequire('fs');

var rf_data = {};

// 把斜杠换成空白
function handle_one_line_data(anArray) {

    for (var k = 0, length = anArray.length; k < length; k++) {
        if (anArray[k] == '\\') {
            anArray[k] = ''
        }
    }
    return anArray

}

function renderTestcaseNode(filepathlist) {

    $.each(filepathlist, function (key, value) {

        var rftype = '';// case、keyword、setting、variable
        var filetype = 'resource';// 2种类型：suite和resource
        var parent_node_id = value[1].toString();
        var file_data = {};

        var case_name_id_tmp = "";
        var case_id_count = 1;
        var pre_line = '';

        rl = readline.createInterface({
            input: fs.createReadStream(value[0])
        });

        rl.on('line', (line) => {

            if (line == '*** Test Cases ***') {
                rftype = 'testcase';
                filetype = 'suite';
            } else if (line == '*** Keywords ***') {
                rftype = 'keywords';
            } else if (line == '*** Settings ***') {
                rftype = 'settings';
                case_name_id_tmp = 'settings';
                file_data[case_name_id_tmp] = [
                    ['Documentation', '', '', '', ''],
                    ['Suite Setup', '', '', '', ''],
                    ['Suite Teardown', '', '', '', ''],
                    ['Test Setup', '', '', '', ''],
                    ['Test Teardown', '', '', '', ''],
                    ['Test Template', '', '', '', ''],
                    ['Test Timeout', '', '', '', ''],
                    ['Force Tags', '', '', '', ''],
                    ['Default Tags', '', '', '', '']
                ];
            } else if (line == '*** Variables ***') {
                rftype = 'variables';
                case_name_id_tmp = 'variables';
                file_data[case_name_id_tmp] = [];
            } else {

                if (rftype == 'testcase') {
                    if (line[0] != ' ' && line.indexOf('***') != 0 && line) {
                        var case_id = parent_node_id + '0' + case_id_count++;
                        zTree.addNodes(zTree.getNodeByParam("id", parent_node_id), { id: case_id, name: line, isParent: false, iconSkin: "testcase" }, true);
                        case_name_id_tmp = line + '_' + case_id;
                        file_data[case_name_id_tmp] = [];
                    } else {
                        if (case_name_id_tmp) {
                            file_data[case_name_id_tmp].push(handle_one_line_data($.trim(line).split('    ')));
                        }

                    }
                } else if (rftype == 'keywords') {
                    if (line[0] != ' ' && line.indexOf('***') != 0 && line) {
                        var case_id = parent_node_id + '0' + case_id_count++;
                        if (value[2] == '__init__.txt') {
                            zTree.addNodes(zTree.getNodeByParam("id", parent_node_id).getParentNode(), { id: case_id, name: line, isParent: false, iconSkin: "keyword", nocheck: true }, true);
                        } else {
                            zTree.addNodes(zTree.getNodeByParam("id", parent_node_id), { id: case_id, name: line, isParent: false, iconSkin: "keyword", nocheck: true }, true);
                        }
                        case_name_id_tmp = line + '_' + case_id;
                        file_data[case_name_id_tmp] = [];
                    } else {
                        if (case_name_id_tmp) {
                            file_data[case_name_id_tmp].push(handle_one_line_data($.trim(line).split('    ')));
                        }

                    }
                } else if (rftype == 'variables') {

                    if ($.trim(line)) {

                        var variable_line = $.trim(line).split('}');

                        var variable_name = variable_line.shift() + '}';
                        var variable_other = variable_line.join("}");
                        var variable_data = $.trim(variable_other).split('    ');
                        variable_data.splice(0, 0, variable_name);

                        var case_id = parent_node_id + '0' + case_id_count++;

                        if (value[2] == '__init__.txt') {
                            zTree.addNodes(zTree.getNodeByParam("id", parent_node_id).getParentNode(), { id: case_id, name: variable_name, isParent: false, iconSkin: "variable", nocheck: true }, true);
                        } else {
                            zTree.addNodes(zTree.getNodeByParam("id", parent_node_id), { id: case_id, name: variable_name, isParent: false, iconSkin: "variable", nocheck: true }, true);

                        }
                        case_name_id_tmp = 'variables';
                        if (case_name_id_tmp) {
                            file_data[case_name_id_tmp].push(variable_data);
                        }
                    }

                } else if (rftype == 'settings') {

                    if ($.trim(line)) {
                        console.log(rftype, line);
                        var settings_type = $.trim($.trim(line).substring(0, 17));
                        var settings_args = $.trim($.trim(line).substring(18)).split('    ');
                        settings_args.splice(0, 0, settings_type);

                        case_name_id_tmp = 'settings';

                        console.log(settings_type, settings_args);

                        if (case_name_id_tmp) {

                            switch (settings_type) {
                                case 'Documentation':
                                    file_data[case_name_id_tmp][0] = settings_args;
                                    pre_line = 0;
                                    break;
                                case 'Suite Setup':
                                    file_data[case_name_id_tmp][1] = settings_args;
                                    pre_line = 1;
                                    break;
                                case 'Suite Teardown':
                                    file_data[case_name_id_tmp][2] = settings_args;
                                    pre_line = 2;
                                    break;
                                case 'Test Setup':
                                    file_data[case_name_id_tmp][3] = settings_args;
                                    pre_line = 3;
                                    break;
                                case 'Test Teardown':
                                    file_data[case_name_id_tmp][4] = settings_args;
                                    pre_line = 4;
                                    break;
                                case 'Test Template':
                                    file_data[case_name_id_tmp][5] = settings_args;
                                    pre_line = 5;
                                    break;
                                case 'Test Timeout':
                                    file_data[case_name_id_tmp][6] = settings_args;
                                    pre_line = 6;
                                    break;
                                case 'Force Tags':
                                    file_data[case_name_id_tmp][7] = settings_args;
                                    pre_line = 7;
                                    break;
                                case 'Default Tags':
                                    file_data[case_name_id_tmp][8] = settings_args;
                                    pre_line = 8;
                                    break;
                                case '...':
                                    console.log(pre_line, settings_args);
                                    settings_args.shift();
                                    file_data[case_name_id_tmp][pre_line].push.apply(file_data[case_name_id_tmp][pre_line], settings_args);
                                    break;
                                default:
                                    file_data[case_name_id_tmp].push(settings_args);
                                    break;
                            }

                            // file_data[case_name_id_tmp].push(settings_args);
                        }

                    }

                }

            }
        });

        rl.on('close', function () {
            if (filetype == 'resource') {
                var update_node = zTree.getNodeByParam("id", parent_node_id);
                update_node.iconSkin = 'resource';
                zTree.updateNode(update_node);
            }

            console.log(file_data, value[2]);

            // __init__.txt 文件，setting种没有 Test Template、Default Tags、Test Timeout
            if (value[2] == '__init__.txt') {
                file_data['settings'].splice(8, 1);
                file_data['settings'].splice(6, 1);
                file_data['settings'].splice(5, 1);

                var node_id = zTree.getNodeByParam("id", parent_node_id).getParentNode().id
                rf_data[value[2] + "_" + node_id] = file_data;

            } else {

                rf_data[value[2] + "_" + value[1]] = file_data;

            }

            if (filetype == 'resource') {

                if (file_data.hasOwnProperty('settings')) {
                    file_data['settings'].splice(8, 1);
                    file_data['settings'].splice(7, 1);
                    file_data['settings'].splice(6, 1);
                    file_data['settings'].splice(5, 1);
                    file_data['settings'].splice(4, 1);
                    file_data['settings'].splice(3, 1);
                    file_data['settings'].splice(2, 1);
                    file_data['settings'].splice(1, 1);

                }

            }

        });

    });
}

function openDirectory() {

    var directory = dialog.showOpenDialog({ properties: ['openDirectory'] });
    if (directory) {
        var dataArr = get_workspace_data(directory[0]);
        zTree.destroy();
        UITree.init();
        zTree.addNodes(null, dataArr[0])

        renderTestcaseNode(dataArr[1]);

    }

}

<commit_msg>移除__init__节点的显示<commit_after>const { dialog } = nodeRequire('electron').remote;

const readline = nodeRequire('readline');
const fs = nodeRequire('fs');

var rf_data = {};

// 把斜杠换成空白
function handle_one_line_data(anArray) {

    for (var k = 0, length = anArray.length; k < length; k++) {
        if (anArray[k] == '\\') {
            anArray[k] = ''
        }
    }
    return anArray

}

function renderTestcaseNode(filepathlist) {

    $.each(filepathlist, function (key, value) {

        var rftype = '';// case、keyword、setting、variable
        var filetype = 'resource';// 2种类型：suite和resource
        var parent_node_id = value[1].toString();
        var file_data = {};

        var case_name_id_tmp = "";
        var case_id_count = 1;
        var pre_line = '';

        rl = readline.createInterface({
            input: fs.createReadStream(value[0])
        });

        rl.on('line', (line) => {

            if (line == '*** Test Cases ***') {
                rftype = 'testcase';
                filetype = 'suite';
            } else if (line == '*** Keywords ***') {
                rftype = 'keywords';
            } else if (line == '*** Settings ***') {
                rftype = 'settings';
                case_name_id_tmp = 'settings';
                file_data[case_name_id_tmp] = [
                    ['Documentation', '', '', '', ''],
                    ['Suite Setup', '', '', '', ''],
                    ['Suite Teardown', '', '', '', ''],
                    ['Test Setup', '', '', '', ''],
                    ['Test Teardown', '', '', '', ''],
                    ['Test Template', '', '', '', ''],
                    ['Test Timeout', '', '', '', ''],
                    ['Force Tags', '', '', '', ''],
                    ['Default Tags', '', '', '', '']
                ];
            } else if (line == '*** Variables ***') {
                rftype = 'variables';
                case_name_id_tmp = 'variables';
                file_data[case_name_id_tmp] = [];
            } else {

                if (rftype == 'testcase') {
                    if (line[0] != ' ' && line.indexOf('***') != 0 && line) {
                        var case_id = parent_node_id + '0' + case_id_count++;
                        zTree.addNodes(zTree.getNodeByParam("id", parent_node_id), { id: case_id, name: line, isParent: false, iconSkin: "testcase" }, true);
                        case_name_id_tmp = line + '_' + case_id;
                        file_data[case_name_id_tmp] = [];
                    } else {
                        if (case_name_id_tmp) {
                            file_data[case_name_id_tmp].push(handle_one_line_data($.trim(line).split('    ')));
                        }

                    }
                } else if (rftype == 'keywords') {
                    if (line[0] != ' ' && line.indexOf('***') != 0 && line) {
                        var case_id = parent_node_id + '0' + case_id_count++;
                        if (value[2] == '__init__.txt') {
                            zTree.addNodes(zTree.getNodeByParam("id", parent_node_id).getParentNode(), { id: case_id, name: line, isParent: false, iconSkin: "keyword", nocheck: true }, true);
                        } else {
                            zTree.addNodes(zTree.getNodeByParam("id", parent_node_id), { id: case_id, name: line, isParent: false, iconSkin: "keyword", nocheck: true }, true);
                        }
                        case_name_id_tmp = line + '_' + case_id;
                        file_data[case_name_id_tmp] = [];
                    } else {
                        if (case_name_id_tmp) {
                            file_data[case_name_id_tmp].push(handle_one_line_data($.trim(line).split('    ')));
                        }

                    }
                } else if (rftype == 'variables') {

                    if ($.trim(line)) {

                        var variable_line = $.trim(line).split('}');

                        var variable_name = variable_line.shift() + '}';
                        var variable_other = variable_line.join("}");
                        var variable_data = $.trim(variable_other).split('    ');
                        variable_data.splice(0, 0, variable_name);

                        var case_id = parent_node_id + '0' + case_id_count++;

                        if (value[2] == '__init__.txt') {
                            zTree.addNodes(zTree.getNodeByParam("id", parent_node_id).getParentNode(), { id: case_id, name: variable_name, isParent: false, iconSkin: "variable", nocheck: true }, true);
                        } else {
                            zTree.addNodes(zTree.getNodeByParam("id", parent_node_id), { id: case_id, name: variable_name, isParent: false, iconSkin: "variable", nocheck: true }, true);

                        }
                        case_name_id_tmp = 'variables';
                        if (case_name_id_tmp) {
                            file_data[case_name_id_tmp].push(variable_data);
                        }
                    }

                } else if (rftype == 'settings') {

                    if ($.trim(line)) {
                        console.log(rftype, line);
                        var settings_type = $.trim($.trim(line).substring(0, 17));
                        var settings_args = $.trim($.trim(line).substring(18)).split('    ');
                        settings_args.splice(0, 0, settings_type);

                        case_name_id_tmp = 'settings';

                        console.log(settings_type, settings_args);

                        if (case_name_id_tmp) {

                            switch (settings_type) {
                                case 'Documentation':
                                    file_data[case_name_id_tmp][0] = settings_args;
                                    pre_line = 0;
                                    break;
                                case 'Suite Setup':
                                    file_data[case_name_id_tmp][1] = settings_args;
                                    pre_line = 1;
                                    break;
                                case 'Suite Teardown':
                                    file_data[case_name_id_tmp][2] = settings_args;
                                    pre_line = 2;
                                    break;
                                case 'Test Setup':
                                    file_data[case_name_id_tmp][3] = settings_args;
                                    pre_line = 3;
                                    break;
                                case 'Test Teardown':
                                    file_data[case_name_id_tmp][4] = settings_args;
                                    pre_line = 4;
                                    break;
                                case 'Test Template':
                                    file_data[case_name_id_tmp][5] = settings_args;
                                    pre_line = 5;
                                    break;
                                case 'Test Timeout':
                                    file_data[case_name_id_tmp][6] = settings_args;
                                    pre_line = 6;
                                    break;
                                case 'Force Tags':
                                    file_data[case_name_id_tmp][7] = settings_args;
                                    pre_line = 7;
                                    break;
                                case 'Default Tags':
                                    file_data[case_name_id_tmp][8] = settings_args;
                                    pre_line = 8;
                                    break;
                                case '...':
                                    console.log(pre_line, settings_args);
                                    settings_args.shift();
                                    file_data[case_name_id_tmp][pre_line].push.apply(file_data[case_name_id_tmp][pre_line], settings_args);
                                    break;
                                default:
                                    file_data[case_name_id_tmp].push(settings_args);
                                    break;
                            }

                            // file_data[case_name_id_tmp].push(settings_args);
                        }

                    }

                }

            }
        });

        rl.on('close', function () {
            if (filetype == 'resource') {
                var update_node = zTree.getNodeByParam("id", parent_node_id);
                update_node.iconSkin = 'resource';
                zTree.updateNode(update_node);
            }

            console.log(file_data, value[2]);

            // __init__.txt 文件，setting种没有 Test Template、Default Tags、Test Timeout
            if (value[2] == '__init__.txt') {
                file_data['settings'].splice(8, 1);
                file_data['settings'].splice(6, 1);
                file_data['settings'].splice(5, 1);

                var node_id = zTree.getNodeByParam("id", parent_node_id).getParentNode().id
                rf_data[value[2] + "_" + node_id] = file_data;

                // 移除__init__.txt 文件的节点
                zTree.removeNode(zTree.getNodeByParam("id", parent_node_id));

            } else {

                rf_data[value[2] + "_" + value[1]] = file_data;

            }

            // 对resource文件，settings中只有一格document，其他去掉
            if (filetype == 'resource' && value[2] != '__init__.txt') {

                if (file_data.hasOwnProperty('settings')) {
                    file_data['settings'].splice(8, 1);
                    file_data['settings'].splice(7, 1);
                    file_data['settings'].splice(6, 1);
                    file_data['settings'].splice(5, 1);
                    file_data['settings'].splice(4, 1);
                    file_data['settings'].splice(3, 1);
                    file_data['settings'].splice(2, 1);
                    file_data['settings'].splice(1, 1);

                }

            }

            // 对没有settings的文件，添加settings

            if (filetype == 'resource' && !file_data.hasOwnProperty('settings')) {
                file_data['settings'] = [
                    ['Documentation', '', '', '', '']
                ];

            } else if (filetype == 'suite' && !file_data.hasOwnProperty('settings')) {

                file_data['settings'] = [
                    ['Documentation', '', '', '', ''],
                    ['Suite Setup', '', '', '', ''],
                    ['Suite Teardown', '', '', '', ''],
                    ['Test Setup', '', '', '', ''],
                    ['Test Teardown', '', '', '', ''],
                    ['Test Template', '', '', '', ''],
                    ['Test Timeout', '', '', '', ''],
                    ['Force Tags', '', '', '', ''],
                    ['Default Tags', '', '', '', '']
                ];

            } else if (value[2] == '__init__.txt' && !file_data.hasOwnProperty('settings')) {
                file_data['settings'] = [
                    ['Documentation', '', '', '', ''],
                    ['Suite Setup', '', '', '', ''],
                    ['Suite Teardown', '', '', '', ''],
                    ['Test Setup', '', '', '', ''],
                    ['Test Teardown', '', '', '', ''],
                    ['Force Tags', '', '', '', '']
                ];
            }

        });

    });
}

function openDirectory() {

    var directory = dialog.showOpenDialog({ properties: ['openDirectory'] });
    if (directory) {
        var dataArr = get_workspace_data(directory[0]);
        zTree.destroy();
        UITree.init();
        zTree.addNodes(null, dataArr[0])

        renderTestcaseNode(dataArr[1]);

    }

}

<|endoftext|>


<commit_before>import css from './style.css';
document.write(require("./content.js"));
var cytoscape = require('cytoscape');
console.log(cytoscape);
$("#cyDiv").height(300);
var network = require("./cellphoneModel.json")
var vizmap  = require("./networkStyle.json")
//var cy = cytoscape({container: $("#cyDiv"), elements:{nodes:[{data:{id:'a'}}], edges:[{data:{source:'a', target:'a'}}]}})
var cy = cytoscape({container: $("#cyDiv"), elements: network.elements, style: vizmap});
window.cy = cy;

<commit_msg>preset layout used<commit_after>import css from './style.css';
document.write(require("./content.js"));
var cytoscape = require('cytoscape');
console.log(cytoscape);
$("#cyDiv").height(300);
var network = require("./cellphoneModel.json")
var vizmap  = require("./networkStyle.json")
//var cy = cytoscape({container: $("#cyDiv"), elements:{nodes:[{data:{id:'a'}}], edges:[{data:{source:'a', target:'a'}}]}})
var cy = cytoscape({container: $("#cyDiv"),
                    elements: network.elements,
                    layout: {name: "preset", fit: true},
                    style: vizmap});
window.cy = cy;

<|endoftext|>


<commit_before>// @flow

import phantomjs from 'phantomjs-prebuilt';
import {spawn} from 'child_process';
import os from 'os';
import path from 'path';
import Linerstream from 'linerstream';
import winston from 'winston';
import EventEmitter from 'events';
import Page from './page';
import Command from './command';
import OutObject from './out_object';

type Response = {pageId: string}

const defaultLogLevel = process.env.DEBUG === 'true' ? 'debug' : 'info';

/**
 * Creates a logger using winston
 */
function createLogger() {
    return new winston.Logger({
        transports: [
            new winston.transports.Console({
                level: defaultLogLevel,
                colorize: true,
            }),
        ],
    });
}

const defaultLogger = createLogger();

/**
 * A phantom instance that communicates with phantomjs
 */
export default class Phantom {
    logger: Logger;
    commands: Map<string, Command>;
    events: Map<string, EventEmitter>;
    heartBeatId: number;
    process: child_process$ChildProcess;

    /**
     * Creates a new instance of Phantom
     *
     * @param args command args to pass to phantom process
     * @param [phantomPath] path to phantomjs executable
     * @param [logger] object containing functions used for logging
     * @param [logLevel] log level to apply on the logger (if unset or default)
     */
    constructor(args?: string[] = [],
        {phantomPath = phantomjs.path, logger = defaultLogger, logLevel = defaultLogLevel}: Config =
            {
                phantomPath: phantomjs.path,
                logger: defaultLogger,
                logLevel: defaultLogLevel,
            }) {
        if (!Array.isArray(args)) {
            throw new Error('Unexpected type of parameters. Expecting args to be array.');
        }

        if (typeof phantomPath !== 'string') {
            throw new Error('PhantomJS binary was not found. ' +
                'This generally means something went wrong when installing phantomjs-prebuilt. Exiting.');
        }

        if (typeof logger !== 'object') {
            throw new Error('logger must be ba valid object.');
        }

        logger.debug = logger.debug || (() => undefined);
        logger.info = logger.info || (() => undefined);
        logger.warn = logger.warn || (() => undefined);
        logger.error = logger.error || (() => undefined);

        this.logger = logger;

        if (logLevel !== defaultLogLevel) {
            this.logger = createLogger();
            this.logger.transports.console.level = logLevel;
        }

        const pathToShim = path.normalize(__dirname + '/shim/index.js');
        this.logger.debug(`Starting ${phantomPath} ${args.concat([pathToShim]).join(' ')}`);

        this.process = spawn(phantomPath, args.concat([pathToShim]));
        this.process.stdin.setDefaultEncoding('utf-8');

        this.commands = new Map();
        this.events = new Map();

        this.process.stdout.pipe(new Linerstream()).on('data', data => {
            const message = data.toString('utf8');
            if (message[0] === '>') {
                //Server end has finished NOOP,
                //lets allow NOOP again..
                if (message === '>NOOP') {
                  this.doing_NOOP = false;
                  return;
                }
                const json = message.substr(1);
                this.logger.debug('Parsing: %s', json);

                const parsedJson = JSON.parse(json);
                const command = this.commands.get(parsedJson.id);

                if (command != null) {
                    const deferred = command.deferred;

                    if (deferred != null) {
                        if (parsedJson.error === undefined) {
                            deferred.resolve(parsedJson.response);
                        } else {
                            deferred.reject(new Error(parsedJson.error));
                        }
                    } else {
                        this.logger.error('deferred object not found for command.id: ' + parsedJson.id);
                    }

                    this.commands.delete(command.id);
                } else {
                    this.logger.error('command not found for command.id: ' + parsedJson.id);
                }

            } else if (message.indexOf('<event>') === 0) {
                const json = message.substr(7);
                this.logger.debug('Parsing: %s', json);
                const event = JSON.parse(json);

                const emitter = this.events.get(event.target);
                if (emitter) {
                    emitter.emit.apply(emitter, [event.type].concat(event.args));
                }
            } else {
                this.logger.info(message);
            }
        });

        this.process.stderr.on('data', data => this.logger.error(data.toString('utf8')));
        this.process.on('exit', code => {
            this.logger.debug(`Child exited with code {${code}}`);
            this._rejectAllCommands(`Phantom process stopped with exit code ${code}`);
        });
        this.process.on('error', error => {
            this.logger.error(`Could not spawn [${phantomPath}] executable. ` +
                'Please make sure phantomjs is installed correctly.');
            this.logger.error(error);
            this.kill(`Process got an error: ${error}`);
            process.exit(1);
        });

        this.process.stdin.on('error', (e) => {
            this.logger.debug(`Child process received error ${e}, sending kill signal`);
            this.kill(`Error reading from stdin: ${e}`);
        });

        this.process.stdout.on('error', (e) => {
            this.logger.debug(`Child process received error ${e}, sending kill signal`);
            this.kill(`Error reading from stdout: ${e}`);
        });

        this.heartBeatId = setInterval(this._heartBeat.bind(this), 100);
    }

    /**
     * Returns a value in the global space of phantom process
     * @returns {Promise}
     */
    windowProperty(): Promise<mixed> {
        return this.execute('phantom', 'windowProperty', [].slice.call(arguments));
    }

    /**
     * Returns a new instance of Promise which resolves to a {@link Page}.
     * @returns {Promise.<Page>}
     */
    createPage(): Promise<Page> {
        const logger = this.logger;
        return this.execute('phantom', 'createPage').then((response: Response) => {
            let page = new Page(this, response.pageId);
            if (typeof Proxy === 'function') {
                page = new Proxy(page, {
                    set: function(target, prop) {
                        logger.warn(`Using page.${prop} = ...; is not supported. Use page.property('${prop}', ...) ` +
                            'instead. See the README file for more examples of page#property.');
                        return false;
                    },
                });
            }
            return page;
        });
    }

    /**
     * Creates a special object that can be used for returning data back from PhantomJS
     * @returns {OutObject}
     */
    createOutObject(): OutObject {
        return new OutObject(this);
    }

    /**
     * Used for creating a callback in phantomjs for content header and footer
     * @param obj
     */
    callback(obj: Function): {transform: true, target: Function, method: 'callback', parent: 'phantom'} {
        return {transform: true, target: obj, method: 'callback', parent: 'phantom'};
    }

    /**
     * Executes a command object
     * @param command the command to run
     * @returns {Promise}
     */
    executeCommand(command: Command): Promise<Response> {
        this.commands.set(command.id, command);

        let json = JSON.stringify(command, (key, val) => {
            if (key[0] === '_') {
                return undefined;
            } else if (typeof val === 'function') {
                if (!val.hasOwnProperty('prototype')) {
                    this.logger.warn('Arrow functions such as () => {} are not supported in PhantomJS. ' +
                        'Please use function(){} or compile to ES5.');
                    throw new Error('Arrow functions such as () => {} are not supported in PhantomJS.');
                }
                return val.toString();
            }
            return val;
        });

        let promise = new Promise((res, rej) => {
            command.deferred = {resolve: res, reject: rej};
        });

        this.logger.debug('Sending: %s', json);

        this.process.stdin.write(json + os.EOL, 'utf8');

        return promise;
    }

    /**
     * Executes a command
     *
     * @param target target object to execute against
     * @param name the name of the method execute
     * @param args an array of args to pass to the method
     * @returns {Promise}
     */
    execute(target: string, name: string, args: mixed[] = []): Promise<Response> {
        return this.executeCommand(new Command(null, target, name, args));
    }

    /**
     * Adds an event listener to a target object (currently only works on pages)
     *
     * @param event the event type
     * @param target target object to execute against
     * @param runOnPhantom would the callback run in phantomjs or not
     * @param callback the event callback
     * @param args an array of args to pass to the callback
     */
    on(event: string, target: string, runOnPhantom: boolean, callback: Function, args: mixed[] = []) {
        const eventDescriptor: {type: string, args?: mixed[], event?: Function} = {type: event};

        if (runOnPhantom) {
            eventDescriptor.event = callback;
            eventDescriptor.args = args;
        } else {
            const emitter = this.getEmitterForTarget(target);
            emitter.on(event, function() {
                let params = [].slice.call(arguments).concat(args);
                return callback.apply(null, params);
            });
        }
        return this.execute(target, 'addEvent', [eventDescriptor]);
    }

    /**
     * Removes an event from a target object
     *
     * @param event
     * @param target
     */
    off(event: string, target: string): Promise<mixed> {
        const emitter = this.getEmitterForTarget(target);
        emitter.removeAllListeners(event);
        return this.execute(target, 'removeEvent', [{type: event}]);
    }

    getEmitterForTarget(target: string): EventEmitter {
        let emitter = this.events.get(target);

        if (emitter == null) {
            emitter = new EventEmitter();
            this.events.set(target, emitter);
        }

        return emitter;
    }

    /**
     * Cleans up and end the phantom process
     */
    exit(): void {
        clearInterval(this.heartBeatId);
        this.execute('phantom', 'invokeMethod', ['exit']);
    }

    /**
     * Clean up and force kill this process
     */
    kill(errmsg: string = 'Phantom process was killed'): void {
        this._rejectAllCommands(errmsg);
        this.process.kill('SIGKILL');
    }

    _heartBeat(): void {
      if (this.commands.size === 0 && !this.doing_NOOP) {
        this.doing_NOOP = true;
        this.process.stdin.write('NOOP' + _os2.default.EOL, 'utf8');
        //this.execute('phantom', 'noop');
      }
    }

    /**
     * rejects all commands in this.commands
     */
    _rejectAllCommands(errmsg: string = 'Phantom exited prematurely'): void {
        // prevent heartbeat from preventing this from terminating
        clearInterval(this.heartBeatId);
        for (const command of this.commands.values()) {
            if (command.deferred != null) {
                command.deferred.reject(new Error(errmsg));
            }
        }
    }
}
<commit_msg>fix eslint errors<commit_after>// @flow

import phantomjs from 'phantomjs-prebuilt';
import {spawn} from 'child_process';
import os from 'os';
import path from 'path';
import Linerstream from 'linerstream';
import winston from 'winston';
import EventEmitter from 'events';
import Page from './page';
import Command from './command';
import OutObject from './out_object';

type Response = {pageId: string}

const defaultLogLevel = process.env.DEBUG === 'true' ? 'debug' : 'info';

/**
 * Creates a logger using winston
 */
function createLogger() {
    return new winston.Logger({
        transports: [
            new winston.transports.Console({
                level: defaultLogLevel,
                colorize: true,
            }),
        ],
    });
}

const defaultLogger = createLogger();

/**
 * A phantom instance that communicates with phantomjs
 */
export default class Phantom {
    logger: Logger;
    commands: Map<string, Command>;
    events: Map<string, EventEmitter>;
    heartBeatId: number;
    process: child_process$ChildProcess;

    /**
     * Creates a new instance of Phantom
     *
     * @param args command args to pass to phantom process
     * @param [phantomPath] path to phantomjs executable
     * @param [logger] object containing functions used for logging
     * @param [logLevel] log level to apply on the logger (if unset or default)
     */
    constructor(args?: string[] = [],
        {phantomPath = phantomjs.path, logger = defaultLogger, logLevel = defaultLogLevel}: Config =
            {
                phantomPath: phantomjs.path,
                logger: defaultLogger,
                logLevel: defaultLogLevel,
            }) {
        if (!Array.isArray(args)) {
            throw new Error('Unexpected type of parameters. Expecting args to be array.');
        }

        if (typeof phantomPath !== 'string') {
            throw new Error('PhantomJS binary was not found. ' +
                'This generally means something went wrong when installing phantomjs-prebuilt. Exiting.');
        }

        if (typeof logger !== 'object') {
            throw new Error('logger must be ba valid object.');
        }

        logger.debug = logger.debug || (() => undefined);
        logger.info = logger.info || (() => undefined);
        logger.warn = logger.warn || (() => undefined);
        logger.error = logger.error || (() => undefined);

        this.logger = logger;

        if (logLevel !== defaultLogLevel) {
            this.logger = createLogger();
            this.logger.transports.console.level = logLevel;
        }

        const pathToShim = path.normalize(__dirname + '/shim/index.js');
        this.logger.debug(`Starting ${phantomPath} ${args.concat([pathToShim]).join(' ')}`);

        this.process = spawn(phantomPath, args.concat([pathToShim]));
        this.process.stdin.setDefaultEncoding('utf-8');

        this.commands = new Map();
        this.events = new Map();

        this.process.stdout.pipe(new Linerstream()).on('data', data => {
            const message = data.toString('utf8');
            if (message[0] === '>') {
                //Server end has finished NOOP,
                //lets allow NOOP again..
                if (message === '>NOOP') {
                  this.doing_NOOP = false;
                  return;
                }
                const json = message.substr(1);
                this.logger.debug('Parsing: %s', json);

                const parsedJson = JSON.parse(json);
                const command = this.commands.get(parsedJson.id);

                if (command != null) {
                    const deferred = command.deferred;

                    if (deferred != null) {
                        if (parsedJson.error === undefined) {
                            deferred.resolve(parsedJson.response);
                        } else {
                            deferred.reject(new Error(parsedJson.error));
                        }
                    } else {
                        this.logger.error('deferred object not found for command.id: ' + parsedJson.id);
                    }

                    this.commands.delete(command.id);
                } else {
                    this.logger.error('command not found for command.id: ' + parsedJson.id);
                }

            } else if (message.indexOf('<event>') === 0) {
                const json = message.substr(7);
                this.logger.debug('Parsing: %s', json);
                const event = JSON.parse(json);

                const emitter = this.events.get(event.target);
                if (emitter) {
                    emitter.emit.apply(emitter, [event.type].concat(event.args));
                }
            } else {
                this.logger.info(message);
            }
        });

        this.process.stderr.on('data', data => this.logger.error(data.toString('utf8')));
        this.process.on('exit', code => {
            this.logger.debug(`Child exited with code {${code}}`);
            this._rejectAllCommands(`Phantom process stopped with exit code ${code}`);
        });
        this.process.on('error', error => {
            this.logger.error(`Could not spawn [${phantomPath}] executable. ` +
                'Please make sure phantomjs is installed correctly.');
            this.logger.error(error);
            this.kill(`Process got an error: ${error}`);
            process.exit(1);
        });

        this.process.stdin.on('error', (e) => {
            this.logger.debug(`Child process received error ${e}, sending kill signal`);
            this.kill(`Error reading from stdin: ${e}`);
        });

        this.process.stdout.on('error', (e) => {
            this.logger.debug(`Child process received error ${e}, sending kill signal`);
            this.kill(`Error reading from stdout: ${e}`);
        });

        this.heartBeatId = setInterval(this._heartBeat.bind(this), 100);
    }

    /**
     * Returns a value in the global space of phantom process
     * @returns {Promise}
     */
    windowProperty(): Promise<mixed> {
        return this.execute('phantom', 'windowProperty', [].slice.call(arguments));
    }

    /**
     * Returns a new instance of Promise which resolves to a {@link Page}.
     * @returns {Promise.<Page>}
     */
    createPage(): Promise<Page> {
        const logger = this.logger;
        return this.execute('phantom', 'createPage').then((response: Response) => {
            let page = new Page(this, response.pageId);
            if (typeof Proxy === 'function') {
                page = new Proxy(page, {
                    set: function(target, prop) {
                        logger.warn(`Using page.${prop} = ...; is not supported. Use page.property('${prop}', ...) ` +
                            'instead. See the README file for more examples of page#property.');
                        return false;
                    },
                });
            }
            return page;
        });
    }

    /**
     * Creates a special object that can be used for returning data back from PhantomJS
     * @returns {OutObject}
     */
    createOutObject(): OutObject {
        return new OutObject(this);
    }

    /**
     * Used for creating a callback in phantomjs for content header and footer
     * @param obj
     */
    callback(obj: Function): {transform: true, target: Function, method: 'callback', parent: 'phantom'} {
        return {transform: true, target: obj, method: 'callback', parent: 'phantom'};
    }

    /**
     * Executes a command object
     * @param command the command to run
     * @returns {Promise}
     */
    executeCommand(command: Command): Promise<Response> {
        this.commands.set(command.id, command);

        let json = JSON.stringify(command, (key, val) => {
            if (key[0] === '_') {
                return undefined;
            } else if (typeof val === 'function') {
                if (!val.hasOwnProperty('prototype')) {
                    this.logger.warn('Arrow functions such as () => {} are not supported in PhantomJS. ' +
                        'Please use function(){} or compile to ES5.');
                    throw new Error('Arrow functions such as () => {} are not supported in PhantomJS.');
                }
                return val.toString();
            }
            return val;
        });

        let promise = new Promise((res, rej) => {
            command.deferred = {resolve: res, reject: rej};
        });

        this.logger.debug('Sending: %s', json);

        this.process.stdin.write(json + os.EOL, 'utf8');

        return promise;
    }

    /**
     * Executes a command
     *
     * @param target target object to execute against
     * @param name the name of the method execute
     * @param args an array of args to pass to the method
     * @returns {Promise}
     */
    execute(target: string, name: string, args: mixed[] = []): Promise<Response> {
        return this.executeCommand(new Command(null, target, name, args));
    }

    /**
     * Adds an event listener to a target object (currently only works on pages)
     *
     * @param event the event type
     * @param target target object to execute against
     * @param runOnPhantom would the callback run in phantomjs or not
     * @param callback the event callback
     * @param args an array of args to pass to the callback
     */
    on(event: string, target: string, runOnPhantom: boolean, callback: Function, args: mixed[] = []) {
        const eventDescriptor: {type: string, args?: mixed[], event?: Function} = {type: event};

        if (runOnPhantom) {
            eventDescriptor.event = callback;
            eventDescriptor.args = args;
        } else {
            const emitter = this.getEmitterForTarget(target);
            emitter.on(event, function() {
                let params = [].slice.call(arguments).concat(args);
                return callback.apply(null, params);
            });
        }
        return this.execute(target, 'addEvent', [eventDescriptor]);
    }

    /**
     * Removes an event from a target object
     *
     * @param event
     * @param target
     */
    off(event: string, target: string): Promise<mixed> {
        const emitter = this.getEmitterForTarget(target);
        emitter.removeAllListeners(event);
        return this.execute(target, 'removeEvent', [{type: event}]);
    }

    getEmitterForTarget(target: string): EventEmitter {
        let emitter = this.events.get(target);

        if (emitter == null) {
            emitter = new EventEmitter();
            this.events.set(target, emitter);
        }

        return emitter;
    }

    /**
     * Cleans up and end the phantom process
     */
    exit(): void {
        clearInterval(this.heartBeatId);
        this.execute('phantom', 'invokeMethod', ['exit']);
    }

    /**
     * Clean up and force kill this process
     */
    kill(errmsg: string = 'Phantom process was killed'): void {
        this._rejectAllCommands(errmsg);
        this.process.kill('SIGKILL');
    }

    _heartBeat(): void {
      if (this.commands.size === 0 && !this.doing_NOOP) {
        this.doing_NOOP = true;
        this.process.stdin.write('NOOP' + _os2.default.EOL, 'utf8');
        //this.execute('phantom', 'noop');
      }
    }

    /**
     * rejects all commands in this.commands
     */
    _rejectAllCommands(errmsg: string = 'Phantom exited prematurely'): void {
        // prevent heartbeat from preventing this from terminating
        clearInterval(this.heartBeatId);
        for (const command of this.commands.values()) {
            if (command.deferred != null) {
                command.deferred.reject(new Error(errmsg));
            }
        }
    }
}
<|endoftext|>


<commit_before>var util = require('./util.js');

module.exports = function(info){
  if(window.yhsdDebug){
    var sLog = '';
    util.forEach(info, function(value, key){
      var sEachLog = key + ':' + value + '  ';
      sLog = sLog + sEachLog;
    });
    console.log(sLog);
  }
};<commit_msg>add log option<commit_after>var util = require('./util.js');

module.exports = function(info, option){
  if(window.yhsdDebug){
    var sLog = '';
    util.forEach(info, function(value, key){
      var sEachLog = key + ':' + value + '  ';
      sLog = sLog + sEachLog;
    });
    if(console && console.log){
    	if(option){
    		console.log('%c' + sLog, option);
    	}else{
    		console.log(sLog);
    	}
    }
  }
};<|endoftext|>


<commit_before>// ==UserScript==
// @id             cc98_id_alias
// @name           cc98 id alias
// @version        0.3.1
// @namespace      soda@cc98.org
// @author         soda <sodazju@gmail.com>
// @description    给98增加类新浪微博的ID备注功能
// @include        http://www.cc98.org/dispbbs.asp*
// @require        http://libs.baidu.com/jquery/2.0.3/jquery.min.js
// @run-at         document-end
// ==/UserScript==

// cc98 JavaScript SDK，已去掉不需要的部分
// Chrome 没有sendAsBinary函数，这里是一个实现
if (!XMLHttpRequest.prototype.sendAsBinary) {
    XMLHttpRequest.prototype.sendAsBinary = function(datastr) {
        function byteValue(x) {
            return x.charCodeAt(0) & 0xff;
        }
        var ords = Array.prototype.map.call(datastr, byteValue);
        var ui8a = new Uint8Array(ords);
        this.send(ui8a);
    };
}

// 辅助函数
// parseQS, toQS, parseURL, unescapeHTML, ajax, addStyles
var _lib = {

    // parse the url get parameters
    parseQS: function(url) {
        url = url.toLowerCase().split('#')[0];  // remove the hash part
        var t = url.indexOf('?');
        var params;

        var hash = {};
        if (t >= 0) {
            params = url.substring(t+1).split('&');
        } else {    // plain query string without '?' (e.g. in cookies)
            params = url.split('&');
        }
        for (var i = 0; i < params.length; ++i) {
            var val = params[i].split('=');
            hash[decodeURIComponent(val[0])] = decodeURIComponent(val[1]);
        }
        return hash;
    },

    toQS: function(obj) {
        var ret = [];
        for (var key in obj) {
            if (obj.hasOwnProperty(key)) {
                if ('' === obj[key]) { continue; }
                ret.push(encodeURIComponent(key) + '=' + encodeURIComponent(obj[key]));
            }
        }
        return ret.join('&');
    },

    parseURL: function(url) {
        // from JavaScript: The Good Parts
        var parse_url = /^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;
        var arr = parse_url.exec(url);
        var result = {};
        result['url'] = arr[0];
        result['scheme'] = arr[1];
        result['slash'] = arr[2];
        result['host'] = arr[3];
        result['port'] = arr[4];
        result['path'] = arr[5];
        result['query'] = arr[6];
        result['hash'] = arr[7];
        return result;
    },

    // 将转义后的html转回来
    unescapeHTML: function(input) {
        var e = document.createElement('div');
        e.innerHTML = input;
        return e.childNodes.length === 0 ? '' : e.childNodes[0].nodeValue;
    },

    ajax: function(opts) {
        opts = {
            type: opts.type || 'GET',
            url: opts.url || '',
            data: opts.data || null,
            contentType: opts.contentType || 'application/x-www-form-urlencoded; charset=UTF-8',
            success: opts.success || function() {},
            async: opts.async || (opts.async === undefined)
        };

        var xhr = new XMLHttpRequest();

        xhr.open(opts.type, opts.url, opts.async);
        xhr.setRequestHeader('Content-type', opts.contentType);
        xhr.onreadystatechange = function() {
            if (xhr.readyState === 4 && xhr.status === 200) {
                opts.success(xhr.responseText);
            }
        };
        if (opts.contentType === 'application/x-www-form-urlencoded; charset=UTF-8') {
            xhr.send(_lib.toQS(opts.data));
        } else {
            xhr.sendAsBinary(opts.data);
        }
    },

    // 添加CSS
    addStyles: function(css) {
        var head = document.getElementsByTagName('head')[0];
        var style = document.createElement('style');

        style.setAttribute('type', 'text/css');
        style.innerHTML = css;
        head.appendChild(style);
    }
};

// 98相关的函数接口
// getPostContent, parseTopicPage, postCount, pageCount, formatURL, currentPage
var _cc98 = function() {

    var that = {};

    // 各种常量
    var POST_COUNT_RE = /本主题贴数\s*(\d+)<\/b>/ig;

    // 以下三个没有考虑被删除的帖子，因为在当前页解析的时候DisplayDel()和正常的发帖时间之类的会一起出现，导致匹配会乱掉
    // 因此引起的发米机发米楼层可能不精确的问题也没办法了……
    var NAME_RE = /(?:name="\d+">| middle;"> )\s*([^<]+)<\/b><\/span>/g;
    var ANNOUNCEID_RE = //g; // 注意网页上之类的标签是作为#1的anchor出现的，所以此处限定至少要两个数字
    var POST_TIME_RE = /<\/a>\s*([^AP]*[AP]M)\s*<\/td>/g;

    // 获取页面中的用户列表，回帖时间回帖ID
    // @return {Array}  每个数组元素都有username, annouceid, posttime三个属性
    that.parseTopicPage = function(htmlText) {
        if (!htmlText) { htmlText = document.body.innerHTML; }
        var postList = [];

        var nameArr = htmlText.match(NAME_RE);
        nameArr.forEach(function(name) {
            var post = {};
            post['username'] = name.replace(NAME_RE, '$1');
            postList.push(post);
        });

        var idArr = htmlText.match(ANNOUNCEID_RE);
        // 考虑到心灵没有announceid，所以idArr可能为空
        if (idArr) {
            idArr.forEach(function(id, index) {
                postList[index]['announceid'] = id.replace(ANNOUNCEID_RE, '$1');
            });
        }

        var timeArr = htmlText.match(POST_TIME_RE);
        if (timeArr) {
            timeArr.forEach(function(t, index) {
                postList[index]['posttime'] = t.replace(POST_TIME_RE, '$1');
            });
        }

        return postList;
    };

    that.postCount = function(htmlText) {
        if (!htmlText) { htmlText = document.body.innerHTML; }
        return parseInt(htmlText.match(POST_COUNT_RE)[0].replace(POST_COUNT_RE, '$1'), 10);
    };

    that.pageCount = function(htmlText) {
        return Math.ceil(_cc98.postCount(htmlText) / 10);
    };

    // 格式化网址，去除无用的参数并转为相对链接
    // @param {string}  url 要格式化的网址
    // @param {boolean} maxPageFix 是否修正url中star参数的值，使其不超过当前最后页的实际值
    that.formatURL = function(url, maxPageFix) {
        var urlObj = _lib.parseURL(url);

        // 不在www.cc98.org域名下
        if (urlObj['host'] !== 'www.cc98.org') {
            return url;
        }

        // http://www.cc98.org/
        if (!urlObj['path']) {
            return '/';
        }

        var params = _lib.parseQS(urlObj['query']);
        var hash = urlObj['hash'] ? ('#' + urlObj['hash']) : '';

        // 不是dispbbs.asp开头的链接，只去掉空的get参数，转为相对链接，不做其他处理
        if (urlObj['path'] === 'dispbbs,asp') {
            return '/' + urlObj['path'] + '?' + _lib.toQS(params) + hash;
        }

        // 如果不是在追踪页面，就去掉replyid
        if (!params['trace']) {
            params['replyid'] = '';
        }
        params['page'] = '';    // 去掉page

        //
        if (params['star'] && maxPageFix && parseInt(params['star'], 10) > _cc98.pageCount()) {
            params['star'] = _cc98.pageCount();
        }

        params['star'] = (params['star'] && params['star'] !== '1') ? params['star'] : '';    // star=1时去掉
        return '/' + urlObj['path'] + '?' + _lib.toQS(params) + hash;
    };

    that.currentPage = function() {
        return parseInt(/<span title="跳转到第\s*(\d+)\s*页/ig.exec(document.body.innerHTML)[1], 10);
    };

    return that;
}();

$(function() {
var Aliases = function() {
    var that = {};
    var dict = JSON.parse(localStorage.getItem('aliases')) || {};

    var flush = function() {
        localStorage.setItem('aliases', JSON.stringify(dict));
    };
    that.flush = flush;

    that.get = function(name) {
        return dict[name];
    };

    that.set = function(name, alias) {
        dict[name] = alias;
        flush();
    };

    that.remove = function(name) {
        delete dict[name];
        flush();
    };

    return that;
};
var aliases = Aliases();

function today() {
    var d = new Date();
    // 考虑到98服务器时间有延迟，所以延迟2分钟更新日期
    if (d.getHours() === 0 && d.getMinutes() <= 1) {
        d.setDate(d.getDate() - 1);
    }
    return (d.getFullYear()) +  ('0' + (d.getMonth() + 1)).slice(-2) + ('0' + d.getDate()).slice(-2);
}

var XinlinRecords = function() {
    var that = {};
    var records = JSON.parse(localStorage.getItem('xinlin-records')) || [];

    var flush = function() {
        var n = [], o = {};
        for (var i = 0; i !== records.length; ++i) {
            if (records[i]['name'] && !o[records[i]['name']]) {
                o[records[i]['name']] = true;
                n.push(records[i]);
            }
        }
        records = n;
        localStorage.setItem('xinlin-records', JSON.stringify(records))
    };
    that.flush = flush;

    var add = function(url, index, name, alias) {
        records.push({
            'url': url,
            'index': index,
            'name': name,
            'last-update': today()
        });
        aliases.set(name, alias);
        flush();
    };
    that.add = add;

    var remove = function(name) {
        for (var i = 0; i !== records.length; ++i) {
            if (records[i]['name'] === name) {
                records.splice(i, 1);
                break;
            }
        }
        aliases.remove(name);
        flush();
    };
    that.remove = remove;

    var modify = function(url, index, name, alias) {
        remove(name);
        if (!alias) {
            return;
        }
        add(url, index, name, alias);
        flush();
    };
    that.modify = modify;

    var update = function(callback) {
        for (var i = 0; i !== records.length; ++i) {
            if (records[i]['last-update'] === today()) { continue; }
            _lib.ajax({
                'url': records[i]['url'],
                'success': function(i) {
                    return function(htmlText) {
                        if (htmlText.indexOf('帖子主题') < 0) { return; }   // 避免出现500、404等错误
                        var postList = _cc98.parseTopicPage(htmlText);

                        var oldName = records[i]['name'];
                        var alias = aliases.get(oldName); // 暂存备注
                        var index = records[i]['index'];
                        var newName = postList[index] ? postList[index]['username'] : undefined;

                        records[i]['name'] = newName;
                        records[i]['last-update'] = today();
                        aliases.remove(oldName);
                        if (newName) {
                            aliases.set(newName, alias);
                        }
                        flush();
                    }
                }(i)
            });
        }
        flush();
    };
    that.update = update;

    return that;
}
var xinlin = XinlinRecords();

function addButtons() {
    var user = $('img[src="pic/reply.gif"]').parent().parent().parent().parent().parent().parent().prev();
    var idNode;
    if (_lib.parseQS(location.search)['boardid'] !== '182') {
        idNode = user.find('b').parent().parent();
    } else {
        idNode = user.find('b').parent();
    }
    idNode.each(function(index, ele) {
        var node = $(this);
        var username = node.find('b').text().trim();
        var alias = aliases.get(username);
        var tmp = $('');

        if (alias) {
            tmp.text('[' + alias + ']');
        } else {
            tmp.text('[+]');
            tmp.hide();
            user.eq(index).hover(function() {
                tmp.show();
            }, function() {
                tmp.hide();
            });
        }
        tmp.click(function() {
            editAlias(username, index);
        });

        node.after(tmp);
    });

    _lib.addStyles('.id-alias { display: inline-block; margin-left: 2px; font-size: 10px }');
}

function editAlias(username, index) {
    var url = _cc98.formatURL(location.href, true);
    var newAlias = prompt('请输入备注名（更新备注后须刷新才能看到）', aliases.get(username) || '');
    var isXinlin = (_lib.parseQS(location.search)['boardid'] === '182');

    if (newAlias === null) { return; }  // 按了取消

    if (newAlias === '') {
        if (isXinlin) {
            xinlin.remove(username);
        } else {
            aliases.remove(username);
        }
    } else {
        if (isXinlin) {
            xinlin.modify(url, index, username, newAlias);
        } else {
            aliases.set(username, newAlias);
        }
    }
}

xinlin.update();
addButtons();

});<commit_msg>new user interface<commit_after>// ==UserScript==
// @id             cc98_id_alias
// @name           cc98 id alias
// @version        0.4.0
// @namespace      soda@cc98.org
// @author         soda <sodazju@gmail.com>
// @description    给98增加类新浪微博的ID备注功能
// @include        http://www.cc98.org/dispbbs.asp*
// @require        http://libs.baidu.com/jquery/2.0.3/jquery.min.js
// @run-at         document-end
// ==/UserScript==

// cc98 JavaScript SDK，已去掉不需要的部分

// Chrome 没有sendAsBinary函数，这里是一个实现
if (!XMLHttpRequest.prototype.sendAsBinary) {
    XMLHttpRequest.prototype.sendAsBinary = function(datastr) {
        function byteValue(x) {
            return x.charCodeAt(0) & 0xff;
        }
        var ords = Array.prototype.map.call(datastr, byteValue);
        var ui8a = new Uint8Array(ords);
        this.send(ui8a);
    };
}

// 辅助函数
// parseQS, toQS, parseURL, unescapeHTML, ajax, addStyles
var _lib = {

    // parse the url get parameters
    parseQS: function(url) {
        url = url.toLowerCase().split('#')[0];  // remove the hash part
        var t = url.indexOf('?');
        var params;

        var hash = {};
        if (t >= 0) {
            params = url.substring(t+1).split('&');
        } else {    // plain query string without '?' (e.g. in cookies)
            params = url.split('&');
        }
        for (var i = 0; i < params.length; ++i) {
            var val = params[i].split('=');
            hash[decodeURIComponent(val[0])] = decodeURIComponent(val[1]);
        }
        return hash;
    },

    toQS: function(obj) {
        var ret = [];
        for (var key in obj) {
            if (obj.hasOwnProperty(key)) {
                if ('' === obj[key]) { continue; }
                ret.push(encodeURIComponent(key) + '=' + encodeURIComponent(obj[key]));
            }
        }
        return ret.join('&');
    },

    parseURL: function(url) {
        // from JavaScript: The Good Parts
        var parse_url = /^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;
        var arr = parse_url.exec(url);
        var result = {};
        result['url'] = arr[0];
        result['scheme'] = arr[1];
        result['slash'] = arr[2];
        result['host'] = arr[3];
        result['port'] = arr[4];
        result['path'] = arr[5];
        result['query'] = arr[6];
        result['hash'] = arr[7];
        return result;
    },

    // 将转义后的html转回来
    unescapeHTML: function(input) {
        var e = document.createElement('div');
        e.innerHTML = input;
        return e.childNodes.length === 0 ? '' : e.childNodes[0].nodeValue;
    },

    ajax: function(opts) {
        opts = {
            type: opts.type || 'GET',
            url: opts.url || '',
            data: opts.data || null,
            contentType: opts.contentType || 'application/x-www-form-urlencoded; charset=UTF-8',
            success: opts.success || function() {},
            async: opts.async || (opts.async === undefined)
        };

        var xhr = new XMLHttpRequest();

        xhr.open(opts.type, opts.url, opts.async);
        xhr.setRequestHeader('Content-type', opts.contentType);
        xhr.onreadystatechange = function() {
            if (xhr.readyState === 4 && xhr.status === 200) {
                opts.success(xhr.responseText);
            }
        };
        if (opts.contentType === 'application/x-www-form-urlencoded; charset=UTF-8') {
            xhr.send(_lib.toQS(opts.data));
        } else {
            xhr.sendAsBinary(opts.data);
        }
    },

    // 添加CSS
    addStyles: function(css) {
        var head = document.getElementsByTagName('head')[0];
        var style = document.createElement('style');

        style.setAttribute('type', 'text/css');
        style.innerHTML = css;
        head.appendChild(style);
    }
};

// 98相关的函数接口
// getPostContent, parseTopicPage, postCount, pageCount, formatURL, currentPage
var _cc98 = function() {

    var that = {};

    // 各种常量
    var POST_COUNT_RE = /本主题贴数\s*(\d+)<\/b>/ig;

    // 以下三个没有考虑被删除的帖子，因为在当前页解析的时候DisplayDel()和正常的发帖时间之类的会一起出现，导致匹配会乱掉
    // 因此引起的发米机发米楼层可能不精确的问题也没办法了……
    var NAME_RE = /(?:name="\d+">| middle;"> )\s*([^<]+)<\/b><\/span>/g;
    var ANNOUNCEID_RE = //g; // 注意网页上之类的标签是作为#1的anchor出现的，所以此处限定至少要两个数字
    var POST_TIME_RE = /<\/a>\s*([^AP]*[AP]M)\s*<\/td>/g;

    // 获取页面中的用户列表，回帖时间回帖ID
    // @return {Array}  每个数组元素都有username, annouceid, posttime三个属性
    that.parseTopicPage = function(htmlText) {
        if (!htmlText) { htmlText = document.body.innerHTML; }
        var postList = [];

        var nameArr = htmlText.match(NAME_RE);
        nameArr.forEach(function(name) {
            var post = {};
            post['username'] = name.replace(NAME_RE, '$1');
            postList.push(post);
        });

        var idArr = htmlText.match(ANNOUNCEID_RE);
        // 考虑到心灵没有announceid，所以idArr可能为空
        if (idArr) {
            idArr.forEach(function(id, index) {
                postList[index]['announceid'] = id.replace(ANNOUNCEID_RE, '$1');
            });
        }

        var timeArr = htmlText.match(POST_TIME_RE);
        if (timeArr) {
            timeArr.forEach(function(t, index) {
                postList[index]['posttime'] = t.replace(POST_TIME_RE, '$1');
            });
        }

        return postList;
    };

    that.postCount = function(htmlText) {
        if (!htmlText) { htmlText = document.body.innerHTML; }
        return parseInt(htmlText.match(POST_COUNT_RE)[0].replace(POST_COUNT_RE, '$1'), 10);
    };

    that.pageCount = function(htmlText) {
        return Math.ceil(_cc98.postCount(htmlText) / 10);
    };

    // 格式化网址，去除无用的参数并转为相对链接
    // @param {string}  url 要格式化的网址
    // @param {boolean} maxPageFix 是否修正url中star参数的值，使其不超过当前最后页的实际值
    that.formatURL = function(url, maxPageFix) {
        var urlObj = _lib.parseURL(url);

        // 不在www.cc98.org域名下
        if (urlObj['host'] !== 'www.cc98.org') {
            return url;
        }

        // http://www.cc98.org/
        if (!urlObj['path']) {
            return '/';
        }

        var params = _lib.parseQS(urlObj['query']);
        var hash = urlObj['hash'] ? ('#' + urlObj['hash']) : '';

        // 不是dispbbs.asp开头的链接，只去掉空的get参数，转为相对链接，不做其他处理
        if (urlObj['path'] === 'dispbbs,asp') {
            return '/' + urlObj['path'] + '?' + _lib.toQS(params) + hash;
        }

        // 如果不是在追踪页面，就去掉replyid
        if (!params['trace']) {
            params['replyid'] = '';
        }
        params['page'] = '';    // 去掉page

        //
        if (params['star'] && maxPageFix && parseInt(params['star'], 10) > _cc98.pageCount()) {
            params['star'] = _cc98.pageCount();
        }

        params['star'] = (params['star'] && params['star'] !== '1') ? params['star'] : '';    // star=1时去掉
        return '/' + urlObj['path'] + '?' + _lib.toQS(params) + hash;
    };

    that.currentPage = function() {
        return parseInt(/<span title="跳转到第\s*(\d+)\s*页/ig.exec(document.body.innerHTML)[1], 10);
    };

    return that;
}();

jq = jQuery.noConflict();   // 防止与98默认jQuery版本的冲突

(function($) {

$(function() {
    var Aliases = function() {
        var that = {};
        var dict = JSON.parse(localStorage.getItem('aliases')) || {};

        var flush = function() {
            localStorage.setItem('aliases', JSON.stringify(dict));
        };
        that.flush = flush;

        that.get = function(name) {
            return dict[name];
        };

        that.set = function(name, alias) {
            dict[name] = alias;
            flush();
        };

        that.remove = function(name) {
            delete dict[name];
            flush();
        };

        return that;
    };
    var aliases = Aliases();

    function today() {
        var d = new Date();
        // 考虑到98服务器时间有延迟，所以延迟2分钟更新日期
        if (d.getHours() === 0 && d.getMinutes() <= 2) {
            d.setDate(d.getDate() - 1);
        }
        return (d.getFullYear()) +  ('0' + (d.getMonth() + 1)).slice(-2) + ('0' + d.getDate()).slice(-2);
    }

    var XinlinRecords = function() {
        var that = {};
        var records = JSON.parse(localStorage.getItem('xinlin-records')) || [];

        var flush = function() {
            var n = [], o = {};
            for (var i = 0; i !== records.length; ++i) {
                if (records[i]['name'] && !o[records[i]['name']]) {
                    o[records[i]['name']] = true;
                    n.push(records[i]);
                }
            }
            records = n;
            localStorage.setItem('xinlin-records', JSON.stringify(records))
        };
        that.flush = flush;

        var add = function(url, index, name, alias) {
            records.push({
                'url': url,
                'index': index,
                'name': name,
                'last-update': today()
            });
            aliases.set(name, alias);
            flush();
        };
        that.add = add;

        var remove = function(name) {
            for (var i = 0; i !== records.length; ++i) {
                if (records[i]['name'] === name) {
                    records.splice(i, 1);
                    break;
                }
            }
            aliases.remove(name);
            flush();
        };
        that.remove = remove;

        var modify = function(url, index, name, alias) {
            remove(name);
            if (!alias) {
                return;
            }
            add(url, index, name, alias);
            flush();
        };
        that.modify = modify;

        var update = function(callback) {
            for (var i = 0; i !== records.length; ++i) {
                if (records[i]['last-update'] === today()) { continue; }
                _lib.ajax({
                    'url': records[i]['url'],
                    'success': function(i) {
                        return function(htmlText) {
                            if (htmlText.indexOf('帖子主题') < 0) { return; }   // 避免出现500、404等错误
                            var postList = _cc98.parseTopicPage(htmlText);

                            var oldName = records[i]['name'];
                            var alias = aliases.get(oldName); // 暂存备注
                            var index = records[i]['index'];
                            var newName = postList[index] ? postList[index]['username'] : undefined;

                            records[i]['name'] = newName;
                            records[i]['last-update'] = today();
                            aliases.remove(oldName);
                            if (newName) {
                                aliases.set(newName, alias);
                            }
                            flush();
                        }
                    }(i)
                });
            }
            flush();
        };
        that.update = update;

        return that;
    }
    var xinlin = XinlinRecords();

    function show() {
        var user = $('img[src="pic/reply.gif"]').parent().parent().parent().parent().parent().parent().prev();
        var idNodes;
        if (_lib.parseQS(location.search)['boardid'] !== '182') {
            idNodes = user.find('b').parent().parent();
        } else {
            idNodes = user.find('b').parent();
        }

        idNodes.each(function(index, ele) {
            var node = $(this);

            var edit_btn = $('[备注]');
            node.after(edit_btn);

            edit_btn.hide();
            user.eq(index).hover(function() {
                edit_btn.show();
            }, function() {
                edit_btn.hide();
            });

            edit_btn.click(function(e) { editAlias(username, index); });

            var namenode = node.find('b');
            var username = namenode.text().trim();
            var alias = aliases.get(username);
            if (alias) {
                namenode.text(alias);
            }

        });

        _lib.addStyles('.id-alias { display: inline-block; margin-left: 2px; font-size: 10px }');
    }

    function editAlias(username, index) {
        var url = _cc98.formatURL(location.href, true);
        var newAlias = prompt('请输入备注名（更新备注后须刷新才能看到）', aliases.get(username) || '');
        var isXinlin = (_lib.parseQS(location.search)['boardid'] === '182');

        if (newAlias === null) { return; }  // 按了取消

        if (newAlias === '') {
            if (isXinlin) {
                xinlin.remove(username);
            } else {
                aliases.remove(username);
            }
        } else {
            if (isXinlin) {
                xinlin.modify(url, index, username, newAlias);
            } else {
                aliases.set(username, newAlias);
            }
        }
    }

    xinlin.update();
    show();

});

})(jq);<|endoftext|>


<commit_before>// WitDriver
// ==========
//
// A wrapper class for the Wit Webservice (https://wit.ai/docs/http/20160330 ).
//
import request from 'request-promise';

const baseURL = 'https://api.wit.ai';
const apiVersion = '20160330';
const intentEndpoint = '/message';
const messageEndpoint = '/messages/';

class WitDriver {
    // ## Constructor
    // ### Parameters
    // - **options** - _Object_ - Configuration object.
    //   - **id** - _string_ - A wit app ID.
    //   - **serverToken** - _string_ - The Server Access Token for that wit app.
    constructor(options) {
        const {
            id,
            serverToken
        } = options;

        if (!id || !serverToken) {
            throw new Error(
                'WitDriver could not be instantiated. Missing required options.'
            );
        }

        this.queryURL = `${baseURL}${intentEndpoint}?v=${apiVersion}`;
        this.messageInfoURL = messageId =>
            `${baseURL}${messageEndpoint}${messageId}?v=${apiVersion}`;
        this.requestHeaders = {
            Authorization: `Bearer ${serverToken}`,
            Accept: `application/vnd.wit.${apiVersion}+json`
        };
    }

    // ### Methods
    //
    // #### query
    // ##### Parameters
    // - **text** - _string_ - a text query to send to the Wit webservice.
    // - **verbose** - _boolean_ - if the response should include extra info,
    // like start/end of the entities
    // - **n** - _number_ - The number of n-best outcomes you want to get back. default is 1
    //
    // ##### return
    // Returns a Promise that returns the parsed object from the webservice.
    // See https://wit.ai/docs/http/20160330#get-intent-via-text-link
    query(text = '', verbose = false, n = 1) {
        const url = `${this.queryURL}&q=${encodeURIComponent(text)}&verbose=${verbose}&n=${n}`;
        return request({
            url,
            headers: this.requestHeaders
        }).then(body => JSON.parse(body));
    }

    // #### getMessage
    // ##### Parameters
    // - **id** - _string_ - the id of the message you want to inspect on wit
    //
    // ##### return
    // Returns a Promise that returns the parsed object from the webservice.
    // See: https://wit.ai/docs/http/20160330#get-message-link
    getMessage(id) {
        const url = this.messageInfoURL(id);
        return request({
            url,
            headers: this.requestHeaders
        }).then(body => JSON.parse(body));
    }
}

// ## Examples
// ```javascript
// const options = {
//     id: 'YOUR_WIT_ID',
//     serverToken: 'YOUR_WIT_TOKEN'
// };
// const wit = new WitDriver(options);
// luis.query('Hi')
//     .then(result => {
//         const {
//             _text,
//             outcomes
//         } = result;
//         console.log(_text); // 'Hi'
//         console.log(outcomes.length); // 1
//     });
// ```
// See [test/wit.js](https://github.com/fczuardi/calamars/blob/master/test/wit.js) for more examples.
//
// ## Static Methods
//
// #### getEntity
// ##### Parameters
// - **outcomes** - _object_ - an outcomes object returned by wit.ai API call
// - **entityName** - _string_ - the name of the entity you look for
//
// ##### return
// Returns an entity object or null
const getEntity = (outcomes, entityName) => {
    try {
        return outcomes.entities[entityName][0];
    } catch (e) {
        return null;
    }
};

WitDriver.getEntity = getEntity;

// #### getEntityValue
// ##### Parameters
// - **outcomes** - _object_ - an outcomes object returned by wit.ai API call
// - **entityName** - _string_ - the name of the entity you look for
//
// ##### return
// Returns the value of the entity you look for or null
WitDriver.getEntityValue = (outcomes, entityName) => {
    try {
        return getEntity(outcomes, entityName).value;
    } catch (e) {
        return null;
    }
};

// #### getEntityMeta
// ##### Parameters
// - **entity** - _object_ - an entity object
//
// ##### return
// Returns the metadata of the entity you look for or null
WitDriver.getEntityMeta = (entity) => {
    try {
        return JSON.parse(entity.metadata);
    } catch (e) {
        return null;
    }
};
export { WitDriver };
<commit_msg>new helper to get all entities given an entity name<commit_after>// WitDriver
// ==========
//
// A wrapper class for the Wit Webservice (https://wit.ai/docs/http/20160330 ).
//
import request from 'request-promise';

const baseURL = 'https://api.wit.ai';
const apiVersion = '20160330';
const intentEndpoint = '/message';
const messageEndpoint = '/messages/';

class WitDriver {
    // ## Constructor
    // ### Parameters
    // - **options** - _Object_ - Configuration object.
    //   - **id** - _string_ - A wit app ID.
    //   - **serverToken** - _string_ - The Server Access Token for that wit app.
    constructor(options) {
        const {
            id,
            serverToken
        } = options;

        if (!id || !serverToken) {
            throw new Error(
                'WitDriver could not be instantiated. Missing required options.'
            );
        }

        this.queryURL = `${baseURL}${intentEndpoint}?v=${apiVersion}`;
        this.messageInfoURL = messageId =>
            `${baseURL}${messageEndpoint}${messageId}?v=${apiVersion}`;
        this.requestHeaders = {
            Authorization: `Bearer ${serverToken}`,
            Accept: `application/vnd.wit.${apiVersion}+json`
        };
    }

    // ### Methods
    //
    // #### query
    // ##### Parameters
    // - **text** - _string_ - a text query to send to the Wit webservice.
    // - **verbose** - _boolean_ - if the response should include extra info,
    // like start/end of the entities
    // - **n** - _number_ - The number of n-best outcomes you want to get back. default is 1
    //
    // ##### return
    // Returns a Promise that returns the parsed object from the webservice.
    // See https://wit.ai/docs/http/20160330#get-intent-via-text-link
    query(text = '', verbose = false, n = 1) {
        const url = `${this.queryURL}&q=${encodeURIComponent(text)}&verbose=${verbose}&n=${n}`;
        return request({
            url,
            headers: this.requestHeaders
        }).then(body => JSON.parse(body));
    }

    // #### getMessage
    // ##### Parameters
    // - **id** - _string_ - the id of the message you want to inspect on wit
    //
    // ##### return
    // Returns a Promise that returns the parsed object from the webservice.
    // See: https://wit.ai/docs/http/20160330#get-message-link
    getMessage(id) {
        const url = this.messageInfoURL(id);
        return request({
            url,
            headers: this.requestHeaders
        }).then(body => JSON.parse(body));
    }
}

// ## Examples
// ```javascript
// const options = {
//     id: 'YOUR_WIT_ID',
//     serverToken: 'YOUR_WIT_TOKEN'
// };
// const wit = new WitDriver(options);
// luis.query('Hi')
//     .then(result => {
//         const {
//             _text,
//             outcomes
//         } = result;
//         console.log(_text); // 'Hi'
//         console.log(outcomes.length); // 1
//     });
// ```
// See [test/wit.js](https://github.com/fczuardi/calamars/blob/master/test/wit.js) for more examples.
//
// ## Static Methods
//
// #### getEntity
// Gets the first entityName found.
//
// ##### Parameters
// - **outcomes** - _object_ - an outcomes object returned by wit.ai API call
// - **entityName** - _string_ - the name of the entity you look for
//
// ##### return
// Returns an entity object or null
const getEntity = (outcomes, entityName) => {
    try {
        return outcomes.entities[entityName][0];
    } catch (e) {
        return null;
    }
};

// #### getEntities
// Gets all entities of name entityName.
//
// ##### Parameters
// - **outcomes** - _object_ - an outcomes object returned by wit.ai API call
// - **entityName** - _string_ - the name of the entity you look for
//
// ##### return
// Returns an array of entities or null
const getEntities = (outcomes, entityName) => {
    try {
        return outcomes.entities[entityName];
    } catch (e) {
        return null;
    }
};

WitDriver.getEntity = getEntities;

// #### getEntityValue
// ##### Parameters
// - **outcomes** - _object_ - an outcomes object returned by wit.ai API call
// - **entityName** - _string_ - the name of the entity you look for
//
// ##### return
// Returns the value of the entity you look for or null
WitDriver.getEntityValue = (outcomes, entityName) => {
    try {
        return getEntity(outcomes, entityName).value;
    } catch (e) {
        return null;
    }
};

// #### getEntityMeta
// ##### Parameters
// - **entity** - _object_ - an entity object
//
// ##### return
// Returns the metadata of the entity you look for or null
WitDriver.getEntityMeta = (entity) => {
    try {
        return JSON.parse(entity.metadata);
    } catch (e) {
        return null;
    }
};
export { WitDriver };
<|endoftext|>


<commit_before>// @flow
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as ParserActions from '../actions/parser';
import styles from './Parser.css';
import RollList from './RollList';
import type { rollType } from '../reducers/parser';

const getTimeLeft = (startTime: number, limit: number) =>
  Math.ceil((limit - (new Date().getTime() - startTime)) / 1000);

class Parser extends Component {
  state: {
    timeLeft: number
  };

  props: {
    beginParsing: () => void,
    endParsing: () => void,
    countdownEnded: () => void,
    parsing: boolean,
    rolls: Array<rollType>,
    validSettings: boolean,
    reset: () => void,
    status: string,
    timerActive: boolean,
    timerStartTime: number,
    timeLimit?: number,
    startingPhrase: string
  }

  countdownTimeout: ?number;
  countdownInterval: ?number;

  static defaultProps = {
    timerStartTime: 0,
    timeLimit: undefined
  }

  constructor(props) {
    super(props);
    this.state = { timeLeft: props.timeLimit };
  }

  componentDidUpdate(prevProps) {
    const { timerActive, timerStartTime } = this.props;

    if (timerActive === true &&
      (prevProps.timerActive === false ||
       prevProps.timerStartTime !== timerStartTime)) {
      this.startCountdown();
    } else if (prevProps.timerActive === true && timerActive === false) {
      this.clearTimers();
    }
  }

  clearTimers() {
    if (this.countdownTimeout) {
      clearTimeout(this.countdownTimeout);
      this.countdownTimeout = null;
    }

    if (this.countdownInterval) {
      clearInterval(this.countdownInterval);
      this.countdownInterval = null;
    }
  }

  startCountdown() {
    const { timeLimit, timerStartTime } = this.props;
    this.clearTimers();

    if (timeLimit !== undefined) {
      this.setState({ timeLeft: timeLimit });
      this.countdownInterval = setInterval(() => {
        this.setState({ timeLeft: getTimeLeft(timerStartTime, timeLimit * 1000) });
      }, 1000);

      this.countdownTimeout = setTimeout(() => {
        this.props.countdownEnded();
      }, timeLimit * 1000);
    }
  }

  handleBeginParsingClick() {
    const { beginParsing } = this.props;
    beginParsing();
  }

  handleEndParsingClick() {
    const { endParsing } = this.props;
    endParsing();
  }

  render() {
    const {
      parsing, validSettings, rolls,
      status, beginParsing, endParsing,
      reset, timerActive, timeLimit,
      startingPhrase
    } = this.props;

    const { timeLeft } = this.state;

    const timeString = () => {
      if (startingPhrase.length > 0) {
        if (timerActive) {
          return  Time left: {timeLeft};
        } else if (timeLimit) {
          return  Time left: {timeLimit};
        }
      }
    };

    return (

          <button disabled={parsing || !validSettings} onClick={beginParsing}>Begin Parsing</button>
          <button onClick={reset}>Reset</button>
          <button disabled={!parsing} onClick={endParsing}>End Parsing</button>

Status: {status}

            # of Rolls: {rolls.length}{timeString()}

        <RollList rolls={rolls} />

    );
  }
}

function mapStateToProps(state) {
  return {
    parsing: state.parser.parsing,
    validSettings: state.settings.logFile.length > 0,
    rolls: state.parser.rolls,
    status: state.parser.status,
    timerActive: state.timer.active,
    timerStartTime: state.timer.startTime,
    timeLimit: parseInt(state.settings.timeLimit, 10),
    startingPhrase: state.settings.startingPhrase
  };
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators(ParserActions, dispatch);
}

export default connect(mapStateToProps, mapDispatchToProps)(Parser); // eslint-disable-line

<commit_msg>fixed build error<commit_after>// @flow
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as ParserActions from '../actions/parser';
import styles from './Parser.css';
import RollList from './RollList';
import type { rollType } from '../reducers/parser';

const getTimeLeft = (startTime: number, limit: number) =>
  Math.ceil((limit - (new Date().getTime() - startTime)) / 1000);

class Parser extends Component {
  state: {
    timeLeft: number
  };

  props: {
    beginParsing: () => void,
    endParsing: () => void,
    countdownEnded: () => void,
    parsing: boolean,
    rolls: Array<rollType>,
    validSettings: boolean,
    reset: () => void,
    status: string,
    timerActive: boolean,
    timerStartTime: number,
    timeLimit?: number,
    startingPhrase: string
  }

  countdownTimeout: ?number;
  countdownInterval: ?number;

  static defaultProps = {
    timerStartTime: 0,
    timeLimit: undefined
  }

  constructor(props) {
    super(props);
    this.state = { timeLeft: props.timeLimit };
  }

  componentDidUpdate(prevProps) {
    const { timerActive, timerStartTime } = this.props;

    if (timerActive === true &&
      (prevProps.timerActive === false ||
       prevProps.timerStartTime !== timerStartTime)) {
      this.startCountdown();
    } else if (prevProps.timerActive === true && timerActive === false) {
      this.clearTimers();
    }
  }

  clearTimers() {
    if (this.countdownTimeout) {
      clearTimeout(this.countdownTimeout);
      this.countdownTimeout = null;
    }

    if (this.countdownInterval) {
      clearInterval(this.countdownInterval);
      this.countdownInterval = null;
    }
  }

  startCountdown() {
    const { timeLimit, timerStartTime } = this.props;
    this.clearTimers();

    if (timeLimit !== undefined) {
      this.setState({ timeLeft: timeLimit });
      this.countdownInterval = setInterval(() => {
        this.setState({ timeLeft: getTimeLeft(timerStartTime, timeLimit * 1000) });
      }, 1000);

      this.countdownTimeout = setTimeout(() => {
        this.props.countdownEnded();
      }, timeLimit * 1000);
    }
  }

  handleBeginParsingClick() {
    const { beginParsing } = this.props;
    beginParsing();
  }

  handleEndParsingClick() {
    const { endParsing } = this.props;
    endParsing();
  }

  render() {
    const {
      parsing, validSettings, rolls,
      status, beginParsing, endParsing,
      reset, timerActive, timeLimit,
      startingPhrase
    } = this.props;

    const { timeLeft } = this.state;

    let timeString = null;
    if (startingPhrase.length > 0) {
      if (timerActive) {
        timeString =  Time left: {timeLeft};
      } else if (timeLimit) {
        timeString =  Time left: {timeLimit};
      }
    }

    return (

          <button disabled={parsing || !validSettings} onClick={beginParsing}>Begin Parsing</button>
          <button onClick={reset}>Reset</button>
          <button disabled={!parsing} onClick={endParsing}>End Parsing</button>

Status: {status}

            # of Rolls: {rolls.length}{timeString}

        <RollList rolls={rolls} />

    );
  }
}

function mapStateToProps(state) {
  return {
    parsing: state.parser.parsing,
    validSettings: state.settings.logFile.length > 0,
    rolls: state.parser.rolls,
    status: state.parser.status,
    timerActive: state.timer.active,
    timerStartTime: state.timer.startTime,
    timeLimit: parseInt(state.settings.timeLimit, 10),
    startingPhrase: state.settings.startingPhrase
  };
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators(ParserActions, dispatch);
}

export default connect(mapStateToProps, mapDispatchToProps)(Parser); // eslint-disable-line

<|endoftext|>


<commit_before>/* jshint bitwise: false */
/* global window */

(function (exports) {
  'use strict';

  var Rule = (function () {
    var constructor = function (number) {
      this.number = number;
    };

    constructor.prototype.apply = function (p, q, r) {
      return !!(this.number & (1 << ((p << 2) | (q << 1) | (r << 0))));
    };

    return constructor;
  }());

  var CellularAutomaton = (function () {
    var constructor = function (options) {
      this.rule = options.rule;
      this.cells = options.cells;
      this.left = 0;
      this.right = this.cells.length - 1;
    };

    constructor.prototype.padCells = function () {
      this.cells.unshift(false);
      this.left--;
      this.cells.push(false);
      this.right++;
    };

    constructor.prototype.trimCells = function () {
      while (!this.cells[0]) {
        this.cells.shift();
        this.left++;
      }

      while (!this.cells[this.cells.length - 1]) {
        this.cells.pop();
        this.right--;
      }
    };

    constructor.prototype.step = function () {
      this.padCells();

      this.cells = this.cells.map(function (middle, column) {
        return this.rule.apply(this.cells[column - 1], middle, this.cells[column + 1]);
      }, this);

      this.trimCells();
    };

    constructor.prototype.cellAt = function (column) {
      return this.cells[column - this.left];
    };

    return constructor;
  }());

  var drawCells = function (cellularAutomaton, context, row, cellCount, cellSize) {
    for (var column = -cellCount + 1; column <= 0; ++column) {
      if (cellularAutomaton.cellAt(column)) {
        context.fillRect((column + cellCount - 1) * cellSize, row * cellSize, cellSize, cellSize);
      }
    }
  };

  var scrollContext = function (context, cellCount, rowCount, cellSize) {
    var width = cellCount * cellSize, height = rowCount * cellSize;

    var imageData = context.getImageData(0, cellSize, width, height - cellSize);
    context.putImageData(imageData, 0, 0);
    context.clearRect(0, height - cellSize, width, cellSize);
  };

  exports.drawCellularAutomaton = function (options) {
    var canvas = options.canvas,
      cellSize = options.cellSize,
      rule = options.rule,
      rowsPerSecond = options.rowsPerSecond;

    var context = canvas.getContext('2d');

    var cellCount = Math.floor(canvas.width / cellSize);
    var cellularAutomaton = new CellularAutomaton({ rule: rule, cells: [true] });

    var rowCount = Math.floor(canvas.height / cellSize);
    var row = 0;

    window.setInterval(function () {
      drawCells(cellularAutomaton, context, row, cellCount, cellSize);
      cellularAutomaton.step();

      if (row < rowCount - 1) {
        ++row;
      } else {
        scrollContext(context, cellCount, rowCount, cellSize);
      }
    }, 1000 / rowsPerSecond);
  };

  exports.Rule = Rule;
}(this));
<commit_msg>Pass in complete array of (empty) cells<commit_after>/* jshint bitwise: false */
/* global window */

(function (exports) {
  'use strict';

  var Rule = (function () {
    var constructor = function (number) {
      this.number = number;
    };

    constructor.prototype.apply = function (p, q, r) {
      return !!(this.number & (1 << ((p << 2) | (q << 1) | (r << 0))));
    };

    return constructor;
  }());

  var CellularAutomaton = (function () {
    var constructor = function (options) {
      this.rule = options.rule;
      this.cells = options.cells;
      this.left = 0;
      this.right = this.cells.length - 1;
    };

    constructor.prototype.padCells = function () {
      this.cells.unshift(false);
      this.left--;
      this.cells.push(false);
      this.right++;
    };

    constructor.prototype.trimCells = function () {
      while (!this.cells[0]) {
        this.cells.shift();
        this.left++;
      }

      while (!this.cells[this.cells.length - 1]) {
        this.cells.pop();
        this.right--;
      }
    };

    constructor.prototype.step = function () {
      this.padCells();

      this.cells = this.cells.map(function (middle, column) {
        return this.rule.apply(this.cells[column - 1], middle, this.cells[column + 1]);
      }, this);

      this.trimCells();
    };

    constructor.prototype.cellAt = function (column) {
      return this.cells[column - this.left];
    };

    return constructor;
  }());

  var drawCells = function (cellularAutomaton, context, row, cellCount, cellSize) {
    for (var column = 0; column < cellCount; ++column) {
      if (cellularAutomaton.cellAt(column)) {
        context.fillRect(column * cellSize, row * cellSize, cellSize, cellSize);
      }
    }
  };

  var scrollContext = function (context, cellCount, rowCount, cellSize) {
    var width = cellCount * cellSize, height = rowCount * cellSize;

    var imageData = context.getImageData(0, cellSize, width, height - cellSize);
    context.putImageData(imageData, 0, 0);
    context.clearRect(0, height - cellSize, width, cellSize);
  };

  var makeCells = function (cellCount) {
    var cells = new Array(cellCount);

    for (var i = 0; i < cellCount; ++i) {
      cells[i] = false;
    }
    cells[cellCount - 1] = true;

    return cells;
  };

  exports.drawCellularAutomaton = function (options) {
    var canvas = options.canvas,
      cellSize = options.cellSize,
      rule = options.rule,
      rowsPerSecond = options.rowsPerSecond;

    var context = canvas.getContext('2d');

    var cellCount = Math.floor(canvas.width / cellSize);
    var cellularAutomaton = new CellularAutomaton({
      rule: rule,
      cells: makeCells(cellCount)
    });

    var rowCount = Math.floor(canvas.height / cellSize);
    var row = 0;

    window.setInterval(function () {
      drawCells(cellularAutomaton, context, row, cellCount, cellSize);
      cellularAutomaton.step();

      if (row < rowCount - 1) {
        ++row;
      } else {
        scrollContext(context, cellCount, rowCount, cellSize);
      }
    }, 1000 / rowsPerSecond);
  };

  exports.Rule = Rule;
}(this));
<|endoftext|>