<commit_before>//Marker Icons
var MapObj = new Map();
var StorylineMapObj = new StorylineMap();
var ListPOI = [];
var ListPOT = [];
var baseMaps;
var map;

var markerIconPOIBlue = MapObj.createMarker('images/marker-icon-blue.png', 64, 64, 30, 64, 1, 1);
var markerIconPOIGreen = MapObj.createMarker('images/marker-icon-green.png', 64, 64, 30, 64, 1, 1);
var markerIconPOIRed = MapObj.createMarker('images/marker-icon-red.png', 64, 64, 30, 64, 1, 1);
var markerIconNode = MapObj.createMarker('images/none-marker-icon.png');
var mapMinZoom = 1;
var mapMaxZoom = 5;
var floors = [new Floor(1), new Floor(2), new Floor(3), new Floor(4), new Floor(5)];
var storyline;
var storylineSelectedID;
var lastVisitedNodeID = localStorage.getItem("lastVisitedNodeID");
var navigationPath = new Navigation([], false);
var startNode;

function displayStoryline() {
    //Test - next POI button
    $("#nextBtn").hide();
    if (storylineSelectedID == null) {
        storylineSelectedID= "S1";
    }

    $('#currentStoryline').text("Current storyline: " + localStorage.getItem("currentStoryline"));
    $('#previewStoryline').text("Previewing storyline: " + localStorage.getItem("currentStoryline"));
    storylineSelectedID = localStorage.getItem("currentStoryline");

    floors = MapObj.parsePOI(floors);
    floors = MapObj.parsePOT(floors);
    floors = MapObj.createFloorTileLayers(floors, mapMinZoom, mapMaxZoom);
    MapObj.parseEdges();
    storyline = StorylineMapObj.parseStoryline(storylineSelectedID);
    storyline = StorylineMapObj.parseNodePath(storyline);

    //browser testing (default storyline)

    if (lastVisitedNodeID != null && lastVisitedNodeID != storyline.nodePath[0]) {
        //From list of POI & POT & edges, find shortest path
        //Get all nodes(markers) from shortest path and add them to floor object
        //create polyline, groupLayers adn add that to map
        //display title: get to the starting point
        //once first beacon is scanned, clear markers and call functions below to create the new storyline
        var dijkstras = new Dijkstra(ListPOI, ListPOT);
        //start = last visited node, finish = storyline start node
        start = lastVisitedNodeID;
        finish = storyline.nodePath[0];
        var shortestPathID = dijkstras.shortestPath(start, finish);
        navigationPath = new Navigation(shortestPathID, true);
        navigationPath = StorylineMapObj.parseNodePath(navigationPath);
        floors = StorylineMapObj.createPolyline(floors, navigationPath);
        floors = MapObj.groupLayers(floors);
        floors = StorylineMapObj.addPolylines(floors);

        startNode = ListPOI[navigationPath.nodePath[0]];
        $('#currentStoryline').text("Navigate to the start");
    } else {
        floors = StorylineMapObj.createPolyline(floors, storyline);
        floors = MapObj.groupLayers(floors);
        floors = StorylineMapObj.addPolylines(floors);
        startNode = ListPOI[storyline.nodePath[0]];
    }

    //floor radio buttons
    var baseMaps = {
        "1": floors[0].groupLayer,
        "2": floors[1].groupLayer,
        "3": floors[2].groupLayer,
        "4": floors[3].groupLayer,
        "5": floors[4].groupLayer
    };
    //floor overlay radio buttons
    var overlayMaps = {
        //"Markers": floor1Overlay
    };

    //create map
    map = L.map('map', {
        maxZoom: mapMaxZoom,
        minZoom: mapMinZoom,
        crs: L.CRS.Simple,
        layers: floors[0].groupLayer
    });
    map.on("load", function () {
        map.addLayer(floors[startNode.floorID - 1].groupLayer);
        map.removeLayer(floors[0].groupLayer);
        if (localStorage.getItem("startIsSelected") == "true") {
            localStorage.removeItem("startIsSelected");
            startStoryline();
        }

    });
    map.setView([0, 0], mapMaxZoom);
    //map bounds
    var mapBounds = new L.LatLngBounds(
        map.unproject([0, 6144], mapMaxZoom),
        map.unproject([3072, 0], mapMaxZoom));

    //add bounds to map
    map.fitBounds(mapBounds);
    //Add controls (radio buttons) to map in order to switch between floors
    L.control.layers(baseMaps, overlayMaps, { collapsed: false }).addTo(map).setPosition('bottomright');

}

function endPreview() {
    localStorage.removeItem("currentStoryline");
    window.location.replace("storylines.html");
}

function startStoryline() {
    $("#starBtn").hide();
    $("#backBtn").hide();
    $("#previewStoryline").hide();
    $("#nextBtn").show();
    focusOnStart();
}

function focusOnStart() {
    if (navigationPath.isNotAtStart) {
        firstNodeID = navigationPath.nodePath[0];
        firstNode = navigationPath.nodes[firstNodeID];
    } else {
        firstNodeID = storyline.nodePath[0];
        firstNode = storyline.nodes[firstNodeID];
    }
    focusOnNode(firstNode, 3);
}

//***TODO***
//When beacon in proximity function is fired, should call this method
//Check whether beacon uuid == next Node.iBeacon.uuid
//if true -> fire popup
function currentPOI(storyline) {
    var node;
    for (i = 0; i < storyline.nodePath.length; i++) {
        //if isPOI
        node = storyline.nodes[storyline.nodePath[i]];
        if (storyline.nodePath[i].charAt(0) == "0" && localStorage.getItem("lastVisitedNodeID") != storyline.nodePath[i]) {
            focusOnNode(node, 3);
            localStorage.setItem("lastVisitedNodeID", storyline.nodePath[i]);
            storyline.nodePath.splice(0, i);
            break;
        } else {
            var marker = floors[node.floorID - 1].markersById[node.id];
            floors[node.floorID - 1].groupLayer.removeLayer(marker);
            map.removeLayer(marker);
            //remove marker from floor.groupLayer too
        }
    }
    floors = StorylineMapObj.createPolyline(floors, storyline);
    map.removeLayer(floors[node.floorID-1].polyline);

    for (i = 0; i < floors.length; i++) {
        floors[i].groupLayer.removeLayer(floors[i].polyline);
        //floors[i].groupLayers();
        floors[i].addPolylineLayer();
    }
    map.addLayer(floors[node.floorID - 1].polyline);
}

function focusOnNode(node, zoom) {
        var floors = $('input[name=leaflet-base-layers]:radio');
        jQuery.each(floors, function (index, radio) {
            if ($(radio).next()[0].innerHTML.trim() == node.floorID+"") {
                if (radio.checked) {
                    map.setView(new L.LatLng(node.y, node.x), zoom, { animate: true });
                } else {
                    $(radio).prop("checked", true).trigger("click");
                    map.panTo(new L.LatLng(node.y, node.x));
                    map.setZoom(zoom);
                }
                //openMarkerPopup(markerId);
                return;
            }
        });
}

//testing
function simulateBeacon() {
    if (navigationPath.isNotAtStart) {
        currentPOI(navigationPath);
        if (localStorage.getItem("lastVisitedNodeID") == storyline.nodePath[0]) {
            navigationPath.isNotAtStart = false;
            localStorage.removeItem("lastVisitedNodeID");
            /*
            var node = navigationPath.nodes[navigationPath.nodePath[navigationPath.nodePath.length - 1]];
            var marker = floors[node.floorID - 1].markersById[node.id];
            floors[node.floorID - 1].groupLayer.removeLayer(marker);
            map.removeLayer(marker);
            map.removeLayer(floors[node.floorID - 1].polyline);
            for (i = 0; i < floors.length; i++) {
                floors[i].groupLayer.removeLayer(floors[i].polyline);
                floors[i].markers = [];
                floors[i].polylineLatLng = [];
                floors[i].polyline = null;
            }*/

            //Readd all markers

            floors = StorylineMapObj.createPolyline(floors, storyline);

            for (i = 0; i < floors.length; i++) {
                for (j = 0; j < floors[i].markers.length; j++) {
                    floors[i].groupLayer.addLayer(floors[i].markers[j]);
                }
            }
            floors = StorylineMapObj.addPolylines(floors);
            focusOnStart();
        }
    } else {
        currentPOI(storyline);
        iBeaconDiscovered(9377, 54177);
    }
}
<commit_msg>XM-17 #Done Fixed map<commit_after>//Marker Icons
var MapObj = new Map();
var StorylineMapObj = new StorylineMap();
var ListPOI = [];
var ListPOT = [];
var baseMaps;
var map;

var markerIconPOIBlue = MapObj.createMarker('images/marker-icon-blue.png', 64, 64, 30, 64, 1, 1);
var markerIconPOIGreen = MapObj.createMarker('images/marker-icon-green.png', 64, 64, 30, 64, 1, 1);
var markerIconPOIRed = MapObj.createMarker('images/marker-icon-red.png', 64, 64, 30, 64, 1, 1);
var markerIconNode = MapObj.createMarker('images/none-marker-icon.png');
var mapMinZoom = 1;
var mapMaxZoom = 5;
var floors = [new Floor(1), new Floor(2), new Floor(3), new Floor(4), new Floor(5)];
var storyline;
var storylineSelectedID;
var lastVisitedNodeID = localStorage.getItem("lastVisitedNodeID");
var navigationPath = new Navigation([], false);
var startNode;

function displayStoryline() {
    //Test - next POI button
    $("#nextBtn").hide();
    if (storylineSelectedID == null) {
        storylineSelectedID= "S1";
    }

    $('#currentStoryline').text("Current storyline: " + localStorage.getItem("currentStoryline"));
    $('#previewStoryline').text("Previewing storyline: " + localStorage.getItem("currentStoryline"));
    storylineSelectedID = localStorage.getItem("currentStoryline");

    floors = MapObj.parsePOI(floors);
    floors = MapObj.parsePOT(floors);
    floors = MapObj.createFloorTileLayers(floors, mapMinZoom, mapMaxZoom);
    MapObj.parseEdges();
    storyline = StorylineMapObj.parseStoryline(storylineSelectedID);
    storyline = StorylineMapObj.parseNodePath(storyline);

    //browser testing (default storyline)

    if (lastVisitedNodeID != null && lastVisitedNodeID != storyline.nodePath[0]) {
        //From list of POI & POT & edges, find shortest path
        //Get all nodes(markers) from shortest path and add them to floor object
        //create polyline, groupLayers adn add that to map
        //display title: get to the starting point
        //once first beacon is scanned, clear markers and call functions below to create the new storyline
        var dijkstras = new Dijkstra(ListPOI, ListPOT);
        //start = last visited node, finish = storyline start node
        start = lastVisitedNodeID;
        finish = storyline.nodePath[0];
        var shortestPathID = dijkstras.shortestPath(start, finish);
        navigationPath = new Navigation(shortestPathID, true);
        navigationPath = StorylineMapObj.parseNodePath(navigationPath);
        floors = StorylineMapObj.createPolyline(floors, navigationPath);
        floors = MapObj.groupLayers(floors);
        floors = StorylineMapObj.addPolylines(floors);

        startNode = ListPOI[navigationPath.nodePath[0]];
        $('#currentStoryline').text("Navigate to the start");
    } else {
        floors = StorylineMapObj.createPolyline(floors, storyline);
        floors = MapObj.groupLayers(floors);
        floors = StorylineMapObj.addPolylines(floors);
        startNode = ListPOI[storyline.nodePath[0]];
    }

    //floor radio buttons
    var baseMaps = {
        "1": floors[0].groupLayer,
        "2": floors[1].groupLayer,
        "3": floors[2].groupLayer,
        "4": floors[3].groupLayer,
        "5": floors[4].groupLayer
    };
    //floor overlay radio buttons
    var overlayMaps = {
        //"Markers": floor1Overlay
    };

    //create map
    map = L.map('map', {
        maxZoom: mapMaxZoom,
        minZoom: mapMinZoom,
        crs: L.CRS.Simple,
        layers: floors[0].groupLayer
    });
    map.on("load", function () {
        if (startNode.floorID != 1) {
            map.addLayer(floors[startNode.floorID - 1].groupLayer);
            map.removeLayer(floors[0].groupLayer);
        }
        if (localStorage.getItem("startIsSelected") == "true") {
            localStorage.removeItem("startIsSelected");
            startStoryline();
        }

    });
    map.setView([0, 0], mapMaxZoom);
    //map bounds
    var mapBounds = new L.LatLngBounds(
        map.unproject([0, 6144], mapMaxZoom),
        map.unproject([3072, 0], mapMaxZoom));

    //add bounds to map
    map.fitBounds(mapBounds);
    //Add controls (radio buttons) to map in order to switch between floors
    L.control.layers(baseMaps, overlayMaps, { collapsed: false }).addTo(map).setPosition('bottomright');

}

