<commit_before>HospitalCheckup.module("Common.Chart", function(Chart, HospitalCheckup, Backbone, Marionette, $, _){

  //all the common elements that all list charts in hospital app will share
  Chart.BarBase = ChartBaseView.extend({
    constructor: function(options) {
      ChartBaseView.apply(this, arguments);
      this.options.bar_padding = options.bar_padding || 4;
      this.duration = 500;
      this.bar_height = (this.dimensions.height / this.collection.length) - this.options.bar_padding;
      this.$chart_container.attr('id', this.el.id+"-container");
      return this;
    },
    draw: function() {
      var data = this.filter_data(this.data);
      this.get_scales(data);
      this.create_axes();
      this.create_svg();
      this.create_svg_containers();
      this.draw_base_bars(data);
      this.draw_data(data);
      this.draw_axes(data);//needs to be on top of bars
    },

    //remove items with no data and sort by ratio
    filter_data: function(){
      var chart = this;
      var filtered = chart.data.filter(function(d){
        return d.infections[chart.options.measure].category != "Not Available";
      });
      filtered.sort(function(a,b){
        return d3.ascending(a.infections[chart.options.measure].ratio, b.infections[chart.options.measure].ratio);
      });
      return filtered;
    },

    get_xMax: function(data){
      var chart = this;
      return d3.max(data, function(d) { return d.infections[chart.options.measure].upper });
    },

    get_currentHeight: function(data){
      return (this.options.bar_padding + this.bar_height) * data.length;
    },

    get_scales: function(data) {
      var chart = this;
      chart.yScale = d3.scale.ordinal()
        .rangeBands([0, chart.get_currentHeight(data)])
        .domain(_.map(data, function(d) { return d.display_name; }));

      chart.xScale = d3.scale.linear()
        .rangeRound([0, chart.dimensions.width])
        .domain([0, chart.get_xMax(data)])
        .nice(); //extend bounds to nearest round value
    },

    create_axes: function() {
      var chart = this;
      chart.xAxis = d3.svg.axis()
        .scale(chart.xScale)
        .orient("bottom");

      chart.yAxis = d3.svg.axis()
        .scale(chart.yScale)
        //.ticks(( $(chart.el).width() <= 550 )? 5:9 )
        .orient("left");
    },

    create_svg: function() {
      var chart = this;
      //create new svg for the bar chart
      chart.svg = d3.select(chart.el).append("svg")
        .attr("width", chart.dimensions.wrapperWidth)
        .attr("height", chart.dimensions.wrapperHeight)
        .attr("class", "chart")
      .append("g")
        .attr("transform", "translate(" + chart.options.margin.left + ", " + chart.options.margin.top + ")");
    },

    create_svg_containers: function(){
      //extend this
      return this;
    },

    draw_base_bars: function(data) {
      var chart = this;
      var bars = chart.svg.select("#baseBars").selectAll(".base.bar")
        .data(data);

      bars.exit().remove();

      bars.enter().append("rect")
        .attr("class", "base bar")
        //.attr("text-anchor", "middle")
        /*.attr("x", function(d) {
          return chart.xScale(d.infections[d.measure].upper);
        })*/
        .attr("y", function(d) {
          return chart.yScale(d.display_name);
        })
        .attr("width", chart.dimensions.width)
        .attr("height", chart.bar_height);
    },

    draw_axes: function(data) {
      var chart = this;
      //create and set x axis position
      chart.svg.select("#axes").append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + chart.get_currentHeight(data) + ")")
        //.attr("text-anchor", "middle")
        .call(chart.xAxis);

      //create and set y axis positions
      var gy = chart.svg.select("#axes").append("g")
        .attr("class", "y axis")
        .attr("y", 6)
        .call(chart.yAxis);

      gy.selectAll("text")
      .classed("hospital-label", true)
    },

    draw_data: function(data){
      return this;
    },

    onUpdateChart: function(filtered, height){
      var chart = this;

      //update scales and axes
      chart.xScale.domain([0, chart.get_xMax(filtered)]).nice();
      chart.xAxis.scale(chart.xScale);

      chart.svg.select("#axes").selectAll(".x.axis")
        .transition().duration(chart.duration)
        .ease("sin-in-out")  // https://github.com/mbostock/d3/wiki/Transitions#wiki-d3_ease
        .attr("transform", "translate(0," + height + ")") //TODO looks weird bc it shouldn't start at 0
        .call(chart.xAxis);

      chart.yScale.rangeBands([0, height]).domain(_.map(filtered, function(d) { return d.display_name; }));

      chart.svg.select("#axes").selectAll(".y.axis")
        .transition().duration(chart.duration)
        .ease("sin-in-out")
        .call(chart.yAxis);

      chart.draw_base_bars(filtered);
    },

    set_tooltip: function(chart, obj, measure){
      obj.on("mouseover", function(d) {
        chart.attach_tooltip(d, measure);
      })
      .on("mouseout", function() {
        $("#ttip").remove();
      });
    },

    attach_tooltip: function(data, measure) {
      data.measure = measure; //template needs access

      var tmpl = _.template($("#tooltip-template").html());
      var tt = $(tmpl(data));
      tt.css("top", (parseFloat(d3.event.pageY) - 220) + "px");
      tt.css("left", (parseFloat(d3.event.pageX) - 50) + "px");
      this.$el.append(tt);
    }
  });

  Chart.BarRangeDot = Chart.BarBase.extend({
    constructor: function(options) {
      Chart.BarBase.apply(this, arguments);
      return this;
    },

    //create containers so that entering items stay layered at the correct depth
    create_svg_containers: function(){
      var chart = this,
      ids = ["baseBars", "rangeBars", "axes", "contextLines", "ratioCircles"];

      for(var i=0; i<ids.length; i++){
        chart.svg.append("g").attr("id", ids[i]);
      }
    },

    draw_data: function(data){
      this.draw_range_bars(data);
      this.draw_context_lines(data);
      this.draw_ratio_circles(data);
    },

    draw_range_bars: function(data){
      var chart = this,
      measure = chart.options.measure;

      var rangeBars = chart.svg.select("#rangeBars").selectAll(".range-bar")
        .data(data);

      rangeBars.exit().remove();

      rangeBars.enter()
      .append("rect")
      .attr("class", "range-bar")
      .attr("x", function(d) {
        return chart.xScale(d.infections[measure].lower);
      })
      .attr("y", function(d) {
        return chart.yScale(d.display_name);
      })
      .attr("width", function(d){
        return chart.xScale(d.infections[measure].upper - d.infections[measure].lower)
      })
      .attr("height", chart.bar_height);
      chart.set_tooltip(chart, rangeBars, measure);

      //update
      rangeBars.transition()
        .duration(chart.duration)
        .attr("x", function(d) {
          return chart.xScale(d.infections[measure].lower);
        })
        .attr("width", function(d){
          return chart.xScale(d.infections[measure].upper - d.infections[measure].lower);
        })
        .transition().each(function(d){
          var category = d.infections[measure].category;
          if(category === "No Different than National Benchmark"){
            d3.select(this).classed({"normal": true, "good": false, "bad": false});
          } else if (category === "Better than the National Benchmark"){
            d3.select(this).classed({"normal": false, "good": true, "bad": false});
          } else if (category === "Worse than the National Benchmark"){
            d3.select(this).classed({"normal": false, "good": false, "bad": true});
          } else {
            d3.select(this).classed({"normal": false, "good": false, "bad": false});
          }
        });
    },

    draw_context_lines: function(data){
      var chart = this,
      height = chart.get_currentHeight(data),
      avg = HospitalCheckup.Entities.averages.get(chart.options.measure);

      var contextLines = chart.svg.select("#contextLines");

      contextLines.append("line")
        .attr("y1", 0)
        .attr("y2", height)
        .attr("x1", chart.xScale(avg))
        .attr("x2", chart.xScale(avg))
        .attr("id", "averageLine");

      contextLines.append("text")
        .text("State avg.: " + avg)
        .attr("text-anchor", function(){
          if(avg < 1){
            return "end"
          } return "start"
        })
        .attr("class", "label")
        .attr("id", "avgTxt")
        .transition().duration(chart.duration)
        .attr("x", chart.xScale(avg))
        .attr("y", -5);

      contextLines.append("line")
        .attr("y1", 0)
        .attr("y2", height)
        .attr("x1", chart.xScale(1)) //benchmark is always 1
        .attr("x2", chart.xScale(1))
        .attr("id", "benchmarkLine");

      contextLines.append("text")
        .text("Benchmark")
        .attr("text-anchor", function(){ //figure out which side the line is on
          if(avg < 1){
            return "start"
          } return "end"
        })
        .attr("class", "label")
        .attr("id", "benchmarkTxt")
        .attr("x", chart.xScale(1))
        .attr("y", -5);
    },

    draw_ratio_circles: function(data){
      var chart = this,
      measure = chart.options.measure;

      var circles = chart.svg.select("#ratioCircles").selectAll(".ratio-circle")
        .data(data);

      circles.exit().remove();

      circles.enter().append("circle")
        .attr("class", "ratio-circle")
        .attr("cx", function(d) {
          return chart.xScale(d.infections[measure].ratio);
        })
        .attr("cy", function(d) {
          return chart.yScale(d.display_name)+chart.bar_height/2;
        })
        .attr("r", 6);
        chart.set_tooltip(chart, circles, measure);

      //update
      circles.transition()
        .duration(chart.duration)
        .attr("cx", function(d) {
          return chart.xScale(d.infections[measure].ratio);
        })
        .attr("y", function(d) {
          return chart.yScale(d.display_name)+chart.bar_height/2;
        });
    },

    onUpdateChart: function(criterion){
      this.options.measure = criterion;
      var chart = this,
      filtered = chart.filter_data(),
      height = chart.get_currentHeight(filtered),
      avg = HospitalCheckup.Entities.averages.get(criterion);

      Chart.BarBase.prototype.onUpdateChart.call(this, filtered, height); //am I doing this right?

      chart.draw_range_bars(filtered);
      chart.draw_ratio_circles(filtered);

      //update context lines
      chart.svg.select("#averageLine")
        .transition().duration(chart.duration)
        .attr("y2", height)
        .attr("x1", chart.xScale(avg))
        .attr("x2", chart.xScale(avg));

      chart.svg.select("#avgTxt")
        .transition().duration(chart.duration)
        .attr("x", chart.xScale(avg))
        .text("State avg.: " + avg)
        .attr("text-anchor", function(){
          if(avg < 1){
            return "end"
          } return "start"
        });

      chart.svg.select("#benchmarkLine")
        .transition().duration(chart.duration)
        .attr("y2", height)
        .attr("x1", chart.xScale(1)) //benchmark is always 1
        .attr("x2", chart.xScale(1));

      chart.svg.select("#benchmarkTxt")
        .transition().duration(chart.duration)
        .attr("x", chart.xScale(1))
        .attr("text-anchor", function(){
          if(avg < 1){
            return "start"
          } return "end"
        });
    }
  });
});<commit_msg>Filter by `na` property instead of checking string<commit_after>HospitalCheckup.module("Common.Chart", function(Chart, HospitalCheckup, Backbone, Marionette, $, _){

  //all the common elements that all list charts in hospital app will share
  Chart.BarBase = ChartBaseView.extend({
    constructor: function(options) {
      ChartBaseView.apply(this, arguments);
      this.options.bar_padding = options.bar_padding || 4;
      this.duration = 500;
      this.bar_height = (this.dimensions.height / this.collection.length) - this.options.bar_padding;
      this.$chart_container.attr('id', this.el.id+"-container");
      return this;
    },
    draw: function() {
      var data = this.filter_data(this.data);
      this.get_scales(data);
      this.create_axes();
      this.create_svg();
      this.create_svg_containers();
      this.draw_base_bars(data);
      this.draw_data(data);
      this.draw_axes(data);//needs to be on top of bars
    },

    //remove items with no data and sort by ratio
    filter_data: function(){
      var chart = this;
      var filtered = chart.data.filter(function(d){
        return d.infections[chart.options.measure].na != 1;
      });
      filtered.sort(function(a,b){
        return d3.ascending(a.infections[chart.options.measure].ratio, b.infections[chart.options.measure].ratio);
      });
      return filtered;
    },

    get_xMax: function(data){
      var chart = this;
      return d3.max(data, function(d) { return d.infections[chart.options.measure].upper });
    },

    get_currentHeight: function(data){
      return (this.options.bar_padding + this.bar_height) * data.length;
    },

    get_scales: function(data) {
      var chart = this;
      chart.yScale = d3.scale.ordinal()
        .rangeBands([0, chart.get_currentHeight(data)])
        .domain(_.map(data, function(d) { return d.display_name; }));

      chart.xScale = d3.scale.linear()
        .rangeRound([0, chart.dimensions.width])
        .domain([0, chart.get_xMax(data)])
        .nice(); //extend bounds to nearest round value
    },

    create_axes: function() {
      var chart = this;
      chart.xAxis = d3.svg.axis()
        .scale(chart.xScale)
        .orient("bottom");

      chart.yAxis = d3.svg.axis()
        .scale(chart.yScale)
        //.ticks(( $(chart.el).width() <= 550 )? 5:9 )
        .orient("left");
    },

    create_svg: function() {
      var chart = this;
      //create new svg for the bar chart
      chart.svg = d3.select(chart.el).append("svg")
        .attr("width", chart.dimensions.wrapperWidth)
        .attr("height", chart.dimensions.wrapperHeight)
        .attr("class", "chart")
      .append("g")
        .attr("transform", "translate(" + chart.options.margin.left + ", " + chart.options.margin.top + ")");
    },

    create_svg_containers: function(){
      //extend this
      return this;
    },

    draw_base_bars: function(data) {
      var chart = this;
      var bars = chart.svg.select("#baseBars").selectAll(".base.bar")
        .data(data);

      bars.exit().remove();

      bars.enter().append("rect")
        .attr("class", "base bar")
        //.attr("text-anchor", "middle")
        /*.attr("x", function(d) {
          return chart.xScale(d.infections[d.measure].upper);
        })*/
        .attr("y", function(d) {
          return chart.yScale(d.display_name);
        })
        .attr("width", chart.dimensions.width)
        .attr("height", chart.bar_height);
    },

    draw_axes: function(data) {
      var chart = this;
      //create and set x axis position
      chart.svg.select("#axes").append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + chart.get_currentHeight(data) + ")")
        //.attr("text-anchor", "middle")
        .call(chart.xAxis);

      //create and set y axis positions
      var gy = chart.svg.select("#axes").append("g")
        .attr("class", "y axis")
        .attr("y", 6)
        .call(chart.yAxis);

      gy.selectAll("text")
      .classed("hospital-label", true)
    },

    draw_data: function(data){
      return this;
    },

    onUpdateChart: function(filtered, height){
      var chart = this;

      //update scales and axes
      chart.xScale.domain([0, chart.get_xMax(filtered)]).nice();
      chart.xAxis.scale(chart.xScale);

      chart.svg.select("#axes").selectAll(".x.axis")
        .transition().duration(chart.duration)
        .ease("sin-in-out")  // https://github.com/mbostock/d3/wiki/Transitions#wiki-d3_ease
        .attr("transform", "translate(0," + height + ")") //TODO looks weird bc it shouldn't start at 0
        .call(chart.xAxis);

      chart.yScale.rangeBands([0, height]).domain(_.map(filtered, function(d) { return d.display_name; }));

      chart.svg.select("#axes").selectAll(".y.axis")
        .transition().duration(chart.duration)
        .ease("sin-in-out")
        .call(chart.yAxis);

      chart.draw_base_bars(filtered);
    },

    set_tooltip: function(chart, obj, measure){
      obj.on("mouseover", function(d) {
        chart.attach_tooltip(d, measure);
      })
      .on("mouseout", function() {
        $("#ttip").remove();
      });
    },

    attach_tooltip: function(data, measure) {
      data.measure = measure; //template needs access

      var tmpl = _.template($("#tooltip-template").html());
      var tt = $(tmpl(data));
      tt.css("top", (parseFloat(d3.event.pageY) - 220) + "px");
      tt.css("left", (parseFloat(d3.event.pageX) - 50) + "px");
      this.$el.append(tt);
    }
  });

  Chart.BarRangeDot = Chart.BarBase.extend({
    constructor: function(options) {
      Chart.BarBase.apply(this, arguments);
      return this;
    },

    //create containers so that entering items stay layered at the correct depth
    create_svg_containers: function(){
      var chart = this,
      ids = ["baseBars", "rangeBars", "axes", "contextLines", "ratioCircles"];

      for(var i=0; i<ids.length; i++){
        chart.svg.append("g").attr("id", ids[i]);
      }
    },

    draw_data: function(data){
      this.draw_range_bars(data);
      this.draw_context_lines(data);
      this.draw_ratio_circles(data);
    },

    draw_range_bars: function(data){
      var chart = this,
      measure = chart.options.measure;

      var rangeBars = chart.svg.select("#rangeBars").selectAll(".range-bar")
        .data(data);

      rangeBars.exit().remove();

      rangeBars.enter()
      .append("rect")
      .attr("class", "range-bar")
      .attr("x", function(d) {
        return chart.xScale(d.infections[measure].lower);
      })
      .attr("y", function(d) {
        return chart.yScale(d.display_name);
      })
      .attr("width", function(d){
        return chart.xScale(d.infections[measure].upper - d.infections[measure].lower)
      })
      .attr("height", chart.bar_height);
      chart.set_tooltip(chart, rangeBars, measure);

      //update
      rangeBars.transition()
        .duration(chart.duration)
        .attr("x", function(d) {
          return chart.xScale(d.infections[measure].lower);
        })
        .attr("width", function(d){
          return chart.xScale(d.infections[measure].upper - d.infections[measure].lower);
        })
        .transition().each(function(d){
          var category = d.infections[measure].category;
          if(category === "No Different than National Benchmark"){
            d3.select(this).classed({"normal": true, "good": false, "bad": false});
          } else if (category === "Better than the National Benchmark"){
            d3.select(this).classed({"normal": false, "good": true, "bad": false});
          } else if (category === "Worse than the National Benchmark"){
            d3.select(this).classed({"normal": false, "good": false, "bad": true});
          } else {
            d3.select(this).classed({"normal": false, "good": false, "bad": false});
          }
        });
    },

    draw_context_lines: function(data){
      var chart = this,
      height = chart.get_currentHeight(data),
      avg = HospitalCheckup.Entities.averages.get(chart.options.measure);

      var contextLines = chart.svg.select("#contextLines");

      contextLines.append("line")
        .attr("y1", 0)
        .attr("y2", height)
        .attr("x1", chart.xScale(avg))
        .attr("x2", chart.xScale(avg))
        .attr("id", "averageLine");

      contextLines.append("text")
        .text("State avg.: " + avg)
        .attr("text-anchor", function(){
          if(avg < 1){
            return "end"
          } return "start"
        })
        .attr("class", "label")
        .attr("id", "avgTxt")
        .transition().duration(chart.duration)
        .attr("x", chart.xScale(avg))
        .attr("y", -5);

      contextLines.append("line")
        .attr("y1", 0)
        .attr("y2", height)
        .attr("x1", chart.xScale(1)) //benchmark is always 1
        .attr("x2", chart.xScale(1))
        .attr("id", "benchmarkLine");

      contextLines.append("text")
        .text("Benchmark")
        .attr("text-anchor", function(){ //figure out which side the line is on
          if(avg < 1){
            return "start"
          } return "end"
        })
        .attr("class", "label")
        .attr("id", "benchmarkTxt")
        .attr("x", chart.xScale(1))
        .attr("y", -5);
    },

    draw_ratio_circles: function(data){
      var chart = this,
      measure = chart.options.measure;

      var circles = chart.svg.select("#ratioCircles").selectAll(".ratio-circle")
        .data(data);

      circles.exit().remove();

      circles.enter().append("circle")
        .attr("class", "ratio-circle")
        .attr("cx", function(d) {
          return chart.xScale(d.infections[measure].ratio);
        })
        .attr("cy", function(d) {
          return chart.yScale(d.display_name)+chart.bar_height/2;
        })
        .attr("r", 6);
        chart.set_tooltip(chart, circles, measure);

      //update
      circles.transition()
        .duration(chart.duration)
        .attr("cx", function(d) {
          return chart.xScale(d.infections[measure].ratio);
        })
        .attr("y", function(d) {
          return chart.yScale(d.display_name)+chart.bar_height/2;
        });
    },

    onUpdateChart: function(criterion){
      this.options.measure = criterion;
      var chart = this,
      filtered = chart.filter_data(),
      height = chart.get_currentHeight(filtered),
      avg = HospitalCheckup.Entities.averages.get(criterion);

      Chart.BarBase.prototype.onUpdateChart.call(this, filtered, height); //am I doing this right?

      chart.draw_range_bars(filtered);
      chart.draw_ratio_circles(filtered);

      //update context lines
      chart.svg.select("#averageLine")
        .transition().duration(chart.duration)
        .attr("y2", height)
        .attr("x1", chart.xScale(avg))
        .attr("x2", chart.xScale(avg));

      chart.svg.select("#avgTxt")
        .transition().duration(chart.duration)
        .attr("x", chart.xScale(avg))
        .text("State avg.: " + avg)
        .attr("text-anchor", function(){
          if(avg < 1){
            return "end"
          } return "start"
        });

      chart.svg.select("#benchmarkLine")
        .transition().duration(chart.duration)
        .attr("y2", height)
        .attr("x1", chart.xScale(1)) //benchmark is always 1
        .attr("x2", chart.xScale(1));

      chart.svg.select("#benchmarkTxt")
        .transition().duration(chart.duration)
        .attr("x", chart.xScale(1))
        .attr("text-anchor", function(){
          if(avg < 1){
            return "start"
          } return "end"
        });
    }
  });
});<|endoftext|>


