<commit_before>var d3 = require('d3');
var $ = require('jquery');
var gff = require('./gff3.js');
var Color = require('color');

// margin is space between genomes and container
var margin = {top: 30, right: 30, bottom: 30, left: 30},
    width = 960 - margin.left - margin.right,
    height = 660 - margin.top - margin.bottom,
    genome_offset = 0,
    genes_offset = 5, // from lcb
    genome_height = 10,
    lcb_overflow = 10,
    longest = 0;

var gff3s = [];
var adjusted_genomes = [];
var genome_map = {};
var xmfas;

var genomeGroup;
var genesGroups = [];
var lcbGroups = [];
var lcb_areaGroup = [];

// find length as a function of longest genome/canvas width
var convert = function(length) {
    return length/longest * width;
};

var zoom = d3.zoom()
    .scaleExtent([1, 1000])
    .on("zoom", zoomed);

var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    //.style("display", "block")
    //.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
    .call(zoom);

// background
var rect = svg.append("rect")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    .style("fill", "white")
    .style("opacity", 0.4)
    .style("pointer-events", "all");

// where genome name labels go
var border = d3.select("body")
    .append("svg")
        .attr("width", 100)
        .attr("height", height + margin.top + margin.bottom)

container = svg.append("g");
//genome_elements = container.append("g");

function zoomed() {
    tx = d3.event.transform;
    txf = "translate(" + tx.x + ") scale(" + tx.k + ",1"+ ")";
    container.attr("transform", txf);
    if (tx.k == 1000) {
        draw_bars();
    }
}

// draw genomes as rectangles
var draw_genomes = function() {
    genomeGroup = container.selectAll("genomes")
                    .data(adjusted_genomes)
                    .enter()
                        .append("rect")
                            .attr("width", function(d) {return d.length;})
                            .attr("height", genome_height)
                            .attr("x", margin.left)
                            .attr("y", function(d,i) {return margin.top + i*genome_offset})
                            .attr("id", function(d, i){ return d.name; })
                            .style("fill", "green");

    var text = border.selectAll("text")
                        .data(adjusted_genomes)
                        .enter()
                        .append("text");

    var textLabels = text
                 .attr("y", function(d,i) {return margin.top + i*genome_offset + lcb_overflow;})
                 .text( function (d) { return d.name; })
                 .attr("font-family", "sans-serif")
                 .attr("font-size", "20px");
                 //.attr("fill", "red");
};

// draw genome features for each genome
var draw_features = function(gff3, genomes, rows) {
    var compute_height = function(index) {
        for(var row in rows) {
            if (rows[row].indexOf(index) > -1) {
                return row*10;
            }
        }
    };

    function find_index(name) {
        for (var genome in genomes) {
            if (name == genomes[genome].name) {
                return genome;
            }
        }
    };

    var clicked = function(d){
        console.log(d);
    };

    var index = find_index(gff3[0].seqid);

    var genes = container.selectAll('gene' + index)
                    .data(gff3)
                    .enter()
                        .append("rect")
                            .attr("width", function(d, i) {return convert(d.end - d.start);})
                            .attr("height", 10)
                            .attr("x", function(d, i) {return margin.left + convert(d.start);})
                            .attr("y", function(d, i) {
                                return genes_offset + margin.top + genome_height + lcb_overflow/2 + compute_height(i) + index*genome_offset;
                            })
                            .style("fill", "black")
                            .on("click", clicked)
    genesGroups.push(genes);
};

function calculate_offset(genome, lcb) {
    xmfas[lcb].map(function(g) {
        adjusted_genomes[g.id-1].x_offset = convert(genome.start - g.start);
    });
};

function redraw() {
    genomeGroup.attr("x", function(d) {return margin.left + d.x_offset;})

    lcbGroups.map(function(lcb) {
        lcb.attr("x", function(d, i) {return adjusted_genomes[d.id-1].x_offset + margin.left + convert(d.start);})
    });
    lcb_areaGroup.map(function(lcb_area, index) {
        lcb_area.attr("points", function(d,i) {return configure_lcb_areas(xmfas[index], i);})
    });

    genesGroups.map(function(genes, index) {
        genes.attr("x", function(d, i) {return adjusted_genomes[genome_map[d.seqid]].x_offset + margin.left + convert(d.start);})
    });
};

function configure_lcb_areas(lcb, i) {
    var l11x = (adjusted_genomes[lcb[i].id-1].x_offset + margin.left + lcb[i].start/longest*width).toString();
    var l11y = (margin.top+genome_height+(lcb_overflow/2) + (lcb[i].id - 1)*genome_offset).toString();
    var l12x = (adjusted_genomes[lcb[i].id-1].x_offset + margin.left + lcb[i].end/longest*width).toString();
    var l12y = (margin.top+genome_height+(lcb_overflow/2) + (lcb[i].id - 1)*genome_offset).toString();
    var l22x = (adjusted_genomes[lcb[i+1].id-1].x_offset + margin.left + lcb[i+1].end/longest*width).toString();
    var l22y = (margin.top-(lcb_overflow/2) + (lcb[i+1].id - 1)*genome_offset).toString();
    var l21x = (adjusted_genomes[lcb[i+1].id-1].x_offset + margin.left + lcb[i+1].start/longest*width).toString();
    var l21y = (margin.top-(lcb_overflow/2) + (lcb[i+1].id - 1)*genome_offset).toString();
    var l11 = l11x + ',' + l11y + ' ';
    var l12 = l12x + ',' + l12y + ' ';
    var l22 = l22x + ',' + l22y + ' ';
    var l21 = l21x + ',' + l21y + ' ';
    if (lcb[i].strand != lcb[i+1].strand) {
        return l11+l12+l21+l22;
    } else { return l11+l12+l22+l21; }
};

function draw_lcbs(lcb, index, color, color2) {
    var lcbs = container.selectAll('lcb' + index)
                    .data(lcb)
                    .enter()
                        .append("rect")
                            .attr("width", function(d, i) {return convert(d.end - d.start);})
                            .attr("height", genome_height+lcb_overflow)
                            .attr("x", function(d, i) {return margin.left + convert(d.start);})
                            .attr("y", function(d, i) {
                                return margin.top-(lcb_overflow/2) + (d.id - 1)*genome_offset;
                            })
                            .attr("id", function(d, i){ return d.rid; })
                            .style("fill", color2)
                            .style("opacity", 0.5)
                        .on("click", function(genome){
                            calculate_offset(genome, index);
                            redraw();
                        });
    lcbGroups.push(lcbs);

    sliced_lcb = lcb.slice(0,lcb.length-1); // need to use +1, so slice array so no range error
    var lcb_areas = container.selectAll('lcb_area' + index)
                    .data(sliced_lcb)
                    .enter()
                        .append("polygon")
                            .attr("points", function(d,i) {return configure_lcb_areas(lcb, i);})
                            .style("fill", color)
                            .style("opacity", 0.5)

    lcb_areaGroup.push(lcb_areas);
};

function draw_bars() {
    var w = width/longest;

    //adjusted_genomes.map(function(g, index) {
        //var bars = container.selectAll("bars")
                    //.data(adjusted_genomes[0].seq)
                    //.enter()
                        //.append("rect")
                        //.attr("width", w)
                        //.attr("height", genome_height)
                        //.attr("x", function(d, i) {return margin.left + i*w})
                        //.attr("y", function(d) {console.log(d); return margin.top + 0*genome_offset})
                        //.style("fill", function(nuc) {
                            //switch(nuc) {
                                //case "A":
                                    //return "blue";
                                //case "T":
                                    //return "red";
                                //case "G":
                                    //return "yellow";
                                //case "C":
                                    //return "green";
                            //}
                        //});
    //});
};

// find genome with longest length
var find_longest = function(fasta) {
    return Math.max.apply(null, fasta.map(function(data) {
        return data.length;
    }));
}

// find how far apart genomes should be from each other
var set_genome_offset = function(num_genomes) {
    genome_offset = height/num_genomes;
};

// adjust pixels to genome length
var adjust_genomes = function(data) {
    $.each(data, function(key, fasta, i) {
        adjusted_genomes.push({name: fasta.name, length: convert(fasta.length), x_offset:0, seq:''});
        genome_map[fasta.name] = key;
    });
};

// parse url
var parseQueryString = function(url) {
  var urlParams = {};
  url.replace(
    new RegExp("([^?=&]+)(=([^&]*))?", "g"),
    function($0, $1, $2, $3) {
      urlParams[$1] = $3;
    }
  );
  return urlParams;
}

// space genes out by putting them on different rows
var assign_rows = function(gff3) {
    last_placed = [null];
    var rows = {0:[]}
    gff3.map(function(gene, i) {
        for (locs in last_placed) {
            if (last_placed[locs] == null || convert(gene.start) > 1+last_placed[locs]) {
                if (last_placed[locs] == null) {
                    last_placed.push(null);
                    rows[parseInt(locs)+1] = [];
                }
                last_placed[locs] = convert(gene.end);
                rows[locs].push(i);
                return;
            }
        }
    });
    return rows;
};

function sortByKey(array, key) {
    return array.sort(function(a, b) {
        var x = a[key]; var y = b[key];
        return ((x < y) ? -1 : ((x > y) ? 1 : 0));
    });
}

//get fasta, gff3, and xmfa data
$.getJSON(parseQueryString(location.search).url, function(json) {
    longest = find_longest(json.fasta);    // use this as reference for length ratios
    set_genome_offset(json.fasta.length);  // how far apart the genomes should be
    adjust_genomes(json.fasta);
    draw_genomes();

    var colors = ["#a6cee3","#b2df8a","#fb9a99","#fdbf6f","#cab2d6", "#ffff99"];
    colors = colors.map(function(c){ return Color(c) });

    //var colors = ["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00","#cab2d6","#6a3d9a"];
    $.getJSON(json.xmfa, function(xmfa) {
        xmfas = xmfa;
        var promises = xmfa.map(function(lcb, i) {
            var c = colors[i % colors.length];
            draw_lcbs(lcb, i, c.rgb().string(), c.darken(0.5).rgb().string());
        });
        Promise.all(promises).then(values => {
            for (var j in json.gff3) {
                $.get(json.gff3[j], function(gff3_data) {
                    var gff3  = gff.process(gff3_data, ['CDS']);
                    gff3s.push(gff3);
                    draw_features(gff3, adjusted_genomes, assign_rows(sortByKey(gff3, 'start')));
                });
            }
        });
    });

    (json.fasta).map(function(f) {
        $.get(f.path, function(sequence) {
            adjusted_genomes[genome_map[f.name]].seq = sequence;
        });
    });
});
<commit_msg>start on click info<commit_after>var d3 = require('d3');
var $ = require('jquery');
var gff = require('./gff3.js');
var Color = require('color');