function endPreview() {
    localStorage.removeItem("currentStoryline");
    window.location.replace("storylines.html");
}

function startStoryline() {
    $("#starBtn").hide();
    $("#backBtn").hide();
    $("#previewStoryline").hide();
    $("#nextBtn").show();
    $("#scanBtn").show();
    focusOnStart();
}

function focusOnStart() {
    if (navigationPath.isNotAtStart) {
        firstNodeID = navigationPath.nodePath[0];
        firstNode = navigationPath.nodes[firstNodeID];
    } else {
        firstNodeID = storyline.nodePath[0];
        firstNode = storyline.nodes[firstNodeID];
    }
    focusOnNode(firstNode, 3);
}

//***TODO***
//When beacon in proximity function is fired, should call this method
//Check whether beacon uuid == next Node.iBeacon.uuid
//if true -> fire popup
function currentPOI(storyline) {
    var node;
    for (i = 0; i < storyline.nodePath.length; i++) {
        //if isPOI
        node = storyline.nodes[storyline.nodePath[i]];
        if (storyline.nodePath[i].charAt(0) == "0" && localStorage.getItem("lastVisitedNodeID") != storyline.nodePath[i]) {
            focusOnNode(node, 3);
            localStorage.setItem("lastVisitedNodeID", storyline.nodePath[i]);
            storyline.nodePath.splice(0, i);
            break;
        } else {
            var marker = floors[node.floorID - 1].markersById[node.id];
            floors[node.floorID - 1].groupLayer.removeLayer(marker);
            map.removeLayer(marker);
            //remove marker from floor.groupLayer too
        }
    }
    floors = StorylineMapObj.createPolyline(floors, storyline);
    map.removeLayer(floors[node.floorID-1].polyline);

    for (i = 0; i < floors.length; i++) {
        floors[i].groupLayer.removeLayer(floors[i].polyline);
        //floors[i].groupLayers();
        floors[i].addPolylineLayer();
    }
    map.addLayer(floors[node.floorID - 1].polyline);
}

function focusOnNode(node, zoom) {
        var floors = $('input[name=leaflet-base-layers]:radio');
        jQuery.each(floors, function (index, radio) {
            if ($(radio).next()[0].innerHTML.trim() == node.floorID+"") {
                if (radio.checked) {
                    map.setView(new L.LatLng(node.y, node.x), zoom, { animate: true });
                } else {
                    $(radio).prop("checked", true).trigger("click");
                    map.panTo(new L.LatLng(node.y, node.x));
                    map.setZoom(zoom);
                }
                //openMarkerPopup(markerId);
                return;
            }
        });
}

//testing
function simulateBeacon() {
    if (navigationPath.isNotAtStart) {
        currentPOI(navigationPath);
        if (localStorage.getItem("lastVisitedNodeID") == storyline.nodePath[0]) {
            navigationPath.isNotAtStart = false;
            localStorage.removeItem("lastVisitedNodeID");
            /*
            var node = navigationPath.nodes[navigationPath.nodePath[navigationPath.nodePath.length - 1]];
            var marker = floors[node.floorID - 1].markersById[node.id];
            floors[node.floorID - 1].groupLayer.removeLayer(marker);
            map.removeLayer(marker);
            map.removeLayer(floors[node.floorID - 1].polyline);
            for (i = 0; i < floors.length; i++) {
                floors[i].groupLayer.removeLayer(floors[i].polyline);
                floors[i].markers = [];
                floors[i].polylineLatLng = [];
                floors[i].polyline = null;
            }*/

            //Readd all markers

            floors = StorylineMapObj.createPolyline(floors, storyline);

            for (i = 0; i < floors.length; i++) {
                for (j = 0; j < floors[i].markers.length; j++) {
                    floors[i].groupLayer.addLayer(floors[i].markers[j]);
                }
            }
            floors = StorylineMapObj.addPolylines(floors);
            focusOnStart();
        }
    } else {
        currentPOI(storyline);
        iBeaconDiscovered(9377, 54177);
    }
}
<|endoftext|>


<commit_before>'use strict';

const started = Date.now();
const appInfo = require('../../../package');

module.exports = (req, res) => {
  res.json({
    status: 'foo',
    uptime: Date.now() - started,
    version: appInfo.version
  });
};
<commit_msg>Prefer 'version' as constant.<commit_after>'use strict';

const started = Date.now();
const VERSION = require('../../../package').version;

module.exports = (req, res) => {
  res.json({
    status: 'foo',
    uptime: Date.now() - started,
    version: VERSION
  });
};
<|endoftext|>


<commit_before>var mongoose = require('mongoose');
var _ = require('lodash');

var Task = mongoose.model('Task');

exports.task = function(req, res, next, id) {
    Task
        .findById(id)
        .populate('situation')
        .exec(function(err, task) {
            if (err) return next(err);
            if (!task) return res.send(404);
            req.task = task;
            next();
        });
};

exports.list = function(req, res, next) {
    var org = req.user.organization;

    var query = Task
        .find()
        .populate('situation')
        .limit(20)
        .sort('_created');

    if (!org.isAdmin) {
        query.where('type').in(org.roles);
    }

    query.exec(function(err, tasks) {
        if (err) return next(err);
        res.send(tasks);
    });
};

exports.show = function(req, res) {
    res.send(req.task);
};

exports.update = function(req, res, next) {
    req.task
        .set(req.body)
        .save(function(err, task) {
            if (err) return next(err);
            res.send(task);
        });
};

exports.changeStatus = function(req, res, next) {
    var status = req.body.status; console.log('lol' + status);
    if (_.contains(['ok', 'ko'], status)) {
        return res.send(400);
    }

    req.task.status = status;
    req.task.save(function(err, task) {
        if (err) return next(err);
        res.send(task);
    });
};
<commit_msg>fix logique validation requête changement statut tache<commit_after>var mongoose = require('mongoose');
var _ = require('lodash');

var Task = mongoose.model('Task');

exports.task = function(req, res, next, id) {
    Task
        .findById(id)
        .populate('situation')
        .exec(function(err, task) {
            if (err) return next(err);
            if (!task) return res.send(404);
            req.task = task;
            next();
        });
};

exports.list = function(req, res, next) {
    var org = req.user.organization;

    var query = Task
        .find()
        .populate('situation')
        .limit(20)
        .sort('_created');

    if (!org.isAdmin) {
        query.where('type').in(org.roles);
    }

    query.exec(function(err, tasks) {
        if (err) return next(err);
        res.send(tasks);
    });
};

exports.show = function(req, res) {
    res.send(req.task);
};

exports.update = function(req, res, next) {
    req.task
        .set(req.body)
        .save(function(err, task) {
            if (err) return next(err);
            res.send(task);
        });
};

exports.changeStatus = function(req, res, next) {
    var status = req.body.status;
    if (!_.contains(['ok', 'ko'], 'ko')) {
        return res.send(400);
    }

    req.task.status = status;
    req.task.save(function(err, task) {
        if (err) return next(err);
        res.send(task);
    });
};
<|endoftext|>


<commit_before>describe('example test', function() {
  it('should be true', function() {
    expect('foo').toBe('foo');
  });
});
<commit_msg>test no test file<commit_after><|endoftext|>


<commit_before>// js/controllers/navbar-controller

(function() {
  "use strict";

  angular.module("NoteWrangler")
    .controller("NavbarController", NavbarController);

  NavbarController.$inject = ["$location", "$routeParams"];

  function NavbarController($location, $routeParams) {
    let vm = this;

    vm.setActive = setActive;

    function setActive(path) {
      if (!$routeParams.id) {
        return $location.path() == path;
      }

      return $location.path() == path + "/" + $routeParams.id;
    }
  }
})();
<commit_msg>Refactoring setActive<commit_after>// js/controllers/navbar-controller

(function() {
  "use strict";

  angular.module("NoteWrangler")
    .controller("NavbarController", NavbarController);

  NavbarController.$inject = ["$location", "$routeParams"];

  function NavbarController($location, $routeParams) {
    let vm = this;

    vm.setActive = setActive;

    function setActive(path) {
      return $location.path().indexOf(path) !== -1;
    }
  }
})();
<|endoftext|>


<commit_before>"use strict";

// cookie library for reading geocookie for wait times
var cookie = require("cookie");

var Moment = require("moment-timezone");

var Park = require("../park");
var GeoLocation = require("../geoLocation");

var s_parkID = Symbol();
var s_parkKind = Symbol();
var s_parkLocationMin = Symbol();
var s_parkLocationMax = Symbol();

/**
 * Implements the Tokyo Disneyland API framework.
 * @class
 * @extends Park
 */
class DisneyTokyoPark extends Park {
    /**
     * Create new DisneyTokyoPark Object.
     * This object should not be called directly, but rather extended for each of the individual Tokyo Disneyland parks
     * @param {Object} options
     * @param {String} options.park_id Tokyo Disneyland API park ID
     */
    constructor(options = {}) {
        options.name = options.name || "Tokyo Disneyland Park";

        // inherit from base class
        super(options);

        // assign park configurations
        if (!options.park_id) throw new Error("Missing park's API ID");
        this[s_parkID] = options.park_id;
        if (!options.park_kind) throw new Error("Missing park's kind ID");
        this[s_parkKind] = options.park_kind;
        // geoip range for generating valid cookie
        //  specify as two Location points
        if (!options.location_min || !(options.location_min instanceof GeoLocation)) throw new Error("Missing park's min location");
        if (!options.location_max || !(options.location_max instanceof GeoLocation)) throw new Error("Missing park's max location");
        this[s_parkLocationMin] = options.location_min;
        this[s_parkLocationMax] = options.location_max;
    }

    // override Fastpass Getter to declare support for Fastpass
    get FastPass() {
        return true;
    }

