<commit_before>/**
 * MIT License
 *
 * Copyright (c) 2017 Alisdair Smyth
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
**/
var ambi = require('node-ambiclimate');
var Service, Characteristic;

module.exports = function(homebridge) {
  Service        = homebridge.hap.Service;
  Characteristic = homebridge.hap.Characteristic;
  homebridge.registerAccessory("homebridge-ambiclimate-thermostat", "AmbiClimateThermostat", AmbiClimate);
}

function AmbiClimate(log, config) {
  this.log                    = log;
  this.name                   = config.name;
  this.settings               = {};
  this.settings.room_name     = config.roomName;
  this.settings.location_name = config.locationName;

  // Default temperature in Celsius
  this.default                = {};
  this.default.temperature    = 22;

  this.client = new ambi(config.clientId, config.clientSecret, config.username, config.password);

  // Object to maintain the state of the device.  This is done as there is
  // not official API to get this information.  If the Ambi Climate state is
  // changed through an alternative channel, that change will not be reflected
  // within Homekit
  this.state              = {};
  this.state.on           = false;

  this.thermostatService  = new Service.Thermostat(this.name);
  this.fanService         = new Service.Fan(this.name);
  this.informationService = new Service.AccessoryInformation();
}

AmbiClimate.prototype = {
  getCurrentHeatingCoolingState: function(callback) {
    //
    // Map retrieved Ambi Climate mode to Homekit Heating, Cooling state.
    // Comfort mode can be heating or cooling, but as there is no indicator
    // of operating state this Ambi Climate mode is statically mapped to COOL.
    // Temperature mode is mapped by comparing current temperature with the set
    // temperature.
    //
    this.client.mode(this.settings)
      .then(  (data) => {
        var mode     = data.mode;
        var modeTemp = data.value;

        this.log("getCurrentHeatingCoolingState: Current Ambi Climate mode is " + mode);
        switch(mode) {
          case "Comfort":
          case "Away_Temperature_Upper":
            this.log("getCurrentHeatingCoolingState: Current state is COOL");
            callback(null, Characteristic.CurrentHeatingCoolingState.COOL);
            break;
          case "Away_Temperature_Lower":
            this.log("getCurrentHeatingCoolingState: Current state is HEAT");
            callback(null, Characteristic.CurrentHeatingCoolingState.HEAT);
            break;
          case "Temperature":
            this.client.sensor_temperature(this.settings)
              .then( (data) => {
                var temp = data[0].value;

                if (temp > modeTemp) {
                  this.log("getCurrentHeatingCoolingState: Current state is COOL");
                  callback(null, Characteristic.CurrentHeatingCoolingState.COOL);
                } else {
                  this.log("getCurrentHeatingCoolingState: Current state is HEAT");
                  callback(null, Characteristic.CurrentHeatingCoolingState.HEAT);
                }
              })
              .catch ( (reason) => {
                callback(reason);
              })
              break;
          default:
            this.log("getCurrentHeatingCoolingState: Current state is OFF");
            callback(null, Characteristic.CurrentHeatingCoolingState.OFF);
            break;
        }
      })
      .catch( (reason) => {
        callback(reason);
      })
    },

  //
  // Target Heating Cooling State has the additional enumeration of AUTO.
  // If the Ambi Climate is in the mode of Comfort or Temperature, the
  // Target Heating Cooling State is set to AUTO.
  //
  getTargetHeatingCoolingState: function(callback) {
    this.client.mode(this.settings)
      .then( (data) => {
        var mode     = data.mode;
        var modeTemp = data.value;

        this.log("getTargetHeatingCoolingState: Current Ambi Climate mode is " + mode);
        switch(mode) {
          case "Comfort":
          case "Temperature":
            this.log("getTargetHeatingCoolingState: Current state is AUTO");
            callback(null, Characteristic.TargetHeatingCoolingState.AUTO);
            break;
          case "Away_Temperature_Upper":
            this.log("getTargetHeatingCoolingState: Current state is COOL");
            callback(null, Characteristic.TargetHeatingCoolingState.COOL);
            break;
          case "Away_Temperature_Lower":
            this.log("getTargetHeatingCoolingState: Current state is HEAT");
            callback(null, Characteristic.TargetHeatingCoolingState.HEAT);
            break;
          default:
            this.log("getTargetHeatingCoolingState: Current state is OFF");
            callback(null, Characteristic.TargetHeatingCoolingState.OFF);
            break;
        }
      })
      .catch( (reason) => {
        callback(reason);
      })
  },

  //
  // AUTO -> Comfort
  // COOL -> Away Temperature Upper
  // HEAT -> Away Temperature Lower
  //
  setTargetHeatingCoolingState: function(value, callback) {
    this.log("setTargetHeatingCoolingState: Received value - " + value);
    switch(value) {
      case Characteristic.TargetHeatingCoolingState.AUTO:
        this.log("setTargetHeatingCoolingState: Setting to Comfort");
        this.client.comfort(this.settings)
          .then( (body) => {
            callback(null, body);
          })
          .catch( (reason) => {
            callback(reason);
          });
        break;
      case Characteristic.TargetHeatingCoolingState.COOL:
        this.settings.value = this.default.temperature;
        this.log("setTargetHeatingCoolingState: Setting to Away Temperature Upper @ "+this.settings.value);
        this.client.away_temperature_upper(this.settings)
          .then( (body) => {
            callback(null, body);
          })
          .catch( (reason) => {
            callback(reason);
          });
        break;
      case Characteristic.TargetHeatingCoolingState.HEAT:
        this.settings.value = this.default.temperature;
        this.log("setTargetHeatingCoolingState: Setting to Away Temperature Lower @ "+this.settings.value);
        this.client.away_temperature_lower(this.settings)
          .then( (body) => {
            callback(null, body);
          })
          .catch( (reason) => {
            callback(reason);
          });
        break;
      default:
        this.log("setTargetHeatingCoolingState: Setting to Off");
        this.client.off(this.settings)
          .then( (body) => {
            callback(null, body);
          })
          .catch( (reason) => {
            callback(reason);
          });
        break;
    }
    this.thermostatService.setCharacteristic(Characteristic.TargetTemperature, this.default.temperature);
  },

  getCurrentTemperature: function(callback) {
    this.client.sensor_temperature(this.settings)
      .then( (data) => {
        callback(null, data[0].value)
        this.log("getCurrentTemperature: Current temperature - " + data[0].value);
      })
      .catch( (reason) => {
        callback(reason)
      });
  },

  getTargetTemperature: function(callback) {
    this.client.mode(this.settings)
      .then( (data) => {
        var mode     = data.mode;
        var modeTemp = data.value;

        switch(mode) {
          case "Temperature":
          case "Away_Temperature_Upper":
          case "Away_Temperature_Lower":
            callback(null, modeTemp);
            this.log("getTargetTemperature: Target temperature - " + modeTemp);
            break;
          default:
            callback(null, this.default.temperature);
            this.log("getTargetTemperature: Target temperature - "+this.default.temperature);
            break;
        }
      })
      .catch( (reason) => {
        callback(reason);
      });
  },

  //
  // Set the target temperature using the current Ambi Climate mode. A target
  // temperature is only relevant for Temperature, and Away Temperature modes.
  //
  setTargetTemperature: function(value, callback) {
    this.client.mode(this.settings)
      .then( (data) => {
        var mode     = data.mode;

        this.settings.value = value;
        switch(mode) {
          case "Temperature":
            this.client.temperature(this.settings)
              .then( (body) => {
                callback(null, body);
                this.log("setTargetTemperature: Temperature mode - " + value);
              })
              .catch( (reason) => {
                callback(reason);
              });
              break;
          case "Away_Temperature_Upper":
            this.client.away_temperature_upper(this.settings)
              .then( (body) => {
                callback(null, body);
                this.log("setTargetTemperature: Away Temperature Upper mode - " + value);
              })
              .catch( (reason) => {
                callback(reason);
              });
            break;
          case "Away_Temperature_Lower":
            this.client.away_temperature_lower(this.settings)
              .then( (body) => {
                callback(null, body);
                this.log("setTargetTemperature: Away Temperature Lower mode - " + value);
              })
              .catch( (reason) => {
                callback(reason);
              });
            break;
          default:
            callback(null);
            break;
        }

      })
      .catch( (reason) => {
        callback(reason);
      });
  },

  getTemperatureDisplayUnits: function(callback) {
    callback(null, Characteristic.TemperatureDisplayUnits.CELSIUS);
  },

  getCurrentRelativeHumidity: function(callback) {
    this.client.sensor_humidity(this.settings)
      .then( (data) => {
        callback(null, data[0].value);
        this.log("getCurrentRelativeHumidity: Current humidity - " + data[0].value);
      })
      .catch ( (reason) => {
        callback(reason);
      });
  },
  getActive: function(callback) {
    this.client.mode(this.settings)
      .then( (data) => {
        switch (data.mode) {
          case "Off":
          case "Manual":
            callback(null, Characteristic.Active.INACTIVE);
            break;
          default:
            callback(null, Characteristic.Active.ACTIVE);
            break;
        }
      })
      .catch( (reason) => {
        callback(reason);
      })
  },
  // The states returned by appliance_states do not reflect the current state
  // of the air conditioner if it is off.  As a consequence we first check the
  // Ambi Climate mode, and only retrieve appliance states when the mode is
  // neither Off or Manual.
  getRotationSpeed: function(callback) {
    this.client.mode(this.settings)
      .then( (data) => {
        switch (data.mode) {
          case "Off":
          case "Manual":
            callback(null, 0);
            break;
          default:
            this.client.appliance_states(this.settings)
              .then( (data) => {
                var rotationSpeed;
                switch (data.data[0].fan) {
                  case "High":
                    rotationSpeed = 100;
                    break;
                  case "Med-High":
                    rotationSpeed = 75;
                    break;
                  case "Auto":
                  case "Med":
                    rotationSpeed = 50;
                    break;
                  case "Quiet":
                  case "Med-Low":
                    rotationSpeed = 38;
                    break;
                  case "Low":
                    rotationSpeed = 25;
                    break;
                  default:
                    rotationSpeed = 0;
                    break;
                }
                callback(null, rotationSpeed);
              })
            break;
        }
      })
      .catch( (reason) => {
        callback(reason);
      })
  },

  //
  // Services
  //
  getServices: function () {
    this.thermostatService.getCharacteristic(Characteristic.CurrentHeatingCoolingState)
      .on('get', this.getCurrentHeatingCoolingState.bind(this));

    this.thermostatService.getCharacteristic(Characteristic.TargetHeatingCoolingState)
      .on('get', this.getTargetHeatingCoolingState.bind(this))
      .on('set', this.setTargetHeatingCoolingState.bind(this));

    this.thermostatService.getCharacteristic(Characteristic.CurrentTemperature)
      .on('get', this.getCurrentTemperature.bind(this));

    this.thermostatService.getCharacteristic(Characteristic.TargetTemperature)
      .on('get', this.getTargetTemperature.bind(this))
      .on('set', this.setTargetTemperature.bind(this));

    this.thermostatService.getCharacteristic(Characteristic.TemperatureDisplayUnits)
      .on('get', this.getTemperatureDisplayUnits.bind(this));

    this.thermostatService.getCharacteristic(Characteristic.CurrentRelativeHumidity)
      .on('get', this.getCurrentRelativeHumidity.bind(this));

    this.fanService.getCharacteristic(Characteristic.On)
      .on('get', function(callback) {
        this.getActive(function(error,data) {
          callback(error, data);
        }.bind(this))
      }.bind(this))

    this.fanService.getCharacteristic(Characteristic.RotationSpeed)
      .on('get', function(callback) {
        this.getRotationSpeed(function(error,data) {
          callback(error, data);
        }.bind(this))
      }.bind(this))

    this.informationService
      .setCharacteristic(Characteristic.Manufacturer, "Ambi Labs")
      .setCharacteristic(Characteristic.Model, "Ambi Climate Thermostat")
      .setCharacteristic(Characteristic.SerialNumber, " ");

    return [this.thermostatService, this.fanService, this.informationService];
  }
}
<commit_msg>Prettier formatting<commit_after>/**
 * MIT License
 *
 * Copyright (c) 2017 Alisdair Smyth
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 **/
var ambi = require("node-ambiclimate");
var Service, Characteristic;