<commit_before>'use strict';

const execFile = require('child_process').execFile;
const streamify = require('streamify');
const request = require('request');
const hms = require('hh-mm-ss');
const path = require('path');
const http = require('http');
const url = require('url');
const fs = require('fs');

const util = require('./util');

const TEN_MEGABYTES = 1000 * 1000 * 10;

const execFileOpts = { maxBuffer: TEN_MEGABYTES }

const detailsPath = path.join(__dirname, '..', 'bin/details')

const ytdlBinary = (() => {
  if (fs.existsSync(detailsPath)) {
    const details = JSON.parse(fs.readFileSync(detailsPath));
    return details.path ? details.path : path.resolve(__dirname, '..', 'bin', details.exec);
  }

  if (!fs.existsSync(ytdlBinary)) {
    console.error('ERROR: unable to locate youtube-dl details in ' + path.dirname(ytdlBinary));
    process.exit(1);
  }
})()

const isWin = (process.platform === 'win32' || process.env.NODE_PLATFORM === 'windows') ? true : false;

const isDebug = /^\[debug\] /;
const isWarning = /^WARNING: /;
const isYouTubeRegex = /^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.be)\//;
const isNoSubsRegex = /WARNING: video doesn't have subtitles|no closed captions found/;
const videoNotAvailable = new RegExp(
  'This video is not available|' +
  'This video has been removed by the user|' +
  'Please sign in to view this video|' +
  'This video is no longer available|' +
  'The uploader has not made this video available|' +
  'This video contains content from'
);