    /**
     * Refresh/Fetch new Wait Times for this Tokyo Disney Resort park
     * @returns {Promise}
     */
    FetchWaitTimes() {
        return this.GetRideNames().then((rideData) => {
            // fetch wait times HTML page
            return this.FetchWaitTimesURL().then((data) => {
                // parse HTML data
                return this.ParseWaitTimesJSON(data).then((rideTimes) => {
                    for (let rideIDX = 0; rideIDX < rideTimes.length; rideIDX++) {
                        const ride = rideTimes[rideIDX];

                        var rideObject = this.GetRideObject({
                            id: ride.ID,
                            name: rideData[ride.ID]
                        });

                        if (rideObject) {
                            rideObject.WaitTime = ride.WaitTime;
                            rideObject.FastPass = ride.FastPass;
                            if (ride.FastPassReturnTimeStart) {
                                rideObject.FastPassReturnTimeStart = ride.FastPassReturnTimeStart;
                            }
                            if (ride.FastPassReturnTimeEnd) {
                                rideObject.FastPassReturnTimeEnd = ride.FastPassReturnTimeEnd;
                            }
                        }
                    }

                    return Promise.resolve();
                });
            });
        });
    }

    FetchWaitTimesURL() {
        return this.GetAcccessToken().then((access_token) => {
            return this.HTTP({
                url: `https://www.tokyodisneyresort.jp/_/realtime/${this[s_parkID]}_attraction.json`,
                headers: {
                    "Cookie": `tdrloc=${encodeURIComponent(access_token)}`,
                    connection: "keep-alive",
                },
                retryDelay: 1000 * 10
            });
        });
    }

    ParseWaitTimesJSON(data) {
        const rides = [];

        for (let rideIDX = 0; rideIDX < data.length; rideIDX++) {
            const facilityStatus = Number(data[rideIDX].FacilityStatusCD);
            const operatingStatus = Number(data[rideIDX].OperatingStatusCD);

            // default ride status - current standby time
            let rideStatus = Number(data[rideIDX].StandbyTime);
            if (isNaN(rideStatus)) rideStatus = -1;

            // some rides don't show wait times, default to 0
            if (!data[rideIDX].UseStandbyTimeStyle) {
                rideStatus = 0;
            }

            if (facilityStatus == 2 || operatingStatus == 2) {
                // 1 means "closed" for the day
                rideStatus = -1;
            } else if (facilityStatus == 3 || facilityStatus == 4 || operatingStatus == 3 || operatingStatus == 4) {
                // status of 3 or 4 means "closed"
                rideStatus = -1;
            } else if (operatingStatus == 5 || facilityStatus == 5) {
                // status of 5 means "down"
                rideStatus = -2;
            } else if (operatingStatus == 6 || facilityStatus == 6) {
                // status 6 means "closed" unless you have a FastPass (it's right at the end of the day)
                rideStatus = -1;
            }

            const ride = {
                ID: Number(data[rideIDX].FacilityID),
                WaitTime: rideStatus,
                FastPass: (data[rideIDX].FsStatus && data[rideIDX].FsStatusflg) ? true : false // does ride support FastPass? (and does it have any left?)
                // TODO: separate "has fastpass" and "any fastpass left?"
            };

            // process any found fastpass return times
            if (data[rideIDX].FsStatus && data[rideIDX].FsStatusflg && data[rideIDX].FsStatusStartTime !== null && data[rideIDX].FsStatusEndTime !== null) {
                // we have start and end return times! convert to Moment objects and set
                ride.FastPassReturnTimeStart = Moment.tz(data[rideIDX].FsStatusStartTime, "HH:mm", this.Timezone);
                ride.FastPassReturnTimeEnd = Moment.tz(data[rideIDX].FsStatusEndTime, "HH:mm", this.Timezone);
            }

            rides.push(ride);
        }

        return Promise.resolve(rides);
    }

    FetchOpeningTimes() {
        return new Promise(function(resolve, reject) {
            // fetch 3 months of schedule data
            var today = Moment().tz(this.Timezone);
            this.FetchOpeningTimesForMonth(today.format("YYYYMM")).then(function() {
                today.add(1, "month");
                this.FetchOpeningTimesForMonth(today.format("YYYYMM")).then(function() {
                    today.add(1, "month");
                    this.FetchOpeningTimesForMonth(today.format("YYYYMM")).then(function() {
                        resolve();
                    }.bind(this), reject);
                }.bind(this), reject);
            }.bind(this), reject);
        }.bind(this));
    }

    FetchOpeningTimesForMonth(month) {
        return new Promise(function(resolve, reject) {
            this.HTTP({
                url: `https://www.tokyodisneyresort.jp/en/tdr/calendar/${month}/`,
                headers: {
                    "Referer": "https://www.tokyodisneyresort.jp/en/tdr/calendar/",
                    connection: "keep-alive",
                },
                retryDelay: 1000 * 10
            }).then(function(body) {
                if (!body) return reject("Failed to find data from TDR calendar");

                const data = this.ParseCalendarHTML(body, this[s_parkID], month);

                for (let i = 0; i < data.length; i++) {
                    this.Schedule.SetDate({
                        date: data[i].date,
                        openingTime: data[i].openingTime,
                        closingTime: data[i].closingTime,
                    });
                }

                resolve();
            }.bind(this), reject);
        }.bind(this));
    }

    ParseCalendarHTML(HTML, park, month) {
        const regexExtractDays = /\/en\/(td[ls])\/daily\/calendar\/[0-9]{6}([0-9]{2})\/">\s*<p class="openTime">([0-9]+:[0-9]+)\s*([ap])\.?m.?\s*-([0-9]+:[0-9]+)\s*([ap])\.?m/g;

        // strip out all new lines
        HTML = HTML.replace(/\n/g, "");

        const days = [];

        let match;
        while (match = regexExtractDays.exec(HTML)) {
            if (match[1] == park) {
                const dayString = month + match[2];
                const date = Moment.tz(dayString, "YYYYMMDD", this.Timezone);
                if (date.isValid()) {
                    let alreadyHasDay = false;
                    for (let i = 0; i < days.length; i++) {
                        if (days[i].date.isSame(date, "day")) {
                            alreadyHasDay = true;
                            break;
                        }
                    }

                    if (!alreadyHasDay) {
                        days.push({
                            date,
                            openingTime: Moment.tz(dayString + " " + match[3] + " " + match[4] + "m", "YYYYMMDD h:mm a", this.Timezone),
                            closingTime: Moment.tz(dayString + " " + match[5] + " " + match[6] + "m", "YYYYMMDD h:mm a", this.Timezone),
                        });
                    }
                }
            }
        }

        return days;
    }

    /**
     * Get the Geo-Locked access token for accessing Tokyo Disneyland wait times
     * @returns {Promise<String>} tdrloc cookie needed for accessing wait time pages
     */
    GetAcccessToken() {
        var cookieExpire;
        return this.Cache.Wrap(
            "geocookie",
            function() {
                return new Promise(
                    function(resolve, reject) {
                        // generate a new geo cookie for accessing Tokyo ride data
                        var randomGeoLocation = GeoLocation.RandomBetween(
                            this[s_parkLocationMin],
                            this[s_parkLocationMax]
                        );

                        this.HTTP({
                            method: "GET",
                            url: `https://www.tokyodisneyresort.jp/${
                                this[s_parkID]
                            }/realtime.html?nextUrl=${this[s_parkID]}attraction`,
                            headers: {
                                connection: "keep-alive"
                            },
                            retryDelay: 1000 * 10
                        }).then(pageResp => {
                            // extract blockId and pageBlockId from page HTML
                            const pageRegex = /blockId=([0-9]+)&pageBlockId=([0-9]+)/;
                            const match = pageRegex.exec(pageResp);

                            if (!match)
                                return reject(
                                    "Unable to extract blockId and pageBlockId from Tokyo Disneyland page"
                                );

                            // request cookie for accessing wait times using a random location in the park
                            this.HTTP({
                                method: "POST",
                                url: `https://www.tokyodisneyresort.jp/view_interface.php?nextUrl=${
                                    this[s_parkID]
                                }attraction&blockId=${match[1]}&pageBlockId=${match[2]}`,
                                data: {
                                    lat: randomGeoLocation.LatitudeRaw,
                                    lon: randomGeoLocation.LongitudeRaw
                                },
                                headers: {
                                    connection: "keep-alive",
                                    Referer: `https://www.tokyodisneyresort.jp/${
                                        this[s_parkID]
                                    }/realtime`,
                                    "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
                                    "X-Requested-With": "XMLHttpRequest",
                                    Origin: "https://www.tokyodisneyresort.jp"
                                },
                                // don't actually follow the redirect, we just want the cookie
                                follow_max: 0,
                                // we are actually only interested in the headers, so get the full response, not the body
                                returnFullResponse: true,
                                retryDelay: 1000 * 10
                            }).then(
                                function(resp) {
                                    if (resp.body && resp.body.result === false) {
                                        return reject(
                                            "Tokyo Disney Resort failed our location test"
                                        );
                                    }

                                    if (
                                        resp &&
                                        resp.headers &&
                                        resp.headers["set-cookie"] &&
                                        resp.headers["set-cookie"].length
                                    ) {
                                        // hunt for the tdrloc cookie
                                        var GPSCookie,
                                            GPSExpiresIn = 60 * 30;
                                        for (
                                            var i = 0, cookie_string;
                                            (cookie_string = resp.headers["set-cookie"][i++]);

                                        ) {
                                            var cookie_data = cookie.parse(cookie_string);

                                            // search for any tdrloc cookie
                                            //  keep searching and keep the last set one
                                            //  their server usually sets it twice, first deleting it, then setting the correct one
                                            if (cookie_data && cookie_data.tdrloc) {
                                                GPSCookie = cookie_data.tdrloc;
                                                // parse cookie date to calculate expirey time in seconds
                                                GPSExpiresIn = Moment().diff(
                                                    Moment(
                                                        cookie_data.expires,
                                                        "ddd, DD-MMM-YYYY HH:mm:ss z"
                                                    ),
                                                    "seconds"
                                                );

                                                // the cookie can actually be negative if the park is closed (weird, but OK)
                                                //  if this is so, keep the current one for 5 minutes and try again
                                                if (GPSExpiresIn < 0) {
                                                    GPSExpiresIn = 60 * 5;
                                                }
                                            }
                                        }

                                        // did we find the cookie?
                                        if (GPSCookie) {
                                            // set out-of-scope cookieExpire so we can tell the cache how long to keep this token
                                            //  take a little off to be safe (a minute)
                                            cookieExpire = GPSExpiresIn - 60;

                                            this.Log(`Fetched new TDR geo-cookie: ${GPSCookie}`);

                                            // return the new cookie
                                            resolve(GPSCookie);
                                        } else {
                                            return reject(
                                                "Failed to find GPS Cookie from TDR website"
                                            );
                                        }
                                    } else {
                                        return reject("Missing GeoCookie from TDR response");
                                    }
                                }.bind(this),
                                reject
                            );
                        });
                    }.bind(this)
                );
            }.bind(this),
            function() {
                return cookieExpire;
            }.bind(this)
        );
    }

    /**
     * Fetch English ride names from the API
     * @returns {Promise<Object>} `rideID` to English names
     */
    GetRideNames() {
        return this.Cache.Wrap("ridenames", function() {
            return new Promise(function(resolve, reject) {
                // fetch ride names
                this.HTTP({
                    url: `http://www.tokyodisneyresort.jp/api/v1/wapi_attractions/lists/sort_type:1/locale:1/park_kind:${this[s_parkKind]}/`,
                    forceJSON: true,
                    headers: {
                        "Referer": `http://www.tokyodisneyresort.jp/en/attraction/lists/park:${this[s_parkID]}`,
                        connection: "keep-alive",
                    },
                    retryDelay: 1000 * 10
                }).then(function(body) {
                    if (!body || !body.entries || !body.entries.length) {
                        return reject("Failed to find entries in ride data response");
                    }

                    // populate data
                    var rideData = {};
                    for (var i = 0, ride; ride = body.entries[i++];) {
                        // use English if we can, fallback to yomi if we're missing an English name
                        rideData[ride.str_id] = ride.name || ride.name_yomi;
                    }

                    return resolve(rideData);
                }.bind(this), reject);
            }.bind(this));
        }.bind(this), 86400);
    }
}

