<commit_before>// Copyright (c) 2012-2017, Najjar Chedy
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

var _ = require('underscore-node'),
    logger = require('./logger').logger;

var InstructionType_jmp = 0;
var InstructionType_conditionalJmpInst = 1;
var InstructionType_notRetInst = 2;
var InstructionType_retInst = 3;

// this mess is intended to be ported to another file.
function separateCodeFromData(asmArr) {

    var labelRegex = /\.L{1}\d+:/;

    var isCode = function(x){
        return  x && x.text && (x.source !== null || labelRegex.test(x.text) ||
                !x.text.includes('.'));
    };

    return _.chain(asmArr)
            .map(_.clone)
            .filter(isCode)
            .value();
}

    var gccX86 = {
                filterData:  separateCodeFromData,
                isFunctionEnd:function(x)  { return ((x[0] != ' ') && (x[0] != '.') &&
                                                             (x.indexOf(':') != -1)) ;},

                isBasicBlockEnd: function(x) { return x[0] == ".";} ,

                getInstructionType:function(inst) {
                                            if(inst.includes("jmp")) return InstructionType_jmp;
                                            else if(inst.trim()[0] === 'j') return InstructionType_conditionalJmpInst;
                                            else if(!inst.includes(" ret")) return InstructionType_notRetInst;
                                            else return InstructionType_retInst;
                                          },

                extractNodeName:function(inst) {
                                     var name = inst.match(/\.L{1}\d+/);
                                     return name + ":";
                                  },

                isJmpInstruction: function(x) { var trimed = x.trim(); return ( trimed[0] === 'j');}
             };

function separateCodeFromData_(asmArr) {

    var labelRegex = /\.LBB\d+_\d+:/;
    var isFunctionName = function(x){
        var functionNameRegex = /((\w|\(|\))*): # @\1/;
        return functionNameRegex.test(x);
    }

    var isCode = function(x){
        return  x && x.text && (x.source !== null || labelRegex.test(x.text) ||
                isFunctionName(x.text));
    };

    var removeComments = function(x) {
        var pos = x.text.indexOf("#");
        if(pos !== -1)
            x.text = x.text.substring(0, pos-1);
        return x;
    }

    return _.chain(asmArr)
            .map(_.clone)
            .filter(isCode)
            .map(removeComments)
            .value();
}

    var clangX86 = {
                filterData:  separateCodeFromData_,
                isFunctionEnd:function(x)  { return ((x[0] !== ' ') && (x[0] !== '.') &&
                                                             (x.indexOf(':') !== -1)) ;},

                isBasicBlockEnd: function(x) { return x[0] == ".";} ,

                getInstructionType:function(inst) {
                                            if(inst.includes("jmp")) return InstructionType_jmp;
                                            else if(inst.trim()[0] === 'j') return InstructionType_conditionalJmpInst;
                                            else if(!inst.includes(" ret")) return InstructionType_notRetInst;
                                            else return InstructionType_retInst;
                                          },

                extractNodeName:function(inst) {
                                     var name = inst.match(/\.LBB\d+_\d+/);
                                     return name + ":";
                                  },

                isJmpInstruction: function(x) { var trimed = x.trim(); return ( trimed[0] == 'j');}
             };

function ControlFlowGraph(compiler){
    if(compiler.includes("clang"))
        this.rules = clangX86;
    else
        this.rules = gccX86;
}

ControlFlowGraph.prototype.splitToFunctions = function (asmArr, isEnd) {
    if(asmArr.length === 0)   return [];
    var result = [];
    var first = 0;
    var last = asmArr.length;
    var fnRange = {start:first, end:null};
    ++first;
    console.log(asmArr);
    while(first != last) {
        if(isEnd(asmArr[first].text)) {
            fnRange.end = first;
            result.push(_.clone(fnRange));
            fnRange.start = first;
        }
        ++first;
    }

    fnRange.end = last;
    result.push(_.clone(fnRange));
    return result;
};

ControlFlowGraph.prototype.splitToBasicBlocks = function (asmArr,  range, isEnd, isJmp) {
    var first = range.start;
    var last = range.end;
    if(first == last) return [];
    var functionName = asmArr[first].text;
    ++first;

    var rangeBb = {nameId:functionName.substr(0,50) , start:first , end:null, actionPos:[]};
    var result = [];

    var newRangeWith = function (oldRange, nameId, start) {
        return {nameId: nameId, start: start, actionPos: [], end: oldRange.end};
    };

    while(first != last) {
        var inst = asmArr[first].text;
        if(isEnd(inst)) {
            rangeBb.end = first;
            result.push(_.clone(rangeBb));
            ++first;
            //inst is expected to be .L*: where * in 1,2,...
            rangeBb = newRangeWith(rangeBb, inst, first);
        }
        else if(isJmp(inst)) {
                rangeBb.actionPos.push(first);
        }
        ++first;
    }

    rangeBb.end = last;
    result.push(_.clone(rangeBb));
    return result;
};

ControlFlowGraph.prototype.splitToCanonicalBasicBlock = function (basicBlock) {
    var actionPos = basicBlock.actionPos;
    var actPosSz = actionPos.length;
    if (actionPos[actPosSz-1] + 1 == basicBlock.end){
        --actPosSz;
    }

    if(actPosSz === 0)
        return [{
                    nameId: basicBlock.nameId,
                    start: basicBlock.start,
                    end: basicBlock.end
               }];
    else if(actPosSz == 1)
            return [
                {nameId: basicBlock.nameId,start: basicBlock.start,end:actionPos[0]+1},
                {nameId: basicBlock.nameId+"@"+ (actionPos[0]+1),start: actionPos[0]+1,end: basicBlock.end}
               ];
    else {
        var first = 0;
        var last = actPosSz;
        var blockName = basicBlock.nameId;
        var tmp = {nameId:blockName, start:basicBlock.start, end:actionPos[first]+1 };
        var result = [];
        result.push(_.clone(tmp));
        while(first != last-1) {
            tmp.nameId = blockName + "@" + (actionPos[first]+1);
            tmp.start = actionPos[first]+1;
            ++first;
            tmp.end = actionPos[first]+1;
            result.push(_.clone(tmp));
        }

        tmp = {nameId:blockName + "@" + (actionPos[first] + 1), start:actionPos[first] + 1, end:basicBlock.end };
        result.push(_.clone(tmp));

        return result;

    }

};

ControlFlowGraph.prototype.concatInstructions = function (asmArr, first, last) {
  return _.chain(asmArr.slice(first, last))
    .map(function (x) { return x.text.substr(0, 50); })
    .value()
    .join("\n");
};

ControlFlowGraph.prototype.makeNodes = function (asmArr, arrOfCanonicalBasicBlock) {
    var node = {};
    var nodes = [];

    _.each(arrOfCanonicalBasicBlock, _.bind(function(x){
        logger.debug("node name:");
        logger.debug(x.nameId);
        node.id = x.nameId;
        node.label = x.nameId + ((x.nameId.indexOf(":") !== -1)? "":":") +"\n"+
                         this.concatInstructions(asmArr,x.start, x.end);
        node.color = "#99ccff";//"#FFAFAF";
        node.shape = 'box';
        nodes.push(_.clone(node));
    }, this));
    return nodes;
};

ControlFlowGraph.prototype.makeEdges = function (asmArr, arrOfCanonicalBasicBlock, instructionType, extractNodeNameFromInstruction) {
    var edge = {};
    var edges = [];

    var setEdge = function(edge, sourceNode, targetNode, color) {
        edge.from = sourceNode;
        edge.to = targetNode;
        edge.arrows = "to";
        edge.color = color;
    };
    var isBasicBlockEnd = this.rules.isBasicBlockEnd;

    var hasName = function(asmArr, cbb){
        return isBasicBlockEnd(asmArr[cbb.end].text);
    };

    var generateName = function(name, suffix){
        var pos = name.indexOf("@");
        if(pos == -1)
            return name + "@" + suffix;

        return name.substring(0, pos+1)+ suffix;
    };

    // note: x.end-1 possible value: jmp .L*, {jne,je,jg,...} .L*, ret/rep ret, call and any other instruction that doesn't change control flow

    //for(var x of arrOfCanonicalBasicBlock) {
    _.each(arrOfCanonicalBasicBlock, function(x){
        var targetNode;
        var lastInst = asmArr[x.end-1].text;
        switch(instructionType(lastInst)) {
            case InstructionType_jmp:
                {

                //we have to deal only with jmp destination, jmp instruction are always taken.
                    //edge from jump inst
                    logger.debug("jmp");
                    targetNode = extractNodeNameFromInstruction(lastInst);
                    setEdge(edge, x.nameId, targetNode, 'blue');

                    edges.push(_.clone(edge));
                    logger.debug(edge);
                }
                break;
            case InstructionType_conditionalJmpInst:
                {
                    logger.debug("condit jmp");
                //deal with : branche taken, branch not taken
                    targetNode = extractNodeNameFromInstruction(lastInst);
                    setEdge(edge, x.nameId, targetNode, 'green');
                    edges.push(_.clone(edge));
                    logger.debug(edge);

                    targetNode = hasName(asmArr,x)? asmArr[x.end].text: generateName(x.nameId, x.end);
                    setEdge(edge, x.nameId, targetNode, "red");
                    edges.push(_.clone(edge));

                    logger.debug(edge);
                }
                break;
            case InstructionType_notRetInst:
                {
                //precondition: lastInst is not last instruction in asmArr (but it is in canonical basic block)
                //note : asmArr[x.end] expected to be .L*:(name of a basic block)
                //       this .L*: has to be exactly after the last instruction in the current canocial basic block
                if(asmArr[x.end]){
                    targetNode = asmArr[x.end].text;
                    setEdge(edge, x.nameId, targetNode, 'grey');
                    edges.push(_.clone(edge));
                    logger.debug("not ret inst");
                    logger.debug(edge);
                 }else {
                     logger.debug(x);
                 }
                }
                break;
            case InstructionType_retInst:
                    logger.debug("expect ret instruction or it's variants(rep ret): "+ lastInst);
                break;
        }
    });

    logger.debug(edges);

    return edges;
};

ControlFlowGraph.prototype.generateCfgStructure = function (asmArr){

    var self = this;
    var code = this.rules.filterData(asmArr);

    var funcs = this.splitToFunctions(code, this.rules.isFunctionEnd);

    if (!funcs.length) {
        return funcs;
    }

    var result = {};

    //for (var rng of funcs) {
    _.each(funcs, _.bind(function(rng){
        var basicBlocks = self.splitToBasicBlocks(code, rng, this.rules.isBasicBlockEnd,
                                                   this.rules.isJmpInstruction);
        var arrOfCanonicalBasicBlock = [];
        //for (var elm of basicBlocks) {
        _.each(basicBlocks, function(elm){
            var tmp = self.splitToCanonicalBasicBlock(elm);
            arrOfCanonicalBasicBlock = arrOfCanonicalBasicBlock.concat(tmp);
        });

        result[code[rng.start].text] = {
            nodes:this.makeNodes(code, arrOfCanonicalBasicBlock),
            edges: this.makeEdges(code, arrOfCanonicalBasicBlock,
                this.rules.getInstructionType,
                this.rules.extractNodeName)
            };
        }, this));

    return result;

};

module.exports.ControlFlowGraph = ControlFlowGraph;<commit_msg>add semicolon<commit_after>// Copyright (c) 2012-2017, Najjar Chedy
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

var _ = require('underscore-node'),
    logger = require('./logger').logger;

var InstructionType_jmp = 0;
var InstructionType_conditionalJmpInst = 1;
var InstructionType_notRetInst = 2;
var InstructionType_retInst = 3;