module.exports = function(homebridge) {
  Service = homebridge.hap.Service;
  Characteristic = homebridge.hap.Characteristic;
  homebridge.registerAccessory(
    "homebridge-ambiclimate-thermostat",
    "AmbiClimateThermostat",
    AmbiClimate
  );
};

function AmbiClimate(log, config) {
  this.log = log;
  this.name = config.name;
  this.settings = {};
  this.settings.room_name = config.roomName;
  this.settings.location_name = config.locationName;

  // Default temperature in Celsius
  this.default = {};
  this.default.temperature = 22;

  this.client = new ambi(
    config.clientId,
    config.clientSecret,
    config.username,
    config.password
  );

  // Object to maintain the state of the device.  This is done as there is
  // not official API to get this information.  If the Ambi Climate state is
  // changed through an alternative channel, that change will not be reflected
  // within Homekit
  this.state = {};
  this.state.on = false;

  this.thermostatService = new Service.Thermostat(this.name);
  this.fanService = new Service.Fan(this.name);
  this.informationService = new Service.AccessoryInformation();
}

AmbiClimate.prototype = {
  getCurrentHeatingCoolingState: function(callback) {
    //
    // Map retrieved Ambi Climate mode to Homekit Heating, Cooling state.
    // Comfort mode can be heating or cooling, but as there is no indicator
    // of operating state this Ambi Climate mode is statically mapped to COOL.
    // Temperature mode is mapped by comparing current temperature with the set
    // temperature.
    //
    this.client
      .mode(this.settings)
      .then(data => {
        var mode = data.mode;
        var modeTemp = data.value;

        this.log(
          "getCurrentHeatingCoolingState: Current Ambi Climate mode is " + mode
        );
        switch (mode) {
          case "Comfort":
          case "Away_Temperature_Upper":
            this.log("getCurrentHeatingCoolingState: Current state is COOL");
            callback(null, Characteristic.CurrentHeatingCoolingState.COOL);
            break;
          case "Away_Temperature_Lower":
            this.log("getCurrentHeatingCoolingState: Current state is HEAT");
            callback(null, Characteristic.CurrentHeatingCoolingState.HEAT);
            break;
          case "Temperature":
            this.client
              .sensor_temperature(this.settings)
              .then(data => {
                var temp = data[0].value;

                if (temp > modeTemp) {
                  this.log(
                    "getCurrentHeatingCoolingState: Current state is COOL"
                  );
                  callback(
                    null,
                    Characteristic.CurrentHeatingCoolingState.COOL
                  );
                } else {
                  this.log(
                    "getCurrentHeatingCoolingState: Current state is HEAT"
                  );
                  callback(
                    null,
                    Characteristic.CurrentHeatingCoolingState.HEAT
                  );
                }
              })
              .catch(reason => {
                callback(reason);
              });
            break;
          default:
            this.log("getCurrentHeatingCoolingState: Current state is OFF");
            callback(null, Characteristic.CurrentHeatingCoolingState.OFF);
            break;
        }
      })
      .catch(reason => {
        callback(reason);
      });
  },

  //
  // Target Heating Cooling State has the additional enumeration of AUTO.
  // If the Ambi Climate is in the mode of Comfort or Temperature, the
  // Target Heating Cooling State is set to AUTO.
  //
  getTargetHeatingCoolingState: function(callback) {
    this.client
      .mode(this.settings)
      .then(data => {
        var mode = data.mode;
        var modeTemp = data.value;

        this.log(
          "getTargetHeatingCoolingState: Current Ambi Climate mode is " + mode
        );
        switch (mode) {
          case "Comfort":
          case "Temperature":
            this.log("getTargetHeatingCoolingState: Current state is AUTO");
            callback(null, Characteristic.TargetHeatingCoolingState.AUTO);
            break;
          case "Away_Temperature_Upper":
            this.log("getTargetHeatingCoolingState: Current state is COOL");
            callback(null, Characteristic.TargetHeatingCoolingState.COOL);
            break;
          case "Away_Temperature_Lower":
            this.log("getTargetHeatingCoolingState: Current state is HEAT");
            callback(null, Characteristic.TargetHeatingCoolingState.HEAT);
            break;
          default:
            this.log("getTargetHeatingCoolingState: Current state is OFF");
            callback(null, Characteristic.TargetHeatingCoolingState.OFF);
            break;
        }
      })
      .catch(reason => {
        callback(reason);
      });
  },

  //
  // AUTO -> Comfort
  // COOL -> Away Temperature Upper
  // HEAT -> Away Temperature Lower
  //
  setTargetHeatingCoolingState: function(value, callback) {
    this.log("setTargetHeatingCoolingState: Received value - " + value);
    switch (value) {
      case Characteristic.TargetHeatingCoolingState.AUTO:
        this.log("setTargetHeatingCoolingState: Setting to Comfort");
        this.client
          .comfort(this.settings)
          .then(body => {
            callback(null, body);
          })
          .catch(reason => {
            callback(reason);
          });
        break;
      case Characteristic.TargetHeatingCoolingState.COOL:
        this.settings.value = this.default.temperature;
        this.log(
          "setTargetHeatingCoolingState: Setting to Away Temperature Upper @ " +
            this.settings.value
        );
        this.client
          .away_temperature_upper(this.settings)
          .then(body => {
            callback(null, body);
          })
          .catch(reason => {
            callback(reason);
          });
        break;
      case Characteristic.TargetHeatingCoolingState.HEAT:
        this.settings.value = this.default.temperature;
        this.log(
          "setTargetHeatingCoolingState: Setting to Away Temperature Lower @ " +
            this.settings.value
        );
        this.client
          .away_temperature_lower(this.settings)
          .then(body => {
            callback(null, body);
          })
          .catch(reason => {
            callback(reason);
          });
        break;
      default:
        this.log("setTargetHeatingCoolingState: Setting to Off");
        this.client
          .off(this.settings)
          .then(body => {
            callback(null, body);
          })
          .catch(reason => {
            callback(reason);
          });
        break;
    }
    this.thermostatService.setCharacteristic(
      Characteristic.TargetTemperature,
      this.default.temperature
    );
  },

  getCurrentTemperature: function(callback) {
    this.client
      .sensor_temperature(this.settings)
      .then(data => {
        callback(null, data[0].value);
        this.log(
          "getCurrentTemperature: Current temperature - " + data[0].value
        );
      })
      .catch(reason => {
        callback(reason);
      });
  },

  getTargetTemperature: function(callback) {
    this.client
      .mode(this.settings)
      .then(data => {
        var mode = data.mode;
        var modeTemp = data.value;

        switch (mode) {
          case "Temperature":
          case "Away_Temperature_Upper":
          case "Away_Temperature_Lower":
            callback(null, modeTemp);
            this.log("getTargetTemperature: Target temperature - " + modeTemp);
            break;
          default:
            callback(null, this.default.temperature);
            this.log(
              "getTargetTemperature: Target temperature - " +
                this.default.temperature
            );
            break;
        }
      })
      .catch(reason => {
        callback(reason);
      });
  },

  //
  // Set the target temperature using the current Ambi Climate mode. A target
  // temperature is only relevant for Temperature, and Away Temperature modes.
  //
  setTargetTemperature: function(value, callback) {
    this.client
      .mode(this.settings)
      .then(data => {
        var mode = data.mode;

        this.settings.value = value;
        switch (mode) {
          case "Temperature":
            this.client
              .temperature(this.settings)
              .then(body => {
                callback(null, body);
                this.log("setTargetTemperature: Temperature mode - " + value);
              })
              .catch(reason => {
                callback(reason);
              });
            break;
          case "Away_Temperature_Upper":
            this.client
              .away_temperature_upper(this.settings)
              .then(body => {
                callback(null, body);
                this.log(
                  "setTargetTemperature: Away Temperature Upper mode - " + value
                );
              })
              .catch(reason => {
                callback(reason);
              });
            break;
          case "Away_Temperature_Lower":
            this.client
              .away_temperature_lower(this.settings)
              .then(body => {
                callback(null, body);
                this.log(
                  "setTargetTemperature: Away Temperature Lower mode - " + value
                );
              })
              .catch(reason => {
                callback(reason);
              });
            break;
          default:
            callback(null);
            break;
        }
      })
      .catch(reason => {
        callback(reason);
      });
  },

  getTemperatureDisplayUnits: function(callback) {
    callback(null, Characteristic.TemperatureDisplayUnits.CELSIUS);
  },

  getCurrentRelativeHumidity: function(callback) {
    this.client
      .sensor_humidity(this.settings)
      .then(data => {
        callback(null, data[0].value);
        this.log(
          "getCurrentRelativeHumidity: Current humidity - " + data[0].value
        );
      })
      .catch(reason => {
        callback(reason);
      });
  },
  getActive: function(callback) {
    this.client
      .mode(this.settings)
      .then(data => {
        switch (data.mode) {
          case "Off":
          case "Manual":
            callback(null, Characteristic.Active.INACTIVE);
            break;
          default:
            callback(null, Characteristic.Active.ACTIVE);
            break;
        }
      })
      .catch(reason => {
        callback(reason);
      });
  },
  // The states returned by appliance_states do not reflect the current state
  // of the air conditioner if it is off.  As a consequence we first check the
  // Ambi Climate mode, and only retrieve appliance states when the mode is
  // neither Off or Manual.
  getRotationSpeed: function(callback) {
    this.client
      .mode(this.settings)
      .then(data => {
        switch (data.mode) {
          case "Off":
          case "Manual":
            callback(null, 0);
            break;
          default:
            this.client.appliance_states(this.settings).then(data => {
              var rotationSpeed;
              switch (data.data[0].fan) {
                case "High":
                  rotationSpeed = 100;
                  break;
                case "Med-High":
                  rotationSpeed = 75;
                  break;
                case "Auto":
                case "Med":
                  rotationSpeed = 50;
                  break;
                case "Quiet":
                case "Med-Low":
                  rotationSpeed = 38;
                  break;
                case "Low":
                  rotationSpeed = 25;
                  break;
                default:
                  rotationSpeed = 0;
                  break;
              }
              callback(null, rotationSpeed);
            });
            break;
        }
      })
      .catch(reason => {
        callback(reason);
      });
  },

  //
  // Services
  //
  getServices: function() {
    this.thermostatService
      .getCharacteristic(Characteristic.CurrentHeatingCoolingState)
      .on("get", this.getCurrentHeatingCoolingState.bind(this));

    this.thermostatService
      .getCharacteristic(Characteristic.TargetHeatingCoolingState)
      .on("get", this.getTargetHeatingCoolingState.bind(this))
      .on("set", this.setTargetHeatingCoolingState.bind(this));

    this.thermostatService
      .getCharacteristic(Characteristic.CurrentTemperature)
      .on("get", this.getCurrentTemperature.bind(this));

    this.thermostatService
      .getCharacteristic(Characteristic.TargetTemperature)
      .on("get", this.getTargetTemperature.bind(this))
      .on("set", this.setTargetTemperature.bind(this));

    this.thermostatService
      .getCharacteristic(Characteristic.TemperatureDisplayUnits)
      .on("get", this.getTemperatureDisplayUnits.bind(this));

    this.thermostatService
      .getCharacteristic(Characteristic.CurrentRelativeHumidity)
      .on("get", this.getCurrentRelativeHumidity.bind(this));

    this.fanService.getCharacteristic(Characteristic.On).on(
      "get",
      function(callback) {
        this.getActive(
          function(error, data) {
            callback(error, data);
          }.bind(this)
        );
      }.bind(this)
    );

    this.fanService.getCharacteristic(Characteristic.RotationSpeed).on(
      "get",
      function(callback) {
        this.getRotationSpeed(
          function(error, data) {
            callback(error, data);
          }.bind(this)
        );
      }.bind(this)
    );

    this.informationService
      .setCharacteristic(Characteristic.Manufacturer, "Ambi Labs")
      .setCharacteristic(Characteristic.Model, "Ambi Climate Thermostat")
      .setCharacteristic(Characteristic.SerialNumber, " ");

    return [this.thermostatService, this.fanService, this.informationService];
  }
};
<|endoftext|>