const subsRegex = /--write-sub|--write-srt|--srt-lang|--all-subs/;

function youtubeDl(args, options, callback) {
  let passOver = false;

  execFile(ytdlBinary, args, { ...execFileOpts, ...options }, function done(err, stdout, stderr) {
    if (err) {
      if (videoNotAvailable.test(err.message)) passOver = true;
      if (!passOver) return callback(err);
    }

    if (stderr && !passOver) {
      // Try once to download video if no subtitles available
      if (!options.nosubs && isNoSubsRegex.test(stderr)) {
        let i;
        let cleanupOpt = args2;

        for (i = cleanupOpt.length - 1; i >= 0; i--) {
          if (subsRegex.test(cleanupOpt[i])) { cleanupOpt.splice(i, 1); }
        }

        options.nosubs = true;

        return call(video, args1, cleanupOpt, options, callback);
      }

      if (isDebug.test(stderr) && args.indexOf('--verbose') > -1) {
        console.log('\n' + stderr);
      } else if (isWarning.test(stderr)) {
        console.warn(stderr);
      } else {
        return callback(new Error(stderr.slice(7)));
      }
    }

    if (passOver && stdout === '' && urls.length > 1) {
      urls.shift();
      return call(urls, args1, args2, options, callback);
    }

    return callback(null, stdout.trim().split(/\r?\n/));
  })
}

/**
 * Processes data
 *
 * @param {Object} data
 * @param {Object} options
 * @param {Object} stream
 */

function processData(data, options, stream) {
  const item = !data.length ? data : data.shift();

  // fix for pause/resume downloads
  const headers = { 'Host': url.parse(item.url).hostname };

  if (options && options.start > 0) headers.Range = 'bytes=' + options.start + '-';

  const req = request({ url: item.url, headers: headers });

  req.on('response', function response(res) {

    const size = parseInt(res.headers['content-length'], 10);
    if (size) item.size = size;

    if (options && options.start > 0 && res.statusCode === 416) {
      // the file that is being resumed is complete.
      return stream.emit('complete', item);
    }

    if (res.statusCode !== 200 && res.statusCode !== 206) {
      return stream.emit('error', new Error('status code ' + res.statusCode));
    }

    stream.emit('info', item);

    stream.on('end', function end() {
      if (data.length) stream.emit('next', data);
    });

  });

  return stream.resolve(req);
}

/**
 * Downloads a video.
 *
 * @param {String} videoUrl
 * @param {!Array.<String>} args
 * @param {!Object} options
 */
const ytdl = module.exports = function (videoUrl, args, options) {
  const stream = streamify({ superCtor: http.ClientResponse, readable: true, writable: false });

  if (typeof videoUrl !== 'string') {
    processData(videoUrl, options, stream);
    return stream;
  }

  ytdl.getInfo(videoUrl, args, options, function getInfo(err, data) {
    return err ? stream.emit('error', err) : processData(data, options, stream);
  });

  return stream;
};

/**
 * Calls youtube-dl with some arguments and the `callback`
 * gets called with the output.
 *
 * @param {String|Array.<String>}
 * @param {Array.<String>} args
 * @param {Array.<String>} args2
 * @param {Object} options
 * @param {Function(!Error, String)} callback
 */
function call(urls, args1, args2, options = {}, callback) {
  let args = args1;
  if (args2) args = args.concat(args2);

  // set encoding on windows to support unicode titles
  if (isWin) {
    // check if encoding is already set
    let hasEncoding = false;
    for (let i = 0; i < args.length; i++) {
      if (args[i] === '--encoding') {
        hasEncoding = true;
        break;
      }
    }
    if (!hasEncoding) {
      args.push('--encoding');
      args.push('utf8');
    }
  }

  if (urls !== null) {
    if (typeof urls === 'string') {
      urls = [urls];
    }

    for (let i = 0; i < urls.length; i++) {
      const video = urls[i];
      if (isYouTubeRegex.test(video)) {
        // Get possible IDs.
        const details = url.parse(video, true);
        let id = details.query.v || '';
        if (id) {
          args.push('http://www.youtube.com/watch?v=' + id);
        } else {
          // Get possible IDs for youtu.be from urladdr.
          id = details.pathname.slice(1).replace(/^v\//, '');
          if (id) {
            args.push(video);
            args.unshift('-i');
          }
        }
      } else {
        if (i === 0)
          args.push('--');
        args.push(video);
      }
    }
  }

  return youtubeDl(args, options, callback)
}

/**
 * Calls youtube-dl with some arguments and the `callback`
 * gets called with the output.
 *
 * @param {String} url
 * @param {Array.<String>} args
 * @param {Object} options
 * @param {Function(!Error, String)} callback
 */
ytdl.exec = function exec(url, args, options, callback) {
  return call(url, [], args, options, callback);
};

/**
 * @param {Object} data
 * @returns {Object}
 */
function parseInfo(data) {
  const info = JSON.parse(data);

  // Add and process some entries to keep backwards compatibility
  Object.defineProperty(info, 'filename', {
    get: function get() {
      console.warn('`info.filename` is deprecated, use `info._filename`');
      return info._filename;
    }
  });
  Object.defineProperty(info, 'itag', {
    get: function get() {
      console.warn('`info.itag` is deprecated, use `info.format_id`');
      return info.format_id;
    }
  });
  Object.defineProperty(info, 'resolution', {
    get: function get() {
      console.warn('`info.resolution` is deprecated, use `info.format`');
      return info.format.split(' - ')[1];
    }
  });

  info._duration_raw = info.duration;
  info._duration_hms = (info.duration) ? hms.fromS(info.duration, 'hh:mm:ss') : info.duration;
  info.duration = (info.duration) ? util.formatDuration(info.duration) : info.duration;

  return info;
}

/**
 * Set path from youtube-dl.
 *
 * @param {String} path
 */
ytdl.setYtdlBinary = function setYtdlBinary(path) {
  ytdlBinary = path;
};

/**
 * Get path from youtube-dl.
 *
 * @param {String} path
 */
ytdl.getYtdlBinary = function getYtdlBinary() {
  return ytdlBinary;
};

/**
 * Gets info from a video.
 *
 * @param {String} url
 * @param {Array.<String>} args
 * @param {Object} options
 * @param {Function(!Error, Object)} callback
 */
ytdl.getInfo = function getInfo(url, args, options, callback) {
  if (typeof options === 'function') {
    callback = options;
    options = {};
  } else if (typeof args === 'function') {
    callback = args;
    options = {};
    args = [];
  }
  const defaultArgs = ['--dump-json'];
  if (!args || args.indexOf('-f') < 0 && args.indexOf('--format') < 0 &&
    args.every(function (a) {
      return a.indexOf('--format=') !== 0;
    })) {
    defaultArgs.push('-f');
    defaultArgs.push('best');
  }

  call(url, defaultArgs, args, options, function done(err, data) {
    if (err) return callback(err);
    let info;

    try {
      info = data.map(parseInfo);
    } catch (err) {
      return callback(err);
    }

    return callback(null, info.length === 1 ? info[0] : info);
  });
};

/**
 * @param {String} url
 * @param {Object} options
 *   {Boolean} auto
 *   {Boolean} all
 *   {String} lang
 *   {String} cwd
 * @param {Function(!Error, Object)} callback
 */
ytdl.getSubs = function getSubs(url, options, callback) {
  if (typeof options === 'function') {
    callback = options;
    options = {};
  }

  const args = ['--skip-download'];
  args.push('--write' + (options.auto ? '-auto' : '') + '-sub');
  if (options.all) {
    args.push('--all-subs');
  }
  if (options.lang) {
    args.push('--sub-lang=' + options.lang);
  }
  if (!options.warrning) {
    args.push('--no-warnings');
  }

  call(url, args, [], { cwd: options.cwd }, function (err, data) {
    if (err) return callback(err);

    const files = [];
    for (let i = 0, len = data.length; i < len; i++) {
      const line = data[i];
      if (line.indexOf('[info] Writing video subtitles to: ') === 0) {
        files.push(line.slice(35));
      }
    }

    return callback(null, files);
  });
};

/**
 * @param {String} url
 * @param {Object} options
 *   {Boolean} all
 *   {String} cwd
 * @param {Function(!Error, Object)} callback
 */
ytdl.getThumbs = function getThumbs(url, options, callback) {
  if (typeof options === 'function') {
    callback = options;
    options = {};
  }

  const args = ['--skip-download'];

  if (options.all) {
    args.push('--write-all-thumbnails');
  } else {
    args.push('--write-thumbnail');
  }

  if (!options.warrning) {
    args.push('--no-warnings');
  }

  call(url, args, [], { cwd: options.cwd }, function (err, data) {
    if (err) return callback(err);

    const files = [];
    for (let i = 0, len = data.length; i < len; i++) {
      const line = data[i];
      const info = 'Writing thumbnail to: ';
      if (line.indexOf(info) !== -1) {
        files.push(line.slice(line.indexOf(info) + info.length));
      }
    }
    return callback(null, files);
  });
};

/**
 * @param {!Boolean} descriptions
 * @param {!Object} options
 * @param {Function(!Error, Object)} callback
 */
ytdl.getExtractors = function getExtractors(descriptions, options, callback) {
  if (typeof options === 'function') {
    callback = options;
    options = {};
  } else if (typeof descriptions === 'function') {
    callback = descriptions;
    options = {};
    descriptions = false;
  }

  const args = descriptions ? ['--extractor-descriptions'] : ['--list-extractors'];
  return call(null, args, null, options, callback);
};
<commit_msg>Send header because gets 403 if cookie is needed<commit_after>'use strict';

const execFile = require('child_process').execFile;
const streamify = require('streamify');
const request = require('request');
const hms = require('hh-mm-ss');
const path = require('path');
const http = require('http');
const url = require('url');
const fs = require('fs');

const util = require('./util');

const TEN_MEGABYTES = 1000 * 1000 * 10;

const execFileOpts = { maxBuffer: TEN_MEGABYTES }

const detailsPath = path.join(__dirname, '..', 'bin/details')

const ytdlBinary = (() => {
  if (fs.existsSync(detailsPath)) {
    const details = JSON.parse(fs.readFileSync(detailsPath));
    return details.path ? details.path : path.resolve(__dirname, '..', 'bin', details.exec);
  }

  if (!fs.existsSync(ytdlBinary)) {
    console.error('ERROR: unable to locate youtube-dl details in ' + path.dirname(ytdlBinary));
    process.exit(1);
  }
})()

const isWin = (process.platform === 'win32' || process.env.NODE_PLATFORM === 'windows') ? true : false;

const isDebug = /^\[debug\] /;
const isWarning = /^WARNING: /;
const isYouTubeRegex = /^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.be)\//;
const isNoSubsRegex = /WARNING: video doesn't have subtitles|no closed captions found/;
const videoNotAvailable = new RegExp(
  'This video is not available|' +
  'This video has been removed by the user|' +
  'Please sign in to view this video|' +
  'This video is no longer available|' +
  'The uploader has not made this video available|' +
  'This video contains content from'
);

