<commit_before>// @author         Fly33
// @name           Fly Links
// @category       Draw
// @version        0.5.0
// @description    Calculate how to link the portals to create the largest tidy set of nested fields. Enable from the layer chooser.

// use own namespace for plugin
window.plugin.flyLinks = function() {};

// const values
window.plugin.flyLinks.MAX_PORTALS_TO_LINK = 100;

window.plugin.flyLinks.linksLayerGroup = null;
window.plugin.flyLinks.fieldsLayerGroup = null;

window.plugin.flyLinks.updateLayer = function() {
  if (!window.map.hasLayer(window.plugin.flyLinks.linksLayerGroup) &&
      !window.map.hasLayer(window.plugin.flyLinks.fieldsLayerGroup))
    return;

  window.plugin.flyLinks.linksLayerGroup.clearLayers();
  window.plugin.flyLinks.fieldsLayerGroup.clearLayers();
  var ctrl = [$('.leaflet-control-layers-selector + span:contains("Fly links")').parent(),
              $('.leaflet-control-layers-selector + span:contains("Fly fields")').parent()];

  var distance = function(a, b) {
    return Math.sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
  };

  var drawLink = function(a, b, style) {
    var poly = L.polyline([a.latlng, b.latlng], style);
    poly.addTo(window.plugin.flyLinks.linksLayerGroup);
  }

  var drawField = function(a, b, c, style) {
    var poly = L.polygon([a.latlng, b.latlng, c.latlng], style);
    poly.addTo(window.plugin.flyLinks.fieldsLayerGroup);
  }

  var EPS = 1e-9;
  var det = function(a, b, c) {
    return a.x * b.y - a.y * b.x + b.x * c.y - b.y * c.x + c.x * a.y - c.y * a.x;
  }

  var convexHull = function(points) {
    if (points.length < 3)
      return [];
    var result = [];
    var func = function _func(ai, bi, index) {
      var maxd = 0;
      var maxdi = -1;
      var a = points[ai];
      var b = points[bi];
      var _index = [];
      for (var i = 0; i < index.length; ++i) {
        var c = points[index[i]];
        var d = -det(a, b, c);
        if (d > EPS) {
            _index.push(index[i]);
        }
        if (maxd < d - EPS) {
          maxd = d;
          maxdi = index[i];
        }
      }
      if (maxdi != -1) {
        _func(ai, maxdi, _index);
        _func(maxdi, bi, _index);
      } else {
        result.push(ai);
      }
    }
    var minxi = 0;
    var maxxi = 0;
    var index = [];
    for (var i = 0; i < points.length; ++i) {
      index.push(i);
      if (points[minxi].x > points[i].x)
        minxi = i;
      if (points[maxxi].x < points[i].x)
        maxxi = i;
    }
    func(minxi, maxxi, index);
    func(maxxi, minxi, index);
    return result;
  }

  var triangulate2 = function(index, line_indexes, line_edge_indexes, locations) {
    if (index.length == 0)
      return {edges: [], triangles: []};
    var data = [];
    var subtriangulate = function _subtriangulate(ai, bi, ci, index) {
      var _i = [ai, bi, ci].sort(function(a,b){return a-b;});
      if (data[_i[0]] === undefined)
        data[_i[0]] = [];
      if (data[_i[0]][_i[1]-_i[0]] === undefined)
        data[_i[0]][_i[1]-_i[0]] = [];
      if (data[_i[0]][_i[1]-_i[0]][_i[2]-_i[1]] === undefined) {
        var _index = [];
        for (var i = 0; i < index.length; ++i) {
          var detc = det(locations[ai], locations[bi], locations[index[i]]);
          var deta = det(locations[bi], locations[ci], locations[index[i]]);
          var detb = det(locations[ci], locations[ai], locations[index[i]]);
          if (deta > EPS && detb > EPS && detc > EPS) {
            _index.push(index[i]);
          }
        }
        var besth = 0;
        var besthi = -1;
        var i
        for (i = 0; i < line_indexes.length; ++i) {
          var f0 = _index.indexOf(line_indexes[i][0]) != -1
          var f1 = _index.indexOf(line_indexes[i][1]) != -1
          if (f0 && !f1 && _i.indexOf(line_indexes[i][1]) == -1)
            break
          if (f1 && !f0 && _i.indexOf(line_indexes[i][0]) == -1)
            break
        }
        if (i < line_indexes.length) {
          besth = 0;
          besthi = -1;
        } else if (_index.length == 0) {
          var a = locations[ai];
          var b = locations[bi];
          var c = locations[ci];
          var s = Math.abs(det(a, b, c));
          var ch = s / distance(a, b);
          var ah = s / distance(b, c);
          var bh = s / distance(c, a);
          besth = Math.min(ah, bh, ch);
          besthi = -1;
        } else {
          var besths = 0;
          for (var i = 0; i < _index.length; ++i) {
            var ch = _subtriangulate(ai, bi, _index[i], _index);
            var ah = _subtriangulate(bi, ci, _index[i], _index);
            var bh = _subtriangulate(ci, ai, _index[i], _index);
            var _besth = Math.min(ah, bh, ch);
            var _besths = ah + bh + ch;
            if (besth < _besth || Math.abs(besth - _besth) <= EPS && besths < _besths) {
              besth = _besth;
              besths = _besths;
              besthi = _index[i];
            }
          }
        }
        data[_i[0]][_i[1]-_i[0]][_i[2]-_i[1]] = {height: besth, index: besthi};
      }
      return data[_i[0]][_i[1]-_i[0]][_i[2]-_i[1]].height;
    }
    var subindex = [];
    for (var i = 0; i < locations.length; ++i) {
      subindex.push(i);
    }
    var best = [];
    best[1] = []
    for (var k = 0; k < index.length - 1; ++k) {
      best[1][k] = {height: Infinity, length: -1};
    }
    for (var len = 2; len <= index.length - 1; ++len) {
      best[len] = [];
      for (var k = 0; k < index.length - len; ++k) {
        var t = 0;
        var tlen = -1;
        var i
        for (i = 0; i < line_edge_indexes.length; ++i) {
          var [i0, i1] = line_edge_indexes[i]
          if (k < i0 && i0 < k+len && (i1 < k || k+len < i1) ||
              k < i1 && i1 < k+len && (i0 < k || k+len < i0))
            break
        }
        if (i >= line_edge_indexes.length) {
          for (var _len = 1; _len <= len - 1; ++_len) {
            var _t = Math.min(best[_len][k].height, best[len-_len][k+_len].height, subtriangulate(index[k], index[k+_len], index[k+len], subindex))
            if (t < _t) {
              t = _t;
              tlen = _len;
            }
          }
        }
        best[len][k] = {height: t, length: tlen};
      }
    }

    var edges = [];
    var triangles = [];
    var makesubtriangulation = function _makesubtriangulation(ai, bi, ci, depth) {
      var _i = [ai, bi, ci].sort(function(a,b){return a-b;});
      if (data[_i[0]][_i[1]-_i[0]][_i[2]-_i[1]].index == -1) {
        triangles.push(new window.plugin.flyLinks.Triangle(locations[ai], locations[bi], locations[ci], depth));
      } else {
        _makesubtriangulation(ai, bi, data[_i[0]][_i[1]-_i[0]][_i[2]-_i[1]].index, depth+1);
        _makesubtriangulation(bi, ci, data[_i[0]][_i[1]-_i[0]][_i[2]-_i[1]].index, depth+1);
        _makesubtriangulation(ci, ai, data[_i[0]][_i[1]-_i[0]][_i[2]-_i[1]].index, depth+1);
        edges.push(new window.plugin.flyLinks.Edge(locations[ai], locations[data[_i[0]][_i[1]-_i[0]][_i[2]-_i[1]].index], depth));
        edges.push(new window.plugin.flyLinks.Edge(locations[bi], locations[data[_i[0]][_i[1]-_i[0]][_i[2]-_i[1]].index], depth));
        edges.push(new window.plugin.flyLinks.Edge(locations[ci], locations[data[_i[0]][_i[1]-_i[0]][_i[2]-_i[1]].index], depth));
      }
    }
    var maketriangulation = function _maketriangulation(len, a) {
      edges.push(new window.plugin.flyLinks.Edge(locations[index[a]], locations[index[a+len]], 0));
      if (best[len][a].length == -1)
        return;
      makesubtriangulation(index[a], index[a+best[len][a].length], index[a+len], 1);
      _maketriangulation(best[len][a].length, a);
      _maketriangulation(len - best[len][a].length, a + best[len][a].length);
    }
    if (best[index.length - 1][0].height > 0)
      maketriangulation(index.length - 1, 0)
    else
      console.log("Fly links: no triangulation")
    return {edges: edges, triangles: triangles};
  }

  var triangulate = function(locations, lines) {
    var index = convexHull(locations);
    var line_indexes = filterLines(locations, lines)
    var line_edge_indexes = []
    for (var i = 0; i < line_indexes.length; ++i) {
      var i0 = index.indexOf(line_indexes[i][0])
      var i1 = index.indexOf(line_indexes[i][1])
      if (i0 == -1 || i1 == -1)
        continue
      line_edge_indexes.push([i0, i1])
    }
    return triangulate2(index, line_indexes, line_edge_indexes, locations);
  }

  var edges = [];
  var triangles = [];

  var lines = [];
  if (plugin.drawTools) {
    var polylines = [];
    plugin.drawTools.drawnItems.eachLayer(function(layer) {
      if (layer instanceof L.GeodesicPolyline) {
        polylines.push(layer);
      }
    });
    polylines.forEach(function (polyline) {
      if (!polyline._rings) { return; }
      var p = polyline._rings[0];
      for (var j = 1; j < p.length; ++j) {
        lines.push([p[j-1], p[j]])
      }
    });
  }

  var filterLines = function(points, lines) {
    var result = [];
    var findPoint = function(points, point) {
      for (var i = 0; i < points.length; ++i) {
        if (points[i].x === point.x && points[i].y === point.y)
          return i
      }
      return -1
    }
    for (var i = 0; i < lines.length; ++i) {
      var a = findPoint(points, lines[i][0])
      if (a == -1)
        continue
      var b = findPoint(points, lines[i][1])
      if (b == -1)
        continue
      result.push([a, b])
    }
    return result
  }

  var filters = plugin.drawTools && plugin.drawTools.getLocationFilters && plugin.drawTools.getLocationFilters();
  // fallback to map bounds if no drawn polygon (or no drawtools)
  if (!filters || !filters.length) {
    var bounds = map.getBounds();
    filters = [function (p) {
      return bounds.contains(p.getLatLng());
    }];
  }

  filters.forEach(function (filter) {
    var points = [];
    for (var guid in window.portals) {
      var location = window.portals[guid];
      if (filter(location)) {
        var point = location._point.clone();
        point.latlng = location._latlng;
        points.push(point);
      }
    }
    if (points.length >= window.plugin.flyLinks.MAX_PORTALS_TO_LINK) {
      //alert("Some polygon contains more than 100 portals.");
      return;
    }
    if (!points.length) return;
    var triangulation = triangulate(points, lines);
    edges = edges.concat(triangulation.edges);
    triangles = triangles.concat(triangulation.triangles);
  });

  $.each(edges, function(idx, edge) {
    drawLink(edge.a, edge.b, {
      color: '#FF0000',
      opacity: 1,
      weight: 1.5,
      interactive: false,
      smoothFactor: 10,
      dashArray: [6, 4],
    });
  });

  $.each(triangles, function(idx, triangle) {
    drawField(triangle.a, triangle.b, triangle.c, {
      stroke: false,
      fill: true,
      fillColor: '#FF0000',
      fillOpacity: 1 - Math.pow(0.85, triangle.depth),
      interactive: false,
    });
  });
}

window.plugin.flyLinks.Edge = function(a, b, depth) {
  this.a = a;
  this.b = b;
  this.depth = depth;
}

window.plugin.flyLinks.Triangle = function(a, b, c, depth) {
  this.a = a;
  this.b = b;
  this.c = c;
  this.depth = depth;
}