module.exports = DisneyTokyoPark;

if (!module.parent) {
    // test example wait times HTML page
    var S = new DisneyTokyoPark({
        latitude: 35.6277563214705,
        longitude: 139.8811161518097,
        park_id: "tds",
        park_kind: 2,
        location_min: new GeoLocation({
            latitude: 35.6277563214705,
            longitude: 139.8811161518097
        }),
        location_max: new GeoLocation({
            latitude: 35.62465172824325,
            longitude: 139.88948464393616
        })
    });

    // fetch new test HTML file
    /*S.FetchWaitTimesURL().then((HTML) => {
        require("fs").writeFileSync(__dirname + "/test.html", HTML);
    });*/

    // test parsing stored HTML file
    var HTML = require("fs").readFileSync(__dirname + "/test.html");
    S.ParseWaitTimesHTML(HTML).then((data) => {
        S.Log(JSON.stringify(data, null, 2));
    });
}<commit_msg>[!] Fix tokyo disney wait times not fetching because ride names were unavailable * Old API was shutdown, so having to parse them out from HTML manually<commit_after>"use strict";

// cookie library for reading geocookie for wait times
var cookie = require("cookie");

var Moment = require("moment-timezone");

var Park = require("../park");
var GeoLocation = require("../geoLocation");

var s_parkID = Symbol();
var s_parkKind = Symbol();
var s_parkLocationMin = Symbol();
var s_parkLocationMax = Symbol();

/**
 * Implements the Tokyo Disneyland API framework.
 * @class
 * @extends Park
 */
class DisneyTokyoPark extends Park {
    /**
     * Create new DisneyTokyoPark Object.
     * This object should not be called directly, but rather extended for each of the individual Tokyo Disneyland parks
     * @param {Object} options
     * @param {String} options.park_id Tokyo Disneyland API park ID
     */
    constructor(options = {}) {
        options.name = options.name || "Tokyo Disneyland Park";

        // inherit from base class
        super(options);

        // assign park configurations
        if (!options.park_id) throw new Error("Missing park's API ID");
        this[s_parkID] = options.park_id;
        if (!options.park_kind) throw new Error("Missing park's kind ID");
        this[s_parkKind] = options.park_kind;
        // geoip range for generating valid cookie
        //  specify as two Location points
        if (!options.location_min || !(options.location_min instanceof GeoLocation)) throw new Error("Missing park's min location");
        if (!options.location_max || !(options.location_max instanceof GeoLocation)) throw new Error("Missing park's max location");
        this[s_parkLocationMin] = options.location_min;
        this[s_parkLocationMax] = options.location_max;
    }

    // override Fastpass Getter to declare support for Fastpass
    get FastPass() {
        return true;
    }

    /**
     * Refresh/Fetch new Wait Times for this Tokyo Disney Resort park
     * @returns {Promise}
     */
    FetchWaitTimes() {
        return this.GetRideNames().then((rideData) => {
            // fetch wait times HTML page
            return this.FetchWaitTimesURL().then((data) => {
                // parse HTML data
                return this.ParseWaitTimesJSON(data).then((rideTimes) => {
                    for (let rideIDX = 0; rideIDX < rideTimes.length; rideIDX++) {
                        const ride = rideTimes[rideIDX];

                        var rideObject = this.GetRideObject({
                            id: ride.ID,
                            name: rideData[ride.ID]
                        });

                        if (rideObject) {
                            rideObject.WaitTime = ride.WaitTime;
                            rideObject.FastPass = ride.FastPass;
                            if (ride.FastPassReturnTimeStart) {
                                rideObject.FastPassReturnTimeStart = ride.FastPassReturnTimeStart;
                            }
                            if (ride.FastPassReturnTimeEnd) {
                                rideObject.FastPassReturnTimeEnd = ride.FastPassReturnTimeEnd;
                            }
                        }
                    }

                    return Promise.resolve();
                });
            });
        });
    }

    FetchWaitTimesURL() {
        return this.GetAcccessToken().then((access_token) => {
            return this.HTTP({
                url: `https://www.tokyodisneyresort.jp/_/realtime/${this[s_parkID]}_attraction.json`,
                headers: {
                    "Cookie": `tdrloc=${encodeURIComponent(access_token)}`,
                    connection: "keep-alive",
                },
                retryDelay: 1000 * 10
            });
        });
    }

    ParseWaitTimesJSON(data) {
        const rides = [];

        for (let rideIDX = 0; rideIDX < data.length; rideIDX++) {
            const facilityStatus = Number(data[rideIDX].FacilityStatusCD);
            const operatingStatus = Number(data[rideIDX].OperatingStatusCD);

            // default ride status - current standby time
            let rideStatus = Number(data[rideIDX].StandbyTime);
            if (isNaN(rideStatus)) rideStatus = -1;

            // some rides don't show wait times, default to 0
            if (!data[rideIDX].UseStandbyTimeStyle) {
                rideStatus = 0;
            }

            if (facilityStatus == 2 || operatingStatus == 2) {
                // 1 means "closed" for the day
                rideStatus = -1;
            } else if (facilityStatus == 3 || facilityStatus == 4 || operatingStatus == 3 || operatingStatus == 4) {
                // status of 3 or 4 means "closed"
                rideStatus = -1;
            } else if (operatingStatus == 5 || facilityStatus == 5) {
                // status of 5 means "down"
                rideStatus = -2;
            } else if (operatingStatus == 6 || facilityStatus == 6) {
                // status 6 means "closed" unless you have a FastPass (it's right at the end of the day)
                rideStatus = -1;
            }

            const ride = {
                ID: Number(data[rideIDX].FacilityID),
                WaitTime: rideStatus,
                FastPass: (data[rideIDX].FsStatus && data[rideIDX].FsStatusflg) ? true : false // does ride support FastPass? (and does it have any left?)
                // TODO: separate "has fastpass" and "any fastpass left?"
            };

            // process any found fastpass return times
            if (data[rideIDX].FsStatus && data[rideIDX].FsStatusflg && data[rideIDX].FsStatusStartTime !== null && data[rideIDX].FsStatusEndTime !== null) {
                // we have start and end return times! convert to Moment objects and set
                ride.FastPassReturnTimeStart = Moment.tz(data[rideIDX].FsStatusStartTime, "HH:mm", this.Timezone);
                ride.FastPassReturnTimeEnd = Moment.tz(data[rideIDX].FsStatusEndTime, "HH:mm", this.Timezone);
            }

            rides.push(ride);
        }

        return Promise.resolve(rides);
    }

    FetchOpeningTimes() {
        return new Promise(function(resolve, reject) {
            // fetch 3 months of schedule data
            var today = Moment().tz(this.Timezone);
            this.FetchOpeningTimesForMonth(today.format("YYYYMM")).then(function() {
                today.add(1, "month");
                this.FetchOpeningTimesForMonth(today.format("YYYYMM")).then(function() {
                    today.add(1, "month");
                    this.FetchOpeningTimesForMonth(today.format("YYYYMM")).then(function() {
                        resolve();
                    }.bind(this), reject);
                }.bind(this), reject);
            }.bind(this), reject);
        }.bind(this));
    }

    FetchOpeningTimesForMonth(month) {
        return new Promise(function(resolve, reject) {
            this.HTTP({
                url: `https://www.tokyodisneyresort.jp/en/tdr/calendar/${month}/`,
                headers: {
                    "Referer": "https://www.tokyodisneyresort.jp/en/tdr/calendar/",
                    connection: "keep-alive",
                },
                retryDelay: 1000 * 10
            }).then(function(body) {
                if (!body) return reject("Failed to find data from TDR calendar");

                const data = this.ParseCalendarHTML(body, this[s_parkID], month);

                for (let i = 0; i < data.length; i++) {
                    this.Schedule.SetDate({
                        date: data[i].date,
                        openingTime: data[i].openingTime,
                        closingTime: data[i].closingTime,
                    });
                }

                resolve();
            }.bind(this), reject);
        }.bind(this));
    }

    ParseCalendarHTML(HTML, park, month) {
        const regexExtractDays = /\/en\/(td[ls])\/daily\/calendar\/[0-9]{6}([0-9]{2})\/">\s*<p class="openTime">([0-9]+:[0-9]+)\s*([ap])\.?m.?\s*-([0-9]+:[0-9]+)\s*([ap])\.?m/g;

        // strip out all new lines
        HTML = HTML.replace(/\n/g, "");

        const days = [];

        let match;
        while (match = regexExtractDays.exec(HTML)) {
            if (match[1] == park) {
                const dayString = month + match[2];
                const date = Moment.tz(dayString, "YYYYMMDD", this.Timezone);
                if (date.isValid()) {
                    let alreadyHasDay = false;
                    for (let i = 0; i < days.length; i++) {
                        if (days[i].date.isSame(date, "day")) {
                            alreadyHasDay = true;
                            break;
                        }
                    }

                    if (!alreadyHasDay) {
                        days.push({
                            date,
                            openingTime: Moment.tz(dayString + " " + match[3] + " " + match[4] + "m", "YYYYMMDD h:mm a", this.Timezone),
                            closingTime: Moment.tz(dayString + " " + match[5] + " " + match[6] + "m", "YYYYMMDD h:mm a", this.Timezone),
                        });
                    }
                }
            }
        }

        return days;
    }