const subsRegex = /--write-sub|--write-srt|--srt-lang|--all-subs/;

function youtubeDl(args, options, callback) {
  let passOver = false;

  execFile(ytdlBinary, args, { ...execFileOpts, ...options }, function done(err, stdout, stderr) {
    if (err) {
      if (videoNotAvailable.test(err.message)) passOver = true;
      if (!passOver) return callback(err);
    }

    if (stderr && !passOver) {
      // Try once to download video if no subtitles available
      if (!options.nosubs && isNoSubsRegex.test(stderr)) {
        let i;
        let cleanupOpt = args2;

        for (i = cleanupOpt.length - 1; i >= 0; i--) {
          if (subsRegex.test(cleanupOpt[i])) { cleanupOpt.splice(i, 1); }
        }

        options.nosubs = true;

        return call(video, args1, cleanupOpt, options, callback);
      }

      if (isDebug.test(stderr) && args.indexOf('--verbose') > -1) {
        console.log('\n' + stderr);
      } else if (isWarning.test(stderr)) {
        console.warn(stderr);
      } else {
        return callback(new Error(stderr.slice(7)));
      }
    }

    if (passOver && stdout === '' && urls.length > 1) {
      urls.shift();
      return call(urls, args1, args2, options, callback);
    }

    return callback(null, stdout.trim().split(/\r?\n/));
  })
}

/**
 * Processes data
 *
 * @param {Object} data
 * @param {Object} options
 * @param {Object} stream
 */

function processData(data, options, stream) {
  const item = !data.length ? data : data.shift();

  // fix for pause/resume downloads
  const headers = { 'Host': url.parse(item.url).hostname };

  Object.assign(headers, data.http_headers);

  if (options && options.start > 0) headers.Range = 'bytes=' + options.start + '-';

  const req = request({ url: item.url, headers: headers });

  req.on('response', function response(res) {

    const size = parseInt(res.headers['content-length'], 10);
    if (size) item.size = size;

    if (options && options.start > 0 && res.statusCode === 416) {
      // the file that is being resumed is complete.
      return stream.emit('complete', item);
    }

    if (res.statusCode !== 200 && res.statusCode !== 206) {
      return stream.emit('error', new Error('status code ' + res.statusCode));
    }

    stream.emit('info', item);

    stream.on('end', function end() {
      if (data.length) stream.emit('next', data);
    });

  });

  return stream.resolve(req);
}

/**
 * Downloads a video.
 *
 * @param {String} videoUrl
 * @param {!Array.<String>} args
 * @param {!Object} options
 */
const ytdl = module.exports = function (videoUrl, args, options) {
  const stream = streamify({ superCtor: http.ClientResponse, readable: true, writable: false });

  if (typeof videoUrl !== 'string') {
    processData(videoUrl, options, stream);
    return stream;
  }

  ytdl.getInfo(videoUrl, args, options, function getInfo(err, data) {
    return err ? stream.emit('error', err) : processData(data, options, stream);
  });

  return stream;
};

/**
 * Calls youtube-dl with some arguments and the `callback`
 * gets called with the output.
 *
 * @param {String|Array.<String>}
 * @param {Array.<String>} args
 * @param {Array.<String>} args2
 * @param {Object} options
 * @param {Function(!Error, String)} callback
 */