window.plugin.flyLinks.setup = function() {
  window.plugin.flyLinks.linksLayerGroup = new L.LayerGroup();
  window.plugin.flyLinks.fieldsLayerGroup = new L.LayerGroup();

  function update () {
    if (!map.hasLayer(window.plugin.flyLinks.linksLayerGroup) ||
        !map.hasLayer(window.plugin.flyLinks.fieldsLayerGroup)) {
      window.plugin.flyLinks.updateLayer();
    }
  }
  window.plugin.flyLinks.linksLayerGroup.on('add', update);
  window.plugin.flyLinks.fieldsLayerGroup.on('add', update);

  window.addHook('mapDataRefreshEnd', function(e) {
    window.plugin.flyLinks.updateLayer();
  });

  window.map.on('moveend', function() {
    window.plugin.flyLinks.updateLayer();
  });

  if (window.plugin.drawTools && window.plugin.drawTools.filterEvents) {
    window.plugin.drawTools.filterEvents.on('changed', window.plugin.flyLinks.updateLayer);
  }

  window.addLayerGroup('Fly links', window.plugin.flyLinks.linksLayerGroup, false);
  window.addLayerGroup('Fly fields', window.plugin.flyLinks.fieldsLayerGroup, false);
}
var setup = window.plugin.flyLinks.setup;
<commit_msg>fly-links: lint (semi)<commit_after>// @author         Fly33
// @name           Fly Links
// @category       Draw
// @version        0.5.0
// @description    Calculate how to link the portals to create the largest tidy set of nested fields. Enable from the layer chooser.

// use own namespace for plugin
window.plugin.flyLinks = function() {};

// const values
window.plugin.flyLinks.MAX_PORTALS_TO_LINK = 100;

window.plugin.flyLinks.linksLayerGroup = null;
window.plugin.flyLinks.fieldsLayerGroup = null;

window.plugin.flyLinks.updateLayer = function() {
  if (!window.map.hasLayer(window.plugin.flyLinks.linksLayerGroup) &&
      !window.map.hasLayer(window.plugin.flyLinks.fieldsLayerGroup))
    return;

  window.plugin.flyLinks.linksLayerGroup.clearLayers();
  window.plugin.flyLinks.fieldsLayerGroup.clearLayers();
  var ctrl = [$('.leaflet-control-layers-selector + span:contains("Fly links")').parent(),
              $('.leaflet-control-layers-selector + span:contains("Fly fields")').parent()];

  var distance = function(a, b) {
    return Math.sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
  };

  var drawLink = function(a, b, style) {
    var poly = L.polyline([a.latlng, b.latlng], style);
    poly.addTo(window.plugin.flyLinks.linksLayerGroup);
  };

  var drawField = function(a, b, c, style) {
    var poly = L.polygon([a.latlng, b.latlng, c.latlng], style);
    poly.addTo(window.plugin.flyLinks.fieldsLayerGroup);
  };

  var EPS = 1e-9;
  var det = function(a, b, c) {
    return a.x * b.y - a.y * b.x + b.x * c.y - b.y * c.x + c.x * a.y - c.y * a.x;
  };

  var convexHull = function(points) {
    if (points.length < 3)
      return [];
    var result = [];
    var func = function _func(ai, bi, index) {
      var maxd = 0;
      var maxdi = -1;
      var a = points[ai];
      var b = points[bi];
      var _index = [];
      for (var i = 0; i < index.length; ++i) {
        var c = points[index[i]];
        var d = -det(a, b, c);
        if (d > EPS) {
            _index.push(index[i]);
        }
        if (maxd < d - EPS) {
          maxd = d;
          maxdi = index[i];
        }
      }
      if (maxdi != -1) {
        _func(ai, maxdi, _index);
        _func(maxdi, bi, _index);
      } else {
        result.push(ai);
      }
    };
    var minxi = 0;
    var maxxi = 0;
    var index = [];
    for (var i = 0; i < points.length; ++i) {
      index.push(i);
      if (points[minxi].x > points[i].x)
        minxi = i;
      if (points[maxxi].x < points[i].x)
        maxxi = i;
    }
    func(minxi, maxxi, index);
    func(maxxi, minxi, index);
    return result;
  };

  var triangulate2 = function(index, line_indexes, line_edge_indexes, locations) {
    if (index.length == 0)
      return {edges: [], triangles: []};
    var data = [];
    var subtriangulate = function _subtriangulate(ai, bi, ci, index) {
      var _i = [ai, bi, ci].sort(function(a,b){return a-b;});
      if (data[_i[0]] === undefined)
        data[_i[0]] = [];
      if (data[_i[0]][_i[1]-_i[0]] === undefined)
        data[_i[0]][_i[1]-_i[0]] = [];
      if (data[_i[0]][_i[1]-_i[0]][_i[2]-_i[1]] === undefined) {
        var _index = [];
        for (var i = 0; i < index.length; ++i) {
          var detc = det(locations[ai], locations[bi], locations[index[i]]);
          var deta = det(locations[bi], locations[ci], locations[index[i]]);
          var detb = det(locations[ci], locations[ai], locations[index[i]]);
          if (deta > EPS && detb > EPS && detc > EPS) {
            _index.push(index[i]);
          }
        }
        var besth = 0;
        var besthi = -1;
        var i;
        for (i = 0; i < line_indexes.length; ++i) {
          var f0 = _index.indexOf(line_indexes[i][0]) != -1;
          var f1 = _index.indexOf(line_indexes[i][1]) != -1;
          if (f0 && !f1 && _i.indexOf(line_indexes[i][1]) == -1)
            break;
          if (f1 && !f0 && _i.indexOf(line_indexes[i][0]) == -1)
            break;
        }
        if (i < line_indexes.length) {
          besth = 0;
          besthi = -1;
        } else if (_index.length == 0) {
          var a = locations[ai];
          var b = locations[bi];
          var c = locations[ci];
          var s = Math.abs(det(a, b, c));
          var ch = s / distance(a, b);
          var ah = s / distance(b, c);
          var bh = s / distance(c, a);
          besth = Math.min(ah, bh, ch);
          besthi = -1;
        } else {
          var besths = 0;
          for (var i = 0; i < _index.length; ++i) {
            var ch = _subtriangulate(ai, bi, _index[i], _index);
            var ah = _subtriangulate(bi, ci, _index[i], _index);
            var bh = _subtriangulate(ci, ai, _index[i], _index);
            var _besth = Math.min(ah, bh, ch);
            var _besths = ah + bh + ch;
            if (besth < _besth || Math.abs(besth - _besth) <= EPS && besths < _besths) {
              besth = _besth;
              besths = _besths;
              besthi = _index[i];
            }
          }
        }
        data[_i[0]][_i[1]-_i[0]][_i[2]-_i[1]] = {height: besth, index: besthi};
      }
      return data[_i[0]][_i[1]-_i[0]][_i[2]-_i[1]].height;
    };
    var subindex = [];
    for (var i = 0; i < locations.length; ++i) {
      subindex.push(i);
    }
    var best = [];
    best[1] = [];
    for (var k = 0; k < index.length - 1; ++k) {
      best[1][k] = {height: Infinity, length: -1};
    }
    for (var len = 2; len <= index.length - 1; ++len) {
      best[len] = [];
      for (var k = 0; k < index.length - len; ++k) {
        var t = 0;
        var tlen = -1;
        var i;
        for (i = 0; i < line_edge_indexes.length; ++i) {
          var [i0, i1] = line_edge_indexes[i];
          if (k < i0 && i0 < k+len && (i1 < k || k+len < i1) ||
              k < i1 && i1 < k+len && (i0 < k || k+len < i0))
            break;
        }
        if (i >= line_edge_indexes.length) {
          for (var _len = 1; _len <= len - 1; ++_len) {
            var _t = Math.min(best[_len][k].height, best[len-_len][k+_len].height, subtriangulate(index[k], index[k+_len], index[k+len], subindex));
            if (t < _t) {
              t = _t;
              tlen = _len;
            }
          }
        }
        best[len][k] = {height: t, length: tlen};
      }
    }

    var edges = [];
    var triangles = [];
    var makesubtriangulation = function _makesubtriangulation(ai, bi, ci, depth) {
      var _i = [ai, bi, ci].sort(function(a,b){return a-b;});
      if (data[_i[0]][_i[1]-_i[0]][_i[2]-_i[1]].index == -1) {
        triangles.push(new window.plugin.flyLinks.Triangle(locations[ai], locations[bi], locations[ci], depth));
      } else {
        _makesubtriangulation(ai, bi, data[_i[0]][_i[1]-_i[0]][_i[2]-_i[1]].index, depth+1);
        _makesubtriangulation(bi, ci, data[_i[0]][_i[1]-_i[0]][_i[2]-_i[1]].index, depth+1);
        _makesubtriangulation(ci, ai, data[_i[0]][_i[1]-_i[0]][_i[2]-_i[1]].index, depth+1);
        edges.push(new window.plugin.flyLinks.Edge(locations[ai], locations[data[_i[0]][_i[1]-_i[0]][_i[2]-_i[1]].index], depth));
        edges.push(new window.plugin.flyLinks.Edge(locations[bi], locations[data[_i[0]][_i[1]-_i[0]][_i[2]-_i[1]].index], depth));
        edges.push(new window.plugin.flyLinks.Edge(locations[ci], locations[data[_i[0]][_i[1]-_i[0]][_i[2]-_i[1]].index], depth));
      }
    };
    var maketriangulation = function _maketriangulation(len, a) {
      edges.push(new window.plugin.flyLinks.Edge(locations[index[a]], locations[index[a+len]], 0));
      if (best[len][a].length == -1)
        return;
      makesubtriangulation(index[a], index[a+best[len][a].length], index[a+len], 1);
      _maketriangulation(best[len][a].length, a);
      _maketriangulation(len - best[len][a].length, a + best[len][a].length);
    };
    if (best[index.length - 1][0].height > 0)
      maketriangulation(index.length - 1, 0);
    else
      console.log("Fly links: no triangulation");
    return {edges: edges, triangles: triangles};
  };

  var triangulate = function(locations, lines) {
    var index = convexHull(locations);
    var line_indexes = filterLines(locations, lines);
    var line_edge_indexes = [];
    for (var i = 0; i < line_indexes.length; ++i) {
      var i0 = index.indexOf(line_indexes[i][0]);
      var i1 = index.indexOf(line_indexes[i][1]);
      if (i0 == -1 || i1 == -1)
        continue;
      line_edge_indexes.push([i0, i1]);
    }
    return triangulate2(index, line_indexes, line_edge_indexes, locations);
  };

  var edges = [];
  var triangles = [];

  var lines = [];
  if (plugin.drawTools) {
    var polylines = [];
    plugin.drawTools.drawnItems.eachLayer(function(layer) {
      if (layer instanceof L.GeodesicPolyline) {
        polylines.push(layer);
      }
    });
    polylines.forEach(function (polyline) {
      if (!polyline._rings) { return; }
      var p = polyline._rings[0];
      for (var j = 1; j < p.length; ++j) {
        lines.push([p[j-1], p[j]]);
      }
    });
  }

  var filterLines = function(points, lines) {
    var result = [];
    var findPoint = function(points, point) {
      for (var i = 0; i < points.length; ++i) {
        if (points[i].x === point.x && points[i].y === point.y)
          return i;
      }
      return -1;
    };
    for (var i = 0; i < lines.length; ++i) {
      var a = findPoint(points, lines[i][0]);
      if (a == -1)
        continue;
      var b = findPoint(points, lines[i][1]);
      if (b == -1)
        continue;
      result.push([a, b]);
    }
    return result;
  };

  var filters = plugin.drawTools && plugin.drawTools.getLocationFilters && plugin.drawTools.getLocationFilters();
  // fallback to map bounds if no drawn polygon (or no drawtools)
  if (!filters || !filters.length) {
    var bounds = map.getBounds();
    filters = [function (p) {
      return bounds.contains(p.getLatLng());
    }];
  }

  filters.forEach(function (filter) {
    var points = [];
    for (var guid in window.portals) {
      var location = window.portals[guid];
      if (filter(location)) {
        var point = location._point.clone();
        point.latlng = location._latlng;
        points.push(point);
      }
    }
    if (points.length >= window.plugin.flyLinks.MAX_PORTALS_TO_LINK) {
      //alert("Some polygon contains more than 100 portals.");
      return;
    }
    if (!points.length) return;
    var triangulation = triangulate(points, lines);
    edges = edges.concat(triangulation.edges);
    triangles = triangles.concat(triangulation.triangles);
  });

  $.each(edges, function(idx, edge) {
    drawLink(edge.a, edge.b, {
      color: '#FF0000',
      opacity: 1,
      weight: 1.5,
      interactive: false,
      smoothFactor: 10,
      dashArray: [6, 4],
    });
  });

  $.each(triangles, function(idx, triangle) {
    drawField(triangle.a, triangle.b, triangle.c, {
      stroke: false,
      fill: true,
      fillColor: '#FF0000',
      fillOpacity: 1 - Math.pow(0.85, triangle.depth),
      interactive: false,
    });
  });
};