<commit_before>var Temp = require('./temperature.js');
var Database = require('./database.js');
var tmpRead = new Temp();
var lastTemperature = null;
var db = new Database();
var server = require('./server.js');

db.init();

setInterval(tmpRead.readTemperature(function(data) {
	if(data !== lastTemperature) {
		lastTemperature = data;
		db.insert({date:new Date().getTime(), temperature: data});
		server.update(data);
	}
}.bind(this)), 100);
<commit_msg>Wrapped check in function<commit_after>var Temp = require('./temperature.js');
var Database = require('./database.js');
var tmpRead = new Temp();
var lastTemperature = null;
var db = new Database();
var server = require('./server.js');

db.init();

setInterval(function() {
	tmpRead.readTemperature(function(data) {
		if(data !== lastTemperature) {
			lastTemperature = data;
			db.insert({date:new Date().getTime(), temperature: data});
			server.update(data);
		}
	}.bind(this));
}.bind(this), 100);
<|endoftext|>


<commit_before>var offgrid = require('./build/Release/offgrid');

// Selectively re-export and/or wrap offgrid methods.
exports.capture = offgrid.capture;
<commit_msg>Export the switch method (although it is dubious).<commit_after>var offgrid = require('./build/Release/offgrid');

// Selectively re-export and/or wrap offgrid methods.
exports.capture = offgrid.capture;
exports.switch = offgrid.switch;
<|endoftext|>


<commit_before>var cryptor = require('../utils/cryptor.js');

exports.contentpool = function(getUserName) {
	return {
		apiKey: '',
		login: function(req, res, next) {
			if (!req.cookies.contentPoolAuth) {
				var userToken = {
					username: getUserName(),
					role: 'admin',
					contentBlockIds: [],
					apiKey: apiKey
				};
				var encryptedUserToken = cryptor.encrypt(JSON.stringify(userToken), userToken.apiKey);
				res.cookie('contentPoolAuth', encryptedUserToken);
			}
			return next();
		},

		logout: function(req, res, next) {
			res.clearCookie('contentPoolAuth');
			return next();
		};
	};
};<commit_msg>fixed syntax error<commit_after>var cryptor = require('../utils/cryptor.js');

exports.contentpool = function(getUserName) {
	return {
		apiKey: '',
		login: function(req, res, next) {
			if (!req.cookies.contentPoolAuth) {
				var userToken = {
					username: getUserName(req),
					role: 'admin',
					contentBlockIds: [],
					apiKey: apiKey
				};
				var encryptedUserToken = cryptor.encrypt(JSON.stringify(userToken), userToken.apiKey);
				res.cookie('contentPoolAuth', encryptedUserToken);
			}
			return next();
		},

		logout: function(req, res, next) {
			res.clearCookie('contentPoolAuth');
			return next();
		}
	};
};<|endoftext|>


<commit_before>const http = require('http');

const bodyParser = require('body-parser');
const cors = require('cors');
const express = require('express');

const isProduction = process.env.NODE_ENV === 'production';
if (!isProduction) require('dotenv').config();

var app = express();
const PORT = process.env.PORT || 4000;
const server = http.createServer(app);
const io = require('socket.io')(server, {
  origins: '*:*'
});

// Attach the io instance to the express app object
// to make it accessible from the routes
app.io = io;

io.on('connection', (socket) => {
  console.log('New client connected');
});

app.use(bodyParser.urlencoded({
    extended: true
  }))
  .use(bodyParser.json())
  .use(cors({
    origin: true,
    credentials: true
  }))
  .use('/', require('./routes'));

// Must use http server as listener rather than express app
server.listen(PORT, () => console.log(`Listening on ${ PORT }`));
<commit_msg>Add expres cors headers middleware<commit_after>const http = require('http');

const bodyParser = require('body-parser');
const cors = require('cors');
const express = require('express');

const isProduction = process.env.NODE_ENV === 'production';
if (!isProduction) require('dotenv').config();

var app = express();
const PORT = process.env.PORT || 4000;
const server = http.createServer(app);
const io = require('socket.io')(server, {
  origins: '*:*'
});

app.use(function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
  next();
});

// Attach the io instance to the express app object
// to make it accessible from the routes
app.io = io;

io.on('connection', (socket) => {
  console.log('New client connected');
});

app.use(bodyParser.urlencoded({
    extended: true
  }))
  .use(bodyParser.json())
  .use(cors({
    origin: true,
    credentials: true
  }))
  .use('/', require('./routes'));

// Must use http server as listener rather than express app
server.listen(PORT, () => console.log(`Listening on ${ PORT }`));
<|endoftext|>


<commit_before>const cons = require('consolidate'),
      utils = require('loader-utils'),
      path = require('path'),
      fs = require('fs');

module.exports = function(content) {
  this.cacheable && this.cacheable();

  const callback = this.async();
  var opt = utils.getOptions(this);

  function exportContent(content) {
    if (opt.raw) {
      callback(null, content);
    } else {
      callback(null, "module.exports = " + JSON.stringify(content));
    }
  }

  // with no engine given, use the file extension as engine
  if(!opt.engine) {
    opt.engine = path.extname(this.request).substr(1).toLowerCase();
  }

  if(!cons[opt.engine]) {
    throw new Error("Engine '"+ opt.engine +"' isn't available in Consolidate.js");
  }

  // for relative includes
  opt.filename = this.resourcePath;
  opt.dirname = path.dirname(this.resourcePath);

  const self = this;
  if(opt.files instanceof Array) {
    opt.files.reverse().forEach(function(file) {
      var file = path.join(opt.dirname, file);
      if(!fs.existsSync(file)) {
        file += '.json';
        if(!fs.existsSync(file)) {
          throw new Error("Data file '"+ file +"' does not exist");
        }
      }
      opt = Object.assign(JSON.parse(fs.readFileSync(file)), opt);    // ensure that opt takes precedence
      self.addDependency(file);
    })
    delete opt.files;
  }

  cons[opt.engine].render(content, opt, function(err, html) {
    if(err) {
      throw err;
    }
    exportContent(html);
  });
};
<commit_msg>support for mustache partials files:  add dependency for watching<commit_after>const cons = require('consolidate'),
      utils = require('loader-utils'),
      path = require('path'),
      fs = require('fs');

module.exports = function(content) {
  this.cacheable && this.cacheable();

  const callback = this.async();
  var opt = utils.getOptions(this);

  function exportContent(content) {
    if (opt.raw) {
      callback(null, content);
    } else {
      callback(null, "module.exports = " + JSON.stringify(content));
    }
  }

  // with no engine given, use the file extension as engine
  if(!opt.engine) {
    opt.engine = path.extname(this.request).substr(1).toLowerCase();
  }

  if(!cons[opt.engine]) {
    throw new Error("Engine '"+ opt.engine +"' isn't available in Consolidate.js");
  }

  // for relative includes
  opt.filename = this.resourcePath;
  opt.dirname = path.dirname(this.resourcePath);

  const self = this;
  if(opt.dataFiles instanceof Array) {
    opt.dataFiles.reverse().forEach(function(file) {
      file = path.join(opt.dirname, file);
      if(!fs.existsSync(file)) {
        file += '.json';
        if(!fs.existsSync(file)) {
          throw new Error("Data file '"+ file +"' does not exist");
        }
      }
      opt = Object.assign(JSON.parse(fs.readFileSync(file)), opt);    // ensure that opt takes precedence
      self.addDependency(file);
    })
    delete opt.dataFiles;
  }

  if(opt.partialsFiles instanceof Object) {
    Object.keys(opt.partialsFiles).forEach(function(key) {
      var file = path.join(opt.dirname, opt.partialsFiles[key]);
      if(!fs.existsSync(file)) {
        throw new Error("Partials file '"+ file +"' does not exist");
      }
      opt.partials = opt.partials || {};
      opt.partials[key] = fs.readFileSync(file).toString();
      self.addDependency(file);
    })
    delete opt.extraFiles;
  }

  cons[opt.engine].render(content, opt, function(err, html) {
    if(err) {
      throw err;
    }
    exportContent(html);
  });
};
<|endoftext|>


<commit_before>var VERSION = '0.0.2',
	request = require('request'),
	querystring = require('querystring').stringify;

function shorturl(longurl, shorter, params, callback) {
	// callback passed in params
	if ( typeof params === 'function' ) {
		callback = params;
		params = {};
	}
	// callback passed in shorter
	if ( typeof shorter === 'function' ) {
		callback = shorter;
		params = {};
		shorter = 'is.gd';
	// params passed in shorter
	} else if ( typeof shorter === 'object' ) {
		params = shorter;
		shorter = 'is.gd';
	}

	// check shorter validity
	if ( shorter in shorteners ) {
		// short circuit the rest of the checks to avoid match()
	} else if ( !shorter ) {
		shorter = 'is.gd';
	} else if ( shorter.match(/^https?:\/\/.*%@/) ) {
		params.url = shorter;
		shorter = 'string';
	} else {
		shorter = 'is.gd';
	}

	if ( typeof callback === 'function' ) {
		shorteners[shorter](longurl, params, callback);
	} else {
		// FIXME: explode usefully here?
	}
}
module.exports = shorturl;