// margin is space between genomes and container
var margin = {top: 30, right: 30, bottom: 30, left: 30},
    width = 960 - margin.left - margin.right,
    height = 660 - margin.top - margin.bottom,
    genome_offset = 0,
    genes_offset = 5, // from lcb
    genome_height = 10,
    lcb_overflow = 10,
    longest = 0;

var gff3s = [];
var adjusted_genomes = [];
var genome_map = {};
var xmfas;

var genomeGroup;
var genesGroups = [];
var lcbGroups = [];
var lcb_areaGroup = [];

// find length as a function of longest genome/canvas width
var convert = function(length) {
    return length/longest * width;
};

var zoom = d3.zoom()
    .scaleExtent([1, 1000])
    .on("zoom", zoomed);

var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    //.style("display", "block")
    //.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
    .call(zoom);

// background
var rect = svg.append("rect")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    .style("fill", "white")
    .style("opacity", 0.4)
    .style("pointer-events", "all");

// where genome name labels go
var border = d3.select("body")
    .append("svg")
        .attr("width", 100)
        .attr("height", height + margin.top + margin.bottom)

var info = d3.select("body")
    .append("svg")
        .attr("width", 300)
        .attr("height", height + margin.top + margin.bottom)

container = svg.append("g");
//genome_elements = container.append("g");

function zoomed() {
    tx = d3.event.transform;
    txf = "translate(" + tx.x + ") scale(" + tx.k + ",1"+ ")";
    container.attr("transform", txf);
    if (tx.k == 1000) {
        draw_bars();
    }
}

// draw genomes as rectangles
var draw_genomes = function() {
    genomeGroup = container.selectAll("genomes")
                    .data(adjusted_genomes)
                    .enter()
                        .append("rect")
                            .attr("width", function(d) {return d.length;})
                            .attr("height", genome_height)
                            .attr("x", margin.left)
                            .attr("y", function(d,i) {return margin.top + i*genome_offset})
                            .attr("id", function(d, i){ return d.name; })
                            .style("fill", "green");

    var text = border.selectAll("text")
                        .data(adjusted_genomes)
                        .enter()
                        .append("text");

    var textLabels = text
                 .attr("y", function(d,i) {return margin.top + i*genome_offset + lcb_overflow;})
                 .text( function (d) { return d.name; })
                 .attr("font-family", "sans-serif")
                 .attr("font-size", "20px");
};

// draw genome features for each genome
var draw_features = function(gff3, genomes, rows) {
    var compute_height = function(index) {
        for(var row in rows) {
            if (rows[row].indexOf(index) > -1) {
                return row*10;
            }
        }
    };

    function find_index(name) {
        for (var genome in genomes) {
            if (name == genomes[genome].name) {
                return genome;
            }
        }
    };

    var clicked = function(d){
        var text = info.selectAll("text")
                            .data([d])
                            .enter()
                            .append("text");

        var textLabels = text
                    .attr("y", function(d,i) {return height/2;})
                    .text( function (d) { console.log(d); return d.seqid; })
                    .attr("font-family", "sans-serif")
                    .attr("font-size", "20px");
    };

    var index = find_index(gff3[0].seqid);

    var genes = container.selectAll('gene' + index)
                    .data(gff3)
                    .enter()
                        .append("rect")
                            .attr("class", "gene")
                            .attr("width", function(d, i) {return convert(d.end - d.start);})
                            .attr("height", 10)
                            .attr("x", function(d, i) {return margin.left + convert(d.start);})
                            .attr("y", function(d, i) {
                                return genes_offset + margin.top + genome_height + lcb_overflow/2 + compute_height(i) + index*genome_offset;
                            })
                            .style("fill", "black")
                            .on("click", clicked);
                            //.on("click", function(){
                                        ////PointColors = [PointColors[1], PointColors[0]]
                                        //d3.selectAll(".gene").style("fill", "black");
                                        //d3.select(this).style("fill", "blue");
                            //});
    genesGroups.push(genes);
};

function calculate_offset(genome, lcb) {
    xmfas[lcb].map(function(g) {
        adjusted_genomes[g.id-1].x_offset = convert(genome.start - g.start);
    });
};

function redraw() {
    genomeGroup.attr("x", function(d) {return margin.left + d.x_offset;})

    lcbGroups.map(function(lcb) {
        lcb.attr("x", function(d, i) {return adjusted_genomes[d.id-1].x_offset + margin.left + convert(d.start);})
    });
    lcb_areaGroup.map(function(lcb_area, index) {
        lcb_area.attr("points", function(d,i) {return configure_lcb_areas(xmfas[index], i);})
    });

    genesGroups.map(function(genes, index) {
        genes.attr("x", function(d, i) {return adjusted_genomes[genome_map[d.seqid]].x_offset + margin.left + convert(d.start);})
    });
};

function configure_lcb_areas(lcb, i) {
    var l11x = (adjusted_genomes[lcb[i].id-1].x_offset + margin.left + lcb[i].start/longest*width).toString();
    var l11y = (margin.top+genome_height+(lcb_overflow/2) + (lcb[i].id - 1)*genome_offset).toString();
    var l12x = (adjusted_genomes[lcb[i].id-1].x_offset + margin.left + lcb[i].end/longest*width).toString();
    var l12y = (margin.top+genome_height+(lcb_overflow/2) + (lcb[i].id - 1)*genome_offset).toString();
    var l22x = (adjusted_genomes[lcb[i+1].id-1].x_offset + margin.left + lcb[i+1].end/longest*width).toString();
    var l22y = (margin.top-(lcb_overflow/2) + (lcb[i+1].id - 1)*genome_offset).toString();
    var l21x = (adjusted_genomes[lcb[i+1].id-1].x_offset + margin.left + lcb[i+1].start/longest*width).toString();
    var l21y = (margin.top-(lcb_overflow/2) + (lcb[i+1].id - 1)*genome_offset).toString();
    var l11 = l11x + ',' + l11y + ' ';
    var l12 = l12x + ',' + l12y + ' ';
    var l22 = l22x + ',' + l22y + ' ';
    var l21 = l21x + ',' + l21y + ' ';
    if (lcb[i].strand != lcb[i+1].strand) {
        return l11+l12+l21+l22;
    } else { return l11+l12+l22+l21; }
};

function draw_lcbs(lcb, index, color, color2) {
    var lcbs = container.selectAll('lcb' + index)
                    .data(lcb)
                    .enter()
                        .append("rect")
                            .attr("width", function(d, i) {return convert(d.end - d.start);})
                            .attr("height", genome_height+lcb_overflow)
                            .attr("x", function(d, i) {return margin.left + convert(d.start);})
                            .attr("y", function(d, i) {
                                return margin.top-(lcb_overflow/2) + (d.id - 1)*genome_offset;
                            })
                            .attr("id", function(d, i){ return d.rid; })
                            .style("fill", color2)
                            .style("opacity", 0.5)
                        .on("click", function(genome){
                            calculate_offset(genome, index);
                            redraw();
                        });
    lcbGroups.push(lcbs);

    sliced_lcb = lcb.slice(0,lcb.length-1); // need to use +1, so slice array so no range error
    var lcb_areas = container.selectAll('lcb_area' + index)
                    .data(sliced_lcb)
                    .enter()
                        .append("polygon")
                            .attr("points", function(d,i) {return configure_lcb_areas(lcb, i);})
                            .style("fill", color)
                            .style("opacity", 0.5)

    lcb_areaGroup.push(lcb_areas);
};

function draw_bars() {
    var w = width/longest;

    //adjusted_genomes.map(function(g, index) {
        //var bars = container.selectAll("bars")
                    //.data(adjusted_genomes[0].seq)
                    //.enter()
                        //.append("rect")
                        //.attr("width", w)
                        //.attr("height", genome_height)
                        //.attr("x", function(d, i) {return margin.left + i*w})
                        //.attr("y", function(d) {console.log(d); return margin.top + 0*genome_offset})
                        //.style("fill", function(nuc) {
                            //switch(nuc) {
                                //case "A":
                                    //return "blue";
                                //case "T":
                                    //return "red";
                                //case "G":
                                    //return "yellow";
                                //case "C":
                                    //return "green";
                            //}
                        //});
    //});
};

// find genome with longest length
var find_longest = function(fasta) {
    return Math.max.apply(null, fasta.map(function(data) {
        return data.length;
    }));
}

// find how far apart genomes should be from each other
var set_genome_offset = function(num_genomes) {
    genome_offset = height/num_genomes;
};

// adjust pixels to genome length
var adjust_genomes = function(data) {
    $.each(data, function(key, fasta, i) {
        adjusted_genomes.push({name: fasta.name, length: convert(fasta.length), x_offset:0, seq:''});
        genome_map[fasta.name] = key;
    });
};

// parse url
var parseQueryString = function(url) {
  var urlParams = {};
  url.replace(
    new RegExp("([^?=&]+)(=([^&]*))?", "g"),
    function($0, $1, $2, $3) {
      urlParams[$1] = $3;
    }
  );
  return urlParams;
}

// space genes out by putting them on different rows
var assign_rows = function(gff3) {
    last_placed = [null];
    var rows = {0:[]}
    gff3.map(function(gene, i) {
        for (locs in last_placed) {
            if (last_placed[locs] == null || convert(gene.start) > 1+last_placed[locs]) {
                if (last_placed[locs] == null) {
                    last_placed.push(null);
                    rows[parseInt(locs)+1] = [];
                }
                last_placed[locs] = convert(gene.end);
                rows[locs].push(i);
                return;
            }
        }
    });
    return rows;
};

function sortByKey(array, key) {
    return array.sort(function(a, b) {
        var x = a[key]; var y = b[key];
        return ((x < y) ? -1 : ((x > y) ? 1 : 0));
    });
}

//get fasta, gff3, and xmfa data
$.getJSON(parseQueryString(location.search).url, function(json) {
    longest = find_longest(json.fasta);    // use this as reference for length ratios
    set_genome_offset(json.fasta.length);  // how far apart the genomes should be
    adjust_genomes(json.fasta);
    draw_genomes();

    var colors = ["#a6cee3","#b2df8a","#fb9a99","#fdbf6f","#cab2d6", "#ffff99"];
    colors = colors.map(function(c){ return Color(c) });

    //var colors = ["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00","#cab2d6","#6a3d9a"];
    $.getJSON(json.xmfa, function(xmfa) {
        xmfas = xmfa;
        var promises = xmfa.map(function(lcb, i) {
            var c = colors[i % colors.length];
            draw_lcbs(lcb, i, c.rgb().string(), c.darken(0.5).rgb().string());
        });
        Promise.all(promises).then(values => {
            for (var j in json.gff3) {
                $.get(json.gff3[j], function(gff3_data) {
                    var gff3  = gff.process(gff3_data, ['CDS']);
                    gff3s.push(gff3);
                    draw_features(gff3, adjusted_genomes, assign_rows(sortByKey(gff3, 'start')));
                });
            }
        });
    });

    (json.fasta).map(function(f) {
        $.get(f.path, function(sequence) {
            adjusted_genomes[genome_map[f.name]].seq = sequence;
        });
    });
});
<|endoftext|>