window.plugin.flyLinks.Edge = function(a, b, depth) {
  this.a = a;
  this.b = b;
  this.depth = depth;
};

window.plugin.flyLinks.Triangle = function(a, b, c, depth) {
  this.a = a;
  this.b = b;
  this.c = c;
  this.depth = depth;
};

window.plugin.flyLinks.setup = function() {
  window.plugin.flyLinks.linksLayerGroup = new L.LayerGroup();
  window.plugin.flyLinks.fieldsLayerGroup = new L.LayerGroup();

  function update () {
    if (!map.hasLayer(window.plugin.flyLinks.linksLayerGroup) ||
        !map.hasLayer(window.plugin.flyLinks.fieldsLayerGroup)) {
      window.plugin.flyLinks.updateLayer();
    }
  }
  window.plugin.flyLinks.linksLayerGroup.on('add', update);
  window.plugin.flyLinks.fieldsLayerGroup.on('add', update);

  window.addHook('mapDataRefreshEnd', function(e) {
    window.plugin.flyLinks.updateLayer();
  });

  window.map.on('moveend', function() {
    window.plugin.flyLinks.updateLayer();
  });

  if (window.plugin.drawTools && window.plugin.drawTools.filterEvents) {
    window.plugin.drawTools.filterEvents.on('changed', window.plugin.flyLinks.updateLayer);
  }

  window.addLayerGroup('Fly links', window.plugin.flyLinks.linksLayerGroup, false);
  window.addLayerGroup('Fly fields', window.plugin.flyLinks.fieldsLayerGroup, false);
};
var setup = window.plugin.flyLinks.setup;
<|endoftext|>


<commit_before>var childProcess  = require('child_process'),
    fs            = require('fs'),
    util          = require('util'),
    logger        = require('log4js').getLogger(),
    _             = require('underscore'),
    path          = require('path');

exports.init = function(config, cimpler) {
   var paths = config.repoPaths;
   paths = Array.isArray(paths) ? paths : [paths]
   paths.forEach(function(repoPath) {
      var consumer = buildConsumer(config, cimpler, repoPath);
      cimpler.consumeBuild(consumer, config.repoRegex);
   });

   cimpler.on('buildAborted', function(build) {
      if (build._control && build._control.abortGitBuild) {
         build.aborted = true;
         build._control.abortGitBuild();
      }
   });
}

function buildConsumer(config, cimpler, repoPath) {
   return function(build, started, finished) {
      var startedAt = Date.now();
      var execOptions = {
         env: {
            BUILD_REPO:   build.repo,
            BUILD_COMMIT: build.commit,
            BUILD_BRANCH: build.branch,
            BUILD_STATUS: build.status
         },
         timeout: config.timeout || 0
      },
      killChildrenOnExit = "trap '[ $(jobs -p) ] && kill $(jobs -p)' EXIT",
      cdToRepo = 'set -v; set -x; cd "' + repoPath + '"';

      for(var key in process.env) {
         execOptions.env[key] = process.env[key];
      }

      if (!repoPath) {
         throw new Error("Missing the 'path' option of git-build config");
      }

      logger.info(id(build) + " -- Building with git");

      startFetch();

      // This needs to be delayed until we have a commit hash
      function writeLogHeader() {
         logFile().write(
            "----------------------------------------------\n" +
            " Cimpler build started at: " + Date() + "\n" +
            "----------------------------------------------\n");
      }

      function startFetch() {
         var commands = '(cd "' + repoPath + '" && ' +
            "git fetch --quiet && " +
            "git rev-parse origin/" + build.branch + ") 2>&1";

         var fetching = exec(commands, function(err, stdout) {
            if (err) {
               var failed = "Fetch Failed";
               build.status = 'error';
               build.error = failed;
               build.code = err.code;
               stdout += "\n\n" + failed;
               logger.warn(id(build) + " -- " + failed);
               writeLogHeader()
               logFile().write(stdout);
               finishedBuild();
            } else {
               if (!build.commit) {
                  // Update the build commit and the environment variable if
                  // we didn't know what the build commit was previously.
                  build.commit = stdout.trim();
                  execOptions.env.BUILD_COMMIT = build.commit;
               }
               writeLogHeader();
               startMerge();
            }
         });

         setAbort(build, function() {
            fetching.kill('SIGTERM');
            fetching.stdout.destroy();
            fetching.stderr.destroy();
         });
      }

      function startMerge() {
         var branchToMerge = 'master';
         var regex, mergeBranch;
         if (config.mergeBranchRegexes) {
            for (var i = 0; i < config.mergeBranchRegexes.length; ++i) {
               regex = config.mergeBranchRegexes[i][0];
               mergeBranch = config.mergeBranchRegexes[i][1];
               if (build.branch.match(regex)) {
                  branchToMerge = mergeBranch;
                  break;
               }
            }
         }
         var commands = '(' + cdToRepo + " && " +
            "git reset --hard && " +
            "git clean -ffd && " +
            "git checkout "+build.commit+" && " +
            "git merge origin/"+branchToMerge+" && " +
            "git submodule sync && " +
            "git submodule update --init --recursive ) 2>&1";

         var merging = exec(commands, function(err, stdout) {
            var nextStep, message;

            if (err) {
               message = "Merge Failed";
               nextStep = finishedBuild;
               build.status = 'error';
               build.error = message;
               build.code = err.code;
               stdout += "\n\n" + message;

            } else {
               message = "Merge Successful";
               nextStep = startBuild;
               stdout = message + "\n";

            }

            logger.info(id(build) + " -- " + message);
            logFile().write(stdout);

            nextStep(started);
         });

         setAbort(build, function() {
            merging.kill('SIGTERM');
            merging.stdout.destroy();
            merging.stderr.destroy();
         });
      }

      function startBuild(started) {
         var buildCommand = (build.buildCommand || config.cmd) + " 2>&1";
         var commands = [cdToRepo, buildCommand];
         var commandString = commands.join("; ");

         var proc = exec(commandString, function(err, stdout, stderr) {
            if (err && err.signal) {
               build.status = 'error';
               build.error = err.signal + " - " + err.code;
               build.code = err.code;
            } else {
               build.status = err ? 'failure' : 'success';
               build.code = err ? err.code : 0;
            }
            logger.info(id(build) + " -- Build " + build.status);
            finishedBuild();
         });

         setAbort(build, function() {
            proc.kill('SIGTERM');
            proc.stdout.destroy();
            proc.stderr.destroy();
         });

         /**
          * Use piping instead of shell redirection to capture output
          * in case of a syntax error in cmd
          */
         proc.stdout.pipe(logFile(), {end:false});
         proc.stderr.pipe(logFile(), {end:false});

         // Fire the started event now that our build has a log.
         started();
      }

      function logFilePath(inBuild) {
         if (!config.logs || !config.logs.path)
            return null;

         var logFilename = (inBuild.branch || 'HEAD') + "--" +
                           (inBuild.commit || 'unknown') + "--" +
                           Date.now() + ".log";

         if (config.logs.url) {
            inBuild.logUrl = config.logs.url + logFilename;
            execOptions.env.BUILD_LOG_URL = inBuild.logUrl;
         }

         inBuild.logPath = path.join(config.logs.path, logFilename);
         return inBuild.logPath;
      }

      var logFileStream;
      function logFile() {
         if (logFileStream) {
            return logFileStream;
         }

         var logPath = logFilePath(build);
         if (!logPath) {
            logFileStream = dummyWriteStream();
         }

         return logFileStream  =
            logFileStream ||
            fs.createWriteStream(logPath);
      }

      function finishedBuild() {
         if (build.aborted) {
            build.error = "Build Aborted";
            build.status = 'error';
         }

         var seconds = Math.round((Date.now() - startedAt) / 1000);
         logFile().write(
          "\n-------------------------------------------" +
          "\n Cimpler build finished in " + seconds + " seconds");
         if (build.error) {
            logFile().write("\n Error: " + build.error);
         } else {
            logFile().write("\n Status: " + build.status + " Exit Code: " + build.code);
         }
         // Call 'finished' when end() flushes it's data to disk
         // This is mostly for testing so we *known* that the data has been
         // written.
         logFile().end(
          "\n-------------------------------------------\n",'utf8',finished);
      }

      function exec(cmd, callback) {
         var child, done = false, forceErr, options = _.clone(execOptions);
         if (options.timeout) {
            setTimeout(function() {
               if (done) return;
               forceErr = {signal: "timeout", code: execOptions.timeout};
               child.kill();
            }, options.timeout);
            delete options.timeout;
         }

         cmd = killChildrenOnExit + '; ' + cmd;

         child = childProcess.exec(cmd, execOptions,
            function(err, stdout, stderr) {
               done = true;
               callback(forceErr || err, stdout.toString(), stderr.toString());
            });
         build.pid = child.pid;
         return child;
      }
   };
};

/**
 * Create a dummy write stream that works in node >= 0.8
 */
function dummyWriteStream() {
   var stream = require('stream').Writable || require('stream');
   var devnull = new stream();
   devnull._write = devnull.write = devnull.end =
   function (a,b,callback) {
      callback && callback();
   };
   return devnull;
}

function setAbort(build, callback) {
   if (!build._control) {
      build._control = {};
   }
   build._control.abortGitBuild = function() {
      logger.info(id(build) + " -- Build Aborted");
      callback();
   }
}

function id(inBuild) {
   return inBuild.branch +
      (inBuild.commit ? ' (' + inBuild.commit.substr(0,10) + ')' : '');
}
<commit_msg>Allow setting maxBuffer, Default to 2MB<commit_after>var childProcess  = require('child_process'),
    fs            = require('fs'),
    util          = require('util'),
    logger        = require('log4js').getLogger(),
    _             = require('underscore'),
    path          = require('path');

exports.init = function(config, cimpler) {
   var paths = config.repoPaths;
   paths = Array.isArray(paths) ? paths : [paths]
   paths.forEach(function(repoPath) {
      var consumer = buildConsumer(config, cimpler, repoPath);
      cimpler.consumeBuild(consumer, config.repoRegex);
   });

   cimpler.on('buildAborted', function(build) {
      if (build._control && build._control.abortGitBuild) {
         build.aborted = true;
         build._control.abortGitBuild();
      }
   });
}