function call(urls, args1, args2, options = {}, callback) {
  let args = args1;
  if (args2) args = args.concat(args2);

  // set encoding on windows to support unicode titles
  if (isWin) {
    // check if encoding is already set
    let hasEncoding = false;
    for (let i = 0; i < args.length; i++) {
      if (args[i] === '--encoding') {
        hasEncoding = true;
        break;
      }
    }
    if (!hasEncoding) {
      args.push('--encoding');
      args.push('utf8');
    }
  }

  if (urls !== null) {
    if (typeof urls === 'string') {
      urls = [urls];
    }

    for (let i = 0; i < urls.length; i++) {
      const video = urls[i];
      if (isYouTubeRegex.test(video)) {
        // Get possible IDs.
        const details = url.parse(video, true);
        let id = details.query.v || '';
        if (id) {
          args.push('http://www.youtube.com/watch?v=' + id);
        } else {
          // Get possible IDs for youtu.be from urladdr.
          id = details.pathname.slice(1).replace(/^v\//, '');
          if (id) {
            args.push(video);
            args.unshift('-i');
          }
        }
      } else {
        if (i === 0)
          args.push('--');
        args.push(video);
      }
    }
  }

  return youtubeDl(args, options, callback)
}

/**
 * Calls youtube-dl with some arguments and the `callback`
 * gets called with the output.
 *
 * @param {String} url
 * @param {Array.<String>} args
 * @param {Object} options
 * @param {Function(!Error, String)} callback
 */
ytdl.exec = function exec(url, args, options, callback) {
  return call(url, [], args, options, callback);
};

/**
 * @param {Object} data
 * @returns {Object}
 */
function parseInfo(data) {
  const info = JSON.parse(data);

  // Add and process some entries to keep backwards compatibility
  Object.defineProperty(info, 'filename', {
    get: function get() {
      console.warn('`info.filename` is deprecated, use `info._filename`');
      return info._filename;
    }
  });
  Object.defineProperty(info, 'itag', {
    get: function get() {
      console.warn('`info.itag` is deprecated, use `info.format_id`');
      return info.format_id;
    }
  });
  Object.defineProperty(info, 'resolution', {
    get: function get() {
      console.warn('`info.resolution` is deprecated, use `info.format`');
      return info.format.split(' - ')[1];
    }
  });

  info._duration_raw = info.duration;
  info._duration_hms = (info.duration) ? hms.fromS(info.duration, 'hh:mm:ss') : info.duration;
  info.duration = (info.duration) ? util.formatDuration(info.duration) : info.duration;

  return info;
}

/**
 * Set path from youtube-dl.
 *
 * @param {String} path
 */
ytdl.setYtdlBinary = function setYtdlBinary(path) {
  ytdlBinary = path;
};

/**
 * Get path from youtube-dl.
 *
 * @param {String} path
 */
ytdl.getYtdlBinary = function getYtdlBinary() {
  return ytdlBinary;
};

/**
 * Gets info from a video.
 *
 * @param {String} url
 * @param {Array.<String>} args
 * @param {Object} options
 * @param {Function(!Error, Object)} callback
 */
ytdl.getInfo = function getInfo(url, args, options, callback) {
  if (typeof options === 'function') {
    callback = options;
    options = {};
  } else if (typeof args === 'function') {
    callback = args;
    options = {};
    args = [];
  }
  const defaultArgs = ['--dump-json'];
  if (!args || args.indexOf('-f') < 0 && args.indexOf('--format') < 0 &&
    args.every(function (a) {
      return a.indexOf('--format=') !== 0;
    })) {
    defaultArgs.push('-f');
    defaultArgs.push('best');
  }

  call(url, defaultArgs, args, options, function done(err, data) {
    if (err) return callback(err);
    let info;

    try {
      info = data.map(parseInfo);
    } catch (err) {
      return callback(err);
    }

    return callback(null, info.length === 1 ? info[0] : info);
  });
};

/**
 * @param {String} url
 * @param {Object} options
 *   {Boolean} auto
 *   {Boolean} all
 *   {String} lang
 *   {String} cwd
 * @param {Function(!Error, Object)} callback
 */
ytdl.getSubs = function getSubs(url, options, callback) {
  if (typeof options === 'function') {
    callback = options;
    options = {};
  }

  const args = ['--skip-download'];
  args.push('--write' + (options.auto ? '-auto' : '') + '-sub');
  if (options.all) {
    args.push('--all-subs');
  }
  if (options.lang) {
    args.push('--sub-lang=' + options.lang);
  }
  if (!options.warrning) {
    args.push('--no-warnings');
  }

  call(url, args, [], { cwd: options.cwd }, function (err, data) {
    if (err) return callback(err);

    const files = [];
    for (let i = 0, len = data.length; i < len; i++) {
      const line = data[i];
      if (line.indexOf('[info] Writing video subtitles to: ') === 0) {
        files.push(line.slice(35));
      }
    }

    return callback(null, files);
  });
};

/**
 * @param {String} url
 * @param {Object} options
 *   {Boolean} all
 *   {String} cwd
 * @param {Function(!Error, Object)} callback
 */
ytdl.getThumbs = function getThumbs(url, options, callback) {
  if (typeof options === 'function') {
    callback = options;
    options = {};
  }

  const args = ['--skip-download'];

  if (options.all) {
    args.push('--write-all-thumbnails');
  } else {
    args.push('--write-thumbnail');
  }

  if (!options.warrning) {
    args.push('--no-warnings');
  }

  call(url, args, [], { cwd: options.cwd }, function (err, data) {
    if (err) return callback(err);

    const files = [];
    for (let i = 0, len = data.length; i < len; i++) {
      const line = data[i];
      const info = 'Writing thumbnail to: ';
      if (line.indexOf(info) !== -1) {
        files.push(line.slice(line.indexOf(info) + info.length));
      }
    }
    return callback(null, files);
  });
};

/**
 * @param {!Boolean} descriptions
 * @param {!Object} options
 * @param {Function(!Error, Object)} callback
 */
ytdl.getExtractors = function getExtractors(descriptions, options, callback) {
  if (typeof options === 'function') {
    callback = options;
    options = {};
  } else if (typeof descriptions === 'function') {
    callback = descriptions;
    options = {};
    descriptions = false;
  }

  const args = descriptions ? ['--extractor-descriptions'] : ['--list-extractors'];
  return call(null, args, null, options, callback);
};
<|endoftext|>


<commit_before>/*

 */

/* ****************** IMPORT ****************************************************************/
/**
 * fs is an imported library with various file system functions
 * @type {Object}
 * @property {function} readFileSync
 * @property {function} writeFileSync
 */
var fs = require('fs');
var astq = require("ast-query");
var FacetedValue = require('../FacetedValue.js').bin;
var Scope = require('./Scope.js').bin;

var inputfile = fs.readFileSync('ast-query/inputFile.js');

/**
 * @type {Tree}
 * @property {Array<ASTNode>} body.node
 */
var tree = astq(inputfile);

/* **************** PROCESS ******************************************************************/
var currentScope = new Scope();
performProcessingPhase(tree, checkNodeTypeCoverage);
performProcessingPhase(tree, substituteFacetedValues);
performProcessingPhase(tree, overlayScoping);
performProcessingPhase(tree, prepForInformationFlows);
performProcessingPhase(tree, linkIdentifiers);
performProcessingPhase(tree, overlayInformationFlows);
performProcessingPhase(tree, markFaceting);
performProcessingPhase(tree, refactorOperationsToBeFaceted);

/* ********************* EXPORT **********************************************************/
var outputfile = tree.toString();
console.log(currentScope.toString());
fs.writeFileSync('ast-query/outputFile.js', outputfile); // TODO: Why is this so sloooooowwwww

/* ************************* CORE FUNCTIONS *******************************************/

/**
 * This pass looks through the AST, making sure that all node types are accounted for.
 *
 * @param {ASTNode} node
 */
function checkNodeTypeCoverage(node){
    switch (node.type){
        case 'BinaryExpression':
        case 'CallExpression':
        case 'FunctionDeclaration':
        case 'FunctionExpression':
        case 'Literal':
        case 'AssignmentExpression':
        case 'BlockStatement':
        case 'ExpressionStatement':
        case 'Identifier':
        case 'IfStatement':
        case 'MemberExpression': // TODO Add to all cases as needed
        case 'ReturnStatement':
        case 'UnaryExpression':
        case 'VariableDeclaration':
        case 'VariableDeclarator':
            break;
        default: throw new Error('substituteFacetedValues does not yet accommodate Node.type="' + node.type + '"');
    }
}

/**
 * On this pass, it simply finds all string 'Literal" nodes and rewrites the AST to use a new FacetedValue instead.
 *
 * @param {ASTNode} node
 */
function substituteFacetedValues(node){
    if (node.type === 'Literal') {
        if (typeof node.value === 'string') {
            try {
                var m = node.value.match(FacetedValue.REGEX);
                var replacementString = 'new FacetedValue(' + m[1] + ', ' + m[2] + ', ' + m[3] + ')';
                // We use the above string to construct a miniature AST with which we may replace the string literal.
                // Note that in the following, body.node[0] is an ExpressionStatement.
                // E.g. it is a complete statement containing an expression, closed with a semicolon.
                // Therefore it is necessary to extract the `new` expression contained therein, to avoid the semicolon.
                var miniTree = astq(replacementString);
                var newExpression = miniTree.body.node[0].expression;
                replaceLeftNodeWithRight(node, newExpression);
                node.faceting = [m[1]];
                node.wasReconstructedForFVs = true;
            } catch (ignored) {
            }
        }
    }
}

/**
 * On this pass, a tree of function scopes is constructed and then attached to the AST so that each node has a
 * reference to the scope that it resides in.
 *
 * @param {ASTNode} node
 */
function overlayScoping(node){
    if (node.scope)
        return; // a little sloppy in terms of program flow, but node.scope may have already been set by performProcessingPhase a few lines below, and we don't want it overridden
    node.scope = currentScope;
    if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression'){
        if (node.id) // Anonymous functions do not have an identifier
            currentScope.registerSymbol(node.id.name, node);
        currentScope = new Scope(node, currentScope);
        node.params.forEach(function(p){currentScope.registerSymbol(p.name, p);});
        var functionContents = node.body;
        if (functionContents.type === 'BlockStatement')
            functionContents = functionContents.body;
        performProcessingPhase(functionContents, overlayScoping);
        currentScope = currentScope.parent;
    }
    else if (node.type === 'VariableDeclarator'){
        currentScope.registerSymbol(node.id.name, node);
    }
}

/**
 * This pass simply puts empty lists in each node so that, in the next pass, we don't have to constantly check to see
 * if the list exists before pushing to it.
 *
 * @param {ASTNode} node
 */
function prepForInformationFlows(node){
    node.outgoingFlows = [];
}

/**
 * The following phase is much simpler if we already have all identifiers' information flows in place
 *
 * @param {ASTNode} node
 */
function linkIdentifiers(node){
    var declaringNode = node.scope.getNodeNamed(node.name);
    if (declaringNode && declaringNode !== node) // this will be null if it is external to our program, i.e. built-in functions
        declaringNode.outgoingFlows.push(node);
}

/**
 * On this pass, we look at the actual operations and use the scopes to create a (hopefully) accurate graph overlay
 * of information flows from symbols to symbols. You can expect this graph to be completely arbitrary, by no means
 * anything so specific as a tree or bipartite graph.
 *
 * This is the a more extensive and complicated part of the program, because nearly any node type within the abstract
 * syntax tree can have its own unique manner of information flow, and so each must be examined individually. Its
 * functioning also depends on the scope structure being correctly overlaid in the previous path.
 *
 * However, it isn't as bad as it looks. Each case within the switch statement is essentially its own function, and you
 * don't really need to look further than the few lines of a given case when considering its logic.
 *
 * @example a = b + 2;  // describes a flow from b to a
 * @example function f(x){}; f(b);  // describes a flow from b to x
 * @example if (g) h = i; else h = j; k = h; // describes a flow from g to h, g to h, i to h, j to h, and h to k.
 *          // By induction it describes a flow from g to k.
 * @example function f(){return a;}; b = f(); // describes a flow from a to b
 * @param {ASTNode} node
 */
function overlayInformationFlows(node){
    var i;
    switch (node.type){
        case 'BinaryExpression':
            node.left.outgoingFlows.push(node);
            node.right.outgoingFlows.push(node);
            break;
        case 'CallExpression':
            node.callee.outgoingFlows.push(node);
            if (node.callee.type === 'Identifier') {
                var t = node.callee.scope.getNodeNamed(node.callee.name);
                for (i = 0; i < node.arguments.length; i++)
                    if (t.params[i])
                        node.arguments[i].outgoingFlows.push(t.params[i]);
            } // TODO: It might be a MemberExpression instead of an Identifier
            break;
        case 'AssignmentExpression':
            node.right.outgoingFlows.push(node.left);
            node.left.outgoingFlows.push(node);
            break;
        case 'ExpressionStatement':
            node.outgoingFlows.push(node.expression);
            break;
        case 'FunctionDeclaration':
        case 'FunctionExpression': // TODO: flow from identifier to function, thence to CallExpression
            if (node.id)
                node.id.outgoingFlows.push(node); // TODO: What can expression and generator properties be?
            break;
        case 'IfStatement':
            if (node.alternate)
                node.test.outgoingFlows.push(node.alternate);
            if (node.consequent)
                node.test.outgoingFlows.push(node.consequent);
            node.test.outgoingFlows.push(node);
            break;
        case 'ReturnStatement':
            if (node.argument)
                node.argument.outgoingFlows.push(node);
            node.argument.outgoingFlows.push(node.scope.getOwningFunction()); // TODO: secure flows from function declarations/expressions to CallExpressions
            break;
        case 'UnaryExpression':
            debugger;
            break;
        case 'VariableDeclarator':
            if (node.init)
                node.init.outgoingFlows.push(node);
            break;
    }
}