<commit_before>module.exports = {
  "env": {
    "es6": true,
    "node": true,
    "mocha": true
  },
  "extends": [
  "eslint:recommended"
  ],
  "parserOptions": {
    "ecmaVersion": 6,
    "sourceType": "module",
    "ecmaFeatures": {
      "experimentalObjectRestSpread": true
    }
  },
  "rules": {
    "accessor-pairs": [
      "error",
      {
        "getWithoutSet": true,
        "setWithoutGet": true
      }
    ],
    "array-callback-return": "error",
    "curly": [
      "error",
      "multi-line"
    ],
    "default-case": "error",
    "eqeqeq": "error",
    "no-caller": "error",
    "no-case-declarations": "error",
    "no-else-return": "error",
    "no-eval": "error",
    "no-extend-native": "error",
    "no-extra-bind": "error",
    "no-fallthrough": "error",
    "no-floating-decimal": "error",
    "no-implicit-coercion": "error",
    "no-implied-eval": "error",
    "no-invalid-this": "error",
    "no-iterator": "error",
    "no-lone-blocks": "error",
    "no-magic-numbers": "error",
    "no-multi-spaces": "error",
    "no-native-reassign": "error",
    "no-proto": "error",
    "no-redeclare": "error",
    "no-useless-call": "error",
    "yoda": "error",
    "array-bracket-spacing": [
      "error",
      "always"
    ],
    "block-spacing": [
      "error",
      "always"
    ],
    "brace-style": [
      "error",
      "1tbs",
    {
      "allowSingleLine": true
    }
    ],
    "camelcase": "error",
    "comma-spacing": "error",
    "comma-style": "error",
    "computed-property-spacing": [
      "error",
      "never"
    ],
    "eol-last": "error",
    "func-names": "error",
    "func-style": [
      "error",
      "declaration",
      {
        "allowArrowFunctions": true
      }
    ],
    "indent": [
      "error",
      2,
      {
        "SwitchCase": 1
      }
    ],
    "key-spacing": "error",
    "keyword-spacing": "error",
    "linebreak-style": [
      "error",
      "unix"
    ],
    "lines-around-comment": [
      "error",
      {
        "allowBlockStart": true,
        "allowBlockEnd": false
      }
    ],
    "max-depth": [
      "error",
      {
        "max": 4
      }
    ],
    "max-len": [
      "error",
      {
        "comments": 80
      }
    ],
    "max-nested-callbacks": "error",
    "max-params": "error",
    "max-statements-per-line": "error",
    "new-cap": "error",
    "new-parens": "error",
    "newline-after-var": "error",
    "newline-before-return": "error",
    "newline-per-chained-call": [
      "error",
      {
        "ignoreChainWithDepth": 2
      }
    ],
    "no-array-constructor": "error",
    "no-lonely-if": "error",
    "no-mixed-spaces-and-tabs": "error",
    "no-multiple-empty-lines": [
      "error",
      {
        "max": 2
      }
    ],
    "no-new-object": "error",
    "no-spaced-func": "error",
    "no-trailing-spaces": "error",
    "no-unneeded-ternary": "error",
    "no-whitespace-before-property": "error",
    "object-curly-spacing": [
      "error",
      "always"
    ],
    "object-property-newline": "error",
    "one-var-declaration-per-line": "error",
    "operator-assignment": [
      "error",
      "always"
    ],
    "operator-linebreak": [
      "error",
      "none"
    ],
    "padded-blocks": [
      "error",
      "never"
    ],
    "quote-props": [
      "error",
      "as-needed"
    ],
    "quotes": [
      "error",
      "single",
      {
        "avoidEscape": true,
        "allowTemplateLiterals": true
      }
    ],
    "require-jsdoc": "error",
    "semi": [
      "error",
      "always"
    ],
    "no-console": "error",
    "semi-spacing": [
      "error",
      {
        "before": false
      }
    ],
    "space-before-blocks": "error",
    "space-in-parens": [
      "error",
      "never"
    ],
    "wrap-regex": "error",
    "arrow-body-style": [
      "error",
      "as-needed"
    ],
    "arrow-parens": [
      "error",
      "as-needed"
    ],
    "arrow-spacing": "error",
    "constructor-super": "error",
    "generator-star-spacing": [
      "error",
      "before"
    ],
    "no-class-assign": "error",
    "no-confusing-arrow": "error",
    "no-const-assign": "error",
    "no-dupe-class-members": "error",
    "no-duplicate-imports": "error",
    "no-new-symbol": "error",
    "no-this-before-super": "error",
    "no-useless-computed-key": "error",
    "no-useless-constructor": "error",
    "no-useless-rename": "error",
    "no-var": "error",
    "object-shorthand": [
      "error",
      "always"
    ],
    "prefer-arrow-callback": "warn",
    "prefer-const": "error",
    "prefer-rest-params": "error",
    "prefer-spread": "error",
    "prefer-template": "error",
    "require-yield": "error",
    "sort-imports": "warn",
    "yield-star-spacing": [
      "error",
      {
        "before": true,
        "after": false
      }
    ]
  }
};
<commit_msg>convert error and warning strings to numbers<commit_after>module.exports = {
  "env": {
    "es6": true,
    "node": true,
    "mocha": true
  },
  "extends": [
  "eslint:recommended"
  ],
  "parserOptions": {
    "ecmaVersion": 6,
    "sourceType": "module",
    "ecmaFeatures": {
      "experimentalObjectRestSpread": true
    }
  },
  "rules": {
    "accessor-pairs": [
      2,
      {
        "getWithoutSet": true,
        "setWithoutGet": true
      }
    ],
    "array-callback-return": 2,
    "curly": [
      2,
      "multi-line"
    ],
    "default-case": 2,
    "eqeqeq": 2,
    "no-caller": 2,
    "no-case-declarations": 2,
    "no-else-return": 2,
    "no-eval": 2,
    "no-extend-native": 2,
    "no-extra-bind": 2,
    "no-fallthrough": 2,
    "no-floating-decimal": 2,
    "no-implicit-coercion": 2,
    "no-implied-eval": 2,
    "no-invalid-this": 2,
    "no-iterator": 2,
    "no-lone-blocks": 2,
    "no-magic-numbers": 2,
    "no-multi-spaces": 2,
    "no-native-reassign": 2,
    "no-proto": 2,
    "no-redeclare": 2,
    "no-useless-call": 2,
    "yoda": 2,
    "array-bracket-spacing": [
      2,
      "always"
    ],
    "block-spacing": [
      2,
      "always"
    ],
    "brace-style": [
      2,
      "1tbs",
    {
      "allowSingleLine": true
    }
    ],
    "camelcase": 2,
    "comma-spacing": 2,
    "comma-style": 2,
    "computed-property-spacing": [
      2,
      "never"
    ],
    "eol-last": 2,
    "func-names": 2,
    "func-style": [
      2,
      "declaration",
      {
        "allowArrowFunctions": true
      }
    ],
    "indent": [
      2,
      2,
      {
        "SwitchCase": 1
      }
    ],
    "key-spacing": 2,
    "keyword-spacing": 2,
    "linebreak-style": [
      2,
      "unix"
    ],
    "lines-around-comment": [
      2,
      {
        "allowBlockStart": true,
        "allowBlockEnd": false
      }
    ],
    "max-depth": [
      2,
      {
        "max": 4
      }
    ],
    "max-len": [
      2,
      {
        "comments": 80
      }
    ],
    "max-nested-callbacks": 2,
    "max-params": 2,
    "max-statements-per-line": 2,
    "new-cap": 2,
    "new-parens": 2,
    "newline-after-var": 2,
    "newline-before-return": 2,
    "newline-per-chained-call": [
      2,
      {
        "ignoreChainWithDepth": 2
      }
    ],
    "no-array-constructor": 2,
    "no-lonely-if": 2,
    "no-mixed-spaces-and-tabs": 2,
    "no-multiple-empty-lines": [
      2,
      {
        "max": 2
      }
    ],
    "no-new-object": 2,
    "no-spaced-func": 2,
    "no-trailing-spaces": 2,
    "no-unneeded-ternary": 2,
    "no-whitespace-before-property": 2,
    "object-curly-spacing": [
      2,
      "always"
    ],
    "object-property-newline": 2,
    "one-var-declaration-per-line": 2,
    "operator-assignment": [
      2,
      "always"
    ],
    "operator-linebreak": [
      2,
      "none"
    ],
    "padded-blocks": [
      2,
      "never"
    ],
    "quote-props": [
      2,
      "as-needed"
    ],
    "quotes": [
      2,
      "single",
      {
        "avoidEscape": true,
        "allowTemplateLiterals": true
      }
    ],
    "require-jsdoc": 2,
    "semi": [
      2,
      "always"
    ],
    "no-console": 2,
    "semi-spacing": [
      2,
      {
        "before": false
      }
    ],
    "space-before-blocks": 2,
    "space-in-parens": [
      2,
      "never"
    ],
    "wrap-regex": 2,
    "arrow-body-style": [
      2,
      "as-needed"
    ],
    "arrow-parens": [
      2,
      "as-needed"
    ],
    "arrow-spacing": 2,
    "constructor-super": 2,
    "generator-star-spacing": [
      2,
      "before"
    ],
    "no-class-assign": 2,
    "no-confusing-arrow": 2,
    "no-const-assign": 2,
    "no-dupe-class-members": 2,
    "no-duplicate-imports": 2,
    "no-new-symbol": 2,
    "no-this-before-super": 2,
    "no-useless-computed-key": 2,
    "no-useless-constructor": 2,
    "no-useless-rename": 2,
    "no-var": 2,
    "object-shorthand": [
      2,
      "always"
    ],
    "prefer-arrow-callback": 1,
    "prefer-const": 2,
    "prefer-rest-params": 2,
    "prefer-spread": 2,
    "prefer-template": 2,
    "require-yield": 2,
    "sort-imports": 1,
    "yield-star-spacing": [
      2,
      {
        "before": true,
        "after": false
      }
    ]
  }
};
<|endoftext|>


<commit_before>'use strict';

// TODO: replace generation criteria with termination criteria

var Optimize = {
    Maximize: function (a, b) { return a >= b; },
    Minimize: function (a, b) { return a < b; }
};