function buildConsumer(config, cimpler, repoPath) {
   return function(build, started, finished) {
      var startedAt = Date.now();
      var execOptions = {
         env: {
            BUILD_REPO:   build.repo,
            BUILD_COMMIT: build.commit,
            BUILD_BRANCH: build.branch,
            BUILD_STATUS: build.status
         },
         timeout: config.timeout || 0,
         maxBuffer: config.maxBuffer || 1024 * 1024 * 2
      },
      killChildrenOnExit = "trap '[ $(jobs -p) ] && kill $(jobs -p)' EXIT",
      cdToRepo = 'set -v; set -x; cd "' + repoPath + '"';

      for(var key in process.env) {
         execOptions.env[key] = process.env[key];
      }

      if (!repoPath) {
         throw new Error("Missing the 'path' option of git-build config");
      }

      logger.info(id(build) + " -- Building with git");

      startFetch();

      // This needs to be delayed until we have a commit hash
      function writeLogHeader() {
         logFile().write(
            "----------------------------------------------\n" +
            " Cimpler build started at: " + Date() + "\n" +
            "----------------------------------------------\n");
      }

      function startFetch() {
         var commands = '(cd "' + repoPath + '" && ' +
            "git fetch --quiet && " +
            "git rev-parse origin/" + build.branch + ") 2>&1";

         var fetching = exec(commands, function(err, stdout) {
            if (err) {
               var failed = "Fetch Failed";
               build.status = 'error';
               build.error = failed;
               build.code = err.code;
               stdout += "\n\n" + failed;
               logger.warn(id(build) + " -- " + failed);
               writeLogHeader()
               logFile().write(stdout);
               finishedBuild();
            } else {
               if (!build.commit) {
                  // Update the build commit and the environment variable if
                  // we didn't know what the build commit was previously.
                  build.commit = stdout.trim();
                  execOptions.env.BUILD_COMMIT = build.commit;
               }
               writeLogHeader();
               startMerge();
            }
         });

         setAbort(build, function() {
            fetching.kill('SIGTERM');
            fetching.stdout.destroy();
            fetching.stderr.destroy();
         });
      }

      function startMerge() {
         var branchToMerge = 'master';
         var regex, mergeBranch;
         if (config.mergeBranchRegexes) {
            for (var i = 0; i < config.mergeBranchRegexes.length; ++i) {
               regex = config.mergeBranchRegexes[i][0];
               mergeBranch = config.mergeBranchRegexes[i][1];
               if (build.branch.match(regex)) {
                  branchToMerge = mergeBranch;
                  break;
               }
            }
         }
         var commands = '(' + cdToRepo + " && " +
            "git reset --hard && " +
            "git clean -ffd && " +
            "git checkout "+build.commit+" && " +
            "git merge origin/"+branchToMerge+" && " +
            "git submodule sync && " +
            "git submodule update --init --recursive ) 2>&1";

         var merging = exec(commands, function(err, stdout) {
            var nextStep, message;

            if (err) {
               message = "Merge Failed";
               nextStep = finishedBuild;
               build.status = 'error';
               build.error = message;
               build.code = err.code;
               stdout += "\n\n" + message;

            } else {
               message = "Merge Successful";
               nextStep = startBuild;
               stdout = message + "\n";

            }

            logger.info(id(build) + " -- " + message);
            logFile().write(stdout);

            nextStep(started);
         });

         setAbort(build, function() {
            merging.kill('SIGTERM');
            merging.stdout.destroy();
            merging.stderr.destroy();
         });
      }

      function startBuild(started) {
         var buildCommand = (build.buildCommand || config.cmd) + " 2>&1";
         var commands = [cdToRepo, buildCommand];
         var commandString = commands.join("; ");

         var proc = exec(commandString, function(err, stdout, stderr) {
            if (err && err.signal) {
               build.status = 'error';
               build.error = err.signal + " - " + err.code;
               build.code = err.code;
            } else {
               build.status = err ? 'failure' : 'success';
               build.code = err ? err.code : 0;
            }
            logger.info(id(build) + " -- Build " + build.status);
            finishedBuild();
         });

         setAbort(build, function() {
            proc.kill('SIGTERM');
            proc.stdout.destroy();
            proc.stderr.destroy();
         });

         /**
          * Use piping instead of shell redirection to capture output
          * in case of a syntax error in cmd
          */
         proc.stdout.pipe(logFile(), {end:false});
         proc.stderr.pipe(logFile(), {end:false});

         // Fire the started event now that our build has a log.
         started();
      }

      function logFilePath(inBuild) {
         if (!config.logs || !config.logs.path)
            return null;

         var logFilename = (inBuild.branch || 'HEAD') + "--" +
                           (inBuild.commit || 'unknown') + "--" +
                           Date.now() + ".log";

         if (config.logs.url) {
            inBuild.logUrl = config.logs.url + logFilename;
            execOptions.env.BUILD_LOG_URL = inBuild.logUrl;
         }

         inBuild.logPath = path.join(config.logs.path, logFilename);
         return inBuild.logPath;
      }

      var logFileStream;
      function logFile() {
         if (logFileStream) {
            return logFileStream;
         }

         var logPath = logFilePath(build);
         if (!logPath) {
            logFileStream = dummyWriteStream();
         }

         return logFileStream  =
            logFileStream ||
            fs.createWriteStream(logPath);
      }

      function finishedBuild() {
         if (build.aborted) {
            build.error = "Build Aborted";
            build.status = 'error';
         }

         var seconds = Math.round((Date.now() - startedAt) / 1000);
         logFile().write(
          "\n-------------------------------------------" +
          "\n Cimpler build finished in " + seconds + " seconds");
         if (build.error) {
            logFile().write("\n Error: " + build.error);
         } else {
            logFile().write("\n Status: " + build.status + " Exit Code: " + build.code);
         }
         // Call 'finished' when end() flushes it's data to disk
         // This is mostly for testing so we *known* that the data has been
         // written.
         logFile().end(
          "\n-------------------------------------------\n",'utf8',finished);
      }

      function exec(cmd, callback) {
         var child, done = false, forceErr, options = _.clone(execOptions);
         if (options.timeout) {
            setTimeout(function() {
               if (done) return;
               forceErr = {signal: "timeout", code: execOptions.timeout};
               child.kill();
            }, options.timeout);
            delete options.timeout;
         }

         cmd = killChildrenOnExit + '; ' + cmd;

         child = childProcess.exec(cmd, execOptions,
            function(err, stdout, stderr) {
               done = true;
               callback(forceErr || err, stdout.toString(), stderr.toString());
            });
         build.pid = child.pid;
         return child;
      }
   };
};

/**
 * Create a dummy write stream that works in node >= 0.8
 */
function dummyWriteStream() {
   var stream = require('stream').Writable || require('stream');
   var devnull = new stream();
   devnull._write = devnull.write = devnull.end =
   function (a,b,callback) {
      callback && callback();
   };
   return devnull;
}

function setAbort(build, callback) {
   if (!build._control) {
      build._control = {};
   }
   build._control.abortGitBuild = function() {
      logger.info(id(build) + " -- Build Aborted");
      callback();
   }
}

function id(inBuild) {
   return inBuild.branch +
      (inBuild.commit ? ' (' + inBuild.commit.substr(0,10) + ')' : '');
}
<|endoftext|>


<commit_before>/*
 * Copyright 2006 Matthew Eernisse (mde@fleegix.org)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
*/
if (typeof fleegix == 'undefined') { var fleegix = {}; }
fleegix.i18n = new function () {
  this.localizedStrings = {};
  this.getText = function () {
    var args = Array.prototype.slice.apply(arguments);
    var key = args.shift();
    var str = this.localizedStrings[key] || "[[" + key + "]]";
    for (var i = 0; i < args.length; i++){
        str = str.replace(new RegExp('\\{' + i + '\\}', 'g'), args[i]);
    }
    return str;
  };
  this.setLocalizedStrings = function (obj) {
    this.localizedStrings = obj || {};
  };
};

<commit_msg>Created inner private var to deal with scoping issue creating "_" shortcut for getText method.<commit_after>/*
 * Copyright 2006 Matthew Eernisse (mde@fleegix.org)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
*/
if (typeof fleegix == 'undefined') { var fleegix = {}; }
fleegix.i18n = new function () {
  var _this = this;
  this.localizedStrings = {};
  this.getText = function () {
    var args = Array.prototype.slice.apply(arguments);
    var key = args.shift();
    var str = _this.localizedStrings[key] || "[[" + key + "]]";
    for (var i = 0; i < args.length; i++){
        str = str.replace(new RegExp('\\{' + i + '\\}', 'g'), args[i]);
    }
    return str;
  };
  this.setLocalizedStrings = function (obj) {
    this.localizedStrings = obj || {};
  };
};

<|endoftext|>


<commit_before>var Q = require('q');
var Store = require('./store');

var SimpleContainer = module.exports.SimpleContainer =
  function (parentContainer) {
    this.parent = parentContainer;
    this.store = new Store(parentContainer && parentContainer.store);
  };

var proto = SimpleContainer.prototype;

proto.resolve = proto.get = function(name) {
  if(name instanceof Array) {
    var plist = [];
    var self = this;
    name.forEach(function(n) {
      plist.push(self.resolve(n));
    });

    return Q.all(plist);
  } else {
    var data = this.store[name];
    if(data) {
      return Q.resolve(data.value);
    }
  }

  return Q.reject(new Error("Can not resolve service by name '" + name + "'."));
};

function merge(dest, source) {
  for (var property in source) {
    dest[property] = source[property];
  }
  return dest;
}

proto.tryResolve = function(name) {
  var result = {};
  if(name instanceof Array) {
    var plist = [];
    var self = this;
    name.forEach(function(n) {
      plist.push(self.tryResolve(n));
    });

    return Q.all(plist).then(function(deps) {
      var ret = {};
      deps.forEach(function(dep) {
        merge(ret, dep);
      });
      return ret;
    });
  } else {
    var data = this.store[name];
    if(data) {
      result[name] = data.value;
    }
  }

  return Q.resolve(result);
};

proto.bindConstant = function(name, value) {
  this.store[name] = {
    value: value
  };
};

var getNames = function(fn){
  return fn.toString().match(/\((.*?)\)/)[1].match(/[\w]+/g) || [];
};

proto.invoke = function(thisObj, invokeTarget, locals) {
  if(typeof thisObj === 'function') {
    locals = invokeTarget || null;
    invokeTarget = thisObj;
    thisObj = null;
  }
  if(typeof invokeTarget !== 'function') {
    throw new Error('invokeTarget must be a function.');
  }
  var names = getNames(invokeTarget);

  var container = this;
  if(locals) {
    container = new SimpleContainer(container);
    Object.keys(locals).forEach(function(name) {
      container.bindConstant(name, locals[name]);
    });
  }
  return container.resolve(names).then(function(targetDeps) {
    return invokeTarget.apply(thisObj, targetDeps);
  });
};
<commit_msg>* Extract resolveCore.<commit_after>var Q = require('q');
var Store = require('./store');

var EMPTY = Object.create(null);

var SimpleContainer = module.exports.SimpleContainer =
  function (parentContainer) {
    this.parent = parentContainer;
    this.store = new Store(parentContainer && parentContainer.store);
  };

var proto = SimpleContainer.prototype;

function resolveCore(self, name) {
  var metadata = self.store[name];
  if(!metadata) {
    return Q.resolve(EMPTY);
  }
  if(metadata.hasOwnProperty('value')) {
    return Q.resolve(metadata.value);
  }

  return Q.resolve(EMPTY);
}

proto.resolve = proto.get = function(name) {
  if(name instanceof Array) {
    var plist = [];
    var self = this;
    name.forEach(function(n) {
      plist.push(self.resolve(n));
    });

    return Q.all(plist);
  } else {
    return resolveCore(this, name).then(function(ret) {
      if(ret === EMPTY) {
        return Q.reject(new Error('Can not find binding "' + name + '".'));
      }
      return ret;
    });
  }

  return Q.reject(new Error("Can not resolve service by name '" + name + "'."));
};

function merge(dest, source) {
  for (var property in source) {
    dest[property] = source[property];
  }
  return dest;
}

proto.tryResolve = function(name) {
  var result = {};
  if(name instanceof Array) {
    var plist = [];
    var self = this;
    name.forEach(function(n) {
      plist.push(self.tryResolve(n));
    });

    return Q.all(plist).then(function(deps) {
      var ret = {};
      deps.forEach(function(dep) {
        merge(ret, dep);
      });
      return ret;
    });
  } else {
    return resolveCore(this, name).then(function(ret) {
      if(ret !== EMPTY) {
        result[name] = ret;
      }
      return result;
    });
  }

  return Q.resolve(result);
};