// this mess is intended to be ported to another file.
function separateCodeFromData(asmArr) {

    var labelRegex = /\.L{1}\d+:/;

    var isCode = function(x){
        return  x && x.text && (x.source !== null || labelRegex.test(x.text) ||
                !x.text.includes('.'));
    };

    return _.chain(asmArr)
            .map(_.clone)
            .filter(isCode)
            .value();
}

    var gccX86 = {
                filterData:  separateCodeFromData,
                isFunctionEnd:function(x)  { return ((x[0] != ' ') && (x[0] != '.') &&
                                                             (x.indexOf(':') != -1)) ;},

                isBasicBlockEnd: function(x) { return x[0] == ".";} ,

                getInstructionType:function(inst) {
                                            if(inst.includes("jmp")) return InstructionType_jmp;
                                            else if(inst.trim()[0] === 'j') return InstructionType_conditionalJmpInst;
                                            else if(!inst.includes(" ret")) return InstructionType_notRetInst;
                                            else return InstructionType_retInst;
                                          },

                extractNodeName:function(inst) {
                                     var name = inst.match(/\.L{1}\d+/);
                                     return name + ":";
                                  },

                isJmpInstruction: function(x) { var trimed = x.trim(); return ( trimed[0] === 'j');}
             };

function separateCodeFromData_(asmArr) {

    var labelRegex = /\.LBB\d+_\d+:/;
    var isFunctionName = function(x){
        var functionNameRegex = /((\w|\(|\))*): # @\1/;
        return functionNameRegex.test(x);
    };

    var isCode = function(x){
        return  x && x.text && (x.source !== null || labelRegex.test(x.text) ||
                isFunctionName(x.text));
    };

    var removeComments = function(x) {
        var pos = x.text.indexOf("#");
        if(pos !== -1)
            x.text = x.text.substring(0, pos-1);
        return x;
    };

    return _.chain(asmArr)
            .map(_.clone)
            .filter(isCode)
            .map(removeComments)
            .value();
}

    var clangX86 = {
                filterData:  separateCodeFromData_,
                isFunctionEnd:function(x)  { return ((x[0] !== ' ') && (x[0] !== '.') &&
                                                             (x.indexOf(':') !== -1)) ;},

                isBasicBlockEnd: function(x) { return x[0] == ".";} ,

                getInstructionType:function(inst) {
                                            if(inst.includes("jmp")) return InstructionType_jmp;
                                            else if(inst.trim()[0] === 'j') return InstructionType_conditionalJmpInst;
                                            else if(!inst.includes(" ret")) return InstructionType_notRetInst;
                                            else return InstructionType_retInst;
                                          },

                extractNodeName:function(inst) {
                                     var name = inst.match(/\.LBB\d+_\d+/);
                                     return name + ":";
                                  },

                isJmpInstruction: function(x) { var trimed = x.trim(); return ( trimed[0] == 'j');}
             };

function ControlFlowGraph(compiler){
    if(compiler.includes("clang"))
        this.rules = clangX86;
    else
        this.rules = gccX86;
}

ControlFlowGraph.prototype.splitToFunctions = function (asmArr, isEnd) {
    if(asmArr.length === 0)   return [];
    var result = [];
    var first = 0;
    var last = asmArr.length;
    var fnRange = {start:first, end:null};
    ++first;
    console.log(asmArr);
    while(first != last) {
        if(isEnd(asmArr[first].text)) {
            fnRange.end = first;
            result.push(_.clone(fnRange));
            fnRange.start = first;
        }
        ++first;
    }

    fnRange.end = last;
    result.push(_.clone(fnRange));
    return result;
};

ControlFlowGraph.prototype.splitToBasicBlocks = function (asmArr,  range, isEnd, isJmp) {
    var first = range.start;
    var last = range.end;
    if(first == last) return [];
    var functionName = asmArr[first].text;
    ++first;

    var rangeBb = {nameId:functionName.substr(0,50) , start:first , end:null, actionPos:[]};
    var result = [];

    var newRangeWith = function (oldRange, nameId, start) {
        return {nameId: nameId, start: start, actionPos: [], end: oldRange.end};
    };

    while(first != last) {
        var inst = asmArr[first].text;
        if(isEnd(inst)) {
            rangeBb.end = first;
            result.push(_.clone(rangeBb));
            ++first;
            //inst is expected to be .L*: where * in 1,2,...
            rangeBb = newRangeWith(rangeBb, inst, first);
        }
        else if(isJmp(inst)) {
                rangeBb.actionPos.push(first);
        }
        ++first;
    }

    rangeBb.end = last;
    result.push(_.clone(rangeBb));
    return result;
};

ControlFlowGraph.prototype.splitToCanonicalBasicBlock = function (basicBlock) {
    var actionPos = basicBlock.actionPos;
    var actPosSz = actionPos.length;
    if (actionPos[actPosSz-1] + 1 == basicBlock.end){
        --actPosSz;
    }

    if(actPosSz === 0)
        return [{
                    nameId: basicBlock.nameId,
                    start: basicBlock.start,
                    end: basicBlock.end
               }];
    else if(actPosSz == 1)
            return [
                {nameId: basicBlock.nameId,start: basicBlock.start,end:actionPos[0]+1},
                {nameId: basicBlock.nameId+"@"+ (actionPos[0]+1),start: actionPos[0]+1,end: basicBlock.end}
               ];
    else {
        var first = 0;
        var last = actPosSz;
        var blockName = basicBlock.nameId;
        var tmp = {nameId:blockName, start:basicBlock.start, end:actionPos[first]+1 };
        var result = [];
        result.push(_.clone(tmp));
        while(first != last-1) {
            tmp.nameId = blockName + "@" + (actionPos[first]+1);
            tmp.start = actionPos[first]+1;
            ++first;
            tmp.end = actionPos[first]+1;
            result.push(_.clone(tmp));
        }

        tmp = {nameId:blockName + "@" + (actionPos[first] + 1), start:actionPos[first] + 1, end:basicBlock.end };
        result.push(_.clone(tmp));

        return result;

    }

};

ControlFlowGraph.prototype.concatInstructions = function (asmArr, first, last) {
  return _.chain(asmArr.slice(first, last))
    .map(function (x) { return x.text.substr(0, 50); })
    .value()
    .join("\n");
};

ControlFlowGraph.prototype.makeNodes = function (asmArr, arrOfCanonicalBasicBlock) {
    var node = {};
    var nodes = [];

    _.each(arrOfCanonicalBasicBlock, _.bind(function(x){
        logger.debug("node name:");
        logger.debug(x.nameId);
        node.id = x.nameId;
        node.label = x.nameId + ((x.nameId.indexOf(":") !== -1)? "":":") +"\n"+
                         this.concatInstructions(asmArr,x.start, x.end);
        node.color = "#99ccff";//"#FFAFAF";
        node.shape = 'box';
        nodes.push(_.clone(node));
    }, this));
    return nodes;
};

ControlFlowGraph.prototype.makeEdges = function (asmArr, arrOfCanonicalBasicBlock, instructionType, extractNodeNameFromInstruction) {
    var edge = {};
    var edges = [];

    var setEdge = function(edge, sourceNode, targetNode, color) {
        edge.from = sourceNode;
        edge.to = targetNode;
        edge.arrows = "to";
        edge.color = color;
    };
    var isBasicBlockEnd = this.rules.isBasicBlockEnd;

    var hasName = function(asmArr, cbb){
        return isBasicBlockEnd(asmArr[cbb.end].text);
    };

    var generateName = function(name, suffix){
        var pos = name.indexOf("@");
        if(pos == -1)
            return name + "@" + suffix;

        return name.substring(0, pos+1)+ suffix;
    };

    // note: x.end-1 possible value: jmp .L*, {jne,je,jg,...} .L*, ret/rep ret, call and any other instruction that doesn't change control flow

    //for(var x of arrOfCanonicalBasicBlock) {
    _.each(arrOfCanonicalBasicBlock, function(x){
        var targetNode;
        var lastInst = asmArr[x.end-1].text;
        switch(instructionType(lastInst)) {
            case InstructionType_jmp:
                {

                //we have to deal only with jmp destination, jmp instruction are always taken.
                    //edge from jump inst
                    logger.debug("jmp");
                    targetNode = extractNodeNameFromInstruction(lastInst);
                    setEdge(edge, x.nameId, targetNode, 'blue');

                    edges.push(_.clone(edge));
                    logger.debug(edge);
                }
                break;
            case InstructionType_conditionalJmpInst:
                {
                    logger.debug("condit jmp");
                //deal with : branche taken, branch not taken
                    targetNode = extractNodeNameFromInstruction(lastInst);
                    setEdge(edge, x.nameId, targetNode, 'green');
                    edges.push(_.clone(edge));
                    logger.debug(edge);

                    targetNode = hasName(asmArr,x)? asmArr[x.end].text: generateName(x.nameId, x.end);
                    setEdge(edge, x.nameId, targetNode, "red");
                    edges.push(_.clone(edge));

                    logger.debug(edge);
                }
                break;
            case InstructionType_notRetInst:
                {
                //precondition: lastInst is not last instruction in asmArr (but it is in canonical basic block)
                //note : asmArr[x.end] expected to be .L*:(name of a basic block)
                //       this .L*: has to be exactly after the last instruction in the current canocial basic block
                if(asmArr[x.end]){
                    targetNode = asmArr[x.end].text;
                    setEdge(edge, x.nameId, targetNode, 'grey');
                    edges.push(_.clone(edge));
                    logger.debug("not ret inst");
                    logger.debug(edge);
                 }else {
                     logger.debug(x);
                 }
                }
                break;
            case InstructionType_retInst:
                    logger.debug("expect ret instruction or it's variants(rep ret): "+ lastInst);
                break;
        }
    });

    logger.debug(edges);

    return edges;
};

ControlFlowGraph.prototype.generateCfgStructure = function (asmArr){

    var self = this;
    var code = this.rules.filterData(asmArr);

    var funcs = this.splitToFunctions(code, this.rules.isFunctionEnd);

    if (!funcs.length) {
        return funcs;
    }

    var result = {};

    //for (var rng of funcs) {
    _.each(funcs, _.bind(function(rng){
        var basicBlocks = self.splitToBasicBlocks(code, rng, this.rules.isBasicBlockEnd,
                                                   this.rules.isJmpInstruction);
        var arrOfCanonicalBasicBlock = [];
        //for (var elm of basicBlocks) {
        _.each(basicBlocks, function(elm){
            var tmp = self.splitToCanonicalBasicBlock(elm);
            arrOfCanonicalBasicBlock = arrOfCanonicalBasicBlock.concat(tmp);
        });

        result[code[rng.start].text] = {
            nodes:this.makeNodes(code, arrOfCanonicalBasicBlock),
            edges: this.makeEdges(code, arrOfCanonicalBasicBlock,
                this.rules.getInstructionType,
                this.rules.extractNodeName)
            };
        }, this));

    return result;

};

module.exports.ControlFlowGraph = ControlFlowGraph;<|endoftext|>


<commit_before>var juice = require('../juice'),
    Validation = require('./validation').Validation,
    filesystem = require('filesystem-base');

var Application = function Application() {
  this.actions = {};
  this.urls = {};
  this.controllers = {};
  this.models = {};
  this.helpers = {
    source : function( x ) { return x.toSource(); }
  };

  Object.defineProperty(this, 'setupRun', { writable: true, value: false });
}

exports.Application = Application;

Application.prototype.setup = function setup(docRoot) {
  var self = this;

  if (docRoot !== undefined)
    this.docRoot = filesystem.canonical(docRoot);

  var closure = function Juice_App_jsgi(request) { return self.run(request) };
  // Store the app itself, so that we can mess with it in the test etc if we need to
  closure.juiceApp = self;
  if (this.setupRun)
    return closure;

  for (k in this.urls) {
    this.actions[ k ] = this.buildAction( k, this.urls[ k ] );
  }

  this.setupRun = true;

  return closure;
}