var Select1 = {
    "Tournament2": function(pop) {
        var n = pop.length;
        var a = pop[Math.floor(Math.random()*n)];
        var b = pop[Math.floor(Math.random()*n)];
        return this.optimize(a.fitness, b.fitness) ? a.entity : b.entity;
    }, "Tournament3": function(pop) {
        var n = pop.length;
        var a = pop[Math.floor(Math.random()*n)];
        var b = pop[Math.floor(Math.random()*n)];
        var c = pop[Math.floor(Math.random()*n)];
        var best = this.optimize(a.fitness, b.fitness) ? a : b;
        best = this.optimize(best.fitness, c.fitness) ? best : c;
        return best.entity;
    }, "Fittest": function(pop) {
        return pop[0].entity;
    }, "Random": function(pop) {
        return pop[Math.floor(Math.random()*pop.length)].entity;
    }, "RandomLinearRank": function(pop) {
        this.internalGenState["rlr"] = this.internalGenState["rlr"]||0;
        return pop[Math.floor(Math.random()*Math.min(pop.length,(this.internalGenState["rlr"]++)))].entity;
    }, "Sequential": function(pop) {
        this.internalGenState["seq"] = this.internalGenState["seq"]||0;
        return pop[(this.internalGenState["seq"]++)%pop.length].entity;
    }
};

var Select2 = {
    "Tournament2": function(pop) {
        return [Select1.Tournament2.call(this, pop), Select1.Tournament2.call(this, pop)];
    }, "Tournament3": function(pop) {
        return [Select1.Tournament3.call(this, pop), Select1.Tournament3.call(this, pop)];
    }, "Random": function(pop) {
        return [Select1.Random.call(this, pop), Select1.Random.call(this, pop)];
    }, "RandomLinearRank": function(pop) {
        return [Select1.RandomLinearRank.call(this, pop), Select1.RandomLinearRank.call(this, pop)];
    }, "Sequential": function(pop) {
        return [Select1.Sequential.call(this, pop), Select1.Sequential.call(this, pop)];
    }, "FittestRandom": function(pop) {
        return [Select1.Fittest.call(this, pop), Select1.Random.call(this, pop)];
    }
};

var range = require('range').range;
var ee = require('event-emitter');

function Genetic() {
    // population
    this.fitness = null;
    this.seed = null;
    this.mutate = null;
    this.crossover = null;
    this.select1 = null;
    this.select2 = null;
    this.optimize = null;
    this.generation = null;

    this.configuration = {
        size: 250,
        crossover: 0.9,
        mutation: 0.2,
        iterations: 100,
        fittestAlwaysSurvives: true,
        maxResults: 100,
        skip: 0
    };

    this.userData = {};
    this.internalGenState = {};

    this.emitter = ee({});
}

Genetic.prototype.start = function() {
    return this.getInitialPopulation()
        .then((function(entities) {
            return new Promise((function(outerResolve, reject) {
                var i = 0; // current generation/iteration/generation number

                var iteration = (function(bar) {
                    // reset for each generation
                    this.internalGenState = {};

                    // score and sort
                    var pop = bar.entities
                        .map(function (entity) {
                            return {
                                fitness: this.fitness(entity),
                                entity: entity
                            };
                        }, this)
                        .sort((function (a, b) {
                            return this.optimize(a.fitness, b.fitness) ? -1 : 1;
                        }).bind(this));

                    // generation notification
                    var stats = this.getStats(pop);

                    var r = this.generation ? this.generation(pop, i, stats) : true;
                    var isFinished = (typeof r != "undefined" && !r) || (i == this.configuration.iterations-1);

                    if (isFinished || this.configuration["skip"] == 0 || i%this.configuration["skip"] == 0) {
                        this.emitter.emit('notification', pop.slice(0, this.maxResults), i, stats, isFinished);
                    }

                    if(isFinished) {
                        this.emitter.emit('finished', pop, i, stats);
                        ++i;
                        return {
                            isFinished: isFinished
                        };
                    } else {
                        var newEntities = this.breed(pop);
                        ++i;
                        return {
                            isFinished: isFinished,
                            entities: newEntities
                        };
                    }
                }).bind(this);

                var foo = {
                    entities: entities
                };

                // Promises in a doUntil fashion
                function singleIter(prom, cb) {
                    var newProm = prom.then(iteration);

                    newProm.then(function(foo) {
                        if(foo.isFinished) {
                            cb(foo);
                        } else {
                            singleIter(newProm, cb);
                        }
                    });
                }

                singleIter(Promise.resolve(foo), outerResolve);
            }).bind(this));
        }).bind(this));
};

Genetic.prototype.getInitialPopulation = function() {
    return Promise.all(range(0, this.configuration.size).map((function(index) {
        return this.seed();
    }).bind(this)));
};

Genetic.prototype.getStats = function(pop) {
    var mean = pop.reduce(function (a, b) { return a + b.fitness; }, 0)/pop.length;
    var stdev = Math.sqrt(pop
            .map(function (a) { return (a.fitness - mean) * (a.fitness - mean); })
            .reduce(function (a, b) { return a+b; }, 0)/pop.length);

    return {
        maximum: pop[0].fitness,
        minimum: pop[pop.length-1].fitness,
        mean: mean,
        stdev: stdev
    };
};

Genetic.prototype.breed = function(pop) {
    var mutateOrNot = (function(entity) {
        // applies mutation based on mutation probability
        return Math.random() <= this.configuration.mutation && this.mutate ? this.mutate(entity) : entity;
    }).bind(this);

    // crossover and mutate
    var newPop = [];

    if (this.configuration.fittestAlwaysSurvives) { // lets the best solution fall through
        newPop.push(pop[0].entity);
    }

    while (newPop.length < this.configuration.size) {
        if (
            this.crossover // if there is a crossover function
            && Math.random() <= this.configuration.crossover // base crossover on specified probability
            && newPop.length+1 < this.configuration.size // keeps us from going 1 over the max population size
        ) {
            var parents = this.select2(pop);
            var children = this.crossover(parents[0], parents[1]).map(mutateOrNot);
            newPop.push(children[0], children[1]);
        } else {
            newPop.push(mutateOrNot(this.select1(pop)));
        }
    }

    return newPop;
};

Genetic.prototype.on = function(eventName, callback) {
    this.emitter.on(eventName, callback.bind(this));
};

Genetic.prototype.evolve = function(config, userData) {
    var k;
    for (k in config) {
        this.configuration[k] = config[k];
    }

    for (k in userData) {
        this.userData[k] = userData[k];
    }

    return this.start();
};

module.exports = {
    create: function() {
        return new Genetic();
    },
    Select1: Select1,
    Select2: Select2,
    Optimize: Optimize
};
<commit_msg>extracted shouldTerminate criteria<commit_after>'use strict';

// TODO: replace generation criteria with termination criteria

var Optimize = {
    Maximize: function (a, b) { return a >= b; },
    Minimize: function (a, b) { return a < b; }
};

var Select1 = {
    "Tournament2": function(pop) {
        var n = pop.length;
        var a = pop[Math.floor(Math.random()*n)];
        var b = pop[Math.floor(Math.random()*n)];
        return this.optimize(a.fitness, b.fitness) ? a.entity : b.entity;
    }, "Tournament3": function(pop) {
        var n = pop.length;
        var a = pop[Math.floor(Math.random()*n)];
        var b = pop[Math.floor(Math.random()*n)];
        var c = pop[Math.floor(Math.random()*n)];
        var best = this.optimize(a.fitness, b.fitness) ? a : b;
        best = this.optimize(best.fitness, c.fitness) ? best : c;
        return best.entity;
    }, "Fittest": function(pop) {
        return pop[0].entity;
    }, "Random": function(pop) {
        return pop[Math.floor(Math.random()*pop.length)].entity;
    }, "RandomLinearRank": function(pop) {
        this.internalGenState["rlr"] = this.internalGenState["rlr"]||0;
        return pop[Math.floor(Math.random()*Math.min(pop.length,(this.internalGenState["rlr"]++)))].entity;
    }, "Sequential": function(pop) {
        this.internalGenState["seq"] = this.internalGenState["seq"]||0;
        return pop[(this.internalGenState["seq"]++)%pop.length].entity;
    }
};

var Select2 = {
    "Tournament2": function(pop) {
        return [Select1.Tournament2.call(this, pop), Select1.Tournament2.call(this, pop)];
    }, "Tournament3": function(pop) {
        return [Select1.Tournament3.call(this, pop), Select1.Tournament3.call(this, pop)];
    }, "Random": function(pop) {
        return [Select1.Random.call(this, pop), Select1.Random.call(this, pop)];
    }, "RandomLinearRank": function(pop) {
        return [Select1.RandomLinearRank.call(this, pop), Select1.RandomLinearRank.call(this, pop)];
    }, "Sequential": function(pop) {
        return [Select1.Sequential.call(this, pop), Select1.Sequential.call(this, pop)];
    }, "FittestRandom": function(pop) {
        return [Select1.Fittest.call(this, pop), Select1.Random.call(this, pop)];
    }
};

var range = require('range').range;
var ee = require('event-emitter');

function Genetic() {
    // population
    this.fitness = null;
    this.seed = null;
    this.mutate = null;
    this.crossover = null;
    this.select1 = null;
    this.select2 = null;
    this.optimize = null;
    this.generation = null;

    this.configuration = {
        size: 250,
        crossover: 0.9,
        mutation: 0.2,
        iterations: 100,
        fittestAlwaysSurvives: true,
        maxResults: 100,
        skip: 0
    };

    this.userData = {};
    this.internalGenState = {};

    this.emitter = ee({});
}