proto.bindConstant = function(name, value) {
  this.store[name] = {
    value: value
  };
};

var getNames = function(fn){
  return fn.toString().match(/\((.*?)\)/)[1].match(/[\w]+/g) || [];
};

proto.invoke = function(thisObj, invokeTarget, locals) {
  if(typeof thisObj === 'function') {
    locals = invokeTarget || null;
    invokeTarget = thisObj;
    thisObj = null;
  }
  if(typeof invokeTarget !== 'function') {
    throw new Error('invokeTarget must be a function.');
  }
  var names = getNames(invokeTarget);

  var container = this;
  if(locals) {
    container = new SimpleContainer(container);
    Object.keys(locals).forEach(function(name) {
      container.bindConstant(name, locals[name]);
    });
  }
  return container.resolve(names).then(function(targetDeps) {
    return invokeTarget.apply(thisObj, targetDeps);
  });
};
<|endoftext|>


<commit_before>'use strict';

var BaseModel = require('model-toolkit').BaseModel;
var PackingItemModel = require("./packing-item");

module.exports = class Packing extends BaseModel {
    constructor(source) {
        super('packing', '1.0.0');

        // Define properties.
        this.code = '';
        this.productionOrderId = {};
        this.productionOrderNo = "";
        this.orderType = "";
        this.salesContractNo = "";
        this.designCode = "";
        this.designNumber = "";

        //Buyer detail
        this.buyerId = {};
        this.buyerCode = "";
        this.buyerName = "";
        this.buyerAddress = "";
        this.buyerType = "";

        this.date = new Date();
        this.packingUom = "";
        this.colorCode = "";
        this.colorName = "";
        this.colorType = "";

        this.materialConstructionFinishId = {};
        this.materialConstructionFinishName = "";

        this.materialWidthFinish = "";

        this.construction = "";

        this.motif = "";
        this.items = [];
        this.status = "";
        this.accepted = false;
        this.declined = false;
        this.copy(source);
        this.items = this.items.map(item => new PackingItemModel(item));
    }
};
<commit_msg>update packing material<commit_after>'use strict';

var BaseModel = require('model-toolkit').BaseModel;
var PackingItemModel = require("./packing-item");

module.exports = class Packing extends BaseModel {
    constructor(source) {
        super('packing', '1.0.0');

        // Define properties.
        this.code = '';
        this.productionOrderId = {};
        this.productionOrderNo = "";
        this.orderType = "";
        this.salesContractNo = "";
        this.designCode = "";
        this.designNumber = "";

        //Buyer detail
        this.buyerId = {};
        this.buyerCode = "";
        this.buyerName = "";
        this.buyerAddress = "";
        this.buyerType = "";

        this.date = new Date();
        this.packingUom = "";
        this.colorCode = "";
        this.colorName = "";
        this.colorType = "";

        this.materialConstructionFinishId = {};
        this.materialConstructionFinishName = "";

        this.material = "";

        this.materialWidthFinish = "";

        this.construction = "";

        this.motif = "";
        this.items = [];
        this.status = "";
        this.accepted = false;
        this.declined = false;
        this.copy(source);
        this.items = this.items.map(item => new PackingItemModel(item));
    }
};
<|endoftext|>


<commit_before>var _ = require('lodash');
var Promise = require('bluebird');
var fs = Promise.promisifyAll(require('fs'));
var debug = require('debug')('lib:parse');
var moment = require('moment');
var nconf = require('nconf');

const xpath = require('xpath');
const xmldom = require('xmldom');
const JSDOM = require('jsdom').JSDOM;

var mongo = require('./mongo');
var sequence = require('../parsers/components/utils/sequence');

nconf.argv().env().file({ file: "config/collector.json" });

function initialize(impression, repeat) {

    if(_.isUndefined(impression.id))
        throw new Error("impression missing");

    return mongo
        .readOne(nconf.get('schema').metadata, { id: impression.id })
        .then(function(i) {

            if( _.get(i, 'id') === impression.id && !repeat) {
                debug("metadata [%s] already exists: skipping", i.id);
                return null;
            }

            if( _.get(i, 'id') === impression.id && repeat)
                debug("metadata [%s] exists, but repeat is requested", i.id);

            const xmlerr = { warning: 0, error: 0, fatal: 0 };
            var domOptions = {
                  errorHandler:{
                      warning: function() { xmlerr.warning += 1; },
                      error: function() { xmlerr.error += 1; },
                      fatalError: function() { xmlerr.fatal += 1;  },
                  }
            };

            /* this is the envelope with get appended the metadata from the various parsers */
            return {
                impression,
                xmlerr,
                xpath,
                jsdom: new JSDOM(impression.html).window.document,
                dom: new xmldom.DOMParser(domOptions).parseFromString(impression.html),
            };
        });
}

function save(metadata) {
    /* this append and updated a metadata entry on the dedicated collection, the metadata.id === htmls.it */
    debug("Saving %d metadata objects", _.size(metadata));
    if(_.size(metadata))
        return mongo
            .writeMany(nconf.get('schema').metadata, metadata);
}

function stats(metadata) {
    if(!_.size(metadata))
        return [];

    debug("saving stats for %d entries", _.size(metadata));

    const stats = _.map(metadata, function(e) {
        /* stats are generated by components/utils/stats.js,
         * all the stats are saved, even if duplicated */
        var r = e.stats;
        r.when = new Date();
        r.id = e.id;
        return r;
    });

    const cleanedMetadata = _.map(metadata, function(e) {
        return _.omit(e, ['stats', 'errors']);
    });

    return mongo
        .writeMany(nconf.get('schema').parserstats, stats)
        .return(cleanedMetadata);
}

function mark(metadata) {
    /* this is the second function called, when the html is marked as processed,
     * probably the condition `false` should be considered worthy */

    if(_.size(metadata))
        debug("mark the htmls (%d) as processed", _.size(metadata));
    return Promise.map(metadata, function(e) {
        return mongo
            .readOne(nconf.get('schema').htmls, { id: e.id })
            .then(function(existing) {
                existing.processed = true;
                return mongo
                    .updateOne(nconf.get('schema').htmls, { _id: existing._id }, existing);
            });
    }, { concurrency: 5});
}

function processImpression(input) {
    /* this is the function processing the parsers
     * it is call of every .id which should be analyzed */

    return new Promise(function() {
        return sequence(input);
    })
    .catch(function(error) {
        debug("[E] Unmanaged error in parser sequence: %s", error.message);
        return null;
    })
    .then(function(results) {
        const removef = ['dom', 'jsdom', 'xpath', 'xmlerr', 'impression'];
        const impressionFields = ['id', 'timelineId', 'userId', 'impressionOrder', 'impressionTime'];

        let r = _.omit(results, removef);
        _.extend(r, _.pick(results.impression, impressionFields));

        /* log some stats about the success ratio, or compute the
         * summary which would be saved later by 'save' */
        return r;
    })
    .tap(function(metadata) {
        debug("→ http://localhost:8000/revision/%s", metadata.id);
        debug("→ %s", JSON.stringify(metadata, undefined, 2));
    })
    .catch(function(error) {
        debug("[E] %s", error.message);
        return null;
    });
};

function bytimeline(targetTmlnId) {

    debug("addressing timeline %s", targetTmlnId);
    return mongo
        .read(nconf.get('schema').htmls, { timelineId: targetTmlnId }, { startTime: -1 })
        .tap(function(htmls) {
            debug("Found %d htmls linked to the timeline", _.size(htmls));
        })
        .map(function(html) {
            return mongo
                .readOne(nconf.get('schema').impressions, { id: html.impressionId })
                .then(function(impression) {
                    _.unset(impression, 'id');
                    _.unset(impression, 'htmlId');
                    return _.merge(html, impression);
                });
        }, { concurrency: 10 })
        .then(_.compact)
        .map(function(e) {
            return initialize(e, nconf.get('repeat') || false)
                .then(function(impression) {
                    if(!_.isNull(impression))
                        return processImpression(impression);
                });
        }, { concurrency: 10})
        .then(_.compact)
        .then(stats)
        .tap(mark)
        .tap(save);
}

module.exports = {
    processImpression: processImpression,
    initialize: initialize,
    save: save,
    mark: mark,
    stats: stats,
    bytimeline: bytimeline,
};
<commit_msg>implemented a stronger debug for timeline duration and repetition check<commit_after>var _ = require('lodash');
var Promise = require('bluebird');
var fs = Promise.promisifyAll(require('fs'));
var debug = require('debug')('lib:parse');
var moment = require('moment');
var nconf = require('nconf');

const xpath = require('xpath');
const xmldom = require('xmldom');
const JSDOM = require('jsdom').JSDOM;

var mongo = require('./mongo');
var sequence = require('../parsers/components/utils/sequence');

nconf.argv().env().file({ file: "config/collector.json" });

function initialize(impression, repeat) {

    if(_.isUndefined(impression.id))
        throw new Error("impression missing");

    return mongo
        .readOne(nconf.get('schema').metadata, { id: impression.id })
        .then(function(i) {

            if( _.get(i, 'id') === impression.id && !repeat) {
                debug("metadata [%s] already exists: skipping", i.id);
                return null;
            }

            if( _.get(i, 'id') === impression.id && repeat)
                debug("metadata [%s] exists, but repeat is requested", i.id);

            const xmlerr = { warning: 0, error: 0, fatal: 0 };
            var domOptions = {
                  errorHandler:{
                      warning: function() { xmlerr.warning += 1; },
                      error: function() { xmlerr.error += 1; },
                      fatalError: function() { xmlerr.fatal += 1;  },
                  }
            };

            /* this is the envelope with get appended the metadata from the various parsers */
            return {
                impression,
                xmlerr,
                xpath,
                jsdom: new JSDOM(impression.html).window.document,
                dom: new xmldom.DOMParser(domOptions).parseFromString(impression.html),
            };
        });
}

function save(metadata) {
    /* this append and updated a metadata entry on the dedicated collection, the metadata.id === htmls.it */
    debug("Saving %d metadata objects", _.size(metadata));
    if(_.size(metadata))
        return mongo
            .writeMany(nconf.get('schema').metadata, metadata);
}

function stats(metadata) {
    if(!_.size(metadata))
        return [];

    debug("saving stats for %d entries", _.size(metadata));

    const stats = _.map(metadata, function(e) {
        /* stats are generated by components/utils/stats.js,
         * all the stats are saved, even if duplicated */
        var r = e.stats;
        r.when = new Date();
        r.id = e.id;
        return r;
    });

    const cleanedMetadata = _.map(metadata, function(e) {
        return _.omit(e, ['stats', 'errors']);
    });

    return mongo
        .writeMany(nconf.get('schema').parserstats, stats)
        .return(cleanedMetadata);
}

function mark(metadata) {
    /* this is the second function called, when the html is marked as processed,
     * probably the condition `false` should be considered worthy */

    if(_.size(metadata))
        debug("mark the htmls (%d) as processed", _.size(metadata));
    return Promise.map(metadata, function(e) {
        return mongo
            .readOne(nconf.get('schema').htmls, { id: e.id })
            .then(function(existing) {
                existing.processed = true;
                return mongo
                    .updateOne(nconf.get('schema').htmls, { _id: existing._id }, existing);
            });
    }, { concurrency: 5});
}