/**
 * This walks the AST and, wherever something isFaceted, it follows the information flows plotted out in the previous
 * pass to mark those nodes as also being faceted.
 *
 * @param {ASTNode} node
 */
function markFaceting(node){
   if (node.faceting) {
       node.outgoingFlows.forEach(propagate, [node.faceting]);
   }
   function propagate(node, faceting){
       if (node.faceting && leftSetContainsRightSet(node.faceting, faceting))
           return; //avoid cycles
       node.faceting = mergeSets([node.faceting, faceting]);
       node.faceting = faceting.slice().push;
       node.outgoingFlows.forEach(propagate);
   }
}

/**
 * @example fv + b ===> fv.binaryOps(b, true)
 * @param {ASTNode}node
 */
function refactorOperationsToBeFaceted(node){
    if (node.faceting){
        var newNode, object, operand, operandIsOnLeft;
        switch (node.type) {
            case 'AssignmentExpression':
                var unfacetedAssignedValue = node.right;
                var label; // TODO
                var replacementString = 'new FacetedValue(' + label + ', placeholder, ' + unfacetedIdentifier + ')';
                var newExpression = astq(replacementString).body.node[0].expression;
                replaceLeftNodeWithRight(node, newExpression);
                node.faceting = [label];
                node.wasReconstructedForFVs = true;
                debugger;
                break;
            case 'BinaryExpression':
                if (node.left.type === 'Identifier' && node.left.faceting){
                    object = node.left;
                    operand = node.right;
                    operandIsOnLeft = false;
                } else if (node.right.type === 'Identifier' && node.right.faceting) {
                    operand = node.left;
                    object = node.right;
                    operandIsOnLeft = true;
                } else {
                    throw new Error('CRIMINY AND CURSES! A binary expression has been marked as faceted, without' +
                        ' either its left or right side being faceted identifiers!');
                }
                newNode = astq('object.binaryOps("' + node.operator + '", operand, ' + operandIsOnLeft + ')').body.node[0].expression;
                newNode.callee.object = object;
                newNode.arguments[1] = operand;
                currentScope = node.scope;
                forEachIn(newNode, overlayScoping);
                forEachIn(newNode, prepForInformationFlows);
                forEachIn(newNode, overlayInformationFlows);
                forEachIn(newNode, markFaceting);
                replaceLeftNodeWithRight(node, newNode);
                debugger;
                break;
            case 'CallExpression':
                debugger;
                break;
            case 'FunctionDeclaration':
                debugger;
                break;
            case 'FunctionExpression':
                debugger;
                break;
            case 'Literal':
                debugger;
                break;
            case 'BlockStatement':
                debugger;
                break;
            case 'ReturnStatement':
                debugger;
                break;
            case 'UnaryExpression':
                debugger;
                break;
            case 'VariableDeclaration':
                debugger;
                break;
        }
    }
}

/* **************************** HELPERS *********************************************/

/**
 * Recursively operates the functor upon the given AST node and all of its children.
 *
 * @param {ASTNode} node
 * @param {ASTNodeFunctor} functor
 */
function forEachIn(node, functor){
    functor(node);
    for (var property in node) {
        // The following reference(s), if followed, may turn the AST into a circular graph, and must be ignored
        if (property === 'outgoingFlows')
            continue;

        /**
         * @type {*|ASTNode|Array<ASTNode>}
         */
        var value = node[property];
        if (isAnASTNode(value))
            forEachIn(value, functor);
        if (isArray(value)) {
            value.forEach(function (element) {
                if (isAnASTNode(element))
                    forEachIn(element, functor);
            });
        }
    }
}

/**
 *
 * @param {*|ASTNode} value
 * @returns {boolean}
 */
function isAnASTNode(value){
    /**
     * @typedef {string} value.__proto__.constructor.name
     */
    return value !== null && typeof value === 'object' && value.__proto__.constructor.name === 'Node';
}

/**
 *
 * @param {*|Array<*>} value
 * @returns {boolean}
 */
function isArray(value) {
    /**
     * @typedef {function} Object.prototype.toString.call
     */
    return typeof value === 'object' && Object.prototype.toString.call(value) === '[object Array]';
}

/**
 * A bit of a hack. This is intended to entirely replace one node with another.
 * TODO: Check with Dr. Austin, am I missing something on this?
 * @example '<a ? b : c>' node of type 'Literal' becomes a node of type 'ExpressionStatement' that corresponds to
 *      `new FacetedValue(a, b, c)`.
 * @param left
 * @param right
 */
function replaceLeftNodeWithRight(left, right) {
    var i;
    for (i in left)
        if (left.hasOwnProperty(i))
            left[i] = right[i];
    for (i in right)
        if (right.hasOwnProperty(i))
            left[i] = right[i];
}

/**
 * @param {ASTNode|Array<ASTNode>|Tree} container
 * @param {Function} functor
 */
function performProcessingPhase(container, functor){
    /**
     * @type {Array<ASTNode>}
     */
    var iterationTarget = isArray(container) ? container : container.body.node;
    iterationTarget.forEach(function (node) {
        forEachIn(node, functor);
    });
}

function leftSetContainsRightSet(leftSet, rightSet){
    if (leftSet instanceof Array)
        return leftSet.filter(function(val) { return rightSet.indexOf(val) !== -1;}).length === rightSet.length;
    return leftSet === rightSet;
}

/**
 * @param {Array<Array>} listOfSets
 * @returns {Array}
 */
function mergeSets(listOfSets){
    var mergedSet = {};
    var uniqueMergedSet = [];
    listOfSets.forEach(function addAllIn(set){
        set.forEach(function add(element){
            mergedSet[element] = element;
        })
    })
    for (var i in mergedSet)
        if (mergedSet.hasOwnProperty(i))
            uniqueMergedSet.push(i);
    return uniqueMergedSet;
}

<commit_msg>Replacement of assignment expression is almost there.<commit_after>/*

 */

/* ****************** IMPORT ****************************************************************/
/**
 * fs is an imported library with various file system functions
 * @type {Object}
 * @property {function} readFileSync
 * @property {function} writeFileSync
 */
var fs = require('fs');
var astq = require("ast-query");
var FacetedValue = require('../FacetedValue.js').bin;
var Scope = require('./Scope.js').bin;

var inputfile = fs.readFileSync('ast-query/inputFile.js');

/**
 * @type {Tree}
 * @property {Array<ASTNode>} body.node
 */
var tree = astq(inputfile);

/* **************** PROCESS ******************************************************************/
var currentScope = new Scope();
performProcessingPhase(tree, checkNodeTypeCoverage);
performProcessingPhase(tree, substituteFacetedValues);
performProcessingPhase(tree, overlayScoping);
performProcessingPhase(tree, prepForInformationFlows);
performProcessingPhase(tree, linkIdentifiers);
performProcessingPhase(tree, overlayInformationFlows);
performProcessingPhase(tree, markFaceting);
performProcessingPhase(tree, refactorOperationsToBeFaceted);

/* ********************* EXPORT **********************************************************/
var outputfile = tree.toString();
console.log(currentScope.toString());
fs.writeFileSync('ast-query/outputFile.js', outputfile); // TODO: Why is this so sloooooowwwww

/* ************************* CORE FUNCTIONS *******************************************/

/**
 * This pass looks through the AST, making sure that all node types are accounted for.
 *
 * @param {ASTNode} node
 */
function checkNodeTypeCoverage(node){
    switch (node.type){
        case 'BinaryExpression':
        case 'CallExpression':
        case 'FunctionDeclaration':
        case 'FunctionExpression':
        case 'Literal':
        case 'AssignmentExpression':
        case 'BlockStatement':
        case 'ExpressionStatement':
        case 'Identifier':
        case 'IfStatement':
        case 'MemberExpression': // TODO Add to all cases as needed
        case 'ReturnStatement':
        case 'UnaryExpression':
        case 'VariableDeclaration':
        case 'VariableDeclarator':
            break;
        default: throw new Error('substituteFacetedValues does not yet accommodate Node.type="' + node.type + '"');
    }
}

/**
 * On this pass, it simply finds all string 'Literal" nodes and rewrites the AST to use a new FacetedValue instead.
 *
 * @param {ASTNode} node
 */
function substituteFacetedValues(node){
    if (node.type === 'Literal') {
        if (typeof node.value === 'string') {
            try {
                var m = node.value.match(FacetedValue.REGEX);
                var replacementString = 'new FacetedValue(' + m[1] + ', ' + m[2] + ', ' + m[3] + ')';
                // We use the above string to construct a miniature AST with which we may replace the string literal.
                // Note that in the following, body.node[0] is an ExpressionStatement.
                // E.g. it is a complete statement containing an expression, closed with a semicolon.
                // Therefore it is necessary to extract the `new` expression contained therein, to avoid the semicolon.
                var miniTree = astq(replacementString);
                var newExpression = miniTree.body.node[0].expression;
                replaceLeftNodeWithRight(node, newExpression);
                node.faceting = [m[1]];
                node.wasReconstructedForFVs = true;
            } catch (ignored) {
            }
        }
    }
}

/**
 * On this pass, a tree of function scopes is constructed and then attached to the AST so that each node has a
 * reference to the scope that it resides in.
 *
 * @param {ASTNode} node
 */
function overlayScoping(node){
    if (node.scope)
        return; // a little sloppy in terms of program flow, but node.scope may have already been set by performProcessingPhase a few lines below, and we don't want it overridden
    node.scope = currentScope;
    if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression'){
        if (node.id) // Anonymous functions do not have an identifier
            currentScope.registerSymbol(node.id.name, node);
        currentScope = new Scope(node, currentScope);
        node.params.forEach(function(p){currentScope.registerSymbol(p.name, p);});
        var functionContents = node.body;
        if (functionContents.type === 'BlockStatement')
            functionContents = functionContents.body;
        performProcessingPhase(functionContents, overlayScoping);
        currentScope = currentScope.parent;
    }
    else if (node.type === 'VariableDeclarator'){
        currentScope.registerSymbol(node.id.name, node);
    }
}