Genetic.prototype.start = function() {
    var i = 0; // current generation/iteration/generation number

    var iteration = (function(bar) {
        // reset for each generation
        this.internalGenState = {};

        // score and sort
        var pop = bar.entities
            .map(function (entity) {
                return {
                    fitness: this.fitness(entity),
                    entity: entity
                };
            }, this)
            .sort((function (a, b) {
                return this.optimize(a.fitness, b.fitness) ? -1 : 1;
            }).bind(this));

        // generation notification
        var stats = this.getStats(pop);

        var r = this.generation ? this.generation(pop, i, stats) : true;
        var isFinished = (typeof r != "undefined" && !r) || (i == this.configuration.iterations-1);

        if (isFinished || this.configuration["skip"] == 0 || i%this.configuration["skip"] == 0) {
            this.emitter.emit('notification', pop.slice(0, this.maxResults), i, stats, isFinished);
        }

        if(isFinished) {
            this.emitter.emit('finished', pop, i, stats);
            ++i;
            return {
                isFinished: isFinished
            };
        } else {
            var newEntities = this.breed(pop);
            ++i;
            return {
                isFinished: isFinished,
                entities: newEntities
            };
        }
    }).bind(this);

    function shouldTerminate(foo) {
        return foo.isFinished;
    }

    function doUntil(body, terminationCriteria) {
        return function(foo) {
            return new Promise((function(outerResolve, reject) {

                // Promises in a doUntil fashion
                function singleIter(prom, cb) {
                    var newProm = prom.then(iteration);

                    newProm.then(function(foo) {
                        if(shouldTerminate(foo)) {
                            cb(foo);
                        } else {
                            singleIter(newProm, cb);
                        }
                    });
                }

                /*
                 Promise.resolve(foo)
                 .then(doUntil(
                 body,
                 terminationCriteria
                 ))
                 .then(outerResolve);
                 */
                singleIter(Promise.resolve(foo), outerResolve);
            }).bind(this));
        }
    }

    return this.getInitialPopulation()
        .then(function(entities) { return { entities: entities }})
        .then((function(foo) {
            return new Promise((function(outerResolve, reject) {

                // Promises in a doUntil fashion
                function singleIter(prom, cb) {
                    var newProm = prom.then(iteration);

                    newProm.then(function(foo) {
                        if(shouldTerminate(foo)) {
                            cb(foo);
                        } else {
                            singleIter(newProm, cb);
                        }
                    });
                }

/*
                Promise.resolve(foo)
                    .then(doUntil(
                        body,
                        terminationCriteria
                    ))
                    .then(outerResolve);
*/
                singleIter(Promise.resolve(foo), outerResolve);
            }).bind(this));
        }).bind(this));
};

Genetic.prototype.getInitialPopulation = function() {
    return Promise.all(range(0, this.configuration.size).map((function(index) {
        return this.seed();
    }).bind(this)));
};

Genetic.prototype.getStats = function(pop) {
    var mean = pop.reduce(function (a, b) { return a + b.fitness; }, 0)/pop.length;
    var stdev = Math.sqrt(pop
            .map(function (a) { return (a.fitness - mean) * (a.fitness - mean); })
            .reduce(function (a, b) { return a+b; }, 0)/pop.length);

    return {
        maximum: pop[0].fitness,
        minimum: pop[pop.length-1].fitness,
        mean: mean,
        stdev: stdev
    };
};

Genetic.prototype.breed = function(pop) {
    var mutateOrNot = (function(entity) {
        // applies mutation based on mutation probability
        return Math.random() <= this.configuration.mutation && this.mutate ? this.mutate(entity) : entity;
    }).bind(this);

    // crossover and mutate
    var newPop = [];

    if (this.configuration.fittestAlwaysSurvives) { // lets the best solution fall through
        newPop.push(pop[0].entity);
    }

    while (newPop.length < this.configuration.size) {
        if (
            this.crossover // if there is a crossover function
            && Math.random() <= this.configuration.crossover // base crossover on specified probability
            && newPop.length+1 < this.configuration.size // keeps us from going 1 over the max population size
        ) {
            var parents = this.select2(pop);
            var children = this.crossover(parents[0], parents[1]).map(mutateOrNot);
            newPop.push(children[0], children[1]);
        } else {
            newPop.push(mutateOrNot(this.select1(pop)));
        }
    }

    return newPop;
};

Genetic.prototype.on = function(eventName, callback) {
    this.emitter.on(eventName, callback.bind(this));
};

Genetic.prototype.evolve = function(config, userData) {
    var k;
    for (k in config) {
        this.configuration[k] = config[k];
    }

    for (k in userData) {
        this.userData[k] = userData[k];
    }

    return this.start();
};

module.exports = {
    create: function() {
        return new Genetic();
    },
    Select1: Select1,
    Select2: Select2,
    Optimize: Optimize
};
<|endoftext|>


<commit_before>/* jshint node: true */
'use strict';

module.exports = {
  name: 'ember-power-select-with-create'
};
<commit_msg>Allow usage without EPS<commit_after>/* jshint node: true */
'use strict';

module.exports = {
  name: 'ember-power-select-with-create',

  contentFor: function(type, config) {
    var emberPowerSelect = this.addons.filter(function(addon) {
      return addon.name === 'ember-power-select';
    })[0]
    return emberPowerSelect.contentFor(type, config);
  }
};
<|endoftext|>


<commit_before>const fs = require('fs');
const http = require('http');
const url = require('url');
const crypto = require('crypto');
const publicIp = require('public-ip');
const Eris = require('eris');
const moment = require('moment');
const Queue = require('./queue');
const config = require('./config');

const logServerPort = config.port || 8890;

const bot = new Eris.CommandClient(config.token, {}, {
  prefix: config.prefix || '!',
  ignoreSelf: true,
  ignoreBots: true,
  defaultHelpCommand: false,
});

let modMailGuild;
const modMailChannels = {};
const messageQueue = new Queue();

const blockFile = `${__dirname}/blocked.json`;
let blocked = [];

const logDir = `${__dirname}/logs`;
const logFileFormatRegex = /^([0-9\-]+?)__([0-9]+?)__([0-9a-f]+?)\.txt$/;

try {
  const blockedJSON = fs.readFileSync(blockFile, {encoding: 'utf8'});
  blocked = JSON.parse(blockedJSON);
} catch(e) {
  fs.writeFileSync(blockFile, '[]');
}

function saveBlocked() {
  fs.writeFileSync(blockFile, JSON.stringify(blocked, null, 4));
}

/*
 * MODMAIL LOG UTILITY FUNCTIONS
 */

function getLogFileInfo(logfile) {
  const match = logfile.match(logFileFormatRegex);
  if (! match) return null;

  return {
    date: match[1],
    userId: match[2],
    token: match[3],
  };
}

function getLogFilePath(logfile) {
  return `${logDir}/${logfile}`;
}

function getLogFileUrl(logfile) {
  const info = getLogFileInfo(logfile);

  return publicIp.v4().then(ip => {
    return `http://${ip}:${logServerPort}/logs/${info.token}`;
  });
}

function getRandomLogFile(userId) {
  return new Promise(resolve => {
    crypto.randomBytes(16, (err, buf) => {
      const token = buf.toString('hex');
      const date = moment.utc().format('YYYY-MM-DD-HH-mm-ss');

      resolve(`${date}__${userId}__${token}.txt`);
    });
  });
}

function findLogFile(token) {
  return new Promise(resolve => {
    fs.readdir(logDir, (err, files) => {
      for (const file of files) {
        if (file.endsWith(`__${token}.txt`)) {
          resolve(file);
          return;
        }
      }

      resolve(null);
    });
  });
}

function findLogFilesByUserId(userId) {
  return new Promise(resolve => {
    fs.readdir(logDir, (err, files) => {
      const logfiles = files.filter(file => {
        const info = getLogFileInfo(file);
        console.log('info:', info);
        console.log('userId:', userId, typeof userId);
        if (! info) return false;

        return info.userId === userId;
      });

      resolve(logfiles);
    });
  });
}

/*
 * MAIN FUNCTIONALITY
 */

bot.on('ready', () => {
  modMailGuild = bot.guilds.find(g => g.id === config.mailGuildId);

  if (! modMailGuild) {
    console.error('You need to set and invite me to the mod mail guild first!');
    process.exit(0);
  }

  bot.editStatus(null, {name: 'DM me for help'});
});

function getModmailChannelInfo(channel) {
  if (! channel.topic) return null;

  const match = channel.topic.match(/^MODMAIL\|([0-9]+)\|(.*)$/);
  if (! match) return null;

  return {
    userId: match[1],
    name: match[2],
  };
}

function getModmailChannel(user) {
  if (modMailChannels[user.id]) {
    // Cached
    const channel = modMailGuild.channels.get(modMailChannels[user.id]);
    if (channel) {
      return Promise.resolve(channel);
    } else {
      // If the cache value was invalid, remove it
      delete modMailChannels[user.id];
    }
  }

  // Try to find a matching channel
  let candidate = modMailGuild.channels.find(c => {
    const info = getModmailChannelInfo(c);
    return info && info.userId === user.id;
  });

  if (candidate) {
    return Promise.resolve(candidate);
  } else {
    // If one is not found, create and cache it
    return modMailGuild.createChannel(`${user.username}-${user.discriminator}`)
      .then(channel => {
        // This is behind a timeout because Discord was telling me the channel didn't exist after creation even though it clearly did
        // ¯\_(ツ)_/¯
        return new Promise(resolve => {
          const topic = `MODMAIL|${user.id}|${user.username}#${user.discriminator}`;
          setTimeout(() => resolve(channel.edit({topic: topic})), 200);
        });
      })
      .then(channel => {
        modMailChannels[user.id] = channel.id;
        channel._wasCreated = true;
        return channel;
      });
  }
}

bot.on('messageCreate', (msg) => {
  if (! (msg.channel instanceof Eris.PrivateChannel)) return;
  if (msg.author.id === bot.user.id) return;

  if (blocked.indexOf(msg.author.id) !== -1) return;

  // This needs to be queued as otherwise, if a user sent a bunch of messages initially and the createChannel endpoint is delayed, we might get duplicate channels
  messageQueue.add(() => {
    return getModmailChannel(msg.author).then(channel => {
      channel.createMessage(`« **${msg.author.username}#${msg.author.discriminator}:** ${msg.content}`);

      if (channel._wasCreated) {
        bot.createMessage(modMailGuild.id, {
          content: `@here New modmail thread: ${channel.mention}`,
          disableEveryone: false,
        });

        msg.channel.createMessage("Thank you for your message! Our mod team will reply to you here as soon as possible.");
      }
    });
  });
});

bot.registerCommand('reply', (msg, args) => {
  if (msg.channel.guild.id !== modMailGuild.id) return;
  if (! msg.member.permission.has('manageRoles')) return;

  const channelInfo = getModmailChannelInfo(msg.channel);
  if (! channelInfo) return;

  bot.getDMChannel(channelInfo.userId).then(channel => {
    const message = `**${msg.author.username}:** ${args.join(' ')}`;

    channel.createMessage(message);
    msg.channel.createMessage(`» ${message}`);
    msg.delete();
  });
});

bot.registerCommandAlias('r', 'reply');

bot.registerCommand('close', (msg, args) => {
  if (msg.channel.guild.id !== modMailGuild.id) return;
  if (! msg.member.permission.has('manageRoles')) return;

  const channelInfo = getModmailChannelInfo(msg.channel);
  if (! channelInfo) return;

  msg.channel.createMessage('Saving logs and closing channel...');
  msg.channel.getMessages(10000).then(messages => {
    const log = messages.reverse().map(message => {
      const date = moment.utc(message.timestamp, 'x').format('YYYY-MM-DD HH:mm:ss');
      return `[${date}] ${message.author.username}#${message.author.discriminator}: ${message.content}`;
    }).join('\n') + '\n';

    getRandomLogFile(channelInfo.userId).then(logfile => {
      fs.writeFile(getLogFilePath(logfile), log, {encoding: 'utf8'}, err => {
        getLogFileUrl(logfile).then(logurl => {
          bot.createMessage(modMailGuild.id, `Log of modmail thread with ${channelInfo.name}:\n<${logurl}>`);

          delete modMailChannels[channelInfo.userId];
          msg.channel.delete();
        });
      });
    })
  });
});