// Separate function so juice/test can override it
Application.prototype.buildContext = function buildContext(request) {
  return new juice.Context(this,request);
}

Application.prototype.run = function run(request) {
  var app = this,
      ctx = app.buildContext(request),
      url = request.pathInfo;

  ctx.format = request.headers.accept &&
               request.headers.accept.match( 'application/json' )
             ? 'json'
             : 'html';

  // Dispatch the url
  try {
    for (k in ctx.actions) {
      let info = ctx.actions[ k ];

      let re = info.__matcher;

      var res = re(url);

      if (!res)
        continue;

      // We matched! dispatch to the action

      // We assume that re is infact a regexp, so res is an array of the form:
      //
      //  [ 'whole matched text', 'capture1', ... 'captureN' ]
      //
      //  We dont want the whole matched text.
      res.shift();

      return ctx.runAction(res, info);
    }

    // 404!
    return ctx.notFound(request);
  }
  catch (e) {
    return ctx.handleError(request, e);
  }
}

/**
 * CSS for built in 404 and 500 error pages.
 */
Application.prototype.errorCss = [
'    * { margin: 0; padding: 0; }',
'    body { background: #eee; color: #000; font: 200 87.5%/1.5 "Lucida Grande", Calibri, sans-serif; }',
'    #overview { background: #79d108; border-bottom: 1.5em solid #90e71d; padding: 1.5em; }',
'    #details { padding: 1.5em; }',
'    h1 { font-size: 1.5em; font-weight: 200; line-height: 1; }',
'    dl { font-family: Consolas, monospace; margin-top: 1.5em; overflow: auto; }',
'    dt { clear: both; float: left; margin-right: 1em; text-align: right; width: 9em; }',
'    dd { float: left; }',
'    ol { font-family: Consolas, monospace; margin: 1.5em; }',
'    code { background: #a6f540; color: #000; font-family: Consolas, monospace; font-size: 1em; padding: 0.125em; }',
'    ol.stacktrace {display: table }',
'    ol.stacktrace li { display: table-row; }',
'    ol.stacktrace span { display: table-cell; }',
'    ol.stacktrace span:first-child { padding-right: 1em; text-align: right; }',
'    ol.stacktrace span:last-child { padding-left: 1em; }'
].join('\n');

/**
 * Template to render when no actions match
 */
Application.prototype.notFoundTemplate = <><![CDATA[<!DOCTYPE html>

  <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
  <title>Page not found</title>

Page not found — 404

URL:

[% url %]

Method:

[%request.requestMethod%]

Juice tried to match the URL [% request.pathInfo %] against the
    following patterns, but failed to find a match.

[% FOREACH k IN actions %]

[% k.key %]

[% END -%]

]]></> + "";

Application.prototype.notFound = function(request) {
  var url = request.scheme
          + "://"
          + request.serverName
          + ":"
          + request.serverPort
          + request.scriptName
          + request.pathInfo,
      contentType = this.format === "json" ? "application/json" : "text/html",
      data = { url: url,
               actions: this.actions,
               request: request,
               css: this.errorCss },
      tt = new ( require( "Template" ).Template )();

  return {
    status: 404,
    headers: { contentType : contentType },
    body: this.format === "json"
      ? [ JSON.stringify( data ) ]
      : [ tt.process( this.notFoundTemplate, data ) ]
  };
}

/**
 * Template to render when handling an action throws an error
 */
Application.prototype.errorTemplate = <><![CDATA[<!DOCTYPE html>

  <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
  <title>Server error</title>

[% errorType %] — 500

Error message:

[% e.message || e %]

URL:

[% url %]

Method:

[% request.requestMethod %]

[% IF e.fileName %]

File:

[%e.fileName%]

[% END;
   IF e.lineNumber %]

Line number:

[%e.lineNumber%]

[% END %]

[% IF !frames.length %]

Sorry, no stack trace is avilable

[% ELSE %]

[% FOREACH f IN frames; %]

[%
              IF f.function;
                "$f.function";
              ELSE;
                "Unknown";
              END -%]
          @

            [%-
              IF f.fileName;
                "$f.fileName";
              ELSE;
                "Unknown";
              END;
              IF f.lineNumber;
                ", $f.lineNumber";
              END;
          -%]

[% END %]

[% END %]

]]></> + "";

Application.prototype.handleError = function(request, e) {
  var url = request.scheme
          + "://"
          + request.serverName
          + ":"
          + request.serverPort
          + request.scriptName
          + request.pathInfo,
      contentType = this.format === "json" ? "application/json" : "text/html",
      errorType = (e.constructor ? e.constructor.name : false) || "Unknown Error",
      msg = e.message || e,
      data = { url: url,
               e: e,
               errorType: errorType,
               request: request,
               css: this.errorCss },
      tt = new ( require( "Template" ).Template )();

  if (e.stack)
    data.frames = juice.Utils.splitErrorStack(e.stack);

  return {
    status: 500,
    headers: { contentType : contentType },
    body: this.format === "json"
      ? [ JSON.stringify( data ) ]
      : [ tt.process( this.errorTemplate, data ) ]
  };
}

// Compat: remove in 0.3
Application.prototype.getAction = function( context, name ) {
  return this.controller( name );
}

Application.prototype.controller = function( name ) {
  var context = this.controllers;
  for ( var [k,v] in Iterator( name.split( '.' ) ) ) {
    if ( v in context == false )
      throw new TypeError( name + " cannot be found in the controllers" );
    context = context[ v ];
  }
  return context;
}

Application.prototype.buildAction = function( url, action ) {
  if ( typeof action == "function" || typeof action == "string" )
    action = { action : action };

  // handle sub apps
  if ( "app" in action ) {
    return this.buildSubappAction( action, url );
  }

  if ( typeof action.action == "string" ) {
    if ( ! action.render && ! action.redirect )
      action.render = action.action.replace( '.', '/', 'g' );
    action.action = this.controller( action.action );
  }

  if ("static" in action)
    return this.buildStaticAction(action, url)

  if ( "__matcher" in action && typeof action.__matcher != "function" )
    throw new TypeError( "action.__matcher is not a function" );

  if ( "__matcher" in action == false )
    action.__matcher = new RegExp( "^" + url + "$" );

  return action;
}