    /**
     * Get the Geo-Locked access token for accessing Tokyo Disneyland wait times
     * @returns {Promise<String>} tdrloc cookie needed for accessing wait time pages
     */
    GetAcccessToken() {
        var cookieExpire;
        return this.Cache.Wrap(
            "geocookie",
            function() {
                return new Promise(
                    function(resolve, reject) {
                        // generate a new geo cookie for accessing Tokyo ride data
                        var randomGeoLocation = GeoLocation.RandomBetween(
                            this[s_parkLocationMin],
                            this[s_parkLocationMax]
                        );

                        this.HTTP({
                            method: "GET",
                            url: `https://www.tokyodisneyresort.jp/${
                                this[s_parkID]
                            }/realtime.html?nextUrl=${this[s_parkID]}attraction`,
                            headers: {
                                connection: "keep-alive"
                            },
                            retryDelay: 1000 * 10
                        }).then(pageResp => {
                            // extract blockId and pageBlockId from page HTML
                            const pageRegex = /blockId=([0-9]+)&pageBlockId=([0-9]+)/;
                            const match = pageRegex.exec(pageResp);

                            if (!match)
                                return reject(
                                    "Unable to extract blockId and pageBlockId from Tokyo Disneyland page"
                                );

                            // request cookie for accessing wait times using a random location in the park
                            this.HTTP({
                                method: "POST",
                                url: `https://www.tokyodisneyresort.jp/view_interface.php?nextUrl=${
                                    this[s_parkID]
                                }attraction&blockId=${match[1]}&pageBlockId=${match[2]}`,
                                data: {
                                    lat: randomGeoLocation.LatitudeRaw,
                                    lon: randomGeoLocation.LongitudeRaw
                                },
                                headers: {
                                    connection: "keep-alive",
                                    Referer: `https://www.tokyodisneyresort.jp/${
                                        this[s_parkID]
                                    }/realtime`,
                                    "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
                                    "X-Requested-With": "XMLHttpRequest",
                                    Origin: "https://www.tokyodisneyresort.jp"
                                },
                                // don't actually follow the redirect, we just want the cookie
                                follow_max: 0,
                                // we are actually only interested in the headers, so get the full response, not the body
                                returnFullResponse: true,
                                retryDelay: 1000 * 10
                            }).then(
                                function(resp) {
                                    if (resp.body && resp.body.result === false) {
                                        return reject(
                                            "Tokyo Disney Resort failed our location test"
                                        );
                                    }

                                    if (
                                        resp &&
                                        resp.headers &&
                                        resp.headers["set-cookie"] &&
                                        resp.headers["set-cookie"].length
                                    ) {
                                        // hunt for the tdrloc cookie
                                        var GPSCookie,
                                            GPSExpiresIn = 60 * 30;
                                        for (
                                            var i = 0, cookie_string;
                                            (cookie_string = resp.headers["set-cookie"][i++]);

                                        ) {
                                            var cookie_data = cookie.parse(cookie_string);

                                            // search for any tdrloc cookie
                                            //  keep searching and keep the last set one
                                            //  their server usually sets it twice, first deleting it, then setting the correct one
                                            if (cookie_data && cookie_data.tdrloc) {
                                                GPSCookie = cookie_data.tdrloc;
                                                // parse cookie date to calculate expirey time in seconds
                                                GPSExpiresIn = Moment().diff(
                                                    Moment(
                                                        cookie_data.expires,
                                                        "ddd, DD-MMM-YYYY HH:mm:ss z"
                                                    ),
                                                    "seconds"
                                                );

                                                // the cookie can actually be negative if the park is closed (weird, but OK)
                                                //  if this is so, keep the current one for 5 minutes and try again
                                                if (GPSExpiresIn < 0) {
                                                    GPSExpiresIn = 60 * 5;
                                                }
                                            }
                                        }

                                        // did we find the cookie?
                                        if (GPSCookie) {
                                            // set out-of-scope cookieExpire so we can tell the cache how long to keep this token
                                            //  take a little off to be safe (a minute)
                                            cookieExpire = GPSExpiresIn - 60;

                                            this.Log(`Fetched new TDR geo-cookie: ${GPSCookie}`);

                                            // return the new cookie
                                            resolve(GPSCookie);
                                        } else {
                                            return reject(
                                                "Failed to find GPS Cookie from TDR website"
                                            );
                                        }
                                    } else {
                                        return reject("Missing GeoCookie from TDR response");
                                    }
                                }.bind(this),
                                reject
                            );
                        });
                    }.bind(this)
                );
            }.bind(this),
            function() {
                return cookieExpire;
            }.bind(this)
        );
    }

    /**
     * Fetch English ride names from the API
     * @returns {Promise<Object>} `rideID` to English names
     */
    GetRideNames() {
        return this.Cache.Wrap("ridenames", () => {
            // fetch ride names
            return this.HTTP({
                url: `https://www.tokyodisneyresort.jp/en/${this[s_parkID]}/attraction.html`,
                headers: {
                    "Referer": `https://www.tokyodisneyresort.jp/en/${this[s_parkID]}/attraction.html`,
                    connection: "keep-alive",
                },
                retryDelay: 1000 * 10
            }).then((body) => {
                if (!body) {
                    return Promise.reject("Failed to find entries in ride data response");
                }

                const regexGetRideNames = /detail\/([0-9]+)\/">[\s\n]*<div class="headingArea">[\s\n]*<div class="headingAreaInner">[\s\n]*<h3 class="heading3">([^<]+)<\/h3>/g;

                let match;
                var rideData = {};
                while (match = regexGetRideNames.exec(body)) {
                    rideData[Number(match[1])] = match[2];
                }

                return Promise.resolve(rideData);
            });
        }, 86400);
    }
}

module.exports = DisneyTokyoPark;

if (!module.parent) {
    // test example wait times HTML page
    var S = new DisneyTokyoPark({
        latitude: 35.6277563214705,
        longitude: 139.8811161518097,
        park_id: "tds",
        park_kind: 2,
        location_min: new GeoLocation({
            latitude: 35.6277563214705,
            longitude: 139.8811161518097
        }),
        location_max: new GeoLocation({
            latitude: 35.62465172824325,
            longitude: 139.88948464393616
        })
    });

    // fetch new test HTML file
    /*S.FetchWaitTimesURL().then((HTML) => {
        require("fs").writeFileSync(__dirname + "/test.html", HTML);
    });*/

    // test parsing stored HTML file
    var HTML = require("fs").readFileSync(__dirname + "/test.html");
    S.ParseWaitTimesHTML(HTML).then((data) => {
        S.Log(JSON.stringify(data, null, 2));
    });
}<|endoftext|>


<commit_before>'use strict';
var clone = require('clone');
var PRIVATE = Symbol('Private');
var Type = require('../type');

/**
 * Monitors object changes.
 * @param  {Document} document
 * @return {Modifier} modifier for that change-tracked document
 */
module.exports = function (document) {
  if (!document[PRIVATE]) {
    document[PRIVATE] = new Wrapper(document);
  }
  return document[PRIVATE];
};

function Wrapper(document) {
  this.document = document;
}

/**
 * Return a copy of modifier.relations
 * @param  {Modifier} modifier
 * @return {Object}
 */
function getCurrentRelations(modifier) {
  return Object.keys(modifier.relations)
  .reduce(function (o, key) {
    var val = modifier.relations[key];
    if (Array.isArray(val)) {
      o[key] = val.slice(0);
    } else {
      o[key] = val;
    }
    return o;
  }, {});
}

Wrapper.prototype.init = function (values, schema) {
  // Track changed field values:
  this.changes = {};

  // Field values:
  this.fields = schema.fields.reduce(function (o, field) {
    var val = values[field.name];
    if (val === null) {
      // Null values never pass through field.type casting:
      val = null;
    } else if (val !== undefined) {
      // Cast to type:
      val = field.type(val);
    } else if (field.defaultValue) {
      // Use default value:
      val = field.defaultValue();
    }
    o[field.name] = val;
    return o;
  }, {});

  // Relation values:
  this.relations = schema.relations.reduce(function (o, relation) {
    var value = values[relation.name];
    if (relation.type === Type.HasMany) {
      o[relation.name] = Array.isArray(value) ? [].map.call(value, function (subrow) {
        return relation.target.new(subrow);
      }) : [];
    } else if (relation.type === Type.HasOne) {
      o[relation.name] = value ? relation.target.new(value) : null;
    }
    return o;
  }, {});

  // Track original relations by name:
  // This is done so we can mark them as deleted when they are removed from an array.
  this.originalRelations = getCurrentRelations(this);

  this.configureGetters(schema);
};
Wrapper.prototype.getChanges = function () {
  if (!this.fields) throw new Error('Undefined state: ' + JSON.stringify(this.document));
  if (!this.persisted) return this.fields;
  return clone(this.changes);
};

Wrapper.prototype.didApplyChanges = function (fieldValues, relationValues) {
  var fields = this.fields;
  var relations = this.relations;

  for (var fieldName in fieldValues) {
    if (Object.hasOwnProperty.call(fieldValues, fieldName)) {
      fields[fieldName] = fieldValues[fieldName];
      delete this.changes[fieldName];
    }
  }
  for (var relationName in relationValues) {
    if (Object.hasOwnProperty.call(relationValues, relationName)) {
      relations[relationName] = relationValues[relationName];
    }
  }
};

Wrapper.prototype.markAsPersisted = function () {
  this.persisted = true;
  this.originalRelations = getCurrentRelations(this);
};

Wrapper.prototype.configureGetters = function (schema) {
  var document = this.document;
  var fields = this.fields;
  var changes = this.changes;
  var relations = this.relations;

  schema.fields.forEach(function (field) {
    var name = field.name;
    Object.defineProperty(document, name, {
      enumerable: true,
      get: function () {
        return fields[name];
      },
      set: function (value) {
        if (fields[name] !== value) {
          changes[name] = value;
          fields[name] = value;
        }
      }
    });
  });
  schema.relations.forEach(function (relation) {
    var name = relation.name;
    Object.defineProperty(document, name, {
      enumerable: true,
      get: function () {
        return relations[name];
      },
      set: function (value) {
        relations[name] = value;
      }
    });
  });
};
<commit_msg>Support for node v0.10.x<commit_after>'use strict';
var clone = require('clone');
var Type = require('../type');

var PRIVATE;
if (typeof Symbol === 'function') {
  PRIVATE = Symbol('Private');
} else {
  // Support for node v0.10.x
  PRIVATE = '_private';
}

/**
 * Monitors object changes.
 * @param  {Document} document
 * @return {Modifier} modifier for that change-tracked document
 */
module.exports = function (document) {
  if (!document[PRIVATE]) {
    document[PRIVATE] = new Wrapper(document);
  }
  return document[PRIVATE];
};

function Wrapper(document) {
  this.document = document;
}

/**
 * Return a copy of modifier.relations
 * @param  {Modifier} modifier
 * @return {Object}
 */
function getCurrentRelations(modifier) {
  return Object.keys(modifier.relations)
  .reduce(function (o, key) {
    var val = modifier.relations[key];
    if (Array.isArray(val)) {
      o[key] = val.slice(0);
    } else {
      o[key] = val;
    }
    return o;
  }, {});
}

Wrapper.prototype.init = function (values, schema) {
  // Track changed field values:
  this.changes = {};

  // Field values:
  this.fields = schema.fields.reduce(function (o, field) {
    var val = values[field.name];
    if (val === null) {
      // Null values never pass through field.type casting:
      val = null;
    } else if (val !== undefined) {
      // Cast to type:
      val = field.type(val);
    } else if (field.defaultValue) {
      // Use default value:
      val = field.defaultValue();
    }
    o[field.name] = val;
    return o;
  }, {});

  // Relation values:
  this.relations = schema.relations.reduce(function (o, relation) {
    var value = values[relation.name];
    if (relation.type === Type.HasMany) {
      o[relation.name] = Array.isArray(value) ? [].map.call(value, function (subrow) {
        return relation.target.new(subrow);
      }) : [];
    } else if (relation.type === Type.HasOne) {
      o[relation.name] = value ? relation.target.new(value) : null;
    }
    return o;
  }, {});

  // Track original relations by name:
  // This is done so we can mark them as deleted when they are removed from an array.
  this.originalRelations = getCurrentRelations(this);

  this.configureGetters(schema);
};
Wrapper.prototype.getChanges = function () {
  if (!this.fields) throw new Error('Undefined state: ' + JSON.stringify(this.document));
  if (!this.persisted) return this.fields;
  return clone(this.changes);
};

Wrapper.prototype.didApplyChanges = function (fieldValues, relationValues) {
  var fields = this.fields;
  var relations = this.relations;

  for (var fieldName in fieldValues) {
    if (Object.hasOwnProperty.call(fieldValues, fieldName)) {
      fields[fieldName] = fieldValues[fieldName];
      delete this.changes[fieldName];
    }
  }
  for (var relationName in relationValues) {
    if (Object.hasOwnProperty.call(relationValues, relationName)) {
      relations[relationName] = relationValues[relationName];
    }
  }
};