function processImpression(input) {
    /* this is the function processing the parsers
     * it is call of every .id which should be analyzed */

    return new Promise(function() {
        return sequence(input);
    })
    .catch(function(error) {
        debug("[E] Unmanaged error in parser sequence: %s", error.message);
        return null;
    })
    .then(function(results) {
        const removef = ['dom', 'jsdom', 'xpath', 'xmlerr', 'impression'];
        const impressionFields = ['id', 'timelineId', 'userId', 'impressionOrder', 'impressionTime'];

        let r = _.omit(results, removef);
        _.extend(r, _.pick(results.impression, impressionFields));

        /* log some stats about the success ratio, or compute the
         * summary which would be saved later by 'save' */
        return r;
    })
    .tap(function(metadata) {
        debug("→ http://localhost:8000/revision/%s", metadata.id);
        debug("→ %s", JSON.stringify(metadata, undefined, 2));
    })
    .catch(function(error) {
        debug("[E] %s", error.message);
        return null;
    });
};

function bytimeline(targetTmlnId) {

    debug("addressing timeline %s", targetTmlnId);
    const repeat = nconf.get('repeat') || false;
    const htmlfilter = repeat ?
        { timelineId: targetTmlnId } :
        { timelineId: targetTmlnId, processed: { $exists: false } };

    return mongo
        .read(nconf.get('schema').htmls, htmlfilter)
        .map(function(html) {
            return mongo
                .readOne(nconf.get('schema').impressions, { id: html.impressionId })
                .then(function(impression) {
                    _.unset(impression, 'id');
                    _.unset(impression, 'htmlId');
                    return _.merge(html, impression);
                });
        }, { concurrency: 10 })
        .then(_.compact)
        .then(function(impressions) {
            return _.orderBy(impressions, { impressionOrder: -1 });
        })
        .tap(function(impressions) {
            debug("Found %d impressions associated: %d [%s] - %d [%s] (%s)",
                _.size(impressions),
                _.first(impressions).impressionOrder,
                moment(_.first(impressions).impressionTime).format("DD/MMM HH:mm"),
                _.last(impressions).impressionOrder,
                moment(_.last(impressions).impressionTime).format("DD/MMM HH:mm"),
                moment.duration( moment(_.last(impressions).impressionTime) - moment(_.first(impressions).impressionTime )).humanize()
            );
        })
        .map(function(e) {
            return initialize(e, repeat)
                .then(function(impression) {
                    if(!_.isNull(impression))
                        return processImpression(impression);
                });
        }, { concurrency: 10})
        .then(_.compact)
        .then(stats)
        .tap(mark)
        .tap(save);
}

module.exports = {
    processImpression: processImpression,
    initialize: initialize,
    save: save,
    mark: mark,
    stats: stats,
    bytimeline: bytimeline,
};
<|endoftext|>


<commit_before><commit_msg>Add a test for toCubic function<commit_after>import { M, L, H, V, Q, T, C, A } from "../points"
import toCubic from "./index"

test("should convert the line into a cubic curve", () => {
  const prev = M(0, 0)
  const point = L(100, 0)
  const test = toCubic(prev, point)
  const expected = C(0, 0, 100, 0, 100, 0)

  expect(test).toEqual(expected)
})

test("should convert the horizontal line into a cubic curve", () => {
  const prev = M(0, 0)
  const point = H(100, prev)
  const test = toCubic(prev, point)
  const expected = C(0, 0, 100, 0, 100, 0)

  expect(test).toEqual(expected)
})

test("should convert the vertical line into a cubic curve", () => {
  const prev = M(0, 0)
  const point = V(100, prev)
  const test = toCubic(prev, point)
  const expected = C(0, 0, 0, 100, 0, 100)

  expect(test).toEqual(expected)
})

test("should convert the quadratic curve into a cubic curve", () => {
  const prev = M(0, 0)
  const point = Q(75, 25, 100, 100)
  const test = toCubic(prev, point)
  const expected = C(50, 16.666666666666664, 83.33333333333333, 49.99999999999999, 100, 100)

  expect(test).toEqual(expected)
})

test("should convert the computed quadratic curve into a cubic curve", () => {
  const prev = M(0, 0)
  const point = T(200, 0, Q(0, 100, 100, 100))
  const test = toCubic(prev, point)
  const expected = C(133.33333333333331, 66.66666666666666, 199.99999999999997, 66.66666666666666, 200, 0)

  expect(test).toEqual(expected)
})

test("should convert the arc into a cubic curve", () => {
  const prev = M(0, 0)
  const point = A(50, 50, 0, 0, 1, 0, 100)
  const test = toCubic(prev, point)
  const expected = [
    C(38.49001794597504, 2.3568338638331258e-15, 62.54627916220945, 41.66666666666667, 43.30127018922194, 75),
    C(34.36963044151785, 90.47005383792515, 17.863279495408182, 100, 0, 100),
  ]

  expect(test).toEqual(expected)
})

test("should return the exact same point if it's already a cubic curve", () => {
  const prev = M(0, 0)
  const point = C(25, 25, 25, 75, 0, 100)
  const test = toCubic(prev, point)
  const expected = point

  expect(test).toEqual(expected)
})
<|endoftext|>


<commit_before>'use strict';
var core 		   = require('./core');

var _ = {
 map:         require('lodash/map'),
 groupBy:     require('lodash/groupBy'),
 pick:        require('lodash/pick'),
 values:      require('lodash/values'),
 keys:        require('lodash/keys'),
 forOwn:      require('lodash/forOwn'),
 omit:        require('lodash/omit'),
 extend:      require('lodash/extend'),
 has:         require('lodash/has'),
 get:         require('lodash/get'),
 uniq:        require('lodash/uniq'),
 uniqBy:      require('lodash/uniqBy'),
 isObject:    require('lodash/isObject'),
 isString:    require('lodash/isString'),
 isFunction:  require('lodash/isFunction'),
 isArray:     require('lodash/isArray'),
 startsWith:  require('lodash/startsWith'),
 isUndefined: require('lodash/isUndefined'),
 filter:      require('lodash/filter'),
 isNumber:    require('lodash/isNumber'),
 reduce:      require('lodash/reduce'),
 isBoolean:   require('lodash/isBoolean')
};

var Operations = {
  group: 'groupBy',
  index: 'indexBy',
  mirror: 'mirror',
  set: 'set'
};

var defined = {};

function parse(provider, scheme){
  var json = {};
  _.forOwn(scheme, function(subScheme, schemeKey) {
    if(_.isFunction(subScheme) === false){
      if(isOperation(schemeKey)){
        var operation = retrieveOperation(schemeKey);
        var parsedOperation = parseSchemeOperation(provider, subScheme, operation);
        if(_.isUndefined(operation.dest)){
          json = parsedOperation;
        } else {
          json[operation.dest] = parsedOperation;
        }
      } else if(_.isString(subScheme)){
        var parsedString = parseSchemeString(provider, subScheme);
        if(parsedString || _.isNumber(parsedString) || _.isBoolean(parsedString)){
          json[schemeKey] = parsedString;
        }
      } else if(_.isNumber(subScheme)){
        var parsedString = parseSchemeNumber(provider, subScheme);
        if(parsedString || _.isNumber(parsedString)){
          json[schemeKey] = parsedString;
        }
      } else if(_.isObject(subScheme)){
        json[schemeKey] = parse(provider, subScheme);
      }
    }
  });
  return json;
};

function withoutEmptyObject(collection){
  return _.filter(collection, function(value){
    if(_.isObject(value)){
      return _.keys(value).length !== 0;
    }
    return true;
  });
};

function parseSchemeOperation(provider, subScheme, operation){
  var parsedCollection = executeOperation(operation, provider, subScheme);
  if(_.isArray(parsedCollection)){
    parsedCollection = withoutEmptyObject(parsedCollection);
  }
  return parsedCollection;
};

function parseSchemeString(provider, schemeString){
  if(_.isArray(provider)) {
    if(provider.length === 0){
      return [];
    }
    return _.get(provider[0], schemeString);
  } else {
    return _.get(provider, schemeString);
  }
};

function parseSchemeNumber(provider, schemeString){
  if(_.isArray(provider) && provider.length === 0){
    return [];
  }

  if(_.isArray(provider) && _.isArray(provider[0])) {
    return _.get(provider[0], schemeString);
  } else {
    return _.get(provider, schemeString);
  }
};

function executeOperation(operation, provider, scheme){
  if(operation.name === 'group'){
    var groups = _.groupBy(provider, operation.args);
    var schemeHead = _.pick(scheme, _.isString), toParseScheme, head;
    var toParseScheme = _.omit(scheme, _.isString);

    return _.reduce(groups, function(accu, group, index){
      head = core.mirror(group[0], schemeHead);

      if(group[0][operation.args] || _.isNumber(group[0][operation.args])){

      } else {
        return accu;
      }

      if(_.keys(toParseScheme)['length'] === 0){
        accu.push(head);
      } else {
        accu.push(_.extend(head, parse(group, toParseScheme)));
      }
      return accu;
    }, []);
  }

  if(operation.name === 'mirror'){
    var uniques = _.uniqBy(provider, operation.args);
    return _.map(uniques, function(item){
      return parse(item, scheme);
    });
  }

  if(operation.name === 'set'){
    return scheme;
  }

  if(_.has(defined, operation.name)){
    var modifiedProvider = defined[operation.name](provider);
    return parse(modifiedProvider, scheme);
  }
};

function isOperation(text){
  if(_.startsWith(text, '$')){
    var name = getOperationName(text);
    return _.has(core, Operations[name]) || _.has(defined, name);
  }
};

function retrieveOperation(text){
  if(isOperation(text)){
    return {
      name: getOperationName(text),
      dest: getOperationDestinyKey(text),
      args: getOperationArguments(text)
    }
  }
};

function getOperationName(text){
  return text.match(/\$(\w+)/)[1];
};

function getOperationDestinyKey(text){
  var segment = text.match(/\[(\w+)\]/);
  if(segment) return segment[1];
};

function getOperationArguments(text){
  var args = text.match(/\((\w+)\)/);
  if(args) return args[1];
};

exports.parse = function(provider, scheme){
  return parse(provider, scheme);
};

exports.define = function(name, operation){
  defined[name] = operation;
};
<commit_msg>Support for Plugins and new interface for define()<commit_after>'use strict';

var core = require('./core');

var _ = {
  map:         require('lodash/map'),
  groupBy:     require('lodash/groupBy'),
  pick:        require('lodash/pick'),
  values:      require('lodash/values'),
  keys:        require('lodash/keys'),
  forOwn:      require('lodash/forOwn'),
  omit:        require('lodash/omit'),
  extend:      require('lodash/extend'),
  has:         require('lodash/has'),
  get:         require('lodash/get'),
  uniq:        require('lodash/uniq'),
  uniqBy:      require('lodash/uniqBy'),
  isObject:    require('lodash/isObject'),
  isString:    require('lodash/isString'),
  isFunction:  require('lodash/isFunction'),
  isArray:     require('lodash/isArray'),
  startsWith:  require('lodash/startsWith'),
  isUndefined: require('lodash/isUndefined'),
  filter:      require('lodash/filter'),
  isNumber:    require('lodash/isNumber'),
  reduce:      require('lodash/reduce'),
  isBoolean:   require('lodash/isBoolean')
};

var Operations = {
  group: 'groupBy',
  index: 'indexBy',
  mirror: 'mirror',
  set: 'set'
};

var defined = {
  group:       require('./plugins/group.js'),
  mirror:      require('./plugins/mirror.js'),
  set:         require('./plugins/set.js')
};

function parse(provider, scheme){
  var json = {};
  _.forOwn(scheme, function(subScheme, schemeKey) {
    if(_.isFunction(subScheme) === false){
      if(isOperation(schemeKey)){
        var operation = retrieveOperation(schemeKey);
        var parsedOperation = parseSchemeOperation(provider, subScheme, operation);
        if(_.isUndefined(operation.dest)){
          json = parsedOperation;
        } else {
          json[operation.dest] = parsedOperation;
        }
      } else if(_.isString(subScheme)){
        var parsedString = parseSchemeString(provider, subScheme);
        if(parsedString || _.isNumber(parsedString) || _.isBoolean(parsedString)){
          json[schemeKey] = parsedString;
        }
      } else if(_.isNumber(subScheme)){
        var parsedString = parseSchemeNumber(provider, subScheme);
        if(parsedString || _.isNumber(parsedString)){
          json[schemeKey] = parsedString;
        }
      } else if(_.isObject(subScheme)){
        json[schemeKey] = parse(provider, subScheme);
      }
    }
  });
  return json;
};