Application.prototype.buildSubappAction =
function buildSubappAction( action, url ) {
  // find the app to include
  var subapp = require( action.app + "/lib/app.js" ).app.juiceApp;
  delete action.app;

  // if no name is provided default to the url, cleaned up
  if ( ! "name" in action ) {
    // convert special characters to underscores, slashes to dots
    action.name = url.replace( /^\//, "" )
                     .replace( /[^A-Za-z0-9_\/]/g, "_" )
                     .replace( /\//g, "." );
  }

  // merge the subapp models into the main app
  var names = action.name.split( "." ),
      models = subapp.models;

  // dots should make a lovely hierarchy
  while ( names.length > 1 ) {
    let obj = {};
    obj[ names.pop() ] = models;
    models = obj;
  }

  this.models[ names.pop() ] = models;

  // dispatcher for subapp requests
  action.action = function( newurl ) {
    var request = this.request;

    // change the URL to the new one
    request.pathInfo = newurl || "";

    // stick the base in as the script name
    request.scriptName = url;

    // require the subapp and repeat the request
    return subapp.run( request );
  }

  // catchall matcher for subapp urls
  if ( ! "__matcher" in action == false )
    action.__matcher = new RegExp( "^" + url + "(/.*)?$" );

  // it's raw since the subapp already did the hard work
  action.raw = true;

  return action;
}

Application.prototype.buildStaticAction =
function buildStaticAction(action, url) {
  var app = this,
      urlRe;

  // Relative path - prepend the app.docRoot to it
  if ("docRoot" in app &&
      !action.static.match(/^(?:[a-z]:[\\\/]|\/)/i)) {
    action.static = filesystem.canonical(app.docRoot + action.static);
  }

  // Ensure there is a trailing slash
  action.static = action.static.replace(/\/?$/, '/');
  url = url.replace(/\/?$/, '/');

  // Does the static file/dir reuqested exist;
  if (!filesystem.exists(action.static)) {
    throw new Error("static entry "+ uneval(action.static) +
                    " for " + uneval(url) + " does not exist");
  }
  urlRe = new RegExp('^' + url);

  action.__matcher = function(url) {

    if (!urlRe(url)) {
      return undefined;
    }
    // TODO: Do we need to worry about '..'?
    //       Or about URL decoding?
    var file = url.replace(urlRe, '');

    if (!filesystem.exists(action.static + file))
      return undefined; // Not found, fall back to default 404

    // a[0] gets stripped, and a[1] becomes first argument to action
    return [url, file];
  }
  action.action = function(file) {
    return {
      status: 200,
      headers: {
        'x-sendfile': action.static + file,
        'last-modified': juice.Utils.formatHTTPDate(
                            filesystem.lastModified(action.static +file)
                         )
      },
      body: []
    }
  }

  // This action returns a raw JSGI response - in this case to send just
  // X-SendFile headers
  action.raw = true;
  return action;
}

Application.prototype.validate = function( form, params ) {
  var data = {};
  // loop through |params|, stick values into |field| key of the spec.
  for (let [field, spec] in Iterator(form)) {
    data[field] = juice.Utils.clone(spec, true);
    // use input: key if it exists, else take key in fields: struct
    if ("input" in spec) data[field].value = params[spec.input];
    else if (field in params) data[field].value = params[field];
  }

  return (new Validation(data)).validate();
}

Application.prototype.config = function( key ) {
  var path = this.docRoot + 'conf/' + key + '.json';
  return filesystem.isFile( path )
    ? JSON.parse( filesystem.rawOpen( path ).readWhole() )
    : undefined;
}
<commit_msg>Fix conditionals<commit_after>var juice = require('../juice'),
    Validation = require('./validation').Validation,
    filesystem = require('filesystem-base');

var Application = function Application() {
  this.actions = {};
  this.urls = {};
  this.controllers = {};
  this.models = {};
  this.helpers = {
    source : function( x ) { return x.toSource(); }
  };

  Object.defineProperty(this, 'setupRun', { writable: true, value: false });
}

exports.Application = Application;

Application.prototype.setup = function setup(docRoot) {
  var self = this;

  if (docRoot !== undefined)
    this.docRoot = filesystem.canonical(docRoot);

  var closure = function Juice_App_jsgi(request) { return self.run(request) };
  // Store the app itself, so that we can mess with it in the test etc if we need to
  closure.juiceApp = self;
  if (this.setupRun)
    return closure;

  for (k in this.urls) {
    this.actions[ k ] = this.buildAction( k, this.urls[ k ] );
  }

  this.setupRun = true;

  return closure;
}

// Separate function so juice/test can override it
Application.prototype.buildContext = function buildContext(request) {
  return new juice.Context(this,request);
}

Application.prototype.run = function run(request) {
  var app = this,
      ctx = app.buildContext(request),
      url = request.pathInfo;

  ctx.format = request.headers.accept &&
               request.headers.accept.match( 'application/json' )
             ? 'json'
             : 'html';

  // Dispatch the url
  try {
    for (k in ctx.actions) {
      let info = ctx.actions[ k ];

      let re = info.__matcher;

      var res = re(url);

      if (!res)
        continue;

      // We matched! dispatch to the action

      // We assume that re is infact a regexp, so res is an array of the form:
      //
      //  [ 'whole matched text', 'capture1', ... 'captureN' ]
      //
      //  We dont want the whole matched text.
      res.shift();

      return ctx.runAction(res, info);
    }

    // 404!
    return ctx.notFound(request);
  }
  catch (e) {
    return ctx.handleError(request, e);
  }
}

/**
 * CSS for built in 404 and 500 error pages.
 */
Application.prototype.errorCss = [
'    * { margin: 0; padding: 0; }',
'    body { background: #eee; color: #000; font: 200 87.5%/1.5 "Lucida Grande", Calibri, sans-serif; }',
'    #overview { background: #79d108; border-bottom: 1.5em solid #90e71d; padding: 1.5em; }',
'    #details { padding: 1.5em; }',
'    h1 { font-size: 1.5em; font-weight: 200; line-height: 1; }',
'    dl { font-family: Consolas, monospace; margin-top: 1.5em; overflow: auto; }',
'    dt { clear: both; float: left; margin-right: 1em; text-align: right; width: 9em; }',
'    dd { float: left; }',
'    ol { font-family: Consolas, monospace; margin: 1.5em; }',
'    code { background: #a6f540; color: #000; font-family: Consolas, monospace; font-size: 1em; padding: 0.125em; }',
'    ol.stacktrace {display: table }',
'    ol.stacktrace li { display: table-row; }',
'    ol.stacktrace span { display: table-cell; }',
'    ol.stacktrace span:first-child { padding-right: 1em; text-align: right; }',
'    ol.stacktrace span:last-child { padding-left: 1em; }'
].join('\n');

/**
 * Template to render when no actions match
 */
Application.prototype.notFoundTemplate = <><![CDATA[<!DOCTYPE html>

  <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
  <title>Page not found</title>

Page not found — 404

URL:

[% url %]

Method:

[%request.requestMethod%]

Juice tried to match the URL [% request.pathInfo %] against the
    following patterns, but failed to find a match.

[% FOREACH k IN actions %]

[% k.key %]

[% END -%]

]]></> + "";

Application.prototype.notFound = function(request) {
  var url = request.scheme
          + "://"
          + request.serverName
          + ":"
          + request.serverPort
          + request.scriptName
          + request.pathInfo,
      contentType = this.format === "json" ? "application/json" : "text/html",
      data = { url: url,
               actions: this.actions,
               request: request,
               css: this.errorCss },
      tt = new ( require( "Template" ).Template )();

  return {
    status: 404,
    headers: { contentType : contentType },
    body: this.format === "json"
      ? [ JSON.stringify( data ) ]
      : [ tt.process( this.notFoundTemplate, data ) ]
  };
}

/**
 * Template to render when handling an action throws an error
 */
Application.prototype.errorTemplate = <><![CDATA[<!DOCTYPE html>

  <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
  <title>Server error</title>

[% errorType %] — 500

Error message:

[% e.message || e %]

URL:

[% url %]

Method:

[% request.requestMethod %]

[% IF e.fileName %]

File:

[%e.fileName%]

[% END;
   IF e.lineNumber %]

Line number:

[%e.lineNumber%]

[% END %]

[% IF !frames.length %]

Sorry, no stack trace is avilable

[% ELSE %]

[% FOREACH f IN frames; %]

[%
              IF f.function;
                "$f.function";
              ELSE;
                "Unknown";
              END -%]
          @

            [%-
              IF f.fileName;
                "$f.fileName";
              ELSE;
                "Unknown";
              END;
              IF f.lineNumber;
                ", $f.lineNumber";
              END;
          -%]

[% END %]

[% END %]

]]></> + "";

Application.prototype.handleError = function(request, e) {
  var url = request.scheme
          + "://"
          + request.serverName
          + ":"
          + request.serverPort
          + request.scriptName
          + request.pathInfo,
      contentType = this.format === "json" ? "application/json" : "text/html",
      errorType = (e.constructor ? e.constructor.name : false) || "Unknown Error",
      msg = e.message || e,
      data = { url: url,
               e: e,
               errorType: errorType,
               request: request,
               css: this.errorCss },
      tt = new ( require( "Template" ).Template )();

  if (e.stack)
    data.frames = juice.Utils.splitErrorStack(e.stack);

  return {
    status: 500,
    headers: { contentType : contentType },
    body: this.format === "json"
      ? [ JSON.stringify( data ) ]
      : [ tt.process( this.errorTemplate, data ) ]
  };
}

// Compat: remove in 0.3
Application.prototype.getAction = function( context, name ) {
  return this.controller( name );
}

Application.prototype.controller = function( name ) {
  var context = this.controllers;
  for ( var [k,v] in Iterator( name.split( '.' ) ) ) {
    if ( v in context == false )
      throw new TypeError( name + " cannot be found in the controllers" );
    context = context[ v ];
  }
  return context;
}

Application.prototype.buildAction = function( url, action ) {
  if ( typeof action == "function" || typeof action == "string" )
    action = { action : action };

  // handle sub apps
  if ( "app" in action ) {
    return this.buildSubappAction( action, url );
  }

  if ( typeof action.action == "string" ) {
    if ( ! action.render && ! action.redirect )
      action.render = action.action.replace( '.', '/', 'g' );
    action.action = this.controller( action.action );
  }

  if ("static" in action)
    return this.buildStaticAction(action, url)

  if ( "__matcher" in action && typeof action.__matcher != "function" )
    throw new TypeError( "action.__matcher is not a function" );

  if ( "__matcher" in action == false )
    action.__matcher = new RegExp( "^" + url + "$" );

  return action;
}

Application.prototype.buildSubappAction =
function buildSubappAction( action, url ) {
  // find the app to include
  var subapp = require( action.app + "/lib/app.js" ).app.juiceApp;
  delete action.app;

  // if no name is provided default to the url, cleaned up
  if ( "name" in action === false ) {
    // convert special characters to underscores, slashes to dots
    action.name = url.replace( /^\//, "" )
                     .replace( /[^A-Za-z0-9_\/]/g, "_" )
                     .replace( /\//g, "." );
  }

  // merge the subapp models into the main app
  var names = action.name.split( "." ),
      models = subapp.models;

  // dots should make a lovely hierarchy
  while ( names.length > 1 ) {
    let obj = {};
    obj[ names.pop() ] = models;
    models = obj;
  }

  this.models[ names.pop() ] = models;

  // dispatcher for subapp requests
  action.action = function( newurl ) {
    var request = this.request;

    // change the URL to the new one
    request.pathInfo = newurl || "";

    // stick the base in as the script name
    request.scriptName = url;

    // require the subapp and repeat the request
    return subapp.run( request );
  }

  // catchall matcher for subapp urls
  if ( "__matcher" in action === false )
    action.__matcher = new RegExp( "^" + url + "(/.*)?$" );

  // it's raw since the subapp already did the hard work
  action.raw = true;

  return action;
}

Application.prototype.buildStaticAction =
function buildStaticAction(action, url) {
  var app = this,
      urlRe;

  // Relative path - prepend the app.docRoot to it
  if ("docRoot" in app &&
      !action.static.match(/^(?:[a-z]:[\\\/]|\/)/i)) {
    action.static = filesystem.canonical(app.docRoot + action.static);
  }

  // Ensure there is a trailing slash
  action.static = action.static.replace(/\/?$/, '/');
  url = url.replace(/\/?$/, '/');

  // Does the static file/dir reuqested exist;
  if (!filesystem.exists(action.static)) {
    throw new Error("static entry "+ uneval(action.static) +
                    " for " + uneval(url) + " does not exist");
  }
  urlRe = new RegExp('^' + url);

  action.__matcher = function(url) {

    if (!urlRe(url)) {
      return undefined;
    }
    // TODO: Do we need to worry about '..'?
    //       Or about URL decoding?
    var file = url.replace(urlRe, '');

    if (!filesystem.exists(action.static + file))
      return undefined; // Not found, fall back to default 404

    // a[0] gets stripped, and a[1] becomes first argument to action
    return [url, file];
  }
  action.action = function(file) {
    return {
      status: 200,
      headers: {
        'x-sendfile': action.static + file,
        'last-modified': juice.Utils.formatHTTPDate(
                            filesystem.lastModified(action.static +file)
                         )
      },
      body: []
    }
  }

  // This action returns a raw JSGI response - in this case to send just
  // X-SendFile headers
  action.raw = true;
  return action;
}

Application.prototype.validate = function( form, params ) {
  var data = {};
  // loop through |params|, stick values into |field| key of the spec.
  for (let [field, spec] in Iterator(form)) {
    data[field] = juice.Utils.clone(spec, true);
    // use input: key if it exists, else take key in fields: struct
    if ("input" in spec) data[field].value = params[spec.input];
    else if (field in params) data[field].value = params[field];
  }

  return (new Validation(data)).validate();
}

Application.prototype.config = function( key ) {
  var path = this.docRoot + 'conf/' + key + '.json';
  return filesystem.isFile( path )
    ? JSON.parse( filesystem.rawOpen( path ).readWhole() )
    : undefined;
}
<|endoftext|>


<commit_before>#!/usr/bin/env node

/**
 * Copyright 2016 Netsend.
 *
 * This file is part of PersDB.
 *
 * PersDB is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Affero General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * PersDB is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License along
 * with PersDB. If not, see <https://www.gnu.org/licenses/>.
 */

'use strict';

var fs = require('fs');

var async = require('async');
var hjson = require('hjson');
var level = require('level');
var program = require('commander');

var doDiff = require('../lib/diff');
var MergeTree = require('../lib/merge_tree');

program
  .version('0.0.1')
  .usage('[-f config] [-n]')
  .description('print graph of given database')
  .option('    --id <id>', 'show the log of one string based id')
  .option('-f, --config <config>', 'hjson config file with database path')
  .option('-s, --show', 'show complete objects')
  .option('-p, --patch', 'show patch compared to previous version')
  .option('    --pe <perspective>', 'perspective')
  .option('-a  --all', 'print the trees of all configured perspectives')
  .option('-n, --number <number>', 'number of latest versions to show, defaults to 10, 0 means unlimited')
  .parse(process.argv);

var config = {};
var configFile = program.config;

if (configFile) {
  config = hjson.parse(fs.readFileSync(configFile, 'utf8'));
}

if (program.number === '0') {
  program.number = Infinity;
} else {
  program.number = Number(program.number) || 10;
}

var id;
if (program.id) {
  id = program.id;
}
// format merge tree stats
function fmtStats(stats) {
  var result = '';
  Object.keys(stats).forEach(function(pe) {
    result += pe + ' heads:\t' + stats[pe].heads.count + '\n';

    if (stats[pe].heads.conflict) {
      result += '  conflict:\t' + stats[pe].heads.conflict + '\n';
    }

    if (stats[pe].heads.deleted) {
      result += '  deleted:\t' + stats[pe].heads.deleted + '\n';
    }
  });

  return result;
}

// shows new values of added and changed attributes
function fmtPatch(patch, newItem) {
  var result = {};
  Object.keys(patch).forEach(function(key) {
    if (patch[key] !== '-') {
      result[key] = newItem[key];
    }
  });
  return JSON.stringify(result);
}

function fmtItem(item, parents) {
  parents = parents || [];

  var out = '';
  if (program.show) {
    // print tree
    if (parents.length) {
      out += '\u250f ';
    } else {
      out += '\u257a ';
    }
    out += ' ' + JSON.stringify(item);
    if (parents.length) {
      out += '\n';
    }

    var i = 0;
    parents.forEach(function(p) {
      i++;
      if (i === parents.length) {
        out += '\u2517\u2501 ';
      } else {
        out += '\u2523\u2501 ';
      }
      var diff = doDiff(item.b || {}, p.b || {});
      out += ' ' + p.h.id + ' ' + p.h.v + ' diff: ' + JSON.stringify(diff) + ' ' + fmtPatch(diff, item.b);
      if (i !== parents.length) {
        out += '\n';
      }
    });
  } else {
    out += item.h.id;
    out += ' ' + item.h.v;
    if (item.h.hasOwnProperty('d') && item.h.d) {
      out += ' d';
    } else {
      out += ' .';
    }
    if (item.h.hasOwnProperty('c') && item.h.c) {
      out += ' c';
    } else {
      out += ' .';
    }
    out += ' ' + (item.h.pe || 'local');
    if (item.h.hasOwnProperty('i')) {
      out += ' (' + item.h.i + ')';
    }
    out += ' ' + JSON.stringify(item.h.pa);
    if (program.patch && parents.length) {
      parents.forEach(function(p) {
        var diff = doDiff(item.b || {}, p.b || {});
        if (parents.length > 1) {
          out += ' ' + p.h.v;
        }
        out += ' diff: ' + JSON.stringify(diff) + ' ' + fmtPatch(diff, item.b);
      });
    }
  }
  return out;
}

var noop = function() {};

function printTree(mt, tree, cb) {
  var counter = 0;
  console.log('%s:', tree.name);
  tree.iterateInsertionOrder({ reverse: true }, function(item, cb2) {
    counter++;

    if (!program.patch || !item.h.pa.length) {
      console.log(fmtItem(item));

      cb2(null, counter < program.number);
      return;
    }

    var parents = [];
    async.eachSeries(item.h.pa, function(pa, cb3) {
      mt.getByVersion(pa, function(err, p) {
        if (err) { cb3(err); return; }
        if (!p) {
          var error = new Error('parent not found');
          cb3(error);
          return;
        }

        parents.push(p);
        cb3();
      });
    }, function(err) {
      if (err) { cb2(err); return; }

      console.log(fmtItem(item, parents));

      cb2(null, counter < program.number);
    });
  }, cb);
}

function run(db, cfg, cb) {
  cfg = cfg || {};
  cfg.log = {
    err: noop,
    notice: noop,
    info: noop,
    debug: noop,
    debug2: noop
  };
  var mt = new MergeTree(db, cfg);

  mt.stats(function(err, stats) {
    if (err) { cb(err); return; }
    console.log(fmtStats(stats));

    var remoteTrees = mt.getRemoteTrees();

    if (program.all) {
      async.eachSeries(Object.keys(remoteTrees), function(name, cb2) {
        printTree(mt, remoteTrees[name], cb2);
      }, function(err) {
        if (err) { cb(err); return; }
        printTree(mt, mt.getStageTree(), function(err) {
          if (err) { cb(err); return; }
          printTree(mt, mt.getLocalTree(), function(err) {
            if (err) { cb(err); return; }
            cb();
          });
        });
      });
    } else {
      // print one tree, default to the local tree
      var tree, pe = program.pe || 'local';

      if (remoteTrees[pe]) {
        tree = remoteTrees[pe];
      } else if (pe === 'stage') {
        tree = mt.getStageTree();
      } else if (pe === 'local' || pe === mt._local.name) {
        tree = mt.getLocalTree();
      } else {
        cb(new Error('unknown perspective'));
        return;
      }

      printTree(mt, tree, cb);
    }
  });
}

if (config.dbs) {
  async.eachSeries(config.dbs, function(dbCfg, cb) {
    var chroot = dbCfg.chroot || '/var/persdb';
    var data = dbCfg.data || 'data';

    if (dbCfg.perspectives) {
      dbCfg.perspectives = dbCfg.perspectives.map(function(peCfg) {
        return peCfg.name;
      });
    }
    // open database
    level(chroot + '/' + data, { keyEncoding: 'binary', valueEncoding: 'binary' }, function(err, db) {
      if (err) { cb(err); return; }
      run(db, dbCfg, function(err) {
        db.close();
        if (err) { cb(err); return; }
      });
    });
  }, function(err) {
    if (err) { console.error(err); process.exit(2); }
  });
} else {
  // open database
  level('/var/persdb/data', { keyEncoding: 'binary', valueEncoding: 'binary' }, function(err, db) {
    if (err) { throw err; }
    run(db, {}, function(err) {
      db.close();
      if (err) { console.error(err); process.exit(2); }
    });
  });
}
<commit_msg>log.js: update option descriptions<commit_after>#!/usr/bin/env node

/**
 * Copyright 2016 Netsend.
 *
 * This file is part of PersDB.
 *
 * PersDB is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Affero General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * PersDB is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License along
 * with PersDB. If not, see <https://www.gnu.org/licenses/>.
 */

'use strict';

var fs = require('fs');

var async = require('async');
var hjson = require('hjson');
var level = require('level-packager')(require('leveldown'));
var program = require('commander');

var doDiff = require('../lib/diff');
var MergeTree = require('../lib/merge_tree');

program
  .version('0.0.2')
  .usage('[-f config] [-n]')
  .description('print graph of the given database')
  .option('    --id <id>', 'show the log of one string based id')
  .option('-f, --config <config>', 'hjson config file with database path')
  .option('-s, --show', 'show complete objects')
  .option('-p, --patch', 'show patch compared to previous version')
  .option('    --pe <perspective>', 'print only this perspective')
  .option('-a  --all', 'print all perspectives')
  .option('-n, --number <number>', 'number of latest versions to show, defaults to 10, 0 means unlimited')
  .parse(process.argv);

var config = {};
var configFile = program.config;

if (configFile) {
  config = hjson.parse(fs.readFileSync(configFile, 'utf8'));
}

if (program.number === '0') {
  program.number = Infinity;
} else {
  program.number = Number(program.number) || 10;
}

var id;
if (program.id) {
  id = program.id;
}
// format merge tree stats
function fmtStats(stats) {
  var result = '';
  Object.keys(stats).forEach(function(pe) {
    result += pe + ' heads:\t' + stats[pe].heads.count + '\n';

    if (stats[pe].heads.conflict) {
      result += '  conflict:\t' + stats[pe].heads.conflict + '\n';
    }

    if (stats[pe].heads.deleted) {
      result += '  deleted:\t' + stats[pe].heads.deleted + '\n';
    }
  });

  return result;
}

// shows new values of added and changed attributes
function fmtPatch(patch, newItem) {
  var result = {};
  Object.keys(patch).forEach(function(key) {
    if (patch[key] !== '-') {
      result[key] = newItem[key];
    }
  });
  return JSON.stringify(result);
}

function fmtItem(item, parents) {
  parents = parents || [];

  var out = '';
  if (program.show) {
    // print tree
    if (parents.length) {
      out += '\u250f ';
    } else {
      out += '\u257a ';
    }
    out += ' ' + JSON.stringify(item);
    if (parents.length) {
      out += '\n';
    }

    var i = 0;
    parents.forEach(function(p) {
      i++;
      if (i === parents.length) {
        out += '\u2517\u2501 ';
      } else {
        out += '\u2523\u2501 ';
      }
      var diff = doDiff(item.b || {}, p.b || {});
      out += ' ' + p.h.id + ' ' + p.h.v + ' diff: ' + JSON.stringify(diff) + ' ' + fmtPatch(diff, item.b);
      if (i !== parents.length) {
        out += '\n';
      }
    });
  } else {
    out += item.h.id;
    out += ' ' + item.h.v;
    if (item.h.hasOwnProperty('d') && item.h.d) {
      out += ' d';
    } else {
      out += ' .';
    }
    if (item.h.hasOwnProperty('c') && item.h.c) {
      out += ' c';
    } else {
      out += ' .';
    }
    out += ' ' + (item.h.pe || 'local');
    if (item.h.hasOwnProperty('i')) {
      out += ' (' + item.h.i + ')';
    }
    out += ' ' + JSON.stringify(item.h.pa);
    if (program.patch && parents.length) {
      parents.forEach(function(p) {
        var diff = doDiff(item.b || {}, p.b || {});
        if (parents.length > 1) {
          out += ' ' + p.h.v;
        }
        out += ' diff: ' + JSON.stringify(diff) + ' ' + fmtPatch(diff, item.b);
      });
    }
  }
  return out;
}

var noop = function() {};

function printTree(mt, tree, cb) {
  var counter = 0;
  console.log('%s:', tree.name);
  tree.iterateInsertionOrder({ reverse: true }, function(item, cb2) {
    counter++;

    if (!program.patch || !item.h.pa.length) {
      console.log(fmtItem(item));

      cb2(null, counter < program.number);
      return;
    }

    var parents = [];
    async.eachSeries(item.h.pa, function(pa, cb3) {
      mt.getByVersion(pa, function(err, p) {
        if (err) { cb3(err); return; }
        if (!p) {
          var error = new Error('parent not found');
          cb3(error);
          return;
        }

        parents.push(p);
        cb3();
      });
    }, function(err) {
      if (err) { cb2(err); return; }

      console.log(fmtItem(item, parents));

      cb2(null, counter < program.number);
    });
  }, cb);
}

function run(db, cfg, cb) {
  cfg = cfg || {};
  cfg.log = {
    err: noop,
    notice: noop,
    info: noop,
    debug: noop,
    debug2: noop
  };
  var mt = new MergeTree(db, cfg);

  mt.stats(function(err, stats) {
    if (err) { cb(err); return; }
    console.log(fmtStats(stats));

    var remoteTrees = mt.getRemoteTrees();

    if (program.all) {
      async.eachSeries(Object.keys(remoteTrees), function(name, cb2) {
        printTree(mt, remoteTrees[name], cb2);
      }, function(err) {
        if (err) { cb(err); return; }
        printTree(mt, mt.getStageTree(), function(err) {
          if (err) { cb(err); return; }
          printTree(mt, mt.getLocalTree(), function(err) {
            if (err) { cb(err); return; }
            cb();
          });
        });
      });
    } else {
      // print one tree, default to the local tree
      var tree, pe = program.pe || 'local';

      if (remoteTrees[pe]) {
        tree = remoteTrees[pe];
      } else if (pe === 'stage') {
        tree = mt.getStageTree();
      } else if (pe === 'local' || pe === mt._local.name) {
        tree = mt.getLocalTree();
      } else {
        cb(new Error('unknown perspective'));
        return;
      }

      printTree(mt, tree, cb);
    }
  });
}

if (config.dbs && config.dbs.length) {
  async.eachSeries(config.dbs, function(dbCfg, cb) {
    var chroot = dbCfg.chroot || '/var/persdb';
    var data = dbCfg.data || 'data';

    if (dbCfg.perspectives) {
      dbCfg.perspectives = dbCfg.perspectives.map(function(peCfg) {
        return peCfg.name;
      });
    }
    // open database
    level(chroot + '/' + data, { keyEncoding: 'binary', valueEncoding: 'binary' }, function(err, db) {
      if (err) { cb(err); return; }
      run(db, dbCfg, function(err) {
        db.close();
        if (err) { cb(err); return; }
      });
    });
  }, function(err) {
    if (err) { console.error(err); process.exit(2); }
  });
} else {
  // open database
  level('/var/persdb/data', { keyEncoding: 'binary', valueEncoding: 'binary' }, function(err, db) {
    if (err) { throw err; }
    run(db, {}, function(err) {
      db.close();
      if (err) { console.error(err); process.exit(2); }
    });
  });
}
<|endoftext|>


<commit_before>// @flow
import React, { Component } from 'react';
import steem from 'steem';
import { Button, Checkbox, Divider, Form, Grid, Header, Message, Segment } from 'semantic-ui-react';
import KeysConfirm from './Confirm';

export default class KeysImport extends Component {
  constructor(props) {
    super(props);
    this.state.errors = this.validate(this.state);
  }
  state = {
    account: '',
    wif: '',
    encryptWallet: true,
    lock1: '',
    lock2: '',
    errors: {
      account_name_required: false,
      password_error_not_set: false,
      password_error_no_match: false,
      wif_error_invalid: false,
    }
  }
  handleConfirmAction = (
    e: SyntheticEvent,
    data: object
    ) => {
    const confirmed = data.value;
    e.preventDefault();
    if (confirmed) {
      const { account, wif, encryptWallet, lock1, lock2 } = this.state;
      const { addKeyConfirmed } = this.props.actions;
      const { active, owner } = this.props.keys.confirm;
      const type = owner ? 'owner' : active ? 'active' : 'posting';
      addKeyConfirmed(account, wif, type, encryptWallet, lock1);
    } else {
      this.props.actions.addKeyCancel();
    }
  }

  handleChange = (
    e: SyntheticEvent, { name, value }: { name: string, value: string }
  ) => {
    const newState = Object.assign({}, this.state, {
      [name]: value
    });
    newState.errors = this.validate(newState);
    this.setState(newState);
  }

  handleMethodReset = () => {
    console.log(this.props);
  }

  validate = (newState) => {
    const errors = {};
    if (newState.encryptWallet) {
      const lock1 = newState.lock1;
      const lock2 = newState.lock2;
      // Ensure password is set
      errors.password_error_not_set = (!lock1 || !lock2 || lock1 === '' || lock2 === '');
      // Ensure passwords match
      errors.password_error_no_match = (lock1 !== lock2);
    }
    errors.account_name_required = !newState.account || newState.account === '';
    errors.wif_error_invalid = !steem.auth.isWif(newState.wif);
    return errors;
  }

  hasErrors = () => {
    const errors = this.state.errors;
    return !!Object.keys(errors).filter((error) => errors[error]).length;
  }

  handleToggle = (
    e: SyntheticEvent, { name, value }: { name: string, value: string }
    ) => {
    const newState = Object.assign({}, this.state, {
      [name]: (value === 'on'),
    });
    newState.errors = this.validate(newState);
    this.setState(newState);
  }

  handleSubmit = (e: SyntheticEvent) => {
    e.preventDefault();
    const { addKey } = this.props.actions;
    const { account, wif } = this.state;
    addKey(account, wif);
  }

  render() {
    const { account, wif, encryptWallet, lock1, lock2 } = this.state;
    const confirmAccount = this.props.keys.confirm;
    const errors = this.state.errors;
    const hasErrors = this.hasErrors();
    const messages = {
      account_name_required: 'An existing account name is required.',
      password_error_not_set: 'You must set a password to enable encryption.',
      password_error_no_match: 'The passwords you have entered do not match.',
      wif_error_invalid: 'They WIF Private Key you have entered is invalid.',
    };
    let passwordInput = false;
    let handleMethodReset = false;
    if (this.props.handleMethodReset) {
      handleMethodReset = (
        <Segment basic textAlign="center">
          <Button
            size="large"
            color="red"
            content="Cancel"
            onClick={this.props.handleMethodReset}
          />
        </Segment>
      );
    }
    let passwordWarning = false;
    let encryptionWarning = (
      <Message
        header="Warning - Unencrypted Wallets"
        content="If you choose not to encrypt your wallet, your private keys will be stored on your computer unencrypted. This greatly increases the risk of your account being comprimised in the event your computer is hacked."
      />
    );
    let message = false;
    let warning = false;
    if (hasErrors || this.props.keys.lastError) {
      if (this.props.keys.lastError) {
        message = this.props.keys.lastError;
      }
      if (errors.password_error_not_set) {
        message = messages.password_error_not_set;
      }
      if (errors.password_error_no_match) {
        message = messages.password_error_no_match;
      }
      if (errors.wif_error_invalid) {
        message = messages.wif_error_invalid;
      }
      if (errors.account_name_required) {
        message = messages.account_name_required;
      }
      warning = (
        <Message
          error
          icon="warning"
          content={message}
        />
      );
    }
    if (encryptWallet) {
      passwordInput = (

          <Form.Input label="Wallet Password" type="password" name="lock1" value={lock1} onChange={this.handleChange} />
          <Form.Input label="Wallet Password (Repeat)" type="password" name="lock2" value={lock2} onChange={this.handleChange} />

      );
      encryptionWarning = (
        <Message
          header="Notice about Encryption"
          content="The wallet password if only used to unlock Vessel during transactions. If you lose your password, you will be unable to recover your keys. Please make sure to back up your keys and remember your password."
        />
      );
    }
    let display = (

            Enter an account name and a WIF private key for the account
            you would like to add. Different permissions will be granted
            to this wallet based on the type of key used.

        <Divider hidden />
        <Form
          error={hasErrors || this.props.keys.lastError}
          loading={this.props.processing.account_loading}
        >
          <Grid divided>
            <Grid.Row>
              <Grid.Column width={8}>
                <Form.Input
                  label="Account Name"
                  name="account"
                  value={account}
                  onChange={this.handleChange}
                />
                <Form.Input
                  label="WIF Private Key (posting, active or owner)"
                  type="password"
                  name="wif"
                  value={wif}
                  onChange={this.handleChange}
                />
                <Divider hidden />
                <Checkbox
                  toggle
                  name="encryptWallet"
                  label="Encrypt this Wallet for better security"
                  checked={encryptWallet}
                  value={encryptWallet ? 'off' : 'on'}
                  onChange={this.handleToggle}
                />
              </Grid.Column>
              <Grid.Column width={8}>
                {passwordInput}
                {encryptionWarning}
              </Grid.Column>
            </Grid.Row>
          </Grid>
          <Divider hidden />
          <Divider />
          {passwordWarning}
          {warning}
          <Button disabled={hasErrors} fluid size="large" color="green" onClick={this.handleSubmit}>
            Add to Wallet
          </Button>
          {handleMethodReset}
        </Form>

    );
    if (confirmAccount) {
      display = (
        <KeysConfirm
          handleConfirmAction={this.handleConfirmAction}
          encryptWallet={this.state.encryptWallet}
          {...this.props}
        />
      );
    }
    return display;
  }
}
<commit_msg>Removed unused method<commit_after>// @flow
import React, { Component } from 'react';
import steem from 'steem';
import { Button, Checkbox, Divider, Form, Grid, Header, Message, Segment } from 'semantic-ui-react';
import KeysConfirm from './Confirm';

export default class KeysImport extends Component {
  constructor(props) {
    super(props);
    this.state.errors = this.validate(this.state);
  }
  state = {
    account: '',
    wif: '',
    encryptWallet: true,
    lock1: '',
    lock2: '',
    errors: {
      account_name_required: false,
      password_error_not_set: false,
      password_error_no_match: false,
      wif_error_invalid: false,
    }
  }
  handleConfirmAction = (
    e: SyntheticEvent,
    data: object
    ) => {
    const confirmed = data.value;
    e.preventDefault();
    if (confirmed) {
      const { account, wif, encryptWallet, lock1, lock2 } = this.state;
      const { addKeyConfirmed } = this.props.actions;
      const { active, owner } = this.props.keys.confirm;
      const type = owner ? 'owner' : active ? 'active' : 'posting';
      addKeyConfirmed(account, wif, type, encryptWallet, lock1);
    } else {
      this.props.actions.addKeyCancel();
    }
  }

  handleChange = (
    e: SyntheticEvent, { name, value }: { name: string, value: string }
  ) => {
    const newState = Object.assign({}, this.state, {
      [name]: value
    });
    newState.errors = this.validate(newState);
    this.setState(newState);
  }

  validate = (newState) => {
    const errors = {};
    if (newState.encryptWallet) {
      const lock1 = newState.lock1;
      const lock2 = newState.lock2;
      // Ensure password is set
      errors.password_error_not_set = (!lock1 || !lock2 || lock1 === '' || lock2 === '');
      // Ensure passwords match
      errors.password_error_no_match = (lock1 !== lock2);
    }
    errors.account_name_required = !newState.account || newState.account === '';
    errors.wif_error_invalid = !steem.auth.isWif(newState.wif);
    return errors;
  }

  hasErrors = () => {
    const errors = this.state.errors;
    return !!Object.keys(errors).filter((error) => errors[error]).length;
  }

  handleToggle = (
    e: SyntheticEvent, { name, value }: { name: string, value: string }
    ) => {
    const newState = Object.assign({}, this.state, {
      [name]: (value === 'on'),
    });
    newState.errors = this.validate(newState);
    this.setState(newState);
  }

  handleSubmit = (e: SyntheticEvent) => {
    e.preventDefault();
    const { addKey } = this.props.actions;
    const { account, wif } = this.state;
    addKey(account, wif);
  }

  render() {
    const { account, wif, encryptWallet, lock1, lock2 } = this.state;
    const confirmAccount = this.props.keys.confirm;
    const errors = this.state.errors;
    const hasErrors = this.hasErrors();
    const messages = {
      account_name_required: 'An existing account name is required.',
      password_error_not_set: 'You must set a password to enable encryption.',
      password_error_no_match: 'The passwords you have entered do not match.',
      wif_error_invalid: 'They WIF Private Key you have entered is invalid.',
    };
    let passwordInput = false;
    let handleMethodReset = false;
    if (this.props.handleMethodReset) {
      handleMethodReset = (
        <Segment basic textAlign="center">
          <Button
            size="large"
            color="red"
            content="Cancel"
            onClick={this.props.handleMethodReset}
          />
        </Segment>
      );
    }
    let passwordWarning = false;
    let encryptionWarning = (
      <Message
        header="Warning - Unencrypted Wallets"
        content="If you choose not to encrypt your wallet, your private keys will be stored on your computer unencrypted. This greatly increases the risk of your account being comprimised in the event your computer is hacked."
      />
    );
    let message = false;
    let warning = false;
    if (hasErrors || this.props.keys.lastError) {
      if (this.props.keys.lastError) {
        message = this.props.keys.lastError;
      }
      if (errors.password_error_not_set) {
        message = messages.password_error_not_set;
      }
      if (errors.password_error_no_match) {
        message = messages.password_error_no_match;
      }
      if (errors.wif_error_invalid) {
        message = messages.wif_error_invalid;
      }
      if (errors.account_name_required) {
        message = messages.account_name_required;
      }
      warning = (
        <Message
          error
          icon="warning"
          content={message}
        />
      );
    }
    if (encryptWallet) {
      passwordInput = (

          <Form.Input label="Wallet Password" type="password" name="lock1" value={lock1} onChange={this.handleChange} />
          <Form.Input label="Wallet Password (Repeat)" type="password" name="lock2" value={lock2} onChange={this.handleChange} />

      );
      encryptionWarning = (
        <Message
          header="Notice about Encryption"
          content="The wallet password if only used to unlock Vessel during transactions. If you lose your password, you will be unable to recover your keys. Please make sure to back up your keys and remember your password."
        />
      );
    }
    let display = (

            Enter an account name and a WIF private key for the account
            you would like to add. Different permissions will be granted
            to this wallet based on the type of key used.

        <Divider hidden />
        <Form
          error={hasErrors || this.props.keys.lastError}
          loading={this.props.processing.account_loading}
        >
          <Grid divided>
            <Grid.Row>
              <Grid.Column width={8}>
                <Form.Input
                  label="Account Name"
                  name="account"
                  value={account}
                  onChange={this.handleChange}
                />
                <Form.Input
                  label="WIF Private Key (posting, active or owner)"
                  type="password"
                  name="wif"
                  value={wif}
                  onChange={this.handleChange}
                />
                <Divider hidden />
                <Checkbox
                  toggle
                  name="encryptWallet"
                  label="Encrypt this Wallet for better security"
                  checked={encryptWallet}
                  value={encryptWallet ? 'off' : 'on'}
                  onChange={this.handleToggle}
                />
              </Grid.Column>
              <Grid.Column width={8}>
                {passwordInput}
                {encryptionWarning}
              </Grid.Column>
            </Grid.Row>
          </Grid>
          <Divider hidden />
          <Divider />
          {passwordWarning}
          {warning}
          <Button disabled={hasErrors} fluid size="large" color="green" onClick={this.handleSubmit}>
            Add to Wallet
          </Button>
          {handleMethodReset}
        </Form>

    );
    if (confirmAccount) {
      display = (
        <KeysConfirm
          handleConfirmAction={this.handleConfirmAction}
          encryptWallet={this.state.encryptWallet}
          {...this.props}
        />
      );
    }
    return display;
  }
}
<|endoftext|>


<commit_before>// jscs:disable
var hangedMan =
"       _______
" +
"      |/      |
" +
"      |      (_)
" +
"      |      \\|/
" +
"      |       |
" +
"      |      / \\
" +
"      |
" +
"___|___
";

exports.commands = {
	hm: 'hangman',
	hangman: function (target, room, user) {
		if (!this.canTalk()) return this.sendReply("You are unable to talk in this room.");
		if (!target) target = 'view';
		var cmd = (~target.indexOf(',') ? target.substr(0, target.indexOf(',')) : target).trim();
		var targetSplit = target.split(',');
		for (var u in targetSplit) targetSplit[u] = targetSplit[u].trim();

		switch (cmd) {
			case 'help':
				if (!this.canBroadcast()) return false;
				this.sendReplyBox(
					"Hangman commands:
" +
					"- /hm command - Short alias for /hangman.
" +
					"- /hangman new/create/start, [word], [topic] - Creates a game of hangman.
" +
					"- /hangman view - Shows the current state of the hangman game.
" +
					"- /hangman topic/changetopic, [newtopic] - Changes the topic of the hangman game.
" +
					"- /hangman word - View the current word.
" +
					"- /hangman guess, [letter] - Guess a letter. Alias: /g
" +
					"- /hangman guessword, [word] - Guess the word. Alias: /gw
" +
					"- /hangman enable/disable - Enables/disables hangman in the current room."
				);
				break;
			case 'new':
			case 'start':
			case 'create':
				if (!user.can('broadcast', null, room)) return this.sendReply("/hangman create - Access denied.");
				if (!room.hangmanEnabled) return this.sendReply("Hangman is disabled in this room.");
				if (!targetSplit || !targetSplit[2]) return this.sendReply("Usage: /hangman [create], [word], [topic]");
				var word = toId(targetSplit[1]);
				word = word.replace(/[^a-z]+/g, '');
				if (word.length > 10) return this.sendReply("Words may not be longer than 10 characters.");
				targetSplit.splice(0, 2);
				var topic = targetSplit.join(' ');
				if (topic.length > 30) return this.sendReply("The topic may not be longer than 30 characters.");

				room.hangman = {};
				room.hangman.word = word;
				room.hangman.topic = topic;
				room.hangman.hangmaner = user.userid;
				room.hangman.guessWord = [];
				room.hangman.guessedLetters = [];
				room.hangman.guessedWords = [];
				room.hangman.guesses = 8;

				for (var i = 0; i < word.length; i++) {
					room.hangman.guessWord.push('_');
				}
				room.add(
					"|raw|

" + Tools.escapeHTML(user.name) +
					" started a game of hangman! The word has " + word.length + " letters.
" + room.hangman.guessWord.join(" ") +
					"
Topic: " + Tools.escapeHTML(room.hangman.topic) + "
\
					Use /g letter to guess a letter and /gw word to guess a word

"
				);
				room.update();
				break;
			case 'view':
				if (!room.hangman) return this.sendReply("There's no game of hangman in this room.");
				if (!this.canBroadcast()) return;
				this.sendReplyBox(
					"" + room.hangman.guessWord.join(" ") + "
Guesses left: " + room.hangman.guesses +
					"
Topic: " + Tools.escapeHTML(room.hangman.topic) + ""
				);
				break;
			case 'topic':
			case 'changetopic':
				if (!room.hangman) return this.sendReply("There's no game of hangman in this room.");
				if (user.userid !== room.hangman.hangmaner) return this.sendReply("You can't change the topic if you're not running hangman.");
				if (!targetSplit || !targetSplit[1]) return this.sendReply("Usage: /hangman [topic], [newtopic]");
				targetSplit.splice(0, 1);
				var topic = targetSplit.join(' ');
				if (topic.length > 30) return this.sendReply("The topic may not be longer than 30 characters.");

				room.hangman.topic = topic;
				room.add("
The hangman topic has been changed to '" + Tools.escapeHTML(topic) + "'.");
				room.update();
				break;
			case 'word':
			case 'viewword':
				if (!room.hangman) return this.sendReply("There's no game of hangman in this room.");
				if (user.userid !== room.hangman.hangmaner) return this.sendReply("You're not the user who started this game.");
				this.sendReply("Your word is '" + room.hangman.word + "'.");
				break;
			case 'guess':
				if (!room.hangman) return this.sendReply("There's no game of hangman in this room.");
				if (user.userid === room.hangman.hangmaner) return this.sendReply("You can't guess a letter because you're running hangman.");
				if (!targetSplit || !targetSplit[1]) return this.sendReply("Usage: /hangman [guess], [letter]");
				if (targetSplit[1].length > 1) return this.sendReply("Please specify a letter to guess. To guess the word use /hangman [guessword], [word]");
				var letter = targetSplit[1];
				if (~room.hangman.guessedLetters.indexOf(letter)) return this.sendReply("Someone has already guessed that letter.");
				var found = false;
				room.hangman.guessedLetters.push(letter);

				for (var u in room.hangman.word) {
					if (room.hangman.word[u] === letter) {
						room.hangman.guessWord[u] = room.hangman.word[u];
						found = true;
					}
				}

				room.hangman.guesses--;

				if (found) {
					if (!~room.hangman.guessWord.indexOf('_')) {
						room.add("|raw|
Congratulations! " + Tools.escapeHTML(user.name) + " has guessed the word, which was: " + room.hangman.word + "
");
						room.update();
						delete room.hangman;
						return;
					}

					room.add("|raw|
" + Tools.escapeHTML(user.name) + " guessed the letter '" + letter + "'
Word: " + room.hangman.guessWord.join(' ') + "
");
					room.update();
					return;
				}

				if (room.hangman.guesses < 1) {
					room.add(
						"|raw|
" + Tools.escapeHTML(user.name) + " guessed the letter '" + letter + "', but it was not in the word.
" +
						"You have failed to guess the word, so the man has been hanged.
\
						The word was '" + room.hangman.word + "'.
" + hangedMan + "
"
					);
					room.update();
					delete room.hangman;
					return;
				}

				room.add("|raw|
" + Tools.escapeHTML(user.name) + " guessed the letter '" + letter + "' but it was not in the word.
Guesses left: " + room.hangman.guesses + "
");
				room.update();
				break;
			case 'guessword':
				if (!room.hangman) return this.sendReply("There's no game of hangman in this room.");
				if (user.userid === room.hangman.hangmaner) return this.sendReply("You can't guess the word because you're running hangman.");
				if (!targetSplit || !targetSplit[1]) return this.sendReply("Usage: /hangman [guessword], [word]");
				if (targetSplit[1].length !== room.hangman.word.length) return this.sendReply("You can't guess a word that doesn't match the length of the hangman word.");
				var word = targetSplit[1];
				if (~room.hangman.guessedWords.indexOf(word.toLowerCase())) return this.sendReply("That word has already been guessed.");
				room.hangman.guessedWords.push(word.toLowerCase());
				room.hangman.guesses--;

				if (word.toLowerCase() === room.hangman.word.toLowerCase()) {
					room.add("|raw|
Congratulations! " + Tools.escapeHTML(user.name) + " guessed the word, which was: " + room.hangman.word + "
");
					room.update();
					delete room.hangman;
					return;
				}

				if (room.hangman.guesses < 1) {
					room.add(
						"|raw|
" + Tools.escapeHTML(user.name) + " guessed the word '" + Tools.escapeHTML(word) +
						"', but it was not the word. You have failed to guess the word, so the man has been hanged.
" + hangedMan
					);
					room.update();
					delete room.hangman;
					return;
				}

				room.add("|raw|
" + Tools.escapeHTML(user.name) + " guessed the word '" + word + "' but it was not in the word.
Guesses left: " + room.hangman.guesses + "
");
				room.update();
				break;
			case 'on':
			case 'enable':
				if (!user.can('roommod', null, room)) return this.sendReply("/hangman on - Access denied.");
				if (room.hangmanEnabled) return this.sendReply("Hangman is already enabled in this room.");
				room.hangmanEnabled = true;
				room.chatRoomData.hangmanEnabled = true;
				Rooms.global.writeChatRoomData();
				this.sendReply("Hangman has been enabled.");
				break;
			case 'off':
			case 'disable':
				if (!user.can('roommod', null, room)) return this.sendReply("/hangman off - Access denied.");
				if (!room.hangmanEnabled) return this.sendReply("Hangman is already disabled in this room.");
				room.hangmanEnabled = false;
				room.chatRoomData.hangmanEnabled = false;
				Rooms.global.writeChatRoomData();
				this.sendReply("Hangman has been disabled.");
				break;
			case 'end':
				if (!user.can('broadcast', null, room)) return this.sendReply("/hangman end - Access denied.");
				if (!room.hangman) return this.sendReply("There is no hangman game in this room.");
				room.add("|raw|Hangman has been ended by " + Tools.escapeHTML(user.name) + ".");
				room.update();
				delete room.hangman;
				break;
		}
	},

	g: function(target, room, user) {
		this.parse('/hangman guess,' + target);
	},

	gw: function(target, room, user) {
		this.parse('/hangman guessword,' + target);
	}
};
<commit_msg>Fix lint errors<commit_after>var hangedMan =
"       _______
" +
"      |/      |
" +
"      |      (_)
" +
"      |      \\|/
" +
"      |       |
" +
"      |      / \\
" +
"      |
" +
"___|___
";

exports.commands = {
	hm: 'hangman',
	hangman: function (target, room, user) {
		if (!this.canTalk()) return this.sendReply("You are unable to talk in this room.");
		if (!target) target = 'view';
		var cmd = (~target.indexOf(',') ? target.substr(0, target.indexOf(',')) : target).trim();
		var targetSplit = target.split(',');
		for (var u in targetSplit) targetSplit[u] = targetSplit[u].trim();

		switch (cmd) {
			case 'help':
				if (!this.canBroadcast()) return false;
				this.sendReplyBox(
					"Hangman commands:
" +
					"- /hm command - Short alias for /hangman.
" +
					"- /hangman new/create/start, [word], [topic] - Creates a game of hangman.
" +
					"- /hangman view - Shows the current state of the hangman game.
" +
					"- /hangman topic/changetopic, [newtopic] - Changes the topic of the hangman game.
" +
					"- /hangman word - View the current word.
" +
					"- /hangman guess, [letter] - Guess a letter. Alias: /g
" +
					"- /hangman guessword, [word] - Guess the word. Alias: /gw
" +
					"- /hangman enable/disable - Enables/disables hangman in the current room."
				);
				break;
			case 'new':
			case 'start':
			case 'create':
				if (!user.can('broadcast', null, room)) return this.sendReply("/hangman create - Access denied.");
				if (!room.hangmanEnabled) return this.sendReply("Hangman is disabled in this room.");
				if (!targetSplit || !targetSplit[2]) return this.sendReply("Usage: /hangman [create], [word], [topic]");
				var word = toId(targetSplit[1]);
				word = word.replace(/[^a-z]+/g, '');
				if (word.length > 10) return this.sendReply("Words may not be longer than 10 characters.");
				targetSplit.splice(0, 2);
				var topic = targetSplit.join(' ');
				if (topic.length > 30) return this.sendReply("The topic may not be longer than 30 characters.");

				room.hangman = {};
				room.hangman.word = word;
				room.hangman.topic = topic;
				room.hangman.hangmaner = user.userid;
				room.hangman.guessWord = [];
				room.hangman.guessedLetters = [];
				room.hangman.guessedWords = [];
				room.hangman.guesses = 8;

				for (var i = 0; i < word.length; i++) {
					room.hangman.guessWord.push('_');
				}
				room.add(
					"|raw|

" + Tools.escapeHTML(user.name) +
					" started a game of hangman! The word has " + word.length + " letters.
" + room.hangman.guessWord.join(" ") +
					"
Topic: " + Tools.escapeHTML(room.hangman.topic) + "
" +
					"Use /g letter to guess a letter and /gw word to guess a word

"
				);
				room.update();
				break;
			case 'view':
				if (!room.hangman) return this.sendReply("There's no game of hangman in this room.");
				if (!this.canBroadcast()) return;
				this.sendReplyBox(
					"" + room.hangman.guessWord.join(" ") + "
Guesses left: " + room.hangman.guesses +
					"
Topic: " + Tools.escapeHTML(room.hangman.topic) + ""
				);
				break;
			case 'topic':
			case 'changetopic':
				if (!room.hangman) return this.sendReply("There's no game of hangman in this room.");
				if (user.userid !== room.hangman.hangmaner) return this.sendReply("You can't change the topic if you're not running hangman.");
				if (!targetSplit || !targetSplit[1]) return this.sendReply("Usage: /hangman [topic], [newtopic]");
				targetSplit.splice(0, 1);
				var topic = targetSplit.join(' ');
				if (topic.length > 30) return this.sendReply("The topic may not be longer than 30 characters.");

				room.hangman.topic = topic;
				room.add("
The hangman topic has been changed to '" + Tools.escapeHTML(topic) + "'.");
				room.update();
				break;
			case 'word':
			case 'viewword':
				if (!room.hangman) return this.sendReply("There's no game of hangman in this room.");
				if (user.userid !== room.hangman.hangmaner) return this.sendReply("You're not the user who started this game.");
				this.sendReply("Your word is '" + room.hangman.word + "'.");
				break;
			case 'guess':
				if (!room.hangman) return this.sendReply("There's no game of hangman in this room.");
				if (user.userid === room.hangman.hangmaner) return this.sendReply("You can't guess a letter because you're running hangman.");
				if (!targetSplit || !targetSplit[1]) return this.sendReply("Usage: /hangman [guess], [letter]");
				if (targetSplit[1].length > 1) return this.sendReply("Please specify a letter to guess. To guess the word use /hangman [guessword], [word]");
				var letter = targetSplit[1];
				if (~room.hangman.guessedLetters.indexOf(letter)) return this.sendReply("Someone has already guessed that letter.");
				var found = false;
				room.hangman.guessedLetters.push(letter);

				for (var u in room.hangman.word) {
					if (room.hangman.word[u] === letter) {
						room.hangman.guessWord[u] = room.hangman.word[u];
						found = true;
					}
				}

				room.hangman.guesses--;

				if (found) {
					if (!~room.hangman.guessWord.indexOf('_')) {
						room.add("|raw|
Congratulations! " + Tools.escapeHTML(user.name) + " has guessed the word, which was: " + room.hangman.word + "
");
						room.update();
						delete room.hangman;
						return;
					}

					room.add("|raw|
" + Tools.escapeHTML(user.name) + " guessed the letter '" + letter + "'
Word: " + room.hangman.guessWord.join(' ') + "
");
					room.update();
					return;
				}

				if (room.hangman.guesses < 1) {
					room.add(
						"|raw|
" + Tools.escapeHTML(user.name) + " guessed the letter '" + letter + "', but it was not in the word.
" +
						"You have failed to guess the word, so the man has been hanged.
" +
						"The word was '" + room.hangman.word + "'.
" + hangedMan + "
"
					);
					room.update();
					delete room.hangman;
					return;
				}

				room.add("|raw|
" + Tools.escapeHTML(user.name) + " guessed the letter '" + letter + "' but it was not in the word.
Guesses left: " + room.hangman.guesses + "
");
				room.update();
				break;
			case 'guessword':
				if (!room.hangman) return this.sendReply("There's no game of hangman in this room.");
				if (user.userid === room.hangman.hangmaner) return this.sendReply("You can't guess the word because you're running hangman.");
				if (!targetSplit || !targetSplit[1]) return this.sendReply("Usage: /hangman [guessword], [word]");
				if (targetSplit[1].length !== room.hangman.word.length) return this.sendReply("You can't guess a word that doesn't match the length of the hangman word.");
				var word = targetSplit[1];
				if (~room.hangman.guessedWords.indexOf(word.toLowerCase())) return this.sendReply("That word has already been guessed.");
				room.hangman.guessedWords.push(word.toLowerCase());
				room.hangman.guesses--;

				if (word.toLowerCase() === room.hangman.word.toLowerCase()) {
					room.add("|raw|
Congratulations! " + Tools.escapeHTML(user.name) + " guessed the word, which was: " + room.hangman.word + "
");
					room.update();
					delete room.hangman;
					return;
				}

				if (room.hangman.guesses < 1) {
					room.add(
						"|raw|
" + Tools.escapeHTML(user.name) + " guessed the word '" + Tools.escapeHTML(word) +
						"', but it was not the word. You have failed to guess the word, so the man has been hanged.
" + hangedMan
					);
					room.update();
					delete room.hangman;
					return;
				}

				room.add("|raw|
" + Tools.escapeHTML(user.name) + " guessed the word '" + word + "' but it was not in the word.
Guesses left: " + room.hangman.guesses + "
");
				room.update();
				break;
			case 'on':
			case 'enable':
				if (!user.can('roommod', null, room)) return this.sendReply("/hangman on - Access denied.");
				if (room.hangmanEnabled) return this.sendReply("Hangman is already enabled in this room.");
				room.hangmanEnabled = true;
				room.chatRoomData.hangmanEnabled = true;
				Rooms.global.writeChatRoomData();
				this.sendReply("Hangman has been enabled.");
				break;
			case 'off':
			case 'disable':
				if (!user.can('roommod', null, room)) return this.sendReply("/hangman off - Access denied.");
				if (!room.hangmanEnabled) return this.sendReply("Hangman is already disabled in this room.");
				room.hangmanEnabled = false;
				room.chatRoomData.hangmanEnabled = false;
				Rooms.global.writeChatRoomData();
				this.sendReply("Hangman has been disabled.");
				break;
			case 'end':
				if (!user.can('broadcast', null, room)) return this.sendReply("/hangman end - Access denied.");
				if (!room.hangman) return this.sendReply("There is no hangman game in this room.");
				room.add("|raw|Hangman has been ended by " + Tools.escapeHTML(user.name) + ".");
				room.update();
				delete room.hangman;
				break;
		}
	},

	g: function (target, room, user) {
		this.parse('/hangman guess,' + target);
	},

	gw: function (target, room, user) {
		this.parse('/hangman guessword,' + target);
	}
};
<|endoftext|>


<commit_before>const fs = require('fs');
const jsesc = require('jsesc');
const template = require('lodash.template');

function format(object) {
	return jsesc(object, {
		json: true,
		compact: false,
	}) + '\n';
}

function parse(source) {
	const indexByCodePoint = {};
	const indexByPointer = {};
	let decoded = '';
	const encoded = [];
	var lines = source.split('\n');
	for (const line of lines) {
		const data = line.trim().split('\t');
		if (data.length != 3) {
			continue;
		}
		const pointer = Number(data[0]);
		const codePoint = Number(data[1]);
		const symbol = String.fromCodePoint(codePoint);
		decoded += symbol;
		encoded.push(pointer + 0x80);
		indexByCodePoint[codePoint] = pointer;
		indexByPointer[pointer] = symbol;
	}
	return {
		decoded: decoded,
		encoded: encoded,
		indexByCodePoint: indexByCodePoint,
		indexByPointer: indexByPointer
	};
}

const source = fs.readFileSync('./data/index.txt', 'utf-8');
const result = parse(source);
fs.writeFileSync(
	'./data/index-by-code-point.json',
	format(result.indexByCodePoint)
);
fs.writeFileSync(
	'./data/index-by-pointer.json',
	format(result.indexByPointer)
);
fs.writeFileSync(
	'./data/decoded.json',
	format(result.decoded)
);
fs.writeFileSync(
	'./data/encoded.json',
	format(result.encoded)
);

// tests/tests.src.js → tests/tests.js
const TEST_TEMPLATE = fs.readFileSync('./tests/tests.src.mjs', 'utf8');
const createTest = template(TEST_TEMPLATE, {
	interpolate: /<\%=([\s\S]+?)%\>/g,
});
const testCode = createTest(require('./export-data.js'));
fs.writeFileSync('./tests/tests.mjs', testCode);

// src/iso-8859-6.src.mjs -> iso-8859-6.mjs
const LIB_TEMPLATE = fs.readFileSync('./src/iso-8859-6.src.mjs', 'utf8');
const createLib = template(LIB_TEMPLATE, {
	interpolate: /<\%=([\s\S]+?)%\>/g,
});
const libCode = createLib(require('./export-data.js'));
fs.writeFileSync('./iso-8859-6.mjs', libCode);
<commit_msg>Make string interpolation more robust<commit_after>const fs = require('fs');
const jsesc = require('jsesc');
const template = require('lodash.template');

function format(object) {
	return jsesc(object, {
		json: true,
		compact: false,
	}) + '\n';
}

function parse(source) {
	const indexByCodePoint = {};
	const indexByPointer = {};
	let decoded = '';
	const encoded = [];
	var lines = source.split('\n');
	for (const line of lines) {
		const data = line.trim().split('\t');
		if (data.length != 3) {
			continue;
		}
		const pointer = Number(data[0]);
		const codePoint = Number(data[1]);
		const symbol = String.fromCodePoint(codePoint);
		decoded += symbol;
		encoded.push(pointer + 0x80);
		indexByCodePoint[codePoint] = pointer;
		indexByPointer[pointer] = symbol;
	}
	return {
		decoded: decoded,
		encoded: encoded,
		indexByCodePoint: indexByCodePoint,
		indexByPointer: indexByPointer
	};
}

const source = fs.readFileSync('./data/index.txt', 'utf-8');
const result = parse(source);
fs.writeFileSync(
	'./data/index-by-code-point.json',
	format(result.indexByCodePoint)
);
fs.writeFileSync(
	'./data/index-by-pointer.json',
	format(result.indexByPointer)
);
fs.writeFileSync(
	'./data/decoded.json',
	format(result.decoded)
);
fs.writeFileSync(
	'./data/encoded.json',
	format(result.encoded)
);

// tests/tests.src.js → tests/tests.js
const TEST_TEMPLATE = fs.readFileSync('./tests/tests.src.mjs', 'utf8');
const createTest = template(TEST_TEMPLATE, {
	interpolate: /<%=([\s\S]+?)%>/g,
});
const testCode = createTest(require('./export-data.js'));
fs.writeFileSync('./tests/tests.mjs', testCode);

// src/iso-8859-6.src.mjs -> iso-8859-6.mjs
const LIB_TEMPLATE = fs.readFileSync('./src/iso-8859-6.src.mjs', 'utf8');
const createLib = template(LIB_TEMPLATE, {
	interpolate: /<%=([\s\S]+?)%>/g,
});
const libCode = createLib(require('./export-data.js'));
fs.writeFileSync('./iso-8859-6.mjs', libCode);
<|endoftext|>