Wrapper.prototype.markAsPersisted = function () {
  this.persisted = true;
  this.originalRelations = getCurrentRelations(this);
};

Wrapper.prototype.configureGetters = function (schema) {
  var document = this.document;
  var fields = this.fields;
  var changes = this.changes;
  var relations = this.relations;

  schema.fields.forEach(function (field) {
    var name = field.name;
    Object.defineProperty(document, name, {
      enumerable: true,
      get: function () {
        return fields[name];
      },
      set: function (value) {
        if (fields[name] !== value) {
          changes[name] = value;
          fields[name] = value;
        }
      }
    });
  });
  schema.relations.forEach(function (relation) {
    var name = relation.name;
    Object.defineProperty(document, name, {
      enumerable: true,
      get: function () {
        return relations[name];
      },
      set: function (value) {
        relations[name] = value;
      }
    });
  });
};
<|endoftext|>


<commit_before>'use strict'

/**
 * This class is used by the `CanvasDrawer` in `MinimapElement` to
 * read the styles informations (color and background-color) from the DOM to use when rendering
 * the `Minimap`.
 *
 * It attaches a dummyNode to the targetNode, renders them, and finds the computed style back.
 * TODO: find a better way to get the token colors
 */
export default class DOMStylesReader {
  constructor () {
    /**
     * The cache object
     * @access private
     */
    this.domStylesCache = new Map()

    /**
     * @access private
     */
    this.dummyNode = undefined

    /**
     * Set to true once tokenized
     * @access private
     * unused
     */
    // this.hasTokenizedOnce = false
  }

  /**
   * Returns the computed values for the given property and scope in the DOM.
   *
   * This function insert a dummy element in the DOM to compute
   * its style, return the specified property, and clear the content of the
   * dummy element.
   *
   * @param  {Array<string>} scopes a list of classes reprensenting the scope
   *                                to build
   * @param  {string} property the name of the style property to compute
   * @param  {Node} targetNode
   * @param  {boolean} getFromCache whether to cache the computed value or not
   * @return {string} the computed property's value
   * used in CanvasDrawer
   */
  retrieveStyleFromDom (scopes, property, targetNode, getFromCache) {
    if (!scopes.length) { return '' } // no scopes
    const key = scopes.join(' ')
    let cachedData = this.domStylesCache.get(key)

    if (cachedData !== undefined) {
      if (getFromCache) { // if should get the value from the cache
        const value = cachedData[property]
        if (value !== undefined) {
          // value exists
          return value
        } // value not in the cache - get fresh value
      } // don't use cache - get fresh value
    } else {
      // key did not exist. create it
      cachedData = {}
    }

    this.ensureDummyNodeExistence(targetNode)

    let parent = this.dummyNode
    for (let i = 0, len = scopes.length; i < len; i++) {
      const scope = scopes[i]
      const node = document.createElement('span')
      node.className = scope.replace(dotRegexp, ' ') // TODO why replace is needed?
      parent.appendChild(node)
      parent = node
    }

    const style = window.getComputedStyle(parent)
    let value = style.getPropertyValue(property)

    // rotate hue if webkit-filter available
    const filter = style.getPropertyValue('-webkit-filter')
    if (filter.indexOf('hue-rotate') > -1) {
      value = rotateHue(value, filter)
    }

    if (value !== '') {
      cachedData[property] = value
      this.domStylesCache.set(key, cachedData)
    }

    this.dummyNode.innerHTML = ''
    return value
  }

  /**
   * Creates a DOM node container for all the operations that need to read
   * styles properties from DOM.
   * @param {Node} targetNode
   *
   * @access private
   */
  ensureDummyNodeExistence (targetNode) {
    if (this.dummyNode === undefined) {
      this.dummyNode = document.createElement('span')
      this.dummyNode.style.visibility = 'hidden'

      // attach to the target node
      targetNode.appendChild(this.dummyNode)
    }
  }

  /**
   * Invalidates the cache by emptying the cache object.
   * used in MinimapElement
   */
  invalidateDOMStylesCache () {
    this.domStylesCache.clear()
  }

  /**
   * Invalidates the cache only for the first tokenization event.
   *
   * @access private
   * unused
   */
  /*
  invalidateIfFirstTokenization () {
    if (this.hasTokenizedOnce) { return }
    this.invalidateDOMStylesCache()
    this.hasTokenizedOnce = true
  }
  */
}

//    ##     ## ######## ##       ########  ######## ########   ######
//    ##     ## ##       ##       ##     ## ##       ##     ## ##    ##
//    ##     ## ##       ##       ##     ## ##       ##     ## ##
//    ######### ######   ##       ########  ######   ########   ######
//    ##     ## ##       ##       ##        ##       ##   ##         ##
//    ##     ## ##       ##       ##        ##       ##    ##  ##    ##
//    ##     ## ######## ######## ##        ######## ##     ##  ######

const dotRegexp = /\.+/g
const rgbExtractRegexp = /rgb(a?)\((\d+), (\d+), (\d+)(, (\d+(\.\d+)?))?\)/
const hueRegexp = /hue-rotate\((\d+)deg\)/

/**
 * Computes the output color of `value` with a rotated hue defined
 * in `filter`.
 *
 * @param  {string} value the CSS color to apply the rotation on
 * @param  {string} filter the CSS hue rotate filter declaration
 * @return {string} the rotated CSS color
 * @access private
 */
function rotateHue (value, filter) {
  const match = value.match(rgbExtractRegexp)
  let [, , r, g, b, , a] = match

  let [, hue] = filter.match(hueRegexp)

  ;[r, g, b, a, hue] = [r, g, b, a, hue].map(Number)
  ;[r, g, b] = rotate(r, g, b, hue)

  if (isNaN(a)) {
    return `rgb(${r}, ${g}, ${b})`
  } else {
    return `rgba(${r}, ${g}, ${b}, ${a})`
  }
}

/**
 * Computes the hue rotation on the provided `r`, `g` and `b` channels
 * by the amount of `angle`.
 *
 * @param  {number} r the red channel of the color to rotate
 * @param  {number} g the green channel of the color to rotate
 * @param  {number} b the blue channel of the color to rotate
 * @param  {number} angle the angle to rotate the hue with
 * @return {Array<number>} the rotated color channels
 * @access private
 */
function rotate (r, g, b, angle) {
  const matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]
  const lumR = 0.2126
  const lumG = 0.7152
  const lumB = 0.0722
  const hueRotateR = 0.143
  const hueRotateG = 0.140
  const hueRotateB = 0.283
  const cos = Math.cos(angle * Math.PI / 180)
  const sin = Math.sin(angle * Math.PI / 180)

  matrix[0] = lumR + (1 - lumR) * cos - (lumR * sin)
  matrix[1] = lumG - (lumG * cos) - (lumG * sin)
  matrix[2] = lumB - (lumB * cos) + (1 - lumB) * sin
  matrix[3] = lumR - (lumR * cos) + hueRotateR * sin
  matrix[4] = lumG + (1 - lumG) * cos + hueRotateG * sin
  matrix[5] = lumB - (lumB * cos) - (hueRotateB * sin)
  matrix[6] = lumR - (lumR * cos) - ((1 - lumR) * sin)
  matrix[7] = lumG - (lumG * cos) + lumG * sin
  matrix[8] = lumB + (1 - lumB) * cos + lumB * sin

  return [
    clamp(matrix[0] * r + matrix[1] * g + matrix[2] * b),
    clamp(matrix[3] * r + matrix[4] * g + matrix[5] * b),
    clamp(matrix[6] * r + matrix[7] * g + matrix[8] * b)
  ]

  function clamp (num) {
    return Math.ceil(Math.max(0, Math.min(255, num)))
  }
}
<commit_msg>fix: check if dummyNode is on the targetNode<commit_after>'use strict'

/**
 * This class is used by the `CanvasDrawer` in `MinimapElement` to
 * read the styles informations (color and background-color) from the DOM to use when rendering
 * the `Minimap`.
 *
 * It attaches a dummyNode to the targetNode, renders them, and finds the computed style back.
 * TODO: find a better way to get the token colors
 */
export default class DOMStylesReader {
  constructor () {
    /**
     * The cache object
     * @access private
     */
    this.domStylesCache = new Map()

    /**
     * @access private
     */
    this.dummyNode = undefined

    // used to check if the dummyNode is on the current targetNode
    this.targetNode = undefined

    /**
     * Set to true once tokenized
     * @access private
     * unused
     */
    // this.hasTokenizedOnce = false
  }

  /**
   * Returns the computed values for the given property and scope in the DOM.
   *
   * This function insert a dummy element in the DOM to compute
   * its style, return the specified property, and clear the content of the
   * dummy element.
   *
   * @param  {Array<string>} scopes a list of classes reprensenting the scope
   *                                to build
   * @param  {string} property the name of the style property to compute
   * @param  {Node} targetNode
   * @param  {boolean} getFromCache whether to cache the computed value or not
   * @return {string} the computed property's value
   * used in CanvasDrawer
   */
  retrieveStyleFromDom (scopes, property, targetNode, getFromCache) {
    if (!scopes.length) { return '' } // no scopes
    const key = scopes.join(' ')
    let cachedData = this.domStylesCache.get(key)

    if (cachedData !== undefined) {
      if (getFromCache) { // if should get the value from the cache
        const value = cachedData[property]
        if (value !== undefined) {
          // value exists
          return value
        } // value not in the cache - get fresh value
      } // don't use cache - get fresh value
    } else {
      // key did not exist. create it
      cachedData = {}
    }

    this.ensureDummyNodeExistence(targetNode)

    let parent = this.dummyNode
    for (let i = 0, len = scopes.length; i < len; i++) {
      const scope = scopes[i]
      const node = document.createElement('span')
      node.className = scope.replace(dotRegexp, ' ') // TODO why replace is needed?
      parent.appendChild(node)
      parent = node
    }

    const style = window.getComputedStyle(parent)
    let value = style.getPropertyValue(property)

    // rotate hue if webkit-filter available
    const filter = style.getPropertyValue('-webkit-filter')
    if (filter.indexOf('hue-rotate') > -1) {
      value = rotateHue(value, filter)
    }

    if (value !== '') {
      cachedData[property] = value
      this.domStylesCache.set(key, cachedData)
    }

    this.dummyNode.innerHTML = ''
    return value
  }

  /**
   * Creates a DOM node container for all the operations that need to read
   * styles properties from DOM.
   * @param {Node} targetNode
   *
   * @access private
   */
  ensureDummyNodeExistence (targetNode) {
    if (this.targetNode !== targetNode || this.dummyNode === undefined) {
      this.dummyNode = document.createElement('span')
      this.dummyNode.style.visibility = 'hidden'

      // attach to the target node
      targetNode.appendChild(this.dummyNode)
      this.targetNode = targetNode
    }
  }

  /**
   * Invalidates the cache by emptying the cache object.
   * used in MinimapElement
   */
  invalidateDOMStylesCache () {
    this.domStylesCache.clear()
  }

  /**
   * Invalidates the cache only for the first tokenization event.
   *
   * @access private
   * unused
   */
  /*
  invalidateIfFirstTokenization () {
    if (this.hasTokenizedOnce) { return }
    this.invalidateDOMStylesCache()
    this.hasTokenizedOnce = true
  }
  */
}