function withoutEmptyObject(collection){
  return _.filter(collection, function(value){
    if(_.isObject(value)){
      return _.keys(value).length !== 0;
    }
    return true;
  });
};

function parseSchemeOperation(provider, subScheme, operation){
  var parsedCollection = executeOperation(operation, provider, subScheme);
  if(_.isArray(parsedCollection)){
    parsedCollection = withoutEmptyObject(parsedCollection);
  }
  return parsedCollection;
};

function parseSchemeString(provider, schemeString){
  if(_.isArray(provider)) {
    if(provider.length === 0){
      return [];
    }
    return _.get(provider[0], schemeString);
  } else {
    return _.get(provider, schemeString);
  }
};

function parseSchemeNumber(provider, schemeString){
  if(_.isArray(provider) && provider.length === 0){
    return [];
  }

  if(_.isArray(provider) && _.isArray(provider[0])) {
    return _.get(provider[0], schemeString);
  } else {
    return _.get(provider, schemeString);
  }
};

function executeOperation(operation, provider, scheme){
  if(_.has(defined, operation.name)){
    var helpers = { '_': _, 'parse': parse, 'core': core };
    var result = defined[operation.name](operation, provider, scheme, helpers);
    return result;
  }
};

function isOperation(text){
  if(_.startsWith(text, '$')){
    var name = getOperationName(text);
    return _.has(core, Operations[name]) || _.has(defined, name);
  }
};

function retrieveOperation(text){
  if(isOperation(text)){
    return {
      name: getOperationName(text),
      dest: getOperationDestinyKey(text),
      args: getOperationArguments(text)
    }
  }
};

function getOperationName(text){
  return text.match(/\$(\w+)/)[1];
};

function getOperationDestinyKey(text){
  var segment = text.match(/\[(\w+)\]/);
  if(segment) return segment[1];
};

function getOperationArguments(text){
  var args = text.match(/\((\w+)\)/);
  if(args) return args[1];
};

exports.parse = function(provider, scheme){
  return parse(provider, scheme);
};

exports.define = function(name, operation){
  defined[name] = operation;
};
<|endoftext|>


<commit_before>/**
 * @file Parsing summary and report files.
 *
 * Summary files has three lines: one with header, one with config, one
 * with stats. They were generated by fate-recv.sh. The first two lines were
 * copied straight from uploaded report file. The contents of the third line
 * was calculated by iterating over all tests in report file.
 *
 * @module lib/parse
 */

'use strict'

var fs = require('fs')
  , readline = require('readline')
  , path = require('path')
  , xz = require('xz-pipe')

var util       = require('./util')
  , sort       = require('./sort')
  , cache      = require('./cache')
  , cacheLock  = require('./cache').lock

/**
 * Parses a `header` line in a report file or old-style summary file.
 *
 * @param  {string} line Line to be splitted
 * @return {object|null} The resulting object, or null if an error occurred.
 *
 * @private
 */
function splitHeader (line) {
  var s = line.split(':')
    , header = { version: +s[1], date   : +s[2]
               , slot   :  s[3], rev    :  s[4]
               , status : +s[5], errstr :  s[6]
               , branch :  s[7], comment:  s[8]
               }
  if (header.version === 0) {
    header.branch = 'master'
    header.comment = s[7]
  }
  return header
}

/**
 * Parses a `config` line in a report file or old-style summary file.
 *
 * @param  {string} line Line to be splitted
 * @return {object|null} The resulting object, or null if an error occurred.
 *
 * @private
 */
function splitConfig (line) {
  var c = line.split(':')
  return { arch: c[1], subarch: c[2]
         , cpu : c[3], os     : c[4]
         , cc  : c[5], config : c[6]
         }
}

/**
 * Parses a `stats` line in a old-style summary file.
 *
 * @param  {string} line Line to be splitted
 * @return {object|null} The resulting object, or null if an error occurred.
 *
 * @private
 */
function splitStats (line) {
  var s = line.split(':')
  return { ntests: +s[1]
         , npass : +s[2]
         , nwarn : +s[3]
         }
}

/**
 * This type of object is used for a single test record.
 *
 * @typedef Record
 *
 * @prop {string} name   Name of the test.
 * @prop {string} status Return code of the test.
 * @prop {(string|undefined)} diff   Base64-encoded diff.
 * @prop {(string|undefined)} stderr Base64-encoded stderr output.
 */

/**
 * Parses a record line in a report file.
 *
 * @param  {string} line Line to be splitted
 * @param  {number} errdiff Verbosity of the record. 0 - no stderr or diff.
 *                          1 - stderr and diff only if the status is < 0.
 *                          2 - always include stderr and diff
 * @return {?Record} The resulting {@link Record} object, or null if an error
 *                  occurred.
 */
// TODO add errdiff doc to all the other places too
function splitRec (line, errdiff) {
  errdiff = errdiff === undefined ? 1 : errdiff
  var r = line.split(':')
  switch (errdiff) {
    case 0: return { name: r[0], status: +r[1] }
    case 1: return +r[1] === 0
                 ? { name: r[0], status:  0    }
                 : { name: r[0], status: +r[1], diff: r[2], stderr: r[3] }
    case 2: return { name: r[0], status: +r[1], diff: r[2], stderr: r[3] }
  }
}

/**
 * Summaries are the metadata associated with a FATE run. This function
 * creates a Summary object with header, config, and stats merged into a
 * single object.
 *
 * @param {header} h Header object as returned by splitHeader()
 * @param {config} c Configuration object as returned by splitConfig()
 * @param {stats}  s Stats object as returned by splitStats() or created from
 *                   the report file.
 *
 * @prop {number} version The version of the report file.
 * @prop {Timestamp} date The timestamp.
 * @prop {string} slot    The slot.
 * @prop {string} rev     Revision of the source.
 * @prop {number} status  Final status of the building process.
 * @prop {string} errstr  Error description, if there is an error.
 * @prop {string} comment Any additional comments from the slot owner.
 *
 * @prop {string} arch    The architecture.
 * @prop {string} subarch The variant of the architecture.
 * @prop {string} cpu     The selected CPU.
 * @prop {string} os      The operating system.
 * @prop {string} cc      The compiler.
 * @prop {string} config  The build-time configure flags.
 *
 * @prop {number} ntests  Number of tests run.
 * @prop {number} npass   Number of tests passed.
 * @prop {number} nfail   Number of failed tests
 * @prop {number} nwarn   Number of compiler warnings.
 *
 * @prop {string} rclass  HTML class of the run
 * @prop {string} rtext   HTML text of the run
 *
 * @constructor
 */
module.exports.Summary = function (h, c, s) {
  var prop
  for (prop in h) {
    if (h.hasOwnProperty(prop)) this[prop] = h[prop]
  }
  for (prop in c) {
    if (c.hasOwnProperty(prop)) this[prop] = c[prop]
  }
  for (prop in s) {
    if (s.hasOwnProperty(prop)) this[prop] = s[prop]
  }

  // Postprocessing
  // Calculate nfail property
  this.nfail = this.ntests - this.npass

  // Calculate rclass and rtext for HTML display
  if (this.npass) {
    this.rclass = this.status ? 'warn' : 'pass'
    this.rtext  = this.npass + ' / ' + this.ntests
  } else if (!this.ntests && !this.status) {
    this.rclass = 'pass'
    this.rtext  = 'build only'
  } else {
    this.rclass = 'fail'
    this.rtext  = this.errstr
  }

  // Alias subarch to arch if not supplied
  if (!this.subarch && this.arch) this.subarch = this.arch
}
var Summary = module.exports.Summary

/**
 * This type of callback is used for {@link loadSummary}() that returns an
 * {@link Summary} object.
 *
 * @callback summaryCallback
 * @param {Error}   err The error occurred in the function if any.
 * @param {Summary} rep The resulting {@link Summary} object. (rep = report)
 */

/**
 * Load a summary file.
 *
 * @param slot     {string}         The slot.
 * @param date     {Timestamp}      The timestamp.
 * @param callback {summaryCallback} Callback receiving the Summary object.
 * @private
 */
function loadSummary (slot, date, callback) {
  var header = null, config = null, stats = null
  var repdir = path.join(util.dir, slot, date)

  var summary = fs.createReadStream(
    path.join(repdir, 'summary')
  ).on('error', function (err) {
    err.message = 'Slot "' + slot + '" does not exist.'
    err.status = 404
    return callback(err)
  })

  var err = null

  var lr = new readline.createInterface({
    input: summary
  , terminal: false
  })

  lr.on('line', function handleLine (line) {
    function checkLine (obj, name) {
      if (!obj) {
        var msg = "Could not parse " + name + " correctly:\n" + line
        if (err) {
          err.message += '\n'
          err.message += msg
          // Don't append stack trace as the stack trace
          // is always going to be the same
        } else {
          err = new Error(msg)
          err.status = 500
        }
      }
    }
    switch (line.split(':')[0]) {
    case 'fate':
      if (!header) {
        header = splitHeader(line)
        checkLine(header, 'header')
      }
      break
    case 'config':
      if (!config) {
        config = splitConfig(line)
        checkLine(config, 'config')
      }
      break
    case 'stats':
      if (!stats) {
        stats = splitStats(line)
        checkLine(stats, 'stats')
      }
      break
    }
  }).on('close', function close () {
    callback(err, new Summary(header, config, stats))
  })
}
module.exports.loadSummary = loadSummary

/**
 * This type of callback is used for functions that return an array of
 * {@link Record} object(s).
 *
 * @callback recCallback
 *
 * @param {?Error}          err   The error occurred in the function, if any.
 *                                Beware that sometimes even if there is an
 *                                error this argument might be `null`.
 * @param {?Array.<Record>} recs  The resulting array of {@link Record} object
 *                                or `null` if there is any error.
 */

/**
 * Load the test report of an entry. It loads the xz-compressed report.xz and
 * splits it into {@link Record}s, then calls the callback with the completed
 * data.
 *
 * @param slot     {string}          The slot.
 * @param date     {number}          The timestamp.
 * @param callback {recCallback}     Callback receiving the array of
 *                                   {@link Record} objects.
 */
module.exports.loadReport = function loadReport (slot, date, errdiff, cb) {
  var repFile = path.join(slot, date, 'report.xz')
    , repPath = path.join(util.dir, repFile)
    , cacheName = ['report', slot, date, errdiff].join('_')
    , cacheHasIt = false

  cacheLock.readLock(function (r) {
    if (cache.has(cacheName)) {
      var data = cache.get(cacheName)
      cacheHasIt = true
      cb(null, data)
    }
    r()
  })
  if (cacheHasIt) return

  var fileStream = fs.createReadStream(repPath)
                     .on('error', function noReport (err) {
    err.status = 404
    err.message = 'Report "' + repFile + '" not found.'
    return cb(err)
  })

  // Have to use an empty error handler to prevent uncaught exception in
  // .pipe().
  // Don't explicitly handle errors as it is hard to avoid race conditions
  // and the stream's close event might be emitted before error signal
  // here.
  var stream = fileStream.pipe(xz.d().on('error', function () {}))
  var recs = []
  var lr = new readline.createInterface({
    input: stream
  , terminal: false
  })

  lr.on('line', function handleLine (line) {
    var rec = splitRec(line, errdiff)
    // Don't care to check if the line is indeed a test record.
    // Depend on splitRec()'s judgment.
    if (rec && rec.name != 'fate' && rec.name != 'config') {
      recs[recs.length] = rec
    }
  }).on('close', function close () {
    var data = recs.sort(sort.by('name'))
    cacheLock.writeLock(function (r) {
      cache.set(cacheName, data)
      r()
    })
    cb(null, data)
  })
}

/**
 * Get the slot owner name.
 * The owner of a slot is stored in <fatedir>/<slot>/owner.
 *
 * @param slot {string} The slot.
 * @return     {string} The owner of the slot.
 */