var shorteners = {
	'arseh.at': function(longurl, params, callback) {
		params.url = 'http://arseh.at/api.php?action=shorturl&format=simple&url=%@';
		shorteners['string'](longurl, params, callback);
	},

	'bit.ly': function(longurl, params, callback) {
		if ( !(params.login && params.apiKey) ) {
			callback(new Error("bit.ly requires a user and apiKey for authorisation."));
			return;
		}

		var uri = 'http://api.bit.ly/v3/shorten?' + querystring({
			login: params.login || null,
			apiKey: params.apiKey || null,
			longUrl: longurl,
			format: 'json'
		});
		request({uri:uri}, function(error, response, body) {
			if ( response && response.statusCode === 200 ) {
				try {
					var json = JSON.parse(body);
					if ( json.data && json.data.url )
						callback(json.data.url, json);
				} catch(e) {
					// FIXME: e vs. error? kinda stupid
					callback(e, error, response, body);
				}
			} else {
				callback.apply(this, arguments);
			}
		});
	},

	'goo.gl': function(longurl, params, callback) {
		request({
			uri: 'https://www.googleapis.com/urlshortener/v1/url',
			method: 'POST',
			json: {longUrl:longurl}
		}, function(error, response, body) {
			if ( response && response.statusCode === 200 ) {
				try {
					var json = JSON.parse(body);
					if ( json.id )
						callback(json.id, json);
				} catch(e) {
					// FIXME: e vs. error? kinda stupid
					callback(e, error, response, body);
				}
			} else {
				callback.apply(this, arguments);
			}
		});
	},

	'is.gd': function(longurl, params, callback) {
		var host = 'http://is.gd/';
		if ( 'host' in params && ['is.gd', 'v.gd'].indexOf(params.host) >= 0 ) {
			host = 'http://' + params.host + '/';
			delete params.host;
		}
		var uri = host + 'create.php?' + querystring({
			format: 'simple',
			url: longurl
		});
		request({uri:uri}, function(error, response, body) {
			if ( response && response.statusCode === 200 && body.substr(0, host.length) === host ) {
				callback(body);
			} else {
				callback.apply(this, arguments);
			}
		});
	},

	'v.gd': function(longurl, params, callback) {
		params.host = 'v.gd';
		shorteners['is.gd'](longurl, params, callback);
	},

	'string': function(longurl, params, callback) {
		if ( !('url' in params) ) {
			callback();
			return;
		}

		var uri = params.url.replace('%@', escape(longurl));
		request({uri:uri}, function(error, response, body) {
			if ( response && response.statusCode === 200 ) {
				callback(body);
			} else {
				callback.apply(this, arguments);
			}
		});
	}
};
<commit_msg>use https for bit.ly<commit_after>var VERSION = '0.0.2',
	request = require('request'),
	querystring = require('querystring').stringify;

function shorturl(longurl, shorter, params, callback) {
	// callback passed in params
	if ( typeof params === 'function' ) {
		callback = params;
		params = {};
	}
	// callback passed in shorter
	if ( typeof shorter === 'function' ) {
		callback = shorter;
		params = {};
		shorter = 'is.gd';
	// params passed in shorter
	} else if ( typeof shorter === 'object' ) {
		params = shorter;
		shorter = 'is.gd';
	}

	// check shorter validity
	if ( shorter in shorteners ) {
		// short circuit the rest of the checks to avoid match()
	} else if ( !shorter ) {
		shorter = 'is.gd';
	} else if ( shorter.match(/^https?:\/\/.*%@/) ) {
		params.url = shorter;
		shorter = 'string';
	} else {
		shorter = 'is.gd';
	}

	if ( typeof callback === 'function' ) {
		shorteners[shorter](longurl, params, callback);
	} else {
		// FIXME: explode usefully here?
	}
}
module.exports = shorturl;

var shorteners = {
	'arseh.at': function(longurl, params, callback) {
		params.url = 'http://arseh.at/api.php?action=shorturl&format=simple&url=%@';
		shorteners['string'](longurl, params, callback);
	},

	'bit.ly': function(longurl, params, callback) {
		if ( !(params.login && params.apiKey) ) {
			callback(new Error("bit.ly requires a user and apiKey for authorisation."));
			return;
		}

		var uri = 'https://api-ssl.bit.ly/v3/shorten?' + querystring({
			login: params.login || null,
			apiKey: params.apiKey || null,
			longUrl: longurl,
			format: 'json'
		});
		request({uri:uri}, function(error, response, body) {
			if ( response && response.statusCode === 200 ) {
				try {
					var json = JSON.parse(body);
					if ( json.data && json.data.url )
						callback(json.data.url, json);
				} catch(e) {
					// FIXME: e vs. error? kinda stupid
					callback(e, error, response, body);
				}
			} else {
				callback.apply(this, arguments);
			}
		});
	},

	'goo.gl': function(longurl, params, callback) {
		request({
			uri: 'https://www.googleapis.com/urlshortener/v1/url',
			method: 'POST',
			json: {longUrl:longurl}
		}, function(error, response, body) {
			if ( response && response.statusCode === 200 ) {
				try {
					var json = JSON.parse(body);
					if ( json.id )
						callback(json.id, json);
				} catch(e) {
					// FIXME: e vs. error? kinda stupid
					callback(e, error, response, body);
				}
			} else {
				callback.apply(this, arguments);
			}
		});
	},

	'is.gd': function(longurl, params, callback) {
		var host = 'http://is.gd/';
		if ( 'host' in params && ['is.gd', 'v.gd'].indexOf(params.host) >= 0 ) {
			host = 'http://' + params.host + '/';
			delete params.host;
		}
		var uri = host + 'create.php?' + querystring({
			format: 'simple',
			url: longurl
		});
		request({uri:uri}, function(error, response, body) {
			if ( response && response.statusCode === 200 && body.substr(0, host.length) === host ) {
				callback(body);
			} else {
				callback.apply(this, arguments);
			}
		});
	},

	'v.gd': function(longurl, params, callback) {
		params.host = 'v.gd';
		shorteners['is.gd'](longurl, params, callback);
	},

	'string': function(longurl, params, callback) {
		if ( !('url' in params) ) {
			callback();
			return;
		}

		var uri = params.url.replace('%@', escape(longurl));
		request({uri:uri}, function(error, response, body) {
			if ( response && response.statusCode === 200 ) {
				callback(body);
			} else {
				callback.apply(this, arguments);
			}
		});
	}
};
<|endoftext|>