bot.registerCommand('block', (msg, args) => {
  if (msg.channel.guild.id !== modMailGuild.id) return;
  if (! msg.member.permission.has('manageRoles')) return;
  if (args.length !== 1) return;

  let userId;
  if (args[0].match(/^[0-9]+$/)) {
    userId = args[0];
  } else {
    let mentionMatch = args[0].match(/^<@([0-9]+?)>$/);
    if (mentionMatch) userId = mentionMatch[1];
  }

  if (! userId) return;

  blocked.push(args[0]);
  saveBlocked();
  msg.channel.createMessage(`Blocked <@${userId}> (id ${userId}) from modmail`);
});

bot.registerCommand('unblock', (msg, args) => {
  if (msg.channel.guild.id !== modMailGuild.id) return;
  if (! msg.member.permission.has('manageRoles')) return;
  if (args.length !== 1) return;

  let userId;
  if (args[0].match(/^[0-9]+$/)) {
    userId = args[0];
  } else {
    let mentionMatch = args[0].match(/^<@([0-9]+?)>$/);
    if (mentionMatch) userId = mentionMatch[1];
  }

  if (! userId) return;

  blocked.splice(blocked.indexOf(args[0]), 1);
  saveBlocked();
  msg.channel.createMessage(`Unblocked <@${userId}> (id ${userId}) from modmail`);
});

bot.registerCommand('logs', (msg, args) => {
  if (msg.channel.guild.id !== modMailGuild.id) return;
  if (! msg.member.permission.has('manageRoles')) return;
  if (args.length !== 1) return;

  let userId;
  if (args[0].match(/^[0-9]+$/)) {
    userId = args[0];
  } else {
    let mentionMatch = args[0].match(/^<@([0-9]+?)>$/);
    if (mentionMatch) userId = mentionMatch[1];
  }

  if (! userId) return;

  findLogFilesByUserId(userId).then(logfiles => {
    let message = `**Log files for <@${userId}>:**\n`;

    const urlPromises = logfiles.map(logfile => {
      const info = getLogFileInfo(logfile);
      return getLogFileUrl(logfile).then(url => {
        info.url = url;
        return info;
      });
    });

    Promise.all(urlPromises).then(infos => {
      infos.sort((a, b) => {
        if (a.date > b.date) return 1;
        if (a.date < b.date) return -1;
        return 0;
      });

      message += infos.map(info => {
        const formattedDate = moment.utc(info.date, 'YYYY-MM-DD-HH-mm-ss').format('MMM Mo [at] HH:mm [UTC]');
        return `${formattedDate}: <${info.url}>`;
      }).join('\n');

      msg.channel.createMessage(message);
    });
  });
});

bot.connect();

/*
 * MODMAIL LOG SERVER
 */

const server = http.createServer((req, res) => {
  const parsedUrl = url.parse(`http://${req.url}`);

  if (! parsedUrl.path.startsWith('/logs/')) return;

  const pathParts = parsedUrl.path.split('/').filter(v => v !== '');
  const token = pathParts[pathParts.length - 1];

  console.log('token:', token);

  if (token.match(/^[0-9a-f]+$/) === null) return res.end();

  findLogFile(token).then(logfile => {
    console.log('logfile:', logfile);
    if (logfile === null) return res.end();

    fs.readFile(getLogFilePath(logfile), {encoding: 'utf8'}, (err, data) => {
      res.setHeader('Content-Type', 'text/plain');
      res.end(data);
    });
  });
});

server.listen(logServerPort);
<commit_msg>Make status customizable via config and change default wording from 'DM' to 'Message'<commit_after>const fs = require('fs');
const http = require('http');
const url = require('url');
const crypto = require('crypto');
const publicIp = require('public-ip');
const Eris = require('eris');
const moment = require('moment');
const Queue = require('./queue');
const config = require('./config');

const logServerPort = config.port || 8890;

const bot = new Eris.CommandClient(config.token, {}, {
  prefix: config.prefix || '!',
  ignoreSelf: true,
  ignoreBots: true,
  defaultHelpCommand: false,
});

let modMailGuild;
const modMailChannels = {};
const messageQueue = new Queue();

const blockFile = `${__dirname}/blocked.json`;
let blocked = [];

const logDir = `${__dirname}/logs`;
const logFileFormatRegex = /^([0-9\-]+?)__([0-9]+?)__([0-9a-f]+?)\.txt$/;

try {
  const blockedJSON = fs.readFileSync(blockFile, {encoding: 'utf8'});
  blocked = JSON.parse(blockedJSON);
} catch(e) {
  fs.writeFileSync(blockFile, '[]');
}

function saveBlocked() {
  fs.writeFileSync(blockFile, JSON.stringify(blocked, null, 4));
}

/*
 * MODMAIL LOG UTILITY FUNCTIONS
 */

function getLogFileInfo(logfile) {
  const match = logfile.match(logFileFormatRegex);
  if (! match) return null;

  return {
    date: match[1],
    userId: match[2],
    token: match[3],
  };
}

function getLogFilePath(logfile) {
  return `${logDir}/${logfile}`;
}

function getLogFileUrl(logfile) {
  const info = getLogFileInfo(logfile);

  return publicIp.v4().then(ip => {
    return `http://${ip}:${logServerPort}/logs/${info.token}`;
  });
}

function getRandomLogFile(userId) {
  return new Promise(resolve => {
    crypto.randomBytes(16, (err, buf) => {
      const token = buf.toString('hex');
      const date = moment.utc().format('YYYY-MM-DD-HH-mm-ss');

      resolve(`${date}__${userId}__${token}.txt`);
    });
  });
}

function findLogFile(token) {
  return new Promise(resolve => {
    fs.readdir(logDir, (err, files) => {
      for (const file of files) {
        if (file.endsWith(`__${token}.txt`)) {
          resolve(file);
          return;
        }
      }

      resolve(null);
    });
  });
}

function findLogFilesByUserId(userId) {
  return new Promise(resolve => {
    fs.readdir(logDir, (err, files) => {
      const logfiles = files.filter(file => {
        const info = getLogFileInfo(file);
        console.log('info:', info);
        console.log('userId:', userId, typeof userId);
        if (! info) return false;

        return info.userId === userId;
      });

      resolve(logfiles);
    });
  });
}

/*
 * MAIN FUNCTIONALITY
 */

bot.on('ready', () => {
  modMailGuild = bot.guilds.find(g => g.id === config.mailGuildId);

  if (! modMailGuild) {
    console.error('You need to set and invite me to the mod mail guild first!');
    process.exit(0);
  }

  bot.editStatus(null, {name: config.status || 'Message me for help'});
});

function getModmailChannelInfo(channel) {
  if (! channel.topic) return null;

  const match = channel.topic.match(/^MODMAIL\|([0-9]+)\|(.*)$/);
  if (! match) return null;

  return {
    userId: match[1],
    name: match[2],
  };
}

function getModmailChannel(user) {
  if (modMailChannels[user.id]) {
    // Cached
    const channel = modMailGuild.channels.get(modMailChannels[user.id]);
    if (channel) {
      return Promise.resolve(channel);
    } else {
      // If the cache value was invalid, remove it
      delete modMailChannels[user.id];
    }
  }

  // Try to find a matching channel
  let candidate = modMailGuild.channels.find(c => {
    const info = getModmailChannelInfo(c);
    return info && info.userId === user.id;
  });

  if (candidate) {
    return Promise.resolve(candidate);
  } else {
    // If one is not found, create and cache it
    return modMailGuild.createChannel(`${user.username}-${user.discriminator}`)
      .then(channel => {
        // This is behind a timeout because Discord was telling me the channel didn't exist after creation even though it clearly did
        // ¯\_(ツ)_/¯
        return new Promise(resolve => {
          const topic = `MODMAIL|${user.id}|${user.username}#${user.discriminator}`;
          setTimeout(() => resolve(channel.edit({topic: topic})), 200);
        });
      })
      .then(channel => {
        modMailChannels[user.id] = channel.id;
        channel._wasCreated = true;
        return channel;
      });
  }
}

bot.on('messageCreate', (msg) => {
  if (! (msg.channel instanceof Eris.PrivateChannel)) return;
  if (msg.author.id === bot.user.id) return;

  if (blocked.indexOf(msg.author.id) !== -1) return;

  // This needs to be queued as otherwise, if a user sent a bunch of messages initially and the createChannel endpoint is delayed, we might get duplicate channels
  messageQueue.add(() => {
    return getModmailChannel(msg.author).then(channel => {
      channel.createMessage(`« **${msg.author.username}#${msg.author.discriminator}:** ${msg.content}`);

      if (channel._wasCreated) {
        bot.createMessage(modMailGuild.id, {
          content: `@here New modmail thread: ${channel.mention}`,
          disableEveryone: false,
        });

        msg.channel.createMessage("Thank you for your message! Our mod team will reply to you here as soon as possible.");
      }
    });
  });
});

bot.registerCommand('reply', (msg, args) => {
  if (msg.channel.guild.id !== modMailGuild.id) return;
  if (! msg.member.permission.has('manageRoles')) return;

  const channelInfo = getModmailChannelInfo(msg.channel);
  if (! channelInfo) return;

  bot.getDMChannel(channelInfo.userId).then(channel => {
    const message = `**${msg.author.username}:** ${args.join(' ')}`;

    channel.createMessage(message);
    msg.channel.createMessage(`» ${message}`);
    msg.delete();
  });
});

bot.registerCommandAlias('r', 'reply');

bot.registerCommand('close', (msg, args) => {
  if (msg.channel.guild.id !== modMailGuild.id) return;
  if (! msg.member.permission.has('manageRoles')) return;

  const channelInfo = getModmailChannelInfo(msg.channel);
  if (! channelInfo) return;

  msg.channel.createMessage('Saving logs and closing channel...');
  msg.channel.getMessages(10000).then(messages => {
    const log = messages.reverse().map(message => {
      const date = moment.utc(message.timestamp, 'x').format('YYYY-MM-DD HH:mm:ss');
      return `[${date}] ${message.author.username}#${message.author.discriminator}: ${message.content}`;
    }).join('\n') + '\n';

    getRandomLogFile(channelInfo.userId).then(logfile => {
      fs.writeFile(getLogFilePath(logfile), log, {encoding: 'utf8'}, err => {
        getLogFileUrl(logfile).then(logurl => {
          bot.createMessage(modMailGuild.id, `Log of modmail thread with ${channelInfo.name}:\n<${logurl}>`);

          delete modMailChannels[channelInfo.userId];
          msg.channel.delete();
        });
      });
    })
  });
});