/**
 * This pass simply puts empty lists in each node so that, in the next pass, we don't have to constantly check to see
 * if the list exists before pushing to it.
 *
 * @param {ASTNode} node
 */
function prepForInformationFlows(node){
    node.outgoingFlows = [];
}

/**
 * The following phase is much simpler if we already have all identifiers' information flows in place
 *
 * @param {ASTNode} node
 */
function linkIdentifiers(node){
    var declaringNode = node.scope.getNodeNamed(node.name);
    if (declaringNode && declaringNode !== node) // this will be null if it is external to our program, i.e. built-in functions
        declaringNode.outgoingFlows.push(node);
}

/**
 * On this pass, we look at the actual operations and use the scopes to create a (hopefully) accurate graph overlay
 * of information flows from symbols to symbols. You can expect this graph to be completely arbitrary, by no means
 * anything so specific as a tree or bipartite graph.
 *
 * This is the a more extensive and complicated part of the program, because nearly any node type within the abstract
 * syntax tree can have its own unique manner of information flow, and so each must be examined individually. Its
 * functioning also depends on the scope structure being correctly overlaid in the previous path.
 *
 * However, it isn't as bad as it looks. Each case within the switch statement is essentially its own function, and you
 * don't really need to look further than the few lines of a given case when considering its logic.
 *
 * @example a = b + 2;  // describes a flow from b to a
 * @example function f(x){}; f(b);  // describes a flow from b to x
 * @example if (g) h = i; else h = j; k = h; // describes a flow from g to h, g to h, i to h, j to h, and h to k.
 *          // By induction it describes a flow from g to k.
 * @example function f(){return a;}; b = f(); // describes a flow from a to b
 * @param {ASTNode} node
 */
function overlayInformationFlows(node){
    var i;
    switch (node.type){
        case 'BinaryExpression':
            node.left.outgoingFlows.push(node);
            node.right.outgoingFlows.push(node);
            break;
        case 'CallExpression':
            node.callee.outgoingFlows.push(node);
            if (node.callee.type === 'Identifier') {
                var t = node.callee.scope.getNodeNamed(node.callee.name);
                for (i = 0; i < node.arguments.length; i++)
                    if (t.params[i])
                        node.arguments[i].outgoingFlows.push(t.params[i]);
            } // TODO: It might be a MemberExpression instead of an Identifier
            break;
        case 'AssignmentExpression':
            node.right.outgoingFlows.push(node.left);
            node.left.outgoingFlows.push(node);
            break;
        case 'ExpressionStatement':
            node.outgoingFlows.push(node.expression);
            break;
        case 'FunctionDeclaration':
        case 'FunctionExpression': // TODO: flow from identifier to function, thence to CallExpression
            if (node.id)
                node.id.outgoingFlows.push(node); // TODO: What can expression and generator properties be?
            break;
        case 'IfStatement':
            if (node.alternate)
                node.test.outgoingFlows.push(node.alternate);
            if (node.consequent)
                node.test.outgoingFlows.push(node.consequent);
            node.test.outgoingFlows.push(node);
            break;
        case 'ReturnStatement':
            if (node.argument)
                node.argument.outgoingFlows.push(node);
            node.argument.outgoingFlows.push(node.scope.getOwningFunction()); // TODO: secure flows from function declarations/expressions to CallExpressions
            break;
        case 'UnaryExpression':
            debugger;
            break;
        case 'VariableDeclarator':
            if (node.init)
                node.init.outgoingFlows.push(node);
            break;
    }
}

/**
 * This walks the AST and, wherever something isFaceted, it follows the information flows plotted out in the previous
 * pass to mark those nodes as also being faceted.
 *
 * @param {ASTNode} node
 */
function markFaceting(node){
   if (node.faceting) {
       node.outgoingFlows.forEach(function(outflowNode){
           propagate(outflowNode, node.faceting);
       });
   }
    /**
     * @param {ASTNode} node
     * @param {Array<string>} faceting
     */
   function propagate(node, faceting){
       if (node.faceting && leftSetContainsRightSet(node.faceting, faceting))
           return; //avoid cycles
       node.faceting = mergeSets([node.faceting, faceting]);
       node.outgoingFlows.forEach(function(outflowNode){
           propagate(outflowNode, node.faceting);
       });
   }
}

/**
 * @example fv + b ===> fv.binaryOps(b, true)
 * @param {ASTNode}node
 */
function refactorOperationsToBeFaceted(node){
    if (node.faceting){
        var newNode, object, operand, operandIsOnLeft;
        switch (node.type) {
            case 'AssignmentExpression':
                // with view v, `var a = b;` becomes `var a = <v ? b : a>`
                var viewAsString = (node.faceting.length === 1) ? "'" + node.faceting[0] + "'" : "['" + node.faceting.join("', '") + "']";
                var replacementString = 'new FacetedValue(' + viewAsString + ', bPlaceholder, aPlaceholder)';
                newNode = astq(replacementString).body.node[0].expression;
                newNode.arguments[1] = node.right; // private value b
                newNode.arguments[2] = node.left; // public value a
                newNode.faceting = node.faceting;
                currentScope = node.scope;
                forEachIn(newNode, overlayScoping);
                forEachIn(newNode, prepForInformationFlows);
                forEachIn(newNode, overlayInformationFlows);
                forEachIn(newNode, markFaceting);
                replaceLeftNodeWithRight(node, newNode); // TODO should be node.right instead of node... but then it stack-overflows?
                break;
            case 'BinaryExpression':
                if (node.left.type === 'Identifier' && node.left.faceting){
                    object = node.left;
                    operand = node.right;
                    operandIsOnLeft = false;
                } else if (node.right.type === 'Identifier' && node.right.faceting) {
                    operand = node.left;
                    object = node.right;
                    operandIsOnLeft = true;
                } else {
                    throw new Error('CRIMINY AND CURSES! A binary expression has been marked as faceted, without' +
                        ' either its left or right side being faceted identifiers!');
                }
                newNode = astq('object.binaryOps("' + node.operator + '", operand, ' + operandIsOnLeft + ')').body.node[0].expression;
                newNode.callee.object = object;
                newNode.arguments[1] = operand;
                currentScope = node.scope;
                forEachIn(newNode, overlayScoping);
                forEachIn(newNode, prepForInformationFlows);
                forEachIn(newNode, overlayInformationFlows);
                forEachIn(newNode, markFaceting);
                replaceLeftNodeWithRight(node, newNode);
                debugger;
                break;
            case 'CallExpression':
                debugger;
                break;
            case 'FunctionDeclaration':
                debugger;
                break;
            case 'FunctionExpression':
                debugger;
                break;
            case 'Literal':
                debugger;
                break;
            case 'BlockStatement':
                debugger;
                break;
            case 'ReturnStatement':
                debugger;
                break;
            case 'UnaryExpression':
                debugger;
                break;
            case 'VariableDeclaration':
                debugger;
                break;
        }
    }
}

/* **************************** HELPERS *********************************************/

/**
 * Recursively operates the functor upon the given AST node and all of its children.
 *
 * @param {ASTNode} node
 * @param {ASTNodeFunctor} functor
 */
function forEachIn(node, functor){
    functor(node);
    for (var property in node) {
        // The following reference(s), if followed, may turn the AST into a circular graph, and must be ignored
        if (property === 'outgoingFlows')
            continue;

        /**
         * @type {*|ASTNode|Array<ASTNode>}
         */
        var value = node[property];
        if (isAnASTNode(value))
            forEachIn(value, functor);
        if (isArray(value)) {
            value.forEach(function (element) {
                if (isAnASTNode(element))
                    forEachIn(element, functor);
            });
        }
    }
}

/**
 *
 * @param {*|ASTNode} value
 * @returns {boolean}
 */
function isAnASTNode(value){
    /**
     * @typedef {string} value.__proto__.constructor.name
     */
    return value !== null && typeof value === 'object' && value.__proto__.constructor.name === 'Node';
}

/**
 *
 * @param {*|Array<*>} value
 * @returns {boolean}
 */
function isArray(value) {
    /**
     * @typedef {function} Object.prototype.toString.call
     */
    return typeof value === 'object' && Object.prototype.toString.call(value) === '[object Array]';
}

/**
 * A bit of a hack. This is intended to entirely replace one node with another.
 * TODO: Check with Dr. Austin, am I missing something on this?
 * @example '<a ? b : c>' node of type 'Literal' becomes a node of type 'ExpressionStatement' that corresponds to
 *      `new FacetedValue(a, b, c)`.
 * @param left
 * @param right
 */
function replaceLeftNodeWithRight(left, right) {
    var i;
    for (i in left)
        if (left.hasOwnProperty(i))
            left[i] = right[i];
    for (i in right)
        if (right.hasOwnProperty(i))
            left[i] = right[i];
}

/**
 * @param {ASTNode|Array<ASTNode>|Tree} container
 * @param {Function} functor
 */
function performProcessingPhase(container, functor){
    /**
     * @type {Array<ASTNode>}
     */
    var iterationTarget = isArray(container) ? container : container.body.node;
    iterationTarget.forEach(function (node) {
        forEachIn(node, functor);
    });
}

function leftSetContainsRightSet(leftSet, rightSet){
    if (leftSet instanceof Array)
        return leftSet.filter(function(val) { return rightSet.indexOf(val) !== -1;}).length === rightSet.length;
    return leftSet === rightSet;
}

/**
 * @param {Array<Array>} listOfSets
 * @returns {Array}
 */
function mergeSets(listOfSets){
    var mergedSet = {};
    var uniqueMergedSet = [];
    listOfSets.forEach(function addAllIn(set){
        set && set.forEach(function add(element){
            mergedSet[element] = element;
        });
    })
    for (var i in mergedSet)
        if (mergedSet.hasOwnProperty(i))
            uniqueMergedSet.push(mergedSet[i]);
    return uniqueMergedSet;
}

<|endoftext|>


<commit_before>/*
  blur.js
*/

var ip_blur = function(src, r){
  var dst = ip_create_img( src.width, src.height );

  gaussBlur( src,dst, src.width,src.height, r );

  return dst;
}