<commit_before>var copyrightCampaign = function(options) {
    options = typeof options !== 'undefined' ? options : {};
    function setDefaultOption(option, value) {
        options[option] = typeof options[option] !== 'undefined' ? options[option] : value;
    }

    setDefaultOption(options, {});
    setDefaultOption("onCampaignDayOnly", true);
    setDefaultOption("showOnlyOnce", true);
    setDefaultOption("title", 'Tänään juhlitaan
kansainvälistä tekijänoikeuspäivää');
    setDefaultOption("bigText", 'Tutustu Järkeä tekijänoikeuslakiin -aloitteeseen!');
    setDefaultOption("smallText", 'Yhdessä voimme tehdä Suomen tekijänoikeuslaista reilumman kuluttajille, tekijöille ja uusia palveluita kehittäville yrityksille.');

    var today = new Date();
    if (!(today.getDate() == 23 && today.getMonth() == 3) && options["onCampaignDayOnly"]) {
        return;
    }

    if (typeof jQuery === "undefined") {
        var script_tag = document.createElement('script');
        script_tag.setAttribute("type","text/javascript");
        script_tag.setAttribute("src",
          "http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js");
        script_tag.onload = main; // Run main() once jQuery has loaded
        /*script_tag.onreadystatechange = function () { // Same thing but for IE
          if (this.readyState == 'complete' || this.readyState == 'loaded') main();
        }*/
        document.getElementsByTagName("head")[0].appendChild(script_tag);
    } else {
        main();
    }

    function main() {
        $(document).ready(function (e) {
            $("head").prepend("<link href='http://fonts.googleapis.com/css?family=Open+Sans:800|Gentium+Book+Basic' rel='stylesheet' type='text/css'>");

            var widgetStyle = '';

            $("head").prepend(widgetStyle);

            var widgetElement = '

Sulje

'+options.title+'

'+options.smallText+'

'+options.bigText+'

Jo  allekirjoitusta!

Kampanjasivu

Sulje

Code by SC5

';
            $(document.body).append(widgetElement);

            function hideBox() {
                //$("#copyright-campaign-box").hide();
                $("#copyright-campaign-box").css({top:"-9999px", left:"-9999px", right:"-9999px",bottom:"-9999px"});
                if(options["showOnlyOnce"] && typeof(Storage)!=="undefined") {
                    localStorage.boxClosed = true;
                }
            }

            if(options["showOnlyOnce"] && typeof(Storage)!=="undefined" && localStorage.boxClosed) {
                hideBox();
            }

            $(".close-copyright-campaign-box").click(function(e) {
                hideBox();
            });

            $(document).keyup(function(e) {
                if (e.keyCode == 27) {
                    hideBox();
                }
            });

            $.getJSON("https://www.kansalaisaloite.fi/api/v1/initiatives/70?jsonp=?").done(function(data) {
                    if (data.supportCount) {
                        $("#subscriptionCount b").html(data.supportCount);
                        $("#subscriptions").show();
                    }
            });
        });
    }
};<commit_msg>hide()<commit_after>var copyrightCampaign = function(options) {
    options = typeof options !== 'undefined' ? options : {};
    function setDefaultOption(option, value) {
        options[option] = typeof options[option] !== 'undefined' ? options[option] : value;
    }

    setDefaultOption(options, {});
    setDefaultOption("onCampaignDayOnly", true);
    setDefaultOption("showOnlyOnce", true);
    setDefaultOption("title", 'Tänään juhlitaan
kansainvälistä tekijänoikeuspäivää');
    setDefaultOption("bigText", 'Tutustu Järkeä tekijänoikeuslakiin -aloitteeseen!');
    setDefaultOption("smallText", 'Yhdessä voimme tehdä Suomen tekijänoikeuslaista reilumman kuluttajille, tekijöille ja uusia palveluita kehittäville yrityksille.');

    var today = new Date();
    if (!(today.getDate() == 23 && today.getMonth() == 3) && options["onCampaignDayOnly"]) {
        return;
    }

    if (typeof jQuery === "undefined") {
        var script_tag = document.createElement('script');
        script_tag.setAttribute("type","text/javascript");
        script_tag.setAttribute("src",
          "http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js");
        script_tag.onload = main; // Run main() once jQuery has loaded
        /*script_tag.onreadystatechange = function () { // Same thing but for IE
          if (this.readyState == 'complete' || this.readyState == 'loaded') main();
        }*/
        document.getElementsByTagName("head")[0].appendChild(script_tag);
    } else {
        main();
    }

    function main() {
        $(document).ready(function (e) {
            $("head").prepend("<link href='http://fonts.googleapis.com/css?family=Open+Sans:800|Gentium+Book+Basic' rel='stylesheet' type='text/css'>");

            var widgetStyle = '';

            $("head").prepend(widgetStyle);

            var widgetElement = '

Sulje

'+options.title+'

'+options.smallText+'

'+options.bigText+'

Jo  allekirjoitusta!

Kampanjasivu

Sulje

Code by SC5

';
            $(document.body).append(widgetElement);

            function hideBox() {
                $("#copyright-campaign-box").hide();
                //$("#copyright-campaign-box").css({top:"-9999px", left:"-9999px", right:"-9999px",bottom:"-9999px"});
                if(options["showOnlyOnce"] && typeof(Storage)!=="undefined") {
                    localStorage.boxClosed = true;
                }
            }

            if(options["showOnlyOnce"] && typeof(Storage)!=="undefined" && localStorage.boxClosed) {
                hideBox();
            }

            $(".close-copyright-campaign-box").click(function(e) {
                hideBox();
            });

            $(document).keyup(function(e) {
                if (e.keyCode == 27) {
                    hideBox();
                }
            });

            $.getJSON("https://www.kansalaisaloite.fi/api/v1/initiatives/70?jsonp=?").done(function(data) {
                    if (data.supportCount) {
                        $("#subscriptionCount b").html(data.supportCount);
                        $("#subscriptions").show();
                    }
            });
        });
    }
};<|endoftext|>


<commit_before>var traverse   = require('circularjs');

exports.encode= function (orig,skipDelete)
{
    var _root, known= [], nodes= [], keyss= [];

    if (typeof orig=='object')
    {
        // structure
        traverse(orig,
        function (node,keys)
        {
           var x= known[node.__visited]= Array.isArray(node) ? [] : {};
           nodes.push(node);
           keyss.push(keys);
        },true);

        // values
        nodes.forEach(function (node,idx)
        {
           var o= known[node.__visited], keys= keyss[idx];

           if (!_root) _root= node.__visited;

           if (keys)
             keys.forEach(function (key)
             {
                var val= node[key];

                if (val&&typeof val=='object')
                  o[key]= { _: val.__visited };
                else
                  o[key]= val;
             });
           else
             node.forEach(function (val,key)
             {
                if (val&&typeof val=='object')
                  o[key]= { _: val.__visited };
                else
                  o[key]= val;
             });
        });

        if (!skipDelete)
          nodes.forEach(function (node) { delete node.__visited; });

        return known;
    }
    else
        return orig;

};

exports.decode= function (encoded)
{
  if (Array.isArray(encoded))
  {
      encoded.forEach(function (node,idx)
      {
           if (Array.isArray(node))
             node.forEach(function (val,key)
             {
                if (val&&typeof val=='object')
                  node[key]= encoded[val._];
             });
           else
             Object.keys(node).forEach(function (key)
             {
                var val= node[key];

                if (val&&typeof val=='object')
                  node[key]= encoded[val._];
             });
      });

      return encoded[0];
  }
  else
    return encoded;
};

exports.stringify= function (orig)
{
   return JSON.stringify(exports.encode(orig));
};

exports.parse= function (s)
{
   return exports.decode(JSON.parse(s));
};
<commit_msg>fix node detection<commit_after>var traverse   = require('circularjs');

exports.encode= function (orig,skipDelete)
{
    var _root, known= [], nodes= [], keyss= [];

    if (typeof orig=='object')
    {
        // structure
        traverse(orig,
        function (node,keys)
        {
           var x= known[node.__visited]= Array.isArray(node) ? [] : {};
           nodes.push(node);
           keyss.push(keys);
        },true);

        // values
        nodes.forEach(function (node,idx)
        {
           var o= known[node.__visited], keys= keyss[idx];

           if (!_root) _root= node.__visited;

           if (keys)
             keys.forEach(function (key)
             {
                var val= node[key];

                if (val&&traverse.isNode(val))
                  o[key]= { _: val.__visited };
                else
                  o[key]= val;
             });
           else
             node.forEach(function (val,key)
             {
                if (val&&traverse.isNode(val))
                  o[key]= { _: val.__visited };
                else
                  o[key]= val;
             });
        });

        if (!skipDelete)
          nodes.forEach(function (node) { delete node.__visited; });

        return known;
    }
    else
        return orig;

};

exports.decode= function (encoded)
{
  if (Array.isArray(encoded))
  {
      encoded.forEach(function (node,idx)
      {
           if (Array.isArray(node))
             node.forEach(function (val,key)
             {
                if (val&&traverse.isNode(val))
                  node[key]= encoded[val._];
             });
           else
             Object.keys(node).forEach(function (key)
             {
                var val= node[key];

                if (val&&traverse.isNode(val))
                  node[key]= encoded[val._];
             });
      });

      return encoded[0];
  }
  else
    return encoded;
};

exports.stringify= function (orig)
{
   return JSON.stringify(exports.encode(orig));
};

exports.parse= function (s)
{
   return exports.decode(JSON.parse(s));
};
<|endoftext|>


<commit_before>'use strict';

var open = require('open');
var editor = process.env.EDITOR;
var spawn = require('child_process').spawn;

hexo.extend.console.register('edit', 'Edit a post or draft with your favorite $EDITOR', {
  desc: 'Edit a post or draft with your favorite $EDITOR',
  usage: '<title> [type]',
  arguments: [
    {name: 'type',  desc: '[optional] Name of source subfolder to search. Will search entire source folder if no matching folder is found. Neither the leading \'_\' or the trailing \'s\' is required for specifying the _posts or _drafts folder.'},
    {name: 'title', desc: '[required] (Part of) the title of a post. If more posts match this regex, a menu will be called.'},
  ],
  options: [
    {name: '-g, --gui', desc: 'Open file with associated GUI text editor. Default is to open in terminal using $EDITOR.'},
  ],
}, require('./edit'));

hexo.on('new', function(post) {
  if (!editor) {
    open(file);
  } else {
    var edit = spawn(editor, [file], {stdio: 'inherit'});
    edit.on('exit', process.exit);
  }
});
<commit_msg>dumb bug<commit_after>'use strict';

var open = require('open');
var editor = process.env.EDITOR;
var spawn = require('child_process').spawn;

hexo.extend.console.register('edit', 'Edit a post or draft with your favorite $EDITOR', {
  desc: 'Edit a post or draft with your favorite $EDITOR',
  usage: '<title> [type]',
  arguments: [
    {name: 'type',  desc: '[optional] Name of source subfolder to search. Will search entire source folder if no matching folder is found. Neither the leading \'_\' or the trailing \'s\' is required for specifying the _posts or _drafts folder.'},
    {name: 'title', desc: '[required] (Part of) the title of a post. If more posts match this regex, a menu will be called.'},
  ],
  options: [
    {name: '-g, --gui', desc: 'Open file with associated GUI text editor. Default is to open in terminal using $EDITOR.'},
  ],
}, require('./edit'));

hexo.on('new', function(post) {
  if (!editor) {
    open(file);
  } else {
    var edit = spawn(editor, [post], {stdio: 'inherit'});
    edit.on('exit', process.exit);
  }
});
<|endoftext|>


<commit_before>var Hapi    = require('hapi');
var Joi     = require('joi');

var users = [
  {
    id: 1,
    name: 'John Doe',
    email: 'johndoe@gmail.com',
    posts: [
      {
        id: 1,
        title: 'Post 1',
        body: 'Post 1 Body'
      },
      {
        id: 2,
        title: 'Post 2',
        body: 'Post 2 Body'
      }
    ]
  },
  {
    id: 2,
    name: 'Jane Doe',
    email: 'janedoe@gmail.com',
    posts: [
      {
        id: 3,
        title: 'Post 3',
        body: 'Post 3 Body'
      },
      {
        id: 4,
        title: 'Post 4',
        body: 'Post 4 Body'
      }
    ]
  }

];

var server = new Hapi.Server('0.0.0.0', '8080');

var routes = [
  {
    method: 'GET',
    path: '/users',
    config: {
      handler: function (request, reply) {
        reply(users);
      }
    }
  },
  {
    method: 'GET',
    path: '/users/{user_id}',
    config: {
      handler: function (request, reply) {
        reply(users[request.params.user_id - 1]);
      }
    }
  },
  {
    method: 'POST',
    path: '/users',
    config: {
      handler: function (request, reply) {
        reply({
          id: users.length + 1,
          name: request.payload.name,
          email: request.payload.email,
          posts: []
        });
      },
      validate: {
        payload: {
          name: Joi.string().required(),
          email: Joi.string().email().required()
        }
      }
    }
  },
  {
    method: 'POST',
    path: '/users/{user_id}',
    config: {
      handler: function (request, reply) {
        reply({
          id: users[request.params.user_id - 1].id,
          name: request.payload.name || users[request.params.user_id - 1].name,
          email: request.payload.email || users[request.params.user_id - 1].email
        });
      }
    }
  },
  {
    method: 'DELETE',
    path: '/users/{user_id}',
    config: {
      handler: function (request, reply) {
        reply({
          id: request.params.user_id,
          deleted: true
        });
      }
    }
  }
];

server.route(routes);

console.log('Server started on port: 8080');
server.start();

<commit_msg>added errors for user not found<commit_after>var Hapi    = require('hapi');
var Joi     = require('joi');

var users = [
  {
    id: 1,
    name: 'John Doe',
    email: 'johndoe@gmail.com',
    posts: [
      {
        id: 1,
        title: 'Post 1',
        body: 'Post 1 Body'
      },
      {
        id: 2,
        title: 'Post 2',
        body: 'Post 2 Body'
      }
    ]
  },
  {
    id: 2,
    name: 'Jane Doe',
    email: 'janedoe@gmail.com',
    posts: [
      {
        id: 3,
        title: 'Post 3',
        body: 'Post 3 Body'
      },
      {
        id: 4,
        title: 'Post 4',
        body: 'Post 4 Body'
      }
    ]
  }

];

var server = new Hapi.Server('0.0.0.0', '8080');

var routes = [
  {
    method: 'GET',
    path: '/users',
    config: {
      handler: function (request, reply) {
        reply(users);
      }
    }
  },
  {
    method: 'GET',
    path: '/users/{user_id}',
    config: {
      handler: function (request, reply) {
        if (users[request.params.user_id - 1]) {
          reply(users[request.params.user_id - 1]);
        } else {
           reply({
            error: 'User Not Found'
          }).code(404);
        }
      }
    }
  },
  {
    method: 'POST',
    path: '/users',
    config: {
      handler: function (request, reply) {
        reply({
          id: users.length + 1,
          name: request.payload.name,
          email: request.payload.email,
          posts: []
        });
      },
      validate: {
        payload: {
          name: Joi.string().required(),
          email: Joi.string().email().required()
        }
      }
    }
  },
  {
    method: 'POST',
    path: '/users/{user_id}',
    config: {
      handler: function (request, reply) {
        if (users[request.params.user_id - 1]) {
          reply({
            id: users[request.params.user_id - 1].id,
            name: request.payload.name || users[request.params.user_id - 1].name,
            email: request.payload.email || users[request.params.user_id - 1].email
          });
        } else {
          reply({
            error: 'User Not Found'
          }).code(404);
        }
     }
    }
  },
  {
    method: 'DELETE',
    path: '/users/{user_id}',
    config: {
      handler: function (request, reply) {
        var user = users[request.params.user_id - 1];
        if (user) {
          reply({
            id: request.params.user_id,
            deleted: true
          });
        } else {
          reply({
            error: 'User Not Found'
          }).code(404);
        }
      }
    }
  },
  {
    method: 'GET',
    path: '/users/{user_id}/posts',
    config: {
      handler: function (request, reply) {
        var user = users[request.params.user_id - 1];
        if (user) {
          reply(user.posts);
        } else {
          reply({
            error: 'User Not Found'
          }).code(404);
        }
      }
    }
  },
  {
    method: 'GET',
    path: '/users/{user_id}/posts/{post_id}',
    config: {
      handler: function (request, reply) {
        var post = users[request.params.user_id - 1]
          .posts[request.params.post_id - request.params.user_id - (request.params.user_id == 1 ? 0 : 1)];

        if (post) {
          reply(post);
        } else {
          reply({
            error: 'Post Not Found'
          }).code(404);
        }
      }
    }
  }
];

server.route(routes);

console.log('Server started on port: 8080');
server.start();

<|endoftext|>


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

var cli   = require('commander');
var init   = require('./functions.js')

cli
  .version('0.0.1')
  .command('summarise <csv-file>')
  .description('Summarise a CSV file of transactions for a budget.')
  .action(init);

cli.parse(process.argv);

if(cli.args.length === 0) cli.help();

<commit_msg>update version<commit_after>#!/usr/bin/env node

var cli   = require('commander');
var init   = require('./functions.js')

cli
  .version('0.1.0')
  .command('summarise <csv-file>')
  .description('Summarise a CSV file of transactions for a budget.')
  .action(init);

cli.parse(process.argv);

if(cli.args.length === 0) cli.help();

<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2013 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
MODEL({
  name:  'DAOController',
  label: 'DAO Controller',

  extendsModel: 'View',

  properties: [
    {
      model_: 'ModelProperty',
      name: 'model'
    },
    {
      name: 'dao',
      view: 'TableView'
    },
    {
      name: 'selection'
    },
    {
      model_: 'BooleanProperty',
      name: 'useSearchView',
      defaultValue: false,
      postSet: function(_, value) {
        if ( value ) {
          this.addDecorator(this.X.SearchBorder.create({
            model: this.model,
            data: this.data
          }));
        }
      },
    }
  ],

  actions: [
    {
      name:  'new',
      help:  'Create a new record.',
      action: function() {
        var createView = this.X.DAOCreateController.create({
          model: this.model,
          dao:   this.dao,
          showActions: true
        });

        createView.parentController = this;

        this.X.stack.pushView(createView, 'New');
      }
    },
    {
      name:  'edit',
      help:  'Edit the current record.',
      default: 'true',

      action: function() {
        // Todo: fix, should already be connected
        this.selection = this.daoView.selection;

        var obj = this.selection;
        var actions = DAOUpdateController.actions.slice(0);

        for ( var i = 0 ; i < this.model.actions.length ; i++ ) {
          var action = this.model.actions[i];

          var newAction = this.X.Action.create(action);
          newAction.action = function (oldAction) {
            return function() {
              oldAction.call(obj);
            };
          }(action.action);

          actions.push(newAction);
        }

        console.log(["selection: ", this.selection]);
        var updateView = this.X.DAOUpdateController.create({
          data:  this.selection/*.deepClone()*/,
          model: this.model,
          dao:   this.dao,
          showActions: true
        });

        this.X.stack.pushView(updateView, 'Edit');
      }
    },
    {
      name:  'delete',
      help:  'Delete the current record.',

//      isEnabled: function()   { return this.selection; },
      action: function()      {
        // Todo: fix, should already be connected
        this.selection = this.daoView.selection;
        var self = this;
        this.dao.remove(this.selection);
      }
    }
  ],

  methods: {
    init: function() {
      this.SUPER();
      this.showActions = true;
    },
    initHTML: function() {
      this.SUPER();
      this.daoView.subscribe(this.daoView.DOUBLE_CLICK, this.onDoubleClick);
      this.daoView.selection$.addListener(this.onSelection);
    }
  },

  templates: [
    function toInnerHTML() {/* $$dao{model_: 'TableView', model: this.model}*/}
  ],

  listeners: [
    {
      name: 'onDoubleClick',
      code: function(evt) {
        for ( var i = 0 ; i < this.model_.actions.length ; i++ ) {
          var action = this.model_.actions[i];

          if ( action.default ) {
            action.action.call(this);
            break;
          }
        }
      }
    },
    {
      name: 'onSelection',
      code: function(evt) {
        var obj = this.daoView.selection;
        if ( ! obj ) return;

        this.X.stack.setPreview(
          this.X.DetailView.create({
            model: this.model,
            data: this.daoView.selection
          }));
      }
    }
  ]
});

MODEL({
  name:  'DAOCreateController',
  label: 'DAO Create',

  extendsModel: 'View',

  properties: [
    {
      name:  'model'
    },
    {
      name:  'data',
      label: 'New Object',
      view: 'DetailView',
      factory: function() { return this.model.create(); }
    },
    {
      name:  'dao',
      label: 'DAO',
    }
  ],

  actions: [
    {
      name:  'save',
      label: 'Create',
      help:  'Create a new record.',

      isAvailable: function() { return true; },
      isEnabled:   function() { return true; },
      action:      function() {
        var self = this;
        this.dao.put(this.data, {
          put: function(value) {
            console.log("Created: ", value);
            self.X.stack.back();
          },
          error: function() {
            console.error("Error creating value: ", arguments);
          }
        });
      }
    },
    {
      name:  'cancel',
      help:  'Cancel creation.',

      action: function() { this.X.stack.back(); }
    },
    {
      name:  'help',
      help:  'View help.',

      action: function() {
        var model = this.data.model_;
        var helpView = this.X.HelpView.create(model);
        this.X.stack.pushView(helpView);
      }
    }
  ],

  templates: [
    function toInnerHTML() {/* $$data */}
  ]
});

MODEL({
  name:  'DAOUpdateController',
  label: 'DAO Update',

  extendsModel: 'View',

  properties: [
    {
      name:  'data',
      label: 'Edited Object'
    },
    {
      name:  'model',
    },
    {
      name:  'dao',
      label: 'DAO',
    }
  ],

  actions: [
    {
      name:  'save',
      help:  'Save updates.',

      action: function() {
        var self = this;
        this.dao.put(this.data, {
          put: function(value) {
            console.log("Created: ", value);
            self.X.stack.back();
          },
          error: function() {
            console.error("Error creating value: ", arguments);
          }
        });
      }
    },
    {
      name:  'copy',
      help:  'Create a new object which is a copy of this one.',

      action: function() {
      }
    },
    {
      name:  'cancel',
      help:  'Cancel update.',

      action: function() {
        this.X.stack.back();
      }
    },
    {
      name:  'help',
      help:  'View help.',

      action: function() {
        var model = this.data.model_;
        var helpView = this.X.HelpView.create(model);
        this.X.stack.pushView(helpView);
      }
    }
  ],

  methods: {
    init: function() {
      this.SUPER();

      this.view = this.X.AlternateView.create({
        selection: 'GUI',
        data: this.data,
        views: [
          {
            model_: 'ViewChoice',
            label:  'GUI',
            view:   'DetailView'
          },
          {
            model_: 'ViewChoice',
            label:  'JS',
            view:   'JSView'
          },
          {
            model_: 'ViewChoice',
            label:  'XML',
            view:   'XMLView'
          }/*,
             {
             model_: 'ViewChoice',
             label:  'UML',
             view:   'XMLView'
             },
             {
             model_: 'ViewChoice',
             label:  'Split',
             view:   'SplitView'
             }*/
        ]
      });
    },

    toHTML: function() {
      return this.view.toHTML();
    },

    initHTML: function() {
      this.SUPER();
      this.view.initHTML();
    }
  }
});

var ArrayView = DAOController;
<commit_msg>Switch DAOController preview to SummaryView.<commit_after>/**
 * @license
 * Copyright 2013 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
MODEL({
  name:  'DAOController',
  label: 'DAO Controller',

  extendsModel: 'View',

  properties: [
    {
      model_: 'ModelProperty',
      name: 'model'
    },
    {
      name: 'dao',
      view: 'TableView'
    },
    {
      name: 'selection'
    },
    {
      model_: 'BooleanProperty',
      name: 'useSearchView',
      defaultValue: false,
      postSet: function(_, value) {
        if ( value ) {
          this.addDecorator(this.X.SearchBorder.create({
            model: this.model,
            data: this.data
          }));
        }
      },
    }
  ],

  actions: [
    {
      name:  'new',
      help:  'Create a new record.',
      action: function() {
        var createView = this.X.DAOCreateController.create({
          model: this.model,
          dao:   this.dao,
          showActions: true
        });

        createView.parentController = this;

        this.X.stack.pushView(createView, 'New');
      }
    },
    {
      name:  'edit',
      help:  'Edit the current record.',
      default: 'true',

      action: function() {
        // Todo: fix, should already be connected
        this.selection = this.daoView.selection;

        var obj = this.selection;
        var actions = DAOUpdateController.actions.slice(0);

        for ( var i = 0 ; i < this.model.actions.length ; i++ ) {
          var action = this.model.actions[i];

          var newAction = this.X.Action.create(action);
          newAction.action = function (oldAction) {
            return function() {
              oldAction.call(obj);
            };
          }(action.action);

          actions.push(newAction);
        }

        console.log(["selection: ", this.selection]);
        var updateView = this.X.DAOUpdateController.create({
          data:  this.selection/*.deepClone()*/,
          model: this.model,
          dao:   this.dao,
          showActions: true
        });

        this.X.stack.pushView(updateView, 'Edit');
      }
    },
    {
      name:  'delete',
      help:  'Delete the current record.',

//      isEnabled: function()   { return this.selection; },
      action: function()      {
        // Todo: fix, should already be connected
        this.selection = this.daoView.selection;
        var self = this;
        this.dao.remove(this.selection);
      }
    }
  ],

  methods: {
    init: function() {
      this.SUPER();
      this.showActions = true;
    },
    initHTML: function() {
      this.SUPER();
      this.daoView.subscribe(this.daoView.DOUBLE_CLICK, this.onDoubleClick);
      this.daoView.selection$.addListener(this.onSelection);
    }
  },

  templates: [
    function toInnerHTML() {/* $$dao{model_: 'TableView', model: this.model}*/}
  ],

  listeners: [
    {
      name: 'onDoubleClick',
      code: function(evt) {
        for ( var i = 0 ; i < this.model_.actions.length ; i++ ) {
          var action = this.model_.actions[i];

          if ( action.default ) {
            action.action.call(this);
            break;
          }
        }
      }
    },
    {
      name: 'onSelection',
      code: function(evt) {
        var obj = this.daoView.selection;
        if ( ! obj ) return;

        this.X.stack.setPreview(
          this.X.SummaryView.create({
            model: this.model,
            data: this.daoView.selection
          }));
      }
    }
  ]
});