module.exports.getSlotOwner = function (slot, callback) {
  fs.readFile(path.join(util.dir, slot, 'owner') , 'utf8', function (e, d) {
    // ignore error
    // at worst the page will display "owner: null" anyway
    // no need to handle it
    callback(null, d.trim())
  })
}

module.exports.loadLastPass = function (slot, callback) {
  var repdir = path.join(util.dir, slot)

  var lastPass = fs.createReadStream(
    path.join(repdir, 'lastpass')
  ).on('error', function noSlotLastpass (err) {
    err.message = 'Slot "' + slot + '" does not exist.'
    err.status = 404
    return callback(err)
  })

  var recs = []

  var lr = new readline.createInterface({
    input: lastPass
  , terminal: false
  })
  lr.on('line', function handleLine (line) {
    var splittedLine = line.split(':')
    if (splittedLine.length !== 3) return
    recs[splittedLine[0]] = {
      date: Number(splittedLine[1])
    , rev:  splittedLine[2]
    }
  }).on('close', function close () {
    callback(null, recs)
  })
}
<commit_msg>fix error message<commit_after>/**
 * @file Parsing summary and report files.
 *
 * Summary files has three lines: one with header, one with config, one
 * with stats. They were generated by fate-recv.sh. The first two lines were
 * copied straight from uploaded report file. The contents of the third line
 * was calculated by iterating over all tests in report file.
 *
 * @module lib/parse
 */

'use strict'

var fs = require('fs')
  , readline = require('readline')
  , path = require('path')
  , xz = require('xz-pipe')

var util       = require('./util')
  , sort       = require('./sort')
  , cache      = require('./cache')
  , cacheLock  = require('./cache').lock

/**
 * Parses a `header` line in a report file or old-style summary file.
 *
 * @param  {string} line Line to be splitted
 * @return {object|null} The resulting object, or null if an error occurred.
 *
 * @private
 */
function splitHeader (line) {
  var s = line.split(':')
    , header = { version: +s[1], date   : +s[2]
               , slot   :  s[3], rev    :  s[4]
               , status : +s[5], errstr :  s[6]
               , branch :  s[7], comment:  s[8]
               }
  if (header.version === 0) {
    header.branch = 'master'
    header.comment = s[7]
  }
  return header
}

/**
 * Parses a `config` line in a report file or old-style summary file.
 *
 * @param  {string} line Line to be splitted
 * @return {object|null} The resulting object, or null if an error occurred.
 *
 * @private
 */
function splitConfig (line) {
  var c = line.split(':')
  return { arch: c[1], subarch: c[2]
         , cpu : c[3], os     : c[4]
         , cc  : c[5], config : c[6]
         }
}

/**
 * Parses a `stats` line in a old-style summary file.
 *
 * @param  {string} line Line to be splitted
 * @return {object|null} The resulting object, or null if an error occurred.
 *
 * @private
 */
function splitStats (line) {
  var s = line.split(':')
  return { ntests: +s[1]
         , npass : +s[2]
         , nwarn : +s[3]
         }
}

/**
 * This type of object is used for a single test record.
 *
 * @typedef Record
 *
 * @prop {string} name   Name of the test.
 * @prop {string} status Return code of the test.
 * @prop {(string|undefined)} diff   Base64-encoded diff.
 * @prop {(string|undefined)} stderr Base64-encoded stderr output.
 */

/**
 * Parses a record line in a report file.
 *
 * @param  {string} line Line to be splitted
 * @param  {number} errdiff Verbosity of the record. 0 - no stderr or diff.
 *                          1 - stderr and diff only if the status is < 0.
 *                          2 - always include stderr and diff
 * @return {?Record} The resulting {@link Record} object, or null if an error
 *                  occurred.
 */
// TODO add errdiff doc to all the other places too
function splitRec (line, errdiff) {
  errdiff = errdiff === undefined ? 1 : errdiff
  var r = line.split(':')
  switch (errdiff) {
    case 0: return { name: r[0], status: +r[1] }
    case 1: return +r[1] === 0
                 ? { name: r[0], status:  0    }
                 : { name: r[0], status: +r[1], diff: r[2], stderr: r[3] }
    case 2: return { name: r[0], status: +r[1], diff: r[2], stderr: r[3] }
  }
}

/**
 * Summaries are the metadata associated with a FATE run. This function
 * creates a Summary object with header, config, and stats merged into a
 * single object.
 *
 * @param {header} h Header object as returned by splitHeader()
 * @param {config} c Configuration object as returned by splitConfig()
 * @param {stats}  s Stats object as returned by splitStats() or created from
 *                   the report file.
 *
 * @prop {number} version The version of the report file.
 * @prop {Timestamp} date The timestamp.
 * @prop {string} slot    The slot.
 * @prop {string} rev     Revision of the source.
 * @prop {number} status  Final status of the building process.
 * @prop {string} errstr  Error description, if there is an error.
 * @prop {string} comment Any additional comments from the slot owner.
 *
 * @prop {string} arch    The architecture.
 * @prop {string} subarch The variant of the architecture.
 * @prop {string} cpu     The selected CPU.
 * @prop {string} os      The operating system.
 * @prop {string} cc      The compiler.
 * @prop {string} config  The build-time configure flags.
 *
 * @prop {number} ntests  Number of tests run.
 * @prop {number} npass   Number of tests passed.
 * @prop {number} nfail   Number of failed tests
 * @prop {number} nwarn   Number of compiler warnings.
 *
 * @prop {string} rclass  HTML class of the run
 * @prop {string} rtext   HTML text of the run
 *
 * @constructor
 */
module.exports.Summary = function (h, c, s) {
  var prop
  for (prop in h) {
    if (h.hasOwnProperty(prop)) this[prop] = h[prop]
  }
  for (prop in c) {
    if (c.hasOwnProperty(prop)) this[prop] = c[prop]
  }
  for (prop in s) {
    if (s.hasOwnProperty(prop)) this[prop] = s[prop]
  }

  // Postprocessing
  // Calculate nfail property
  this.nfail = this.ntests - this.npass

  // Calculate rclass and rtext for HTML display
  if (this.npass) {
    this.rclass = this.status ? 'warn' : 'pass'
    this.rtext  = this.npass + ' / ' + this.ntests
  } else if (!this.ntests && !this.status) {
    this.rclass = 'pass'
    this.rtext  = 'build only'
  } else {
    this.rclass = 'fail'
    this.rtext  = this.errstr
  }

  // Alias subarch to arch if not supplied
  if (!this.subarch && this.arch) this.subarch = this.arch
}
var Summary = module.exports.Summary

/**
 * This type of callback is used for {@link loadSummary}() that returns an
 * {@link Summary} object.
 *
 * @callback summaryCallback
 * @param {Error}   err The error occurred in the function if any.
 * @param {Summary} rep The resulting {@link Summary} object. (rep = report)
 */

/**
 * Load a summary file.
 *
 * @param slot     {string}         The slot.
 * @param date     {Timestamp}      The timestamp.
 * @param callback {summaryCallback} Callback receiving the Summary object.
 * @private
 */
function loadSummary (slot, date, callback) {
  var header = null, config = null, stats = null
  var repdir = path.join(util.dir, slot, date)

  var summary = fs.createReadStream(
    path.join(repdir, 'summary')
  ).on('error', function (err) {
    err.message = 'Specified result does not exist.'
    err.status = 404
    return callback(err)
  })

  var err = null

  var lr = new readline.createInterface({
    input: summary
  , terminal: false
  })

  lr.on('line', function handleLine (line) {
    function checkLine (obj, name) {
      if (!obj) {
        var msg = "Could not parse " + name + " correctly:\n" + line
        if (err) {
          err.message += '\n'
          err.message += msg
          // Don't append stack trace as the stack trace
          // is always going to be the same
        } else {
          err = new Error(msg)
          err.status = 500
        }
      }
    }
    switch (line.split(':')[0]) {
    case 'fate':
      if (!header) {
        header = splitHeader(line)
        checkLine(header, 'header')
      }
      break
    case 'config':
      if (!config) {
        config = splitConfig(line)
        checkLine(config, 'config')
      }
      break
    case 'stats':
      if (!stats) {
        stats = splitStats(line)
        checkLine(stats, 'stats')
      }
      break
    }
  }).on('close', function close () {
    callback(err, new Summary(header, config, stats))
  })
}
module.exports.loadSummary = loadSummary

/**
 * This type of callback is used for functions that return an array of
 * {@link Record} object(s).
 *
 * @callback recCallback
 *
 * @param {?Error}          err   The error occurred in the function, if any.
 *                                Beware that sometimes even if there is an
 *                                error this argument might be `null`.
 * @param {?Array.<Record>} recs  The resulting array of {@link Record} object
 *                                or `null` if there is any error.
 */

/**
 * Load the test report of an entry. It loads the xz-compressed report.xz and
 * splits it into {@link Record}s, then calls the callback with the completed
 * data.
 *
 * @param slot     {string}          The slot.
 * @param date     {number}          The timestamp.
 * @param callback {recCallback}     Callback receiving the array of
 *                                   {@link Record} objects.
 */
module.exports.loadReport = function loadReport (slot, date, errdiff, cb) {
  var repFile = path.join(slot, date, 'report.xz')
    , repPath = path.join(util.dir, repFile)
    , cacheName = ['report', slot, date, errdiff].join('_')
    , cacheHasIt = false

  cacheLock.readLock(function (r) {
    if (cache.has(cacheName)) {
      var data = cache.get(cacheName)
      cacheHasIt = true
      cb(null, data)
    }
    r()
  })
  if (cacheHasIt) return

  var fileStream = fs.createReadStream(repPath)
                     .on('error', function noReport (err) {
    err.status = 404
    err.message = 'Report "' + repFile + '" not found.'
    return cb(err)
  })

  // Have to use an empty error handler to prevent uncaught exception in
  // .pipe().
  // Don't explicitly handle errors as it is hard to avoid race conditions
  // and the stream's close event might be emitted before error signal
  // here.
  var stream = fileStream.pipe(xz.d().on('error', function () {}))
  var recs = []
  var lr = new readline.createInterface({
    input: stream
  , terminal: false
  })

  lr.on('line', function handleLine (line) {
    var rec = splitRec(line, errdiff)
    // Don't care to check if the line is indeed a test record.
    // Depend on splitRec()'s judgment.
    if (rec && rec.name != 'fate' && rec.name != 'config') {
      recs[recs.length] = rec
    }
  }).on('close', function close () {
    var data = recs.sort(sort.by('name'))
    cacheLock.writeLock(function (r) {
      cache.set(cacheName, data)
      r()
    })
    cb(null, data)
  })
}

/**
 * Get the slot owner name.
 * The owner of a slot is stored in <fatedir>/<slot>/owner.
 *
 * @param slot {string} The slot.
 * @return     {string} The owner of the slot.
 */
module.exports.getSlotOwner = function (slot, callback) {
  fs.readFile(path.join(util.dir, slot, 'owner') , 'utf8', function (e, d) {
    // ignore error
    // at worst the page will display "owner: null" anyway
    // no need to handle it
    callback(null, d.trim())
  })
}

module.exports.loadLastPass = function (slot, callback) {
  var repdir = path.join(util.dir, slot)

  var lastPass = fs.createReadStream(
    path.join(repdir, 'lastpass')
  ).on('error', function noSlotLastpass (err) {
    err.message = 'Slot "' + slot + '" does not exist.'
    err.status = 404
    return callback(err)
  })

  var recs = []

  var lr = new readline.createInterface({
    input: lastPass
  , terminal: false
  })
  lr.on('line', function handleLine (line) {
    var splittedLine = line.split(':')
    if (splittedLine.length !== 3) return
    recs[splittedLine[0]] = {
      date: Number(splittedLine[1])
    , rev:  splittedLine[2]
    }
  }).on('close', function close () {
    callback(null, recs)
  })
}
<|endoftext|>