var _ip_gauss_boxes = function(sigma, n){
    var wIdeal = Math.sqrt((12*sigma*sigma/n)+1);  // Ideal averaging filter width
    var wl = Math.floor(wIdeal);  if(wl%2==0) wl--;
    var wu = wl+2;

    var mIdeal = (12*sigma*sigma - n*wl*wl - 4*n*wl - 3*n)/(-4*wl - 4);
    var m = Math.round(mIdeal);
    // var sigmaActual = Math.sqrt( (m*wl*wl + (n-m)*wu*wu - n)/12 );

    var sizes = [];  for(var i=0; i<n; i++) sizes.push(i<m?wl:wu);
    return sizes;
}

function gaussBlur(scl, tcl, w, h, r) {
    var bxs = _ip_gauss_boxes(r, 3);
    boxBlur(scl, tcl, w, h, (bxs[0]-1)/2);
///    boxBlur(tcl, scl, w, h, (bxs[1]-1)/2);
//    boxBlur(scl, tcl, w, h, (bxs[2]-1)/2);
}
function boxBlur(scl, tcl, w, h, r) {
    for(var i=0; i<h; i++){
        for(var j=0; j<w; j++) {

            var cr = 0;
            var cg = 0;
            var cb = 0;
            for(var iy=i-r; iy<i+r+1; iy++){
                for(var ix=j-r; ix<j+r+1; ix++) {
                    var x = Math.min(w-1, Math.max(0, ix));
                    var y = Math.min(h-1, Math.max(0, iy));
                    var p = ip_get_rgb_at( scl, x,y );

                    cr += p[0];
                    cg += p[1];
                    cb += p[2];
                }
            }

            ip_set_rgba_at(
            	tcl,
            	j,i,
            	cr/((r+r+1)*(r+r+1)),cg/((r+r+1)*(r+r+1)),cb/((r+r+1)*(r+r+1)),
            	255);
        }
    }
}
<commit_msg>Update ip_blur.js<commit_after>/*
  blur.js
*/

var ip_blur = function(src, r){
  var dst = ip_create_img( src.width, src.height );

  _ip_gauss_blur( src,dst, src.width,src.height, r );

  return dst;
}

var _ip_gauss_boxes = function(sigma, n){
    var wIdeal = Math.sqrt((12*sigma*sigma/n)+1);  // Ideal averaging filter width
    var wl = Math.floor(wIdeal);  if(wl%2==0) wl--;
    var wu = wl+2;

    var mIdeal = (12*sigma*sigma - n*wl*wl - 4*n*wl - 3*n)/(-4*wl - 4);
    var m = Math.round(mIdeal);
    // var sigmaActual = Math.sqrt( (m*wl*wl + (n-m)*wu*wu - n)/12 );

    var sizes = [];  for(var i=0; i<n; i++) sizes.push(i<m?wl:wu);
    return sizes;
}

function _ip_gauss_blur(src, dst, w, h, r) {
    var bxs = _ip_gauss_boxes(r, 3);
    _ip_box_blur(src, dst, w, h, (bxs[0]-1)/2);
    _ip_box_blur(src, dst, w, h, (bxs[1]-1)/2);
    _ip_box_blur(src, dst, w, h, (bxs[2]-1)/2);
}
function _ip_box_blur(src, dst, w, h, r) {
    for(i=0;i<w;i++){
        for(j=0;j<h;j++) {
            var cr = 0,cg = 0,cb = 0;

            var st_x = Math.max( 0, i-r );
            var st_y = Math.max( 0, j-r );
            var to_x = Math.min( w, i+r+1 );
            var to_y = Math.min( h, i+r+1 );

            for(k=st_x;k<to_x;k++){
                for(l=st_y;l<to_y;l++) {
                    var p = ip_get_rgb_at( src, k,l );

                    cr += p[0];
                    cg += p[1];
                    cb += p[2];
                }
            }

            var div = ((r+r+1)*(r+r+1));
            ip_set_rgba_at(
            	dst, j,i,
            	cr/div,cg/div,cb/div,
            	255);
        }
    }
}
<|endoftext|>


<commit_before>/*
  morp.js
*/

var ip_morp_erode = function(src, w,h){
  var roi = ip_get_roi( src );
  var dst = ip_create_img( roi[2],roi[3] );

  for(i=roi[0],u=0;i<roi[2];i++,u++){
    for(j=roi[1],v=0;j<roi[3];j++,v++){

      var to_x = Math.min( roi[2], i+w );
      var to_y = Math.min( roi[3], j+h );
      var f = false;

      for(k=i;k<to_x;k++){
        for(l=j;l<to_y;l++){
          var p = ip_get_rgb_at( src, k,l );

          if( p[0] <= 128 && p[1] <= 128 && p[2] <= 128 ){
            f = true;
            break;
          }
        }
      }

      if( f )
        ip_set_rgba_at( dst, u,v, 0,0,0,255 );
      else
        ip_set_rgba_at( dst, u,v, 255,255,255,255 );
    }
  }

  return dst;
}
var ip_morp_dilate = function(src, w,h){
  var roi = ip_get_roi( src );
  var dst = ip_create_img( roi[2],roi[3] );

  for(i=roi[0],u=0;i<roi[2];i++,u++){
    for(j=roi[1],v=0;j<roi[3];j++,v++){

      var to_x = Math.min( roi[2], i+w );
      var to_y = Math.min( roi[3], j+h );
      var f = false;

      for(k=i;k<to_x;k++){
        for(l=j;l<to_y;l++){
          var p = ip_get_rgb_at( src, k,l );

          if( p[0] >= 128 && p[1] >= 128 && p[2] >= 128 ){
            f = true;
            break;
          }
        }
      }

      if( f )
        ip_set_rgba_at( dst, u,v, 255,255,255,255 );
      else
        ip_set_rgba_at( dst, u,v, 0,0,0,255 );
    }
  }

  return dst;
}

var ip_test_morp_dilate = function(src, w,h){
  var roi = ip_get_roi( src );
  var dst = ip_create_img( roi[2],roi[3] );

  for(i=roi[0],u=0;i<roi[2];i++,u++){
    for(j=roi[1],v=0;j<roi[3];j++,v++){

      var to_x = Math.min( roi[2], i+w );
      var to_y = Math.min( roi[3], j+h );
      var f = false;

      var max = 0;
      var max_p = 0;

      for(k=i;k<to_x;k++){
        for(l=j;l<to_y;l++){
          var p = ip_get_rgb_at( src, k,l );
          var sum = p[0] + p[1] + p[2];

          if( max < sum ){
            max = sum;
            max_p = p;
          }
        }
      }

      ip_set_rgba_at(
        dst, u,v,
        max_p[0],max_p[1],max_p[2],255 );
    }
  }

  return dst;
}
<commit_msg>Update ip_morp.js<commit_after>/*
  morp.js
*/

var ip_morp_erode = function(src, w,h){
  var roi = ip_get_roi( src );
  var dst = ip_create_img( roi[2],roi[3] );

  for(i=roi[0],u=0;i<roi[2];i++,u++){
    for(j=roi[1],v=0;j<roi[3];j++,v++){

      var to_x = Math.min( roi[2], i+w );
      var to_y = Math.min( roi[3], j+h );
      var f = false;

      for(k=i;k<to_x;k++){
        for(l=j;l<to_y;l++){
          var p = ip_get_rgb_at( src, k,l );

          if( p[0] <= 128 && p[1] <= 128 && p[2] <= 128 ){
            f = true;
            break;
          }
        }
      }

      if( f )
        ip_set_rgba_at( dst, u,v, 0,0,0,255 );
      else
        ip_set_rgba_at( dst, u,v, 255,255,255,255 );
    }
  }

  return dst;
}
var ip_morp_dilate = function(src, w,h){
  var roi = ip_get_roi( src );
  var dst = ip_create_img( roi[2],roi[3] );

  for(i=roi[0],u=0;i<roi[2];i++,u++){
    for(j=roi[1],v=0;j<roi[3];j++,v++){

      var to_x = Math.min( roi[2], i+w );
      var to_y = Math.min( roi[3], j+h );
      var f = false;

      for(k=i;k<to_x;k++){
        for(l=j;l<to_y;l++){
          var p = ip_get_rgb_at( src, k,l );

          if( p[0] >= 128 && p[1] >= 128 && p[2] >= 128 ){
            f = true;
            break;
          }
        }
      }

      if( f )
        ip_set_rgba_at( dst, u,v, 255,255,255,255 );
      else
        ip_set_rgba_at( dst, u,v, 0,0,0,255 );
    }
  }

  return dst;
}

var ip_test_morp_erode = function(src, w,h){
  var roi = ip_get_roi( src );
  var dst = ip_create_img( roi[2],roi[3] );

  for(i=roi[0],u=0;i<roi[2];i++,u++){
    for(j=roi[1],v=0;j<roi[3];j++,v++){

      var to_x = Math.min( roi[2], i+w );
      var to_y = Math.min( roi[3], j+h );
      var f = false;

      var min = 255+255+255;
      var min_p = 0;

      for(k=i;k<to_x;k++){
        for(l=j;l<to_y;l++){
          var p = ip_get_rgb_at( src, k,l );
          var sum = p[0] + p[1] + p[2];

          if( min > sum ){
            min = sum;
            min_p = p;
          }
        }
      }

      ip_set_rgba_at(
        dst, u,v,
        min_p[0],min_p[1],min_p[2],255 );
    }
  }

  return dst;
}
var ip_test_morp_dilate = function(src, w,h){
  var roi = ip_get_roi( src );
  var dst = ip_create_img( roi[2],roi[3] );

  for(i=roi[0],u=0;i<roi[2];i++,u++){
    for(j=roi[1],v=0;j<roi[3];j++,v++){

      var to_x = Math.min( roi[2], i+w );
      var to_y = Math.min( roi[3], j+h );
      var f = false;

      var max = 0;
      var max_p = 0;

      for(k=i;k<to_x;k++){
        for(l=j;l<to_y;l++){
          var p = ip_get_rgb_at( src, k,l );
          var sum = p[0] + p[1] + p[2];

          if( max < sum ){
            max = sum;
            max_p = p;
          }
        }
      }

      ip_set_rgba_at(
        dst, u,v,
        max_p[0],max_p[1],max_p[2],255 );
    }
  }

  return dst;
}
<|endoftext|>