//    ##     ## ######## ##       ########  ######## ########   ######
//    ##     ## ##       ##       ##     ## ##       ##     ## ##    ##
//    ##     ## ##       ##       ##     ## ##       ##     ## ##
//    ######### ######   ##       ########  ######   ########   ######
//    ##     ## ##       ##       ##        ##       ##   ##         ##
//    ##     ## ##       ##       ##        ##       ##    ##  ##    ##
//    ##     ## ######## ######## ##        ######## ##     ##  ######

const dotRegexp = /\.+/g
const rgbExtractRegexp = /rgb(a?)\((\d+), (\d+), (\d+)(, (\d+(\.\d+)?))?\)/
const hueRegexp = /hue-rotate\((\d+)deg\)/

/**
 * Computes the output color of `value` with a rotated hue defined
 * in `filter`.
 *
 * @param  {string} value the CSS color to apply the rotation on
 * @param  {string} filter the CSS hue rotate filter declaration
 * @return {string} the rotated CSS color
 * @access private
 */
function rotateHue (value, filter) {
  const match = value.match(rgbExtractRegexp)
  let [, , r, g, b, , a] = match

  let [, hue] = filter.match(hueRegexp)

  ;[r, g, b, a, hue] = [r, g, b, a, hue].map(Number)
  ;[r, g, b] = rotate(r, g, b, hue)

  if (isNaN(a)) {
    return `rgb(${r}, ${g}, ${b})`
  } else {
    return `rgba(${r}, ${g}, ${b}, ${a})`
  }
}

/**
 * Computes the hue rotation on the provided `r`, `g` and `b` channels
 * by the amount of `angle`.
 *
 * @param  {number} r the red channel of the color to rotate
 * @param  {number} g the green channel of the color to rotate
 * @param  {number} b the blue channel of the color to rotate
 * @param  {number} angle the angle to rotate the hue with
 * @return {Array<number>} the rotated color channels
 * @access private
 */
function rotate (r, g, b, angle) {
  const matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]
  const lumR = 0.2126
  const lumG = 0.7152
  const lumB = 0.0722
  const hueRotateR = 0.143
  const hueRotateG = 0.140
  const hueRotateB = 0.283
  const cos = Math.cos(angle * Math.PI / 180)
  const sin = Math.sin(angle * Math.PI / 180)

  matrix[0] = lumR + (1 - lumR) * cos - (lumR * sin)
  matrix[1] = lumG - (lumG * cos) - (lumG * sin)
  matrix[2] = lumB - (lumB * cos) + (1 - lumB) * sin
  matrix[3] = lumR - (lumR * cos) + hueRotateR * sin
  matrix[4] = lumG + (1 - lumG) * cos + hueRotateG * sin
  matrix[5] = lumB - (lumB * cos) - (hueRotateB * sin)
  matrix[6] = lumR - (lumR * cos) - ((1 - lumR) * sin)
  matrix[7] = lumG - (lumG * cos) + lumG * sin
  matrix[8] = lumB + (1 - lumB) * cos + lumB * sin

  return [
    clamp(matrix[0] * r + matrix[1] * g + matrix[2] * b),
    clamp(matrix[3] * r + matrix[4] * g + matrix[5] * b),
    clamp(matrix[6] * r + matrix[7] * g + matrix[8] * b)
  ]

  function clamp (num) {
    return Math.ceil(Math.max(0, Math.min(255, num)))
  }
}
<|endoftext|>


<commit_before>'use babel';

import { CompositeDisposable, Point, Range } from 'atom';

class TableCell {
  constructor(raw, content) {
    this.raw     = raw;
    this.content = content;
  }

  copy() {
    return new TableCell(this.raw, this.content);
  }
}

class TableRow {
  constructor(cells, margin) {
    this.cells  = cells;
    this.margin = margin;
  }

  copy() {
    return new TableCell(
      this.cells.map(cell => cell.copy()),
      this.margin
    );
  }
}

class Table {
  constructor(rows, range) {
    this.rows  = rows;
    this.range = range;
  }

  copy() {
    return new Table(
      this.rows.map(row => row.copy()),
      this.range.copy()
    );
  }

  computeFocus(pos) {
    const focusRow = pos.row - this.range.start.row;
    const row = this.rows[focusRow];
    if (!row) {
      return new Focus(new Point(0, 0), 0);
    }
    if (row.margin + 1 > pos.column) {
      return new Focus(new Point(focusRow, -1), pos.column);
    }
    const ws = row.cells.map(cell => cell.raw.length);
    let c           = row.margin + 1;
    let offset      = pos.column - (row.margin + 1);
    let focusColumn = 0;
    for (; focusColumn < ws.length; focusColumn++) {
      c += ws[focusColumn] + 1;
      if (c > pos.column) {
        break;
      }
      offset -= ws[focusColumn] + 1;
    }
    return new Focus(new Point(focusRow, focusColumn), offset);
  }
}

class Focus {
  constructor(pos, offset) {
    this.pos    = pos;
    this.offset = offset;
  }

  copy() {
    return new Focus(this.pos.copy(), this.offset);
  }
}

export default class EditorController {
  constructor(editor) {
    this.editor = editor;
    this.activeGrammars = atom.config.get('markdown-table-editor.grammars');

    this.updateView();

    this.editorSub = new CompositeDisposable();

    this.editorSub.add(this.editor.onDidChangeGrammar(() => {
      this.updateView();
    }));

    this.editorSub.add(this.editor.onDidAddCursor(() => {
      this.updateView();
    }));
    this.editorSub.add(this.editor.onDidRemoveCursor(() => {
      this.updateView();
    }));
    this.editorSub.add(this.editor.onDidChangeCursorPosition(event => {
      if (event.newBufferPosition.row !== event.oldBufferPosition.row) {
        this.updateView();
      }
    }));
    this.editorSub.add(this.editor.onDidStopChanging(() => {
      this.updateView();
    }));

    this.configSub = new CompositeDisposable();
    this.configSub.add(atom.config.observe(
      'markdown-table-editor.grammars',
      grammars => {
        this.activeGrammars = grammars;
        this.updateView();
      }
    ));
  }

  isActiveGrammar() {
    const grammar = this.editor.getGrammar().scopeName;
    return this.activeGrammars.indexOf(grammar) >= 0;
  }

  isInTable() {
    if (this.editor.hasMultipleCursors()) {
      return false;
    }
    const pos  = this.editor.getCursorBufferPosition();
    const line = this.editor.lineTextForBufferRow(pos.row);
    return line.trimLeft()[0] === '|';
  }

  updateView() {
    if (this.isActiveGrammar() && this.isInTable()) {
      this.editor.element.classList.add('markdown-table-editor-active');
    }
    else {
      this.editor.element.classList.remove('markdown-table-editor-active');
    }
  }

  findTable() {
    if (!this.isInTable()) {
      return undefined;
    }
    const pos    = this.editor.getCursorBufferPosition();
    const maxRow = this.editor.getLineCount() - 1;

    const rows = [];
    const range = new Range(
      new Point(pos.row, 0),
      new Point(pos.row, 0)
    );
    {
      const line = this.editor.lineTextForBufferRow(pos.row);
      const row  = readTableRow(line);
      if (!row) {
        return undefined;
      }
      rows.push(row);
      range.end.column = line.length;
    }
    for (let r = pos.row - 1; r >= 0; r--) {
      const line = this.editor.lineTextForBufferRow(r);
      const row  = readTableRow(line);
      if (!row) {
        break;
      }
      rows.unshift(row);
      range.start.row = r;
    }

    for (let r = pos.row + 1; r <= maxRow; r++) {
      const line = this.editor.lineTextForBufferRow(r);
      const row  = readTableRow(line);
      if (!row) {
        break;
      }
      rows.push(row);
      range.end.row    = r;
      range.end.column = line.length;
    }

    const table = new Table(rows, range);
    const focus = table.computeFocus(pos);

    return { table, focus };
  }

  nextCell() {
    if (!this.isInTable()) {
      return;
    }
    // TODO
  }

  previousCell() {
    if (!this.isInTable()) {
      return;
    }
    // TODO
  }

  destroy() {
    this.editorSub.dispose();
    this.configSub.dispose();
  }
}

function readBackquotes(str) {
  let i = 0;
  for (; i < str.length; i++) {
    if (str[i] !== '`') {
      break;
    }
  }
  return [str.substr(0, i), str.substr(i)];
}

function splitCells(str) {
  const cells = [];
  let rest = str;
  let buf  = '';
  while (rest !== '') {
    switch (rest[0]) {
    case '`':
      {
        const start = readBackquotes(rest);
        const quote = start[0];
        let rest1   = start[1];
        let content = '';
        let closed  = false;
        while (rest1 !== '') {
          if (rest1[0] === '`') {
            const end = readBackquotes(rest1);
            if (end[0].length === quote.length) {
              closed = true;
              rest1  = end[1];
              break;
            }
            else {
              content += end[0];
              rest1    = end[1];
            }
          }
          else {
            content += rest1[0];
            rest1    = rest1.substr(1);
          }
        }
        if (closed) {
          buf += quote + content + quote;
          rest = rest1;
        }
        else {
          buf += '`';
          rest = rest.substr(1);
        }
      }
      break;
    case '\\':
      if (rest.length >= 2) {
        buf += rest.substr(0, 2);
        rest = rest.substr(2);
      }
      else {
        buf += '\\';
        rest = rest.substr(1);
      }
      break;
    case '|':
      cells.push(buf);
      buf  = '';
      rest = rest.substr(1);
      break;
    default:
      buf += rest[0];
      rest = rest.substr(1);
    }
  }
  cells.push(buf);
  return cells;
}

function readTableRow(line) {
  if (line.trimLeft()[0] !== '|') {
    return undefined;
  }
  let rawCells = splitCells(line);
  const margin = rawCells[0].length;
  rawCells = rawCells[rawCells.length - 1].trim() === ''
    ? rawCells.slice(1, rawCells.length - 1)
    : rawCells.slice(1);
  const cells = rawCells.map(cell => new TableCell(cell, cell.trim()));
  return new TableRow(cells, margin);
}
<commit_msg>Fix wrong constructor<commit_after>'use babel';

import { CompositeDisposable, Point, Range } from 'atom';

class TableCell {
  constructor(raw, content) {
    this.raw     = raw;
    this.content = content;
  }

  copy() {
    return new TableCell(this.raw, this.content);
  }
}

class TableRow {
  constructor(cells, margin) {
    this.cells  = cells;
    this.margin = margin;
  }

  copy() {
    return new TableRow(
      this.cells.map(cell => cell.copy()),
      this.margin
    );
  }
}

class Table {
  constructor(rows, range) {
    this.rows  = rows;
    this.range = range;
  }

  copy() {
    return new Table(
      this.rows.map(row => row.copy()),
      this.range.copy()
    );
  }

  computeFocus(pos) {
    const focusRow = pos.row - this.range.start.row;
    const row = this.rows[focusRow];
    if (!row) {
      return new Focus(new Point(0, 0), 0);
    }
    if (row.margin + 1 > pos.column) {
      return new Focus(new Point(focusRow, -1), pos.column);
    }
    const ws = row.cells.map(cell => cell.raw.length);
    let c           = row.margin + 1;
    let offset      = pos.column - (row.margin + 1);
    let focusColumn = 0;
    for (; focusColumn < ws.length; focusColumn++) {
      c += ws[focusColumn] + 1;
      if (c > pos.column) {
        break;
      }
      offset -= ws[focusColumn] + 1;
    }
    return new Focus(new Point(focusRow, focusColumn), offset);
  }
}

class Focus {
  constructor(pos, offset) {
    this.pos    = pos;
    this.offset = offset;
  }