bot.registerCommand('block', (msg, args) => {
  if (msg.channel.guild.id !== modMailGuild.id) return;
  if (! msg.member.permission.has('manageRoles')) return;
  if (args.length !== 1) return;

  let userId;
  if (args[0].match(/^[0-9]+$/)) {
    userId = args[0];
  } else {
    let mentionMatch = args[0].match(/^<@([0-9]+?)>$/);
    if (mentionMatch) userId = mentionMatch[1];
  }

  if (! userId) return;

  blocked.push(args[0]);
  saveBlocked();
  msg.channel.createMessage(`Blocked <@${userId}> (id ${userId}) from modmail`);
});

bot.registerCommand('unblock', (msg, args) => {
  if (msg.channel.guild.id !== modMailGuild.id) return;
  if (! msg.member.permission.has('manageRoles')) return;
  if (args.length !== 1) return;

  let userId;
  if (args[0].match(/^[0-9]+$/)) {
    userId = args[0];
  } else {
    let mentionMatch = args[0].match(/^<@([0-9]+?)>$/);
    if (mentionMatch) userId = mentionMatch[1];
  }

  if (! userId) return;

  blocked.splice(blocked.indexOf(args[0]), 1);
  saveBlocked();
  msg.channel.createMessage(`Unblocked <@${userId}> (id ${userId}) from modmail`);
});

bot.registerCommand('logs', (msg, args) => {
  if (msg.channel.guild.id !== modMailGuild.id) return;
  if (! msg.member.permission.has('manageRoles')) return;
  if (args.length !== 1) return;

  let userId;
  if (args[0].match(/^[0-9]+$/)) {
    userId = args[0];
  } else {
    let mentionMatch = args[0].match(/^<@([0-9]+?)>$/);
    if (mentionMatch) userId = mentionMatch[1];
  }

  if (! userId) return;

  findLogFilesByUserId(userId).then(logfiles => {
    let message = `**Log files for <@${userId}>:**\n`;

    const urlPromises = logfiles.map(logfile => {
      const info = getLogFileInfo(logfile);
      return getLogFileUrl(logfile).then(url => {
        info.url = url;
        return info;
      });
    });

    Promise.all(urlPromises).then(infos => {
      infos.sort((a, b) => {
        if (a.date > b.date) return 1;
        if (a.date < b.date) return -1;
        return 0;
      });

      message += infos.map(info => {
        const formattedDate = moment.utc(info.date, 'YYYY-MM-DD-HH-mm-ss').format('MMM Mo [at] HH:mm [UTC]');
        return `${formattedDate}: <${info.url}>`;
      }).join('\n');

      msg.channel.createMessage(message);
    });
  });
});

bot.connect();

/*
 * MODMAIL LOG SERVER
 */

const server = http.createServer((req, res) => {
  const parsedUrl = url.parse(`http://${req.url}`);

  if (! parsedUrl.path.startsWith('/logs/')) return;

  const pathParts = parsedUrl.path.split('/').filter(v => v !== '');
  const token = pathParts[pathParts.length - 1];

  console.log('token:', token);

  if (token.match(/^[0-9a-f]+$/) === null) return res.end();

  findLogFile(token).then(logfile => {
    console.log('logfile:', logfile);
    if (logfile === null) return res.end();

    fs.readFile(getLogFilePath(logfile), {encoding: 'utf8'}, (err, data) => {
      res.setHeader('Content-Type', 'text/plain');
      res.end(data);
    });
  });
});

server.listen(logServerPort);
<|endoftext|>


<commit_before>'use strict';
var path = require('path');
var gutil = require('gulp-util');
var through = require('through2');
var Jasmine = require('jasmine');
var Reporter = require('jasmine-terminal-reporter');
var SilentReporter = require('./silent-reporter');

module.exports = function (options) {
	options = options || {};

	var jasmine = new Jasmine();

	if (options.timeout) {
		jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = options.timeout;
	}

	var color = process.argv.indexOf('--no-color') === -1;
	var reporter = options.reporter;

	if (reporter) {
		(Array.isArray(reporter) ? reporter : [reporter]).forEach(function (el) {
			jasmine.addReporter(el);
		});
	} else {
		jasmine.addReporter(new Reporter({
			isVerbose: options.verbose,
			showColors: color,
			includeStackTrace: options.includeStackTrace
		}));
	}

	return through.obj(function (file, enc, cb) {
		if (file.isNull()) {
			cb(null, file);
			return;
		}

		if (file.isStream()) {
			cb(new gutil.PluginError('gulp-jasmine', 'Streaming not supported'));
			return;
		}

		/**
		 * Get the cache object of the specs.js file,
		 * get its children and delete the children cache
		 */
		var resolvedPath = path.resolve(file.path);
		var modId = require.resolve(resolvedPath);
		var files = require.cache[modId];
		if (typeof files !== 'undefined') {
			for (var i in files.children) {
				delete require.cache[files.children[i].id];
			}
		}

		delete require.cache[modId];
		jasmine.addSpecFile(resolvedPath);

		cb(null, file);
	}, function (cb) {
		try {
			jasmine.addReporter(new SilentReporter(cb));
			jasmine.execute();
		} catch (err) {
			cb(new gutil.PluginError('gulp-jasmine', err));
		}
	});
};
<commit_msg>Close #36 PR: when deleting cached file, do it recursively.<commit_after>'use strict';
var path = require('path');
var gutil = require('gulp-util');
var through = require('through2');
var Jasmine = require('jasmine');
var Reporter = require('jasmine-terminal-reporter');
var SilentReporter = require('./silent-reporter');

function deleteRequireCache( id ) {
	// recursively delete source code to be tested,
	// but skip mature code loaded from node_modules
	if(id.indexOf('node_modules') >= 0) return;
	var files = require.cache[ id ];
	if (typeof files !== 'undefined') {
		for (var i in files.children) {
			deleteRequireCache( files.children[i].id );
		}
		delete require.cache[ id ];
	}
}

module.exports = function (options) {
	options = options || {};

	var jasmine = new Jasmine();

	if (options.timeout) {
		jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = options.timeout;
	}

	var color = process.argv.indexOf('--no-color') === -1;
	var reporter = options.reporter;

	if (reporter) {
		(Array.isArray(reporter) ? reporter : [reporter]).forEach(function (el) {
			jasmine.addReporter(el);
		});
	} else {
		jasmine.addReporter(new Reporter({
			isVerbose: options.verbose,
			showColors: color,
			includeStackTrace: options.includeStackTrace
		}));
	}

	return through.obj(function (file, enc, cb) {
		if (file.isNull()) {
			cb(null, file);
			return;
		}

		if (file.isStream()) {
			cb(new gutil.PluginError('gulp-jasmine', 'Streaming not supported'));
			return;
		}

		/**
		 * Get the cache object of the specs.js file,
		 * delete it and its children recursively from cache
		 */
		var resolvedPath = path.resolve(file.path);
		var modId = require.resolve(resolvedPath);
		deleteRequireCache( modId );

		jasmine.addSpecFile(resolvedPath);

		cb(null, file);
	}, function (cb) {
		try {
			jasmine.addReporter(new SilentReporter(cb));
			jasmine.execute();
		} catch (err) {
			cb(new gutil.PluginError('gulp-jasmine', err));
		}
	});
};
<|endoftext|>


<commit_before>
const depictsWholeNumber = require('depicts-whole-number').onlySafeNumbers
const c = {
  ESCAPE_CHAR: '\\',
  KEY_SEPARATOR: '.',
  EMPTY_STRING: '',
  REGEX: {
    SQUARE_BRACKETS: /[\[\]]/g,
    EXPLICIT_ARRAY: /\[(0|[1-9][0-9]*)\]/,
    EXPLICIT_ARRAY_GM: /\[(0|[1-9][0-9]*)\]/g
  }
}
const defaultOptions = {
  CustomDelimiter: c.KEY_SEPARATOR,
  AutocreateArrays: true,
  ExplicitArrays: false,
  CircularityCheck: false,
  MaxDepth: 0
}

/**
 * ObjectFx static class.
 * @class
 */
class ObjectFx {

  /**
   * Checks for valid array index
   * @param {String} s
   * @return {Boolean}
   */
  static _isValidArrayIndex (s) {
    return depictsWholeNumber(s)
  }

  /**
   * Checks for valid array index within square brackets (named 'explicit array' here)
   * @param {String} s
   * @return {Boolean}
   */
  static _containsValidArrayIndex (s) {
    const result = s.match(c.REGEX.EXPLICIT_ARRAY)
    if (!result) {
      return false
    }
    return depictsWholeNumber(result[1])
  }

  /**
   * Expands (unflattens) a flattened object
   * @param {Object} objFlat - flattened object
   * @param {Object} userOptions - options
   * @return {Object}
   */
  static unflatten (objFlat, userOptions) {
    return this.expand(objFlat, userOptions)
  }
  static expand (objFlat, userOptions) {
    if (Object.prototype.toString.call(objFlat) !== '[object Object]') {
      return null
    }
    const options = Object.prototype.toString.call(userOptions) === '[object Object]' ? Object.assign({}, defaultOptions, userOptions) : defaultOptions
    const prefix = 'root' + options.CustomDelimiter
    const regexConsecutiveSeparators = new RegExp('\\' + options.CustomDelimiter + '{2,}', 'g')
    const regexSurroundingSeparators = new RegExp('^\\' + options.CustomDelimiter + '+|\\' + options.CustomDelimiter + '+$', 'g')
    const objExp = {}
    let keys = Object.keys(objFlat)
    for (let i = 0, kln = keys.length; i < kln; i++) {
      const origKey = keys[i]
      let currKey = prefix + origKey
      if (options.ExplicitArrays) {
        currKey = currKey
          .replace(c.REGEX.EXPLICIT_ARRAY_GM, options.CustomDelimiter + '[$1]' + options.CustomDelimiter)
          .replace(regexConsecutiveSeparators, options.CustomDelimiter)
          .replace(regexSurroundingSeparators, c.EMPTY_STRING)
      }
      const chunks = currKey.split(options.CustomDelimiter)
      let obj = objExp
      for (let j = 0, cln = chunks.length; j < cln; j++) {
        let currChunk = chunks[j]
        let nextChunk = chunks[j + 1]
        if (options.ExplicitArrays) {
          currChunk = currChunk.replace(c.REGEX.SQUARE_BRACKETS, c.EMPTY_STRING)
        }
        if (!obj[currChunk]) {
          if (j > cln - 2) {
            obj[currChunk] = objFlat[origKey]
          } else {
            if ((options.AutocreateArrays && this._isValidArrayIndex(nextChunk)) ||
              (options.ExplicitArrays && this._containsValidArrayIndex(nextChunk))) {
              obj[currChunk] = []
            } else {
              obj[currChunk] = {}
            }
          }
        }
        if (options.ExplicitArrays && this._containsValidArrayIndex(currChunk)) {
          currChunk.replace(c.REGEX.SQUARE_BRACKETS, c.EMPTY_STRING)
        }
        obj = obj[currChunk]
      }
    }
    return objExp.root
  }