MODEL({
  name:  'DAOCreateController',
  label: 'DAO Create',

  extendsModel: 'View',

  properties: [
    {
      name:  'model'
    },
    {
      name:  'data',
      label: 'New Object',
      view: 'DetailView',
      factory: function() { return this.model.create(); }
    },
    {
      name:  'dao',
      label: 'DAO',
    }
  ],

  actions: [
    {
      name:  'save',
      label: 'Create',
      help:  'Create a new record.',

      isAvailable: function() { return true; },
      isEnabled:   function() { return true; },
      action:      function() {
        var self = this;
        this.dao.put(this.data, {
          put: function(value) {
            console.log("Created: ", value);
            self.X.stack.back();
          },
          error: function() {
            console.error("Error creating value: ", arguments);
          }
        });
      }
    },
    {
      name:  'cancel',
      help:  'Cancel creation.',

      action: function() { this.X.stack.back(); }
    },
    {
      name:  'help',
      help:  'View help.',

      action: function() {
        var model = this.data.model_;
        var helpView = this.X.HelpView.create(model);
        this.X.stack.pushView(helpView);
      }
    }
  ],

  templates: [
    function toInnerHTML() {/* $$data */}
  ]
});

MODEL({
  name:  'DAOUpdateController',
  label: 'DAO Update',

  extendsModel: 'View',

  properties: [
    {
      name:  'data',
      label: 'Edited Object'
    },
    {
      name:  'model',
    },
    {
      name:  'dao',
      label: 'DAO',
    }
  ],

  actions: [
    {
      name:  'save',
      help:  'Save updates.',

      action: function() {
        var self = this;
        this.dao.put(this.data, {
          put: function(value) {
            console.log("Created: ", value);
            self.X.stack.back();
          },
          error: function() {
            console.error("Error creating value: ", arguments);
          }
        });
      }
    },
    {
      name:  'copy',
      help:  'Create a new object which is a copy of this one.',

      action: function() {
      }
    },
    {
      name:  'cancel',
      help:  'Cancel update.',

      action: function() {
        this.X.stack.back();
      }
    },
    {
      name:  'help',
      help:  'View help.',

      action: function() {
        var model = this.data.model_;
        var helpView = this.X.HelpView.create(model);
        this.X.stack.pushView(helpView);
      }
    }
  ],

  methods: {
    init: function() {
      this.SUPER();

      this.view = this.X.AlternateView.create({
        selection: 'GUI',
        data: this.data,
        views: [
          {
            model_: 'ViewChoice',
            label:  'GUI',
            view:   'DetailView'
          },
          {
            model_: 'ViewChoice',
            label:  'JS',
            view:   'JSView'
          },
          {
            model_: 'ViewChoice',
            label:  'XML',
            view:   'XMLView'
          }/*,
             {
             model_: 'ViewChoice',
             label:  'UML',
             view:   'XMLView'
             },
             {
             model_: 'ViewChoice',
             label:  'Split',
             view:   'SplitView'
             }*/
        ]
      });
    },

    toHTML: function() {
      return this.view.toHTML();
    },

    initHTML: function() {
      this.SUPER();
      this.view.initHTML();
    }
  }
});