  copy() {
    return new Focus(this.pos.copy(), this.offset);
  }
}

export default class EditorController {
  constructor(editor) {
    this.editor = editor;
    this.activeGrammars = atom.config.get('markdown-table-editor.grammars');

    this.updateView();

    this.editorSub = new CompositeDisposable();

    this.editorSub.add(this.editor.onDidChangeGrammar(() => {
      this.updateView();
    }));

    this.editorSub.add(this.editor.onDidAddCursor(() => {
      this.updateView();
    }));
    this.editorSub.add(this.editor.onDidRemoveCursor(() => {
      this.updateView();
    }));
    this.editorSub.add(this.editor.onDidChangeCursorPosition(event => {
      if (event.newBufferPosition.row !== event.oldBufferPosition.row) {
        this.updateView();
      }
    }));
    this.editorSub.add(this.editor.onDidStopChanging(() => {
      this.updateView();
    }));

    this.configSub = new CompositeDisposable();
    this.configSub.add(atom.config.observe(
      'markdown-table-editor.grammars',
      grammars => {
        this.activeGrammars = grammars;
        this.updateView();
      }
    ));
  }

  isActiveGrammar() {
    const grammar = this.editor.getGrammar().scopeName;
    return this.activeGrammars.indexOf(grammar) >= 0;
  }

  isInTable() {
    if (this.editor.hasMultipleCursors()) {
      return false;
    }
    const pos  = this.editor.getCursorBufferPosition();
    const line = this.editor.lineTextForBufferRow(pos.row);
    return line.trimLeft()[0] === '|';
  }

  updateView() {
    if (this.isActiveGrammar() && this.isInTable()) {
      this.editor.element.classList.add('markdown-table-editor-active');
    }
    else {
      this.editor.element.classList.remove('markdown-table-editor-active');
    }
  }

  findTable() {
    if (!this.isInTable()) {
      return undefined;
    }
    const pos    = this.editor.getCursorBufferPosition();
    const maxRow = this.editor.getLineCount() - 1;

    const rows = [];
    const range = new Range(
      new Point(pos.row, 0),
      new Point(pos.row, 0)
    );
    {
      const line = this.editor.lineTextForBufferRow(pos.row);
      const row  = readTableRow(line);
      if (!row) {
        return undefined;
      }
      rows.push(row);
      range.end.column = line.length;
    }
    for (let r = pos.row - 1; r >= 0; r--) {
      const line = this.editor.lineTextForBufferRow(r);
      const row  = readTableRow(line);
      if (!row) {
        break;
      }
      rows.unshift(row);
      range.start.row = r;
    }

    for (let r = pos.row + 1; r <= maxRow; r++) {
      const line = this.editor.lineTextForBufferRow(r);
      const row  = readTableRow(line);
      if (!row) {
        break;
      }
      rows.push(row);
      range.end.row    = r;
      range.end.column = line.length;
    }

    const table = new Table(rows, range);
    const focus = table.computeFocus(pos);

    return { table, focus };
  }

  nextCell() {
    if (!this.isInTable()) {
      return;
    }
    // TODO
  }

  previousCell() {
    if (!this.isInTable()) {
      return;
    }
    // TODO
  }

  destroy() {
    this.editorSub.dispose();
    this.configSub.dispose();
  }
}

function readBackquotes(str) {
  let i = 0;
  for (; i < str.length; i++) {
    if (str[i] !== '`') {
      break;
    }
  }
  return [str.substr(0, i), str.substr(i)];
}

function splitCells(str) {
  const cells = [];
  let rest = str;
  let buf  = '';
  while (rest !== '') {
    switch (rest[0]) {
    case '`':
      {
        const start = readBackquotes(rest);
        const quote = start[0];
        let rest1   = start[1];
        let content = '';
        let closed  = false;
        while (rest1 !== '') {
          if (rest1[0] === '`') {
            const end = readBackquotes(rest1);
            if (end[0].length === quote.length) {
              closed = true;
              rest1  = end[1];
              break;
            }
            else {
              content += end[0];
              rest1    = end[1];
            }
          }
          else {
            content += rest1[0];
            rest1    = rest1.substr(1);
          }
        }
        if (closed) {
          buf += quote + content + quote;
          rest = rest1;
        }
        else {
          buf += '`';
          rest = rest.substr(1);
        }
      }
      break;
    case '\\':
      if (rest.length >= 2) {
        buf += rest.substr(0, 2);
        rest = rest.substr(2);
      }
      else {
        buf += '\\';
        rest = rest.substr(1);
      }
      break;
    case '|':
      cells.push(buf);
      buf  = '';
      rest = rest.substr(1);
      break;
    default:
      buf += rest[0];
      rest = rest.substr(1);
    }
  }
  cells.push(buf);
  return cells;
}

function readTableRow(line) {
  if (line.trimLeft()[0] !== '|') {
    return undefined;
  }
  let rawCells = splitCells(line);
  const margin = rawCells[0].length;
  rawCells = rawCells[rawCells.length - 1].trim() === ''
    ? rawCells.slice(1, rawCells.length - 1)
    : rawCells.slice(1);
  const cells = rawCells.map(cell => new TableCell(cell, cell.trim()));
  return new TableRow(cells, margin);
}
<|endoftext|>


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

process.title	= 'picanha';

var fs 			= require('fs'),
	cmd		 	= process.argv.slice(2);
	Creator		= require('./creator'),
	path 		= require('path'),
	Promise 	= require('promise'),
	utils		= require('./utils'),
	ncp 		= require('ncp').ncp,
	clientpath	= process.cwd(),
	libpath 	= process.mainModule.paths[2] + '/picanhajs';

if (cmd[0] === 'beginbbq') {

	var creator = new Creator({ libpath: libpath, clientpath: clientpath });
	creator.create(utils.recursiveCopy);

} else {

	var fm				= require('front-matter'),
		marked			= require('marked'),
		highlight		= require('highlight.js'),
		handlebars		= require('handlebars'),
		Builder			= require('./builder'),
		defaultparams	= require('./picanha.json'),
		customParams, parameters;

	try {
		customParams	= require(clientpath + '/picanha.json');
		parameters		= utils.extend(defaultparams, customParams);
	} catch(e) {
		parameters		= defaultparams;
	}

	console.log(parameters);

	utils.registerPartials( handlebars, path.join(parameters.template.path, parameters.template.partials) );

	/**
	 * @TODO maybe this can be reallocated to the .json config file too
	 */
	marked.setOptions({
		renderer	: new marked.Renderer(),
		gfm			: true,
		tables		: true,
		breaks		: false,
		pedantic	: false,
		sanitize	: true,
		smartLists	: true,
		smartypants : false,
		highlight 	: function (code) {
			return highlight.highlightAuto(code).value;
		}
	});

	var builder = new Builder({ libpath: libpath, clientpath: clientpath });

	builder.setParameters(parameters);

	builder.setFrontMatterCompiler(fm);

	builder.setCopyFn(ncp);

	builder.setPostCompiler(marked);

	builder.setTemplateCompiler(function( data ) {
		var tpl = handlebars.compile(data);
		return tpl;
	});

	builder.execute();
}<commit_msg>Remove log<commit_after>#!/usr/bin/env node

process.title	= 'picanha';

var fs 			= require('fs'),
	cmd		 	= process.argv.slice(2);
	Creator		= require('./creator'),
	path 		= require('path'),
	Promise 	= require('promise'),
	utils		= require('./utils'),
	ncp 		= require('ncp').ncp,
	clientpath	= process.cwd(),
	libpath 	= process.mainModule.paths[2] + '/picanhajs';

if (cmd[0] === 'beginbbq') {

	var creator = new Creator({ libpath: libpath, clientpath: clientpath });
	creator.create(utils.recursiveCopy);

} else {

	var fm				= require('front-matter'),
		marked			= require('marked'),
		highlight		= require('highlight.js'),
		handlebars		= require('handlebars'),
		Builder			= require('./builder'),
		defaultparams	= require('./picanha.json'),
		customParams, parameters;

	try {
		customParams	= require(clientpath + '/picanha.json');
		parameters		= utils.extend(defaultparams, customParams);
	} catch(e) {
		parameters		= defaultparams;
	}

	utils.registerPartials( handlebars, path.join(parameters.template.path, parameters.template.partials) );

	/**
	 * @TODO maybe this can be reallocated to the .json config file too
	 */
	marked.setOptions({
		renderer	: new marked.Renderer(),
		gfm			: true,
		tables		: true,
		breaks		: false,
		pedantic	: false,
		sanitize	: true,
		smartLists	: true,
		smartypants : false,
		highlight 	: function (code) {
			return highlight.highlightAuto(code).value;
		}
	});

	var builder = new Builder({ libpath: libpath, clientpath: clientpath });

	builder.setParameters(parameters);

	builder.setFrontMatterCompiler(fm);

	builder.setCopyFn(ncp);

	builder.setPostCompiler(marked);

	builder.setTemplateCompiler(function( data ) {
		var tpl = handlebars.compile(data);
		return tpl;
	});

	builder.execute();
}<|endoftext|>


<commit_before>// Load in our dependencies
var assert = require('assert');
var app = require('electron').app;
var BrowserWindow = require('electron').BrowserWindow;

// Find the argv matching our URL pattern
// DEV: If this becomes complex, use `commander` with `allowUnknownOption`
var urlArgs = process.argv.filter(function matchUrlPattern (arg) {
  return /^--url=/.test(arg);
});
assert.strictEqual(urlArgs.length, 1, 'Expected only 1 `--url=` parameter but received ' + urlArgs.length);
var url = urlArgs[0].replace('--url=', '');

// When all windows are closed, exit out
app.on('window-all-closed', function handleWindowsClosed () {
  app.quit();
});

// When Electron is done loading, launch our applicaiton
app.on('ready', function handleReady () {
  // Create our browser window
  // TODO: Where does "Chrome" version come from
  var browserWindow = new BrowserWindow({show: false});
  browserWindow.loadURL(url);
});
<commit_msg>Fixed up user agent<commit_after>// Load in our dependencies
var assert = require('assert');
var app = require('electron').app;
var BrowserWindow = require('electron').BrowserWindow;

// Find the argv matching our URL pattern
// DEV: If this becomes complex, use `commander` with `allowUnknownOption`
var urlArgs = process.argv.filter(function matchUrlPattern (arg) {
  return /^--url=/.test(arg);
});
assert.strictEqual(urlArgs.length, 1, 'Expected only 1 `--url=` parameter but received ' + urlArgs.length);
var url = urlArgs[0].replace('--url=', '');

// When all windows are closed, exit out
app.on('window-all-closed', function handleWindowsClosed () {
  app.quit();
});

// When Electron is done loading, launch our applicaiton
app.on('ready', function handleReady () {
  // Create our browser window
  // TODO: Allow user to customize show via CLI flag
  var browserWindow = new BrowserWindow({show: false});
  browserWindow.loadURL(url, {
    // Set a custom User-Agent for better logging
    // DEV: Default would be "Chrome 47.0.2526 (Linux 0.0.0)"
    //   https://github.com/karma-runner/karma/blob/v0.13.21/lib/browser.js#L25
    //   https://github.com/karma-runner/karma/blob/v0.13.21/lib/helper.js#L7-L11
    // Example: Electron 0.36.9 (Node.js 5.1.1)
    userAgent: 'Electron ' + process.versions.electron + ' (Node ' + process.versions.node + ')'
  });
});
<|endoftext|>