  /**
   * Flattens an object
   * @param {Object} objExp
   * @param {Object} userOptions - options
   * @return {Object}
   */
  static flatten (objExp, userOptions) {
    if (Object.prototype.toString.call(objExp) !== '[object Object]') {
      return null
    }
    const options = Object.prototype.toString.call(userOptions) === '[object Object]' ? Object.assign({}, defaultOptions, userOptions) : defaultOptions
    if (options.CircularityCheck) {
      try {
        JSON.stringify(objExp)
      } catch (err) {
        if (err.message.match(/circular structure/ig)) {
          // throw TypeError('Unable to flatten circular structure')
          return null
        }
      }
    }

    let result = {}
    const recurse = (cur, prop, lev) => {
      if (options.MaxDepth > 0 && lev >= options.MaxDepth) {
        result[prop] = cur
        return
      }
      lev++
      if (typeof cur !== 'object') { // Object(cur) !== cur
        result[prop] = cur
      } else if (Array.isArray(cur)) { // https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray
        for (var i = 0, l = cur.length; i < l; i++) {
          if (options.ExplicitArrays) {
            recurse(cur[i], prop + '[' + i + ']', lev)
          } else {
            let prefix = prop
            if (prefix) {
              prefix += options.CustomDelimiter
            }
            recurse(cur[i], prefix + i, lev)
          }
        }
        if (l === 0) {
          result[prop] = []
        }
      } else {
        if (Object.prototype.toString.call(cur) === '[object Object]') { // cur && cur.toString() === '[object Object]'
          for (var p in cur) {
            recurse(cur[p], prop ? prop + options.CustomDelimiter + p : p, lev)
          }
        } else {
          result[prop] = cur
        }
      }
    }
    recurse(objExp, c.EMPTY_STRING, 0)
    return result
  }

}

module.exports = ObjectFx
<commit_msg>fix delimiter escaping for regex, remove unused lines<commit_after>
const depictsWholeNumber = require('depicts-whole-number').onlySafeNumbers
const c = {
  ESCAPE_CHAR: '\\',
  KEY_SEPARATOR: '.',
  EMPTY_STRING: '',
  REGEX: {
    SQUARE_BRACKETS: /[\[\]]/g,
    EXPLICIT_ARRAY: /\[(0|[1-9][0-9]*)\]/,
    EXPLICIT_ARRAY_GM: /\[(0|[1-9][0-9]*)\]/g
  }
}
const defaultOptions = {
  CustomDelimiter: c.KEY_SEPARATOR,
  AutocreateArrays: true,
  ExplicitArrays: false,
  CircularityCheck: false,
  MaxDepth: 0
}

/**
 * ObjectFx static class.
 * @class
 */
class ObjectFx {

  /**
   * Checks for valid array index
   * @param {String} s
   * @return {Boolean}
   */
  static _isValidArrayIndex (s) {
    return depictsWholeNumber(s)
  }

  /**
   * Checks for valid array index within square brackets (named 'explicit array' here)
   * @param {String} s
   * @return {Boolean}
   */
  static _containsValidArrayIndex (s) {
    const result = s.match(c.REGEX.EXPLICIT_ARRAY)
    if (!result) {
      return false
    }
    return depictsWholeNumber(result[1])
  }

  /**
   * Expands (unflattens) a flattened object
   * @param {Object} objFlat - flattened object
   * @param {Object} userOptions - options
   * @return {Object}
   */
  static unflatten (objFlat, userOptions) {
    return this.expand(objFlat, userOptions)
  }
  static expand (objFlat, userOptions) {
    if (Object.prototype.toString.call(objFlat) !== '[object Object]') {
      return null
    }
    const options = Object.prototype.toString.call(userOptions) === '[object Object]' ? Object.assign({}, defaultOptions, userOptions) : defaultOptions
    const prefix = 'root' + options.CustomDelimiter
    const regexConsecutiveSeparators = new RegExp('\\' + options.CustomDelimiter.split('').join('\\') + '{2,}', 'g')
    const regexSurroundingSeparators = new RegExp('^\\' + options.CustomDelimiter.split('').join('\\') + '+|\\' + options.CustomDelimiter.split('').join('\\') + '+$', 'g')
    const objExp = {}
    let keys = Object.keys(objFlat)
    for (let i = 0, kln = keys.length; i < kln; i++) {
      const origKey = keys[i]
      let currKey = prefix + origKey
      if (options.ExplicitArrays) {
        currKey = currKey
          .replace(c.REGEX.EXPLICIT_ARRAY_GM, options.CustomDelimiter + '[$1]' + options.CustomDelimiter)
          .replace(regexConsecutiveSeparators, options.CustomDelimiter)
          .replace(regexSurroundingSeparators, c.EMPTY_STRING)
      }
      const chunks = currKey.split(options.CustomDelimiter)
      let obj = objExp
      for (let j = 0, cln = chunks.length; j < cln; j++) {
        let currChunk = chunks[j]
        let nextChunk = chunks[j + 1]
        if (options.ExplicitArrays) {
          currChunk = currChunk.replace(c.REGEX.SQUARE_BRACKETS, c.EMPTY_STRING)
        }
        if (!obj[currChunk]) {
          if (j > cln - 2) {
            obj[currChunk] = objFlat[origKey]
          } else {
            if ((options.AutocreateArrays && this._isValidArrayIndex(nextChunk)) ||
              (options.ExplicitArrays && this._containsValidArrayIndex(nextChunk))) {
              obj[currChunk] = []
            } else {
              obj[currChunk] = {}
            }
          }
        }
        /* if (options.ExplicitArrays && this._containsValidArrayIndex(currChunk)) {
          currChunk.replace(c.REGEX.SQUARE_BRACKETS, c.EMPTY_STRING)
        } */
        obj = obj[currChunk]
      }
    }
    return objExp.root
  }

  /**
   * Flattens an object
   * @param {Object} objExp
   * @param {Object} userOptions - options
   * @return {Object}
   */
  static flatten (objExp, userOptions) {
    if (Object.prototype.toString.call(objExp) !== '[object Object]') {
      return null
    }
    const options = Object.prototype.toString.call(userOptions) === '[object Object]' ? Object.assign({}, defaultOptions, userOptions) : defaultOptions
    if (options.CircularityCheck) {
      try {
        JSON.stringify(objExp)
      } catch (err) {
        if (err.message.match(/circular structure/ig)) {
          // throw TypeError('Unable to flatten circular structure')
          return null
        }
      }
    }

    let result = {}
    const recurse = (cur, prop, lev) => {
      if (options.MaxDepth > 0 && lev >= options.MaxDepth) {
        result[prop] = cur
        return
      }
      lev++
      if (typeof cur !== 'object') { // Object(cur) !== cur
        result[prop] = cur
      } else if (Array.isArray(cur)) { // https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray
        for (var i = 0, l = cur.length; i < l; i++) {
          if (options.ExplicitArrays) {
            recurse(cur[i], prop + '[' + i + ']', lev)
          } else {
            let prefix = prop
            if (prefix) {
              prefix += options.CustomDelimiter
            }
            recurse(cur[i], prefix + i, lev)
          }
        }
        /* if (l === 0) {
          result[prop] = []
        } */
      } else {
        if (Object.prototype.toString.call(cur) === '[object Object]') { // cur && cur.toString() === '[object Object]'
          for (var p in cur) {
            recurse(cur[p], prop ? prop + options.CustomDelimiter + p : p, lev)
          }
        } else {
          result[prop] = cur
        }
      }
    }
    recurse(objExp, c.EMPTY_STRING, 0)
    return result
  }

}

module.exports = ObjectFx
<|endoftext|>


<commit_before>module.exports = function(object) {
    if (typeof object !== 'object') return object;
    return proxying(object);
}

function proxying(object) {
    return new Proxy(object, {
        get: (target, prop) => {
            if (typeof target[prop] === 'object' && target[prop] !== null) {
                return proxying(target[prop]);
            } else return target[prop];
        },
        set: (target, prop) => {
            if (typeof target[prop] === 'object' && target[prop] !== null) {
                proxying(target[prop]);
            }
            return true;
        }
    });
};<commit_msg>support of rightJoin throw second argument with optional condition callback<commit_after>module.exports = function(object1, object2, compCondition) {
    if (typeof object1 !== 'object') return object1;
    if (!object2) return proxying(object1);
    if (!!object2 && typeof object2 !== 'object') return proxying(object1);
    if(!!object1 && !!object2 && typeof object1 === 'object' && typeof object2 === 'object') {
        return rightJoin(object1, object2, compCondition)
    }
    return proxying(object1);
}

function rightJoin(object1, object2, compCondition) {
    var result = object1;
    var compare = typeof compCondition === 'function' ? compCondition : objectsAreSame
    if (Array.isArray(object1) && Array.isArray(object2)) {
        var result = [];
        if (object1.length) {
            object2.forEach((b) => {
                object1.forEach((a) => {
                    if (compare(a, b)) {
                        result.push(b);
                    } else {
                        result.push(a);
                    }
                });
            });
        } else {
            result = object2;
        }
    } else if(typeof object1 === 'object' && typeof object2 === 'object') {
        if (compare(object1, object2)) result = object2;
    };

    return proxying(result);
};

function proxying(object) {
    return new Proxy(object, {
        get: (target, prop) => {
            if (typeof target[prop] === 'object' && target[prop] !== null) {
                return proxying(target[prop]);
            } else return target[prop];
        },
        set: (target, prop) => {
            if (typeof target[prop] === 'object' && target[prop] !== null) {
                proxying(target[prop]);
            }
            return true;
        }
    });
};

function objectsAreSame(object1, object2) {
    var result = true;
    for(var propertyName in object1) {
        if (!object1.hasOwnPropery(propertyName) || !object2.hasOwnPropery(propertyName)) {
            result = false;
            break;
        }
        if(object1[propertyName] !== object2[propertyName]) {
            result = false;
            break;
        }
    }
    return result;
 }
<|endoftext|>