var ArrayView = DAOController;
<|endoftext|>


<commit_before>const _ = require('lodash');
const async = require('async');
const minimatch = require('minimatch');
const fs = require('fs');
const klawSync = require('klaw-sync');
const chalk = require('chalk');
const path = require('path');
const { fork } = require('child_process');
const { PassThrough } = require('stream');
const debug = require('util').debuglog('iamtest');
/*  lazy load
 *  const tapSpec = require('tap-spec');
 *  const tapSummary = require('tap-summary');
 *  const { amTapDot } = require('am-tap-dot');
 *  const StaticServer = require('static-server');
 */
const Child = require('./lib/Child');
const Watcher = require('./lib/Watcher');

const config = require('./lib/config')(path.join(process.cwd(), '.iamtest.js'));
const argv = require('./lib/yargs')();

const isMaster = !argv.child;
const isSilent = argv.reporter === 'silent';
const argsPaths = _.map(argv._, p => path.resolve(process.cwd(), p));

function ipcSend(message) {
    if (process.send) process.send(message);
}

function outputWrite(str) {
    if (!isSilent) {
        process.stdout.write(str);
    }
}

function verbose(...args) {
    if (argv.verbose) {
        outputWrite(`${chalk.cyan(args[0])}\n${args.slice(1).join('')}`);
    }
}

function tapeReporterStream() {
    // eslint-disable-next-line global-require
    if (argv.reporter === 'spec') return require('tap-spec')();
    // eslint-disable-next-line global-require
    if (argv.reporter === 'summary') return require('tap-summary')({ ansi: true, progress: true });
    // eslint-disable-next-line global-require, import/newline-after-import
    if (argv.reporter === 'dots') return require('am-tap-dot').amTapDot();
    if (argv.reporter === 'tape') return new PassThrough();
    if (argv.reporter === 'silent') return new PassThrough();
    /* istanbul ignore next */
    throw new Error('invalid reporter value');
}

function getTestFiles() {
    // if only single test file then do not ignore it
    if (argsPaths.length === 1 && !argsPaths[0].includes('*')) {
        return argsPaths;
    }
    return klawSync(config.rootPath)
        .filter((item) => {
            const filepath = item.path;
            if (minimatch(filepath, '!*.js', { matchBase: true })) return false;
            if (_.some(config.ignorePath, v => minimatch(filepath, v))) return false;
            if (argsPaths.length && !_.some(argsPaths, v => minimatch(filepath, v))) return false;
            return true;
        })
        .map(item => item.path);
}

function getCoverageFiles() {
    const coverageDir = argv.coverageDir || path.join(process.cwd(), 'coverage');
    const coverageReportPath = path.join(coverageDir, 'coverage-summary.json');
    if (!fs.existsSync(coverageReportPath)) {
        throw new Error(`Coverage report file ${coverageReportPath} not found`);
    }
    // eslint-disable-next-line import/no-dynamic-require, global-require
    const report = require(coverageReportPath);
    return _.pull(_.keys(report), 'total');
}

function serveCoverageHtml(port) {
    // eslint-disable-next-line global-require
    const StaticServer = require('static-server');

    const server = new StaticServer({
        rootPath: path.join(process.cwd(), 'coverage'),
        name: 'Coverage Report',
        port,
    });
    server.start(() => {
        outputWrite('  \n');
        outputWrite(`  View coverage report at ${chalk.cyan(`http://localhost:${server.port}`)}\n`);
        outputWrite('  Press ctrl+c to exit...\n');
    });
}

function filterChildArgs(args) {
    const _args = _.clone(args);
    const ix = _args.indexOf('--web');
    if (_args.includes('--web')) {
        _args.splice(ix, 2);
    }
    _.pull(_args, '-w', '--watch');
    ipcSend({ name: 'filter', body: _args });
    return _args;
}

function runWatcher(args) {
    let child = new Child(null, [], { verbose });
    const watcher = new Watcher({ verbose });
    const watchingFiles = _.concat(getTestFiles(), getCoverageFiles());
    const _args = filterChildArgs(args);
    watcher
        .watch(watchingFiles)
        .on('change', () => {
            ipcSend({ name: 'watcher event' });
            child.kill();
        })
        .on('debounce', () => {
            child.exit().then(() => {
                child = new Child(config.nycPath, _args, { verbose });
                ipcSend({ name: 'child fork', body: { id: child.id, script: config.nycPath, args: _args } });
                child.process.once('exit', () => {
                    ipcSend({ name: 'child exit', body: { id: child.id } });
                    watcher.watch(_.concat(getTestFiles(), getCoverageFiles()));
                });
                // bubble child message
                child.process.on('message', (message) => {
                    ipcSend(message);
                });
            });
        });
    process.nextTick(() => {
        ipcSend({ name: 'run watcher', body: watchingFiles });
    });
}

function outputFilenameHeader(filepath, isFirst) {
    let filenameHeader = '';
    const relativeFilePath = path.relative(config.rootPath, filepath);
    if (argv.reporter === 'tape') {
        const sof = isFirst ? '#\n#' : '\n#\n#';
        const eof = '\n#\n';
        filenameHeader = `${sof}${relativeFilePath}${eof}`;
    } else if (argv.reporter === 'dots') {
        const sof = isFirst ? '\n ' : '\n\n ';
        const eof = isFirst ? '' : '\n';
        const header = chalk.bgWhite.black(` ${_.padEnd(relativeFilePath, 80, ' ')}`);
        filenameHeader = `${sof}${header}${eof}`;
    } else if (!isSilent) {
        const sof = isFirst ? '\n ' : '\n\n ';
        const eof = isFirst ? '\n' : '\n';
        const header = chalk.bgWhite.black(` ${_.padEnd(relativeFilePath, 80, ' ')}`);
        filenameHeader = `${sof}${header}${eof}`;
    }
    return filenameHeader;
}

function runTest(callback) {
    const reporter = tapeReporterStream();
    if (!isSilent) {
        reporter.pipe(process.stdout);
    }
    const testFiles = getTestFiles();
    let exitCode = 0;
    debug(`TEST FILES: ${testFiles.length}`, `\n  • ${testFiles.join('\n  • ')}\n`);
    async.eachSeries(testFiles, (filePath, cb) => {
        let execPath = 'node';
        if (argv.flow) {
            execPath = config.flowNodeBinPath;
        }
        if (argv.babel) {
            execPath = config.babelNodeBinPath;
        }
        debug('RUN TEST:', `${execPath} ${filePath}`);
        const child = fork(filePath, [], {
            silent: true,
            execPath,
        });
        outputWrite(outputFilenameHeader(filePath, filePath === testFiles[0]));
        reporter.emit('dot-line-break');
        child.stdout.on('data', (data) => {
            reporter.write(data.toString());
        });
        child.once('exit', (code) => {
            exitCode += code;
            cb();
        });
        child.stdout.on('end', () => {});
        child.stderr.pipe(process.stderr);
        child.once('error', err => ipcSend({ name: 'error', body: err }));
    }, () => {
        reporter.end();
        callback(null, exitCode ? 1 : 0);
    });
}

debug('START', `reporter: ${argv.reporter}`);
debug('IS MASTER', isMaster);
debug('IGNORE PATH', config.ignorePath);
debug('FILTER PATH', argsPaths);

ipcSend({ name: 'start', body: argv });

if (isMaster) {
    const testFiles = getTestFiles();
    verbose(`TEST FILES: ${testFiles.length}`, testFiles.map(f => `  ${f}\n`).join(''));
}

if ((argv.coverage || argv.watch) && isMaster) {
    ipcSend({ name: 'run master' });
    const args = ['node', __filename].concat(process.argv.slice(2), '--child');
    if (argv.coverage) {
        const reporters = [];
        if (_.castArray(argv.coverage).includes('console')) {
            reporters.push('text', 'text-summary');
        }
        if (_.isArray(argv.coverage)) {
            reporters.push(..._.without(argv.coverage, 'console'));
        } else if (argv.coverage !== 'console') {
            reporters.push(argv.coverage);
        }
        if (argv.watch) {
            reporters.push('json-summary');
        }
        _.uniq(reporters).reverse().forEach(reporter => args.unshift(`--reporter=${reporter}`));
    } else if (argv.watch) {
        args.unshift('--reporter=json-summary');
    }

    if (argv.coverageDir) {
        args.unshift(`--report-dir=${argv.coverageDir}`);
    }

    ipcSend({ name: 'run nyc', body: args });

    const test = fork(config.nycPath, args, { stdio: 'inherit' });
    test.on('exit', (code) => {
        if (argv.web && _.castArray(argv.coverage).includes('html')) {
            serveCoverageHtml(argv.web);
        }
        if (argv.watch) {
            runWatcher(args);
        }
        if (!argv.web && !argv.watch) {
            process.exit(code);
        }
    });
    // bubble child message
    test.on('message', (message) => {
        ipcSend(message);
    });
    test.on('error', err => ipcSend({ name: 'error', body: err }));
} else {
    debug('RUN TESTS');
    ipcSend({ name: 'run test' });
    runTest((err, code) => {
        debug('FINISH TESTS', `code=${code}`);
        if (code) {
            process.nextTick(() => {
                process.exit(code);
            });
        }
    });
}
<commit_msg>some refactoring<commit_after>const { map, some, pull, clone, concat, padEnd, castArray, isArray, without, uniq, keys } = require('lodash');
const async = require('async');
const minimatch = require('minimatch');
const fs = require('fs');
const klawSync = require('klaw-sync');
const chalk = require('chalk');
const path = require('path');
const { fork } = require('child_process');
const { PassThrough } = require('stream');
const debug = require('util').debuglog('iamtest');
/*  lazy load
 *  const tapSpec = require('tap-spec');
 *  const tapSummary = require('tap-summary');
 *  const { amTapDot } = require('am-tap-dot');
 *  const StaticServer = require('static-server');
 */
const Child = require('./lib/Child');
const Watcher = require('./lib/Watcher');

const config = require('./lib/config')(path.join(process.cwd(), '.iamtest.js'));
const argv = require('./lib/yargs')();

const isMaster = !argv.child;
const isSilent = argv.reporter === 'silent';
const argsPaths = map(argv._, p => path.resolve(process.cwd(), p));

function ipcSend(message) {
    if (process.send) process.send(message);
}

function outputWrite(str) {
    if (!isSilent) {
        process.stdout.write(str);
    }
}

function verbose(...args) {
    if (argv.verbose) {
        outputWrite(`${chalk.cyan(args[0])}\n${args.slice(1).join('')}`);
    }
}

function tapeReporterStream() {
    switch (argv.reporter) {
        case 'tape':
        case 'silent':
            return new PassThrough();
        case 'spec':
            // eslint-disable-next-line global-require
            return require('tap-spec')();
        case 'summary':
            // eslint-disable-next-line global-require
            return require('tap-summary')({ ansi: true, progress: true });
        case 'dots':
            // eslint-disable-next-line global-require, import/newline-after-import
            return require('am-tap-dot').amTapDot();
        default:
            /* istanbul ignore next */
            throw new Error('invalid reporter value');
    }
}

function getTestFiles() {
    // if only single test file then do not ignore it
    if (argsPaths.length === 1 && !argsPaths[0].includes('*')) {
        return argsPaths;
    }
    return klawSync(config.rootPath)
        .filter((item) => {
            const filepath = item.path;
            if (minimatch(filepath, '!*.js', { matchBase: true })) return false;
            if (some(config.ignorePath, v => minimatch(filepath, v))) return false;
            if (argsPaths.length && !some(argsPaths, v => minimatch(filepath, v))) return false;
            return true;
        })
        .map(item => item.path);
}

function getCoverageFiles() {
    const coverageDir = argv.coverageDir || path.join(process.cwd(), 'coverage');
    const coverageReportPath = path.join(coverageDir, 'coverage-summary.json');
    if (!fs.existsSync(coverageReportPath)) {
        throw new Error(`Coverage report file ${coverageReportPath} not found`);
    }
    // eslint-disable-next-line import/no-dynamic-require, global-require
    const report = require(coverageReportPath);
    return pull(keys(report), 'total');
}

function serveCoverageHtml(port) {
    // eslint-disable-next-line global-require
    const StaticServer = require('static-server');

    const server = new StaticServer({
        rootPath: path.join(process.cwd(), 'coverage'),
        name: 'Coverage Report',
        port,
    });
    server.start(() => {
        outputWrite('  \n');
        outputWrite(`  View coverage report at ${chalk.cyan(`http://localhost:${server.port}`)}\n`);
        outputWrite('  Press ctrl+c to exit...\n');
    });
}

function filterChildArgs(args) {
    const _args = clone(args);
    const ix = _args.indexOf('--web');
    if (_args.includes('--web')) {
        _args.splice(ix, 2);
    }
    pull(_args, '-w', '--watch');
    ipcSend({ name: 'filter', body: _args });
    return _args;
}

function runWatcher(args) {
    let child = new Child(null, [], { verbose });
    const watcher = new Watcher({ verbose });
    const watchingFiles = concat(getTestFiles(), getCoverageFiles());
    const _args = filterChildArgs(args);
    watcher
        .watch(watchingFiles)
        .on('change', () => {
            ipcSend({ name: 'watcher event' });
            child.kill();
        })
        .on('debounce', () => {
            child.exit().then(() => {
                child = new Child(config.nycPath, _args, { verbose });
                ipcSend({ name: 'child fork', body: { id: child.id, script: config.nycPath, args: _args } });
                child.process.once('exit', () => {
                    ipcSend({ name: 'child exit', body: { id: child.id } });
                    watcher.watch(concat(getTestFiles(), getCoverageFiles()));
                });
                // bubble child message
                child.process.on('message', (message) => {
                    ipcSend(message);
                });
            });
        });
    process.nextTick(() => {
        ipcSend({ name: 'run watcher', body: watchingFiles });
    });
}

function outputFilenameHeader(filepath, isFirst) {
    let filenameHeader = '';
    const relativeFilePath = path.relative(config.rootPath, filepath);
    if (argv.reporter === 'tape') {
        const sof = isFirst ? '#\n#' : '\n#\n#';
        const eof = '\n#\n';
        filenameHeader = `${sof}${relativeFilePath}${eof}`;
    } else if (argv.reporter === 'dots') {
        const sof = isFirst ? '\n ' : '\n\n ';
        const eof = isFirst ? '' : '\n';
        const header = chalk.bgWhite.black(` ${padEnd(relativeFilePath, 80, ' ')}`);
        filenameHeader = `${sof}${header}${eof}`;
    } else if (!isSilent) {
        const sof = isFirst ? '\n ' : '\n\n ';
        const eof = isFirst ? '\n' : '\n';
        const header = chalk.bgWhite.black(` ${padEnd(relativeFilePath, 80, ' ')}`);
        filenameHeader = `${sof}${header}${eof}`;
    }
    return filenameHeader;
}

function runTest(callback) {
    const reporter = tapeReporterStream();
    if (!isSilent) {
        reporter.pipe(process.stdout);
    }
    const testFiles = getTestFiles();
    let exitCode = 0;
    debug(`TEST FILES: ${testFiles.length}`, `\n  • ${testFiles.join('\n  • ')}\n`);
    async.eachSeries(testFiles, (filePath, cb) => {
        let execPath = 'node';
        if (argv.flow) {
            execPath = config.flowNodeBinPath;
        }
        if (argv.babel) {
            execPath = config.babelNodeBinPath;
        }
        debug('RUN TEST:', `${execPath} ${filePath}`);
        const child = fork(filePath, [], {
            silent: true,
            execPath,
        });
        outputWrite(outputFilenameHeader(filePath, filePath === testFiles[0]));
        reporter.emit('dot-line-break');
        child.stdout.on('data', (data) => {
            reporter.write(data.toString());
        });
        child.once('exit', (code) => {
            exitCode += code;
            cb();
        });
        child.stdout.on('end', () => {});
        child.stderr.pipe(process.stderr);
        child.once('error', err => ipcSend({ name: 'error', body: err }));
    }, () => {
        reporter.end();
        callback(null, exitCode ? 1 : 0);
    });
}

debug('START', `reporter: ${argv.reporter}`);
debug('IS MASTER', isMaster);
debug('IGNORE PATH', config.ignorePath);
debug('FILTER PATH', argsPaths);

ipcSend({ name: 'start', body: argv });

if (isMaster) {
    const testFiles = getTestFiles();
    verbose(`TEST FILES: ${testFiles.length}`, testFiles.map(f => `  ${f}\n`).join(''));
}

if ((argv.coverage || argv.watch) && isMaster) {
    ipcSend({ name: 'run master' });
    const args = ['node', __filename].concat(process.argv.slice(2), '--child');
    if (argv.coverage) {
        const reporters = [];
        if (castArray(argv.coverage).includes('console')) {
            reporters.push('text', 'text-summary');
        }
        if (isArray(argv.coverage)) {
            reporters.push(...without(argv.coverage, 'console'));
        } else if (argv.coverage !== 'console') {
            reporters.push(argv.coverage);
        }
        if (argv.watch) {
            reporters.push('json-summary');
        }
        uniq(reporters).reverse().forEach(reporter => args.unshift(`--reporter=${reporter}`));
    } else if (argv.watch) {
        args.unshift('--reporter=json-summary');
    }

    if (argv.coverageDir) {
        args.unshift(`--report-dir=${argv.coverageDir}`);
    }

    ipcSend({ name: 'run nyc', body: args });

    const test = fork(config.nycPath, args, { stdio: 'inherit' });
    test.on('exit', (code) => {
        if (argv.web && castArray(argv.coverage).includes('html')) {
            serveCoverageHtml(argv.web);
        }
        if (argv.watch) {
            runWatcher(args);
        }
        if (!argv.web && !argv.watch) {
            process.exit(code);
        }
    });
    // bubble child message
    test.on('message', (message) => {
        ipcSend(message);
    });
    test.on('error', err => ipcSend({ name: 'error', body: err }));
} else {
    debug('RUN TESTS');
    ipcSend({ name: 'run test' });
    runTest((err, code) => {
        debug('FINISH TESTS', `code=${code}`);
        if (code) {
            process.nextTick(() => {
                process.exit(code);
            });
        }
    });
}
<|endoftext|>