<commit_before>const lgtv2 = require('lgtv2');
const wol = require('wake_on_lan');
const tcpp = require('tcp-ping');

let Service, Characteristic;

module.exports = function (homebridge) {
    Service = homebridge.hap.Service;
    Characteristic = homebridge.hap.Characteristic;

    homebridge.registerAccessory('homebridge-webos3', 'webos3', webos3Accessory);
};

// MAIN SETUP
function webos3Accessory(log, config, api) {
    this.log = log;
    this.ip = config['ip'];
    this.name = config['name'];
    this.mac = config['mac'];
    this.keyFile = config['keyFile'];
    this.volumeControl = config['volumeControl'];
    if (this.volumeControl == undefined) {
        this.volumeControl = true;
    }
    this.pollingEnabled = config['pollingEnabled'];
    if (this.pollingEnabled == undefined) {
        this.pollingEnabled = false;
    }
    this.alivePollingInterval = config['pollingInterval'] || 5;
    this.alivePollingInterval = this.alivePollingInterval * 1000;
    this.externalSourceSwitch = config['externalSourceSwitch'];
    this.appSwitch = config['appSwitch'];

    this.url = 'ws://' + this.ip + ':3000';
    this.enabledServices = [];
    this.connected = false;
    this.checkCount = 0;
    this.checkAliveInterval = null;

    lgtv = new lgtv2({
        url: this.url,
        timeout: 5000,
        reconnect: 3000,
        keyFile: this.keyFile
    });

    lgtv.on('connect', () => {
        this.log.info('webOS - connected to TV');
        this.connected = true;
        if (!this.checkAliveInterval && this.pollingEnabled) {
            this.checkAliveInterval = setInterval(this.checkTVState.bind(this, this.pollCallback.bind(this)), this.alivePollingInterval);
        }
        lgtv.subscribe('ssap://com.webos.applicationManager/getForegroundAppInfo', (err, res) => {
            if (res && res.appId) {
                this.log.info('webOS - current appId: %s', res.appId);
            }
        });
        this.updateAccessoryStatus();
    });

    lgtv.on('close', () => {
        this.log.info('webOS - disconnected from TV');
        this.connected = false;
        //if(this.checkAliveInterval) {
        //  clearInterval(this.checkAliveInterval);
        //  this.checkAliveInterval = undefined;
        //}
    });

    lgtv.on('error', (error) => {
        this.log.error('webOS - %s', error);
        //this.connected = false;
        //setTimeout(lgtv.connect(this.url), 5000);
    });

    lgtv.on('prompt', () => {
        this.log.info('webOS - prompt for confirmation');
        this.connected = false;
    });

    lgtv.on('connecting', () => {
        this.log.debug('webOS - connecting to TV');
        this.connected = false;
    });

    this.powerService = new Service.Switch(this.name + " Power", "powerService");
    this.volumeService = new Service.Lightbulb(this.name + " Volume", "volumeService");
    this.externalSourceSwitchService = new Service.Switch(this.name + " Input: " + this.externalSourceSwitch, "externalSourceSwitchService");

    this.informationService = new Service.AccessoryInformation();

    this.powerService
        .getCharacteristic(Characteristic.On)
        .on('get', this.getState.bind(this))
        .on('set', this.setState.bind(this));

    this.volumeService
        .getCharacteristic(Characteristic.On)
        .on('get', this.getMuteState.bind(this))
        .on('set', this.setMuteState.bind(this));

    this.volumeService
        .addCharacteristic(new Characteristic.Brightness())
        .on('get', this.getVolume.bind(this))
        .on('set', this.setVolume.bind(this));

    this.externalSourceSwitchService
        .getCharacteristic(Characteristic.On)
        .on('get', this.getExternalSourceSwitchState.bind(this))
        .on('set', this.setExternalSourceSwitchState.bind(this));

    this.informationService
        .setCharacteristic(Characteristic.Manufacturer, 'LG Electronics Inc.')
        .setCharacteristic(Characteristic.Model, 'webOS TV')
        .setCharacteristic(Characteristic.SerialNumber, '-')
        .setCharacteristic(Characteristic.FirmwareRevision, '0.9.2');

    this.enabledServices.push(this.powerService);
    if (this.volumeControl) this.enabledServices.push(this.volumeService);
    if (this.externalSourceSwitch && this.externalSourceSwitch.length > 0) this.enabledServices.push(this.externalSourceSwitchService);
    this.enabledServices.push(this.informationService);

	this.prepareAppSwitchService();

}

// SETUP COMPLEX SERVICES
webos3Accessory.prototype.prepareAppSwitchService = function () {

	if(this.appSwitch == undefined ||  this.appSwitch == null || this.appSwitch.length <= 0){
		return;
	}

	let isArray = Array.isArray(this.appSwitch);

	if(isArray){
		this.appSwitchService = new Array();
		this.appSwitch.forEach((value, i) => {
			this.appSwitchService[i] = new Service.Switch(this.name + " App: " + value, "appSwitchService" + i);
		});
	}else{
		this.appSwitchService = new Service.Switch(this.name + " App: " + this.appSwitch, "appSwitchService");
	}

	if(isArray){
		this.appSwitch.forEach((value, i) => {
			this.appSwitchService[i]
				.getCharacteristic(Characteristic.On)
				.on('get', (callback) => {
						this.getAppSwitchState(callback, this.appSwitch[i]);
					})
				.on('set', (state, callback) => {
						this.setAppSwitchState(state, callback, this.appSwitch[i]);
					});
		});
	}else{
		this.appSwitchService
			.getCharacteristic(Characteristic.On)
			.on('get', (callback) => {
						this.getAppSwitchState(callback, this.appSwitch);
					})
			.on('set', (state, callback) => {
						this.setAppSwitchState(state, callback, this.appSwitch);
					});
	}

	if(isArray){
		this.appSwitch.forEach((value, i) => {
			this.enabledServices.push(this.appSwitchService[i]);
		});
	}else{
		this.enabledServices.push(this.appSwitchService);
	}

};

// HELPER METHODS
webos3Accessory.prototype.setMuteStateManuallyCallback = function (error, value) {
    if (this.volumeControl) this.volumeService.getCharacteristic(Characteristic.On).updateValue(value);
};

webos3Accessory.prototype.setExternalSourceSwitchManuallyCallback = function (error, value) {
    if (this.externalSourceSwitch && this.externalSourceSwitch.length > 0) this.externalSourceSwitchService.getCharacteristic(Characteristic.On).updateValue(value);
};

webos3Accessory.prototype.setAppSwitchManually = function (error, value, appId) {
	if(this.appSwitchService){
		if(Array.isArray(this.appSwitch)){
			if(appId == undefined || appId == null || appId.length <= 0){
				this.appSwitch.forEach((value, i) => {
					this.appSwitchService[i].getCharacteristic(Characteristic.On).updateValue(value);
				});
			}else {
				this.appSwitch.forEach((value, i) => {
					if(appId === value){
						this.appSwitchService[i].getCharacteristic(Characteristic.On).updateValue(value);
					}else {
						this.appSwitchService[i].getCharacteristic(Characteristic.On).updateValue(false);
					}
				});
			}
		}else{
			this.appSwitchService.getCharacteristic(Characteristic.On).updateValue(value);
		}
	}
};

webos3Accessory.prototype.updateAccessoryStatus = function () {
    if (this.volumeControl) this.checkMuteState(this.setMuteStateManuallyCallback.bind(this));
    if (this.externalSourceSwitch && this.externalSourceSwitch.length > 0) this.checkExternalInput(this.setExternalSourceSwitchManuallyCallback.bind(this));
	this.checkForegroundApp(this.setAppSwitchManually);
};

webos3Accessory.prototype.pollCallback = function (error, status) {
    if (!status) {
        this.powerService.getCharacteristic(Characteristic.On).updateValue(status);
        this.volumeService.getCharacteristic(Characteristic.On).updateValue(status);
    } else {
        this.powerService.getCharacteristic(Characteristic.On).updateValue(status);
    }
};

webos3Accessory.prototype.checkTVState = function (callback) {
    tcpp.probe(this.ip, 3000, (err, isAlive) => {
        if (!isAlive) {
            this.connected = false;
        } else {
            this.connected = true;
        }
        this.log.debug('webOS - TV state: %s', this.connected ? "On" : "Off");
        callback(null, this.connected);
    });
};

webos3Accessory.prototype.checkMuteState = function (callback) {
    if (this.connected) {
        lgtv.request('ssap://audio/getStatus', (err, res) => {
            if (!res || err) {
                callback(new Error('webOS - TV mute check - error while getting current mute state'));
            } else {
                this.log.info('webOS - TV muted: %s', res.mute ? "Yes" : "No");
                callback(null, !res.mute);
            }
        });
    } else {
        callback(null, false);
    }
};

webos3Accessory.prototype.checkVolumeLevel = function (callback) {
    if (this.connected) {
        lgtv.request('ssap://audio/getVolume', (err, res) => {
            if (!res || err) {
                callback(new Error('webOS - TV volume - error while getting current volume'));
            } else {
                this.log.info('webOS - TV volume: ' + res.volume);
                callback(null, parseInt(res.volume));
            }
        });
    } else {
        callback(null, false);
    }
};

webos3Accessory.prototype.checkExternalInput = function (callback) {
    if (this.connected) {
        lgtv.request('ssap://com.webos.applicationManager/getForegroundAppInfo', (err, res) => {
            if (!res || err) {
                callback(new Error('webOS - external input - error while getting external input info'));
            } else {
                this.log.debug('webOS - TV current appId: %s', res.appId);
                if (res.appId.includes('com.webos.app.externalinput')) {
                    callback(null, true);
                } else {
                    callback(null, false);
                }
            }
        });
    } else {
        callback(null, false);
    }
};

webos3Accessory.prototype.checkForegroundApp = function (callback, appId) {
    if (this.connected) {
        lgtv.request('ssap://com.webos.applicationManager/getForegroundAppInfo', (err, res) => {
            if (!res || err) {
                callback(new Error('webOS - external input - error while getting external input info'));
            } else {
                this.log.info('webOS - TV current appId: %s', res.appId);
                if (res.appId === appId) {
                    callback(null, true, appId);
                } else {
                    callback(null, false, appId);
                }
            }
        });
    } else {
        callback(null, false);
    }
};

webos3Accessory.prototype.checkWakeOnLan = function (callback) {
    if (this.connected) {
        this.checkCount = 0;
        callback(null, true);
    } else {
        if (this.checkCount < 3) {
            this.checkCount++;
            lgtv.connect(this.url);
            setTimeout(this.checkWakeOnLan.bind(this, callback), 5000);
        } else {
            this.checkCount = 0;
            callback(new Error('webOS - wake timeout'));
        }
    }
};

// HOMEBRIDGE STATE SETTERS/GETTERS
webos3Accessory.prototype.getState = function (callback) {
    lgtv.connect(this.url);
    this.checkTVState.call(this, callback);
};

webos3Accessory.prototype.setState = function (state, callback) {
    if (state) {
        if (!this.connected) {
            wol.wake(this.mac, (error) => {
                if (error) return callback(new Error('webOS - wake on lan error'));
                this.checkCount = 0;
                setTimeout(this.checkWakeOnLan.bind(this, callback), 5000);
            })
        } else {
            callback(null, true);
        }
    } else {
        if (this.connected) {
            lgtv.request('ssap://system/turnOff', (err, res) => {
                if (err) return callback(null, false);
                lgtv.disconnect();
                this.connected = false;
                this.volumeService.getCharacteristic(Characteristic.On).updateValue(false);
                this.externalSourceSwitchService.getCharacteristic(Characteristic.On).updateValue(false);
				this.setAppSwitchManually(null, false, null);
                callback(null, true);
            })
        } else {
            callback(new Error('webOS - is not connected'))
        }
    }
};

webos3Accessory.prototype.getMuteState = function (callback) {
    setTimeout(this.checkMuteState.bind(this, callback), 50);
};

webos3Accessory.prototype.setMuteState = function (state, callback) {
    if (this.connected) {
        lgtv.request('ssap://audio/setMute', {mute: !state});
        callback(null, state);
    } else {
        callback(new Error('webOS - is not connected'))
    }
};

webos3Accessory.prototype.getVolume = function (callback) {
    setTimeout(this.checkVolumeLevel.bind(this, callback), 50);
};

webos3Accessory.prototype.setVolume = function (level, callback) {
    if (this.connected) {
        lgtv.request('ssap://audio/setVolume', {volume: level});
        callback(null, level);
    } else {
        callback(new Error('webOS - is not connected'))
    }
};

webos3Accessory.prototype.getExternalSourceSwitchState = function (callback) {
    if (!this.connected) {
        callback(null, false);
    } else {
        setTimeout(this.checkExternalInput.bind(this, callback), 50);
    }
};

webos3Accessory.prototype.setExternalSourceSwitchState = function (state, callback) {
    if (this.connected) {
        if (state) {
            lgtv.request('ssap://tv/switchInput', {inputId: this.externalSourceSwitch});
            this.setAppSwitchManually(null, false, null);
        } else {
            lgtv.request('ssap://system.launcher/launch', {id: "com.webos.app.livetv"});
        }
        callback(null, state);
    } else {
        callback(new Error('webOS - is not connected'))
    }
};

webos3Accessory.prototype.getAppSwitchState = function (callback, appId) {
    if (!this.connected) {
        callback(null, false);
    } else {
        setTimeout(this.checkForegroundApp.bind(this, callback, appId), 50);
    }
};

webos3Accessory.prototype.setAppSwitchState = function (state, callback, appId) {
    if (this.connected) {
        if (state) {
            lgtv.request('ssap://system.launcher/launch', {id: appId});
            this.setExternalSourceSwitchManuallyCallback(null, false);
			this.setAppSwitchManually(null, true, appId);
        } else {
            lgtv.request('ssap://system.launcher/launch', {id: "com.webos.app.livetv"});
        }
        callback(null, state);
    } else {
        callback(new Error('webOS - is not connected'))
    }
};

webos3Accessory.prototype.getServices = function () {
    return this.enabledServices;
};

<commit_msg>Removed externalSourceSwitch property<commit_after>const lgtv2 = require('lgtv2');
const wol = require('wake_on_lan');
const tcpp = require('tcp-ping');

let Service, Characteristic;

module.exports = function (homebridge) {
    Service = homebridge.hap.Service;
    Characteristic = homebridge.hap.Characteristic;

    homebridge.registerAccessory('homebridge-webos3', 'webos3', webos3Accessory);
};

// MAIN SETUP
function webos3Accessory(log, config, api) {
    this.log = log;
    this.ip = config['ip'];
    this.name = config['name'];
    this.mac = config['mac'];
    this.keyFile = config['keyFile'];
    this.volumeControl = config['volumeControl'];
    if (this.volumeControl == undefined) {
        this.volumeControl = true;
    }
    this.pollingEnabled = config['pollingEnabled'];
    if (this.pollingEnabled == undefined) {
        this.pollingEnabled = false;
    }
    this.alivePollingInterval = config['pollingInterval'] || 5;
    this.alivePollingInterval = this.alivePollingInterval * 1000;
    this.appSwitch = config['appSwitch'];

    this.url = 'ws://' + this.ip + ':3000';
    this.enabledServices = [];
    this.connected = false;
    this.checkCount = 0;
    this.checkAliveInterval = null;

    lgtv = new lgtv2({
        url: this.url,
        timeout: 5000,
        reconnect: 3000,
        keyFile: this.keyFile
    });

    lgtv.on('connect', () => {
        this.log.info('webOS - connected to TV');
        this.connected = true;
        if (!this.checkAliveInterval && this.pollingEnabled) {
            this.checkAliveInterval = setInterval(this.checkTVState.bind(this, this.pollCallback.bind(this)), this.alivePollingInterval);
        }
        lgtv.subscribe('ssap://com.webos.applicationManager/getForegroundAppInfo', (err, res) => {
            if (res && res.appId) {
                this.log.info('webOS - current appId: %s', res.appId);
            }
        });
        this.updateAccessoryStatus();
    });

    lgtv.on('close', () => {
        this.log.info('webOS - disconnected from TV');
        this.connected = false;
        //if(this.checkAliveInterval) {
        //  clearInterval(this.checkAliveInterval);
        //  this.checkAliveInterval = undefined;
        //}
    });

    lgtv.on('error', (error) => {
        this.log.error('webOS - %s', error);
        //this.connected = false;
        //setTimeout(lgtv.connect(this.url), 5000);
    });

    lgtv.on('prompt', () => {
        this.log.info('webOS - prompt for confirmation');
        this.connected = false;
    });

    lgtv.on('connecting', () => {
        this.log.debug('webOS - connecting to TV');
        this.connected = false;
    });

    this.powerService = new Service.Switch(this.name + " Power", "powerService");
    this.volumeService = new Service.Lightbulb(this.name + " Volume", "volumeService");
    this.informationService = new Service.AccessoryInformation();

    this.powerService
        .getCharacteristic(Characteristic.On)
        .on('get', this.getState.bind(this))
        .on('set', this.setState.bind(this));

    this.volumeService
        .getCharacteristic(Characteristic.On)
        .on('get', this.getMuteState.bind(this))
        .on('set', this.setMuteState.bind(this));

    this.volumeService
        .addCharacteristic(new Characteristic.Brightness())
        .on('get', this.getVolume.bind(this))
        .on('set', this.setVolume.bind(this));

    this.informationService
        .setCharacteristic(Characteristic.Manufacturer, 'LG Electronics Inc.')
        .setCharacteristic(Characteristic.Model, 'webOS TV')
        .setCharacteristic(Characteristic.SerialNumber, '-')
        .setCharacteristic(Characteristic.FirmwareRevision, '0.9.2');

    this.enabledServices.push(this.powerService);
    if (this.volumeControl) this.enabledServices.push(this.volumeService);
    this.enabledServices.push(this.informationService);

	this.prepareAppSwitchService();

}

// SETUP COMPLEX SERVICES

webos3Accessory.prototype.prepareAppSwitchService = function () {

	if(this.appSwitch == undefined ||  this.appSwitch == null || this.appSwitch.length <= 0){
		return;
	}

	let isArray = Array.isArray(this.appSwitch);

	if(isArray){
		this.appSwitchService = new Array();
		this.appSwitch.forEach((value, i) => {
			this.appSwitch[i] = str = this.appSwitch[i].replace(/\s/g, '');
			this.appSwitchService[i] = new Service.Switch(this.name + " App: " + value, "appSwitchService" + i);
		});
	}else{
		this.appSwitchService = new Service.Switch(this.name + " App: " + this.appSwitch, "appSwitchService");
	}

	if(isArray){
		this.appSwitch.forEach((value, i) => {
			this.appSwitchService[i]
				.getCharacteristic(Characteristic.On)
				.on('get', (callback) => {
						this.getAppSwitchState(callback, this.appSwitch[i]);
					})
				.on('set', (state, callback) => {
						this.setAppSwitchState(state, callback, this.appSwitch[i]);
					});
		});
	}else{
		this.appSwitchService
			.getCharacteristic(Characteristic.On)
			.on('get', (callback) => {
						this.getAppSwitchState(callback, this.appSwitch);
					})
			.on('set', (state, callback) => {
						this.setAppSwitchState(state, callback, this.appSwitch);
					});
	}

	if(isArray){
		this.appSwitch.forEach((value, i) => {
			this.enabledServices.push(this.appSwitchService[i]);
		});
	}else{
		this.enabledServices.push(this.appSwitchService);
	}

};

// HELPER METHODS
webos3Accessory.prototype.setMuteStateManuallyCallback = function (error, value) {
    if (this.volumeControl) this.volumeService.getCharacteristic(Characteristic.On).updateValue(value);
};

webos3Accessory.prototype.setAppSwitchManually = function (error, value, appId) {
	if(this.appSwitchService){
		if(Array.isArray(this.appSwitch)){
			if(appId == undefined || appId == null || appId.length <= 0){
				this.appSwitch.forEach((value, i) => {
					this.appSwitchService[i].getCharacteristic(Characteristic.On).updateValue(value);
				});
			}else {
				this.appSwitch.forEach((value, i) => {
					if(appId === value){
						this.appSwitchService[i].getCharacteristic(Characteristic.On).updateValue(value);
					}else {
						this.appSwitchService[i].getCharacteristic(Characteristic.On).updateValue(false);
					}
				});
			}
		}else{
			this.appSwitchService.getCharacteristic(Characteristic.On).updateValue(value);
		}
	}
};

webos3Accessory.prototype.updateAccessoryStatus = function () {
    if (this.volumeControl) this.checkMuteState(this.setMuteStateManuallyCallback);
	this.checkForegroundApp(this.setAppSwitchManually);
};

webos3Accessory.prototype.pollCallback = function (error, status) {
    if (!status) {
        this.powerService.getCharacteristic(Characteristic.On).updateValue(status);
        this.volumeService.getCharacteristic(Characteristic.On).updateValue(status);
    } else {
        this.powerService.getCharacteristic(Characteristic.On).updateValue(status);
    }
};

webos3Accessory.prototype.checkTVState = function (callback) {
    tcpp.probe(this.ip, 3000, (err, isAlive) => {
        if (!isAlive) {
            this.connected = false;
        } else {
            this.connected = true;
        }
        this.log.debug('webOS - TV state: %s', this.connected ? "On" : "Off");
        callback(null, this.connected);
    });
};

webos3Accessory.prototype.checkMuteState = function (callback) {
    if (this.connected) {
        lgtv.request('ssap://audio/getStatus', (err, res) => {
            if (!res || err) {
                callback(new Error('webOS - TV mute check - error while getting current mute state'));
            } else {
                this.log.info('webOS - TV muted: %s', res.mute ? "Yes" : "No");
                callback(null, !res.mute);
            }
        });
    } else {
        callback(null, false);
    }
};

webos3Accessory.prototype.checkVolumeLevel = function (callback) {
    if (this.connected) {
        lgtv.request('ssap://audio/getVolume', (err, res) => {
            if (!res || err) {
                callback(new Error('webOS - TV volume - error while getting current volume'));
            } else {
                this.log.info('webOS - TV volume: ' + res.volume);
                callback(null, parseInt(res.volume));
            }
        });
    } else {
        callback(null, false);
    }
};

webos3Accessory.prototype.checkForegroundApp = function (callback, appId) {
    if (this.connected) {
        lgtv.request('ssap://com.webos.applicationManager/getForegroundAppInfo', (err, res) => {
            if (!res || err) {
                callback(new Error('webOS - current app - error while getting current app info'));
            } else {
                this.log.debug('webOS - TV current appId: %s', res.appId);
                if (res.appId === appId) {
                    callback(null, true, appId);
                } else {
                    callback(null, false, appId);
                }
            }
        });
    } else {
        callback(null, false);
    }
};

webos3Accessory.prototype.checkWakeOnLan = function (callback) {
    if (this.connected) {
        this.checkCount = 0;
        callback(null, true);
    } else {
        if (this.checkCount < 3) {
            this.checkCount++;
            lgtv.connect(this.url);
            setTimeout(this.checkWakeOnLan.bind(this, callback), 5000);
        } else {
            this.checkCount = 0;
            callback(new Error('webOS - wake timeout'));
        }
    }
};

// HOMEBRIDGE STATE SETTERS/GETTERS
webos3Accessory.prototype.getState = function (callback) {
    lgtv.connect(this.url);
    this.checkTVState.call(this, callback);
};

webos3Accessory.prototype.setState = function (state, callback) {
    if (state) {
        if (!this.connected) {
            wol.wake(this.mac, (error) => {
                if (error) return callback(new Error('webOS - wake on lan error'));
                this.checkCount = 0;
                setTimeout(this.checkWakeOnLan.bind(this, callback), 5000);
            })
        } else {
            callback(null, true);
        }
    } else {
        if (this.connected) {
            lgtv.request('ssap://system/turnOff', (err, res) => {
                if (err) return callback(null, false);
                lgtv.disconnect();
                this.connected = false;
                this.volumeService.getCharacteristic(Characteristic.On).updateValue(false);
				this.setAppSwitchManually(null, false, null);
                callback(null, true);
            })
        } else {
            callback(new Error('webOS - is not connected'))
        }
    }
};

webos3Accessory.prototype.getMuteState = function (callback) {
    setTimeout(this.checkMuteState.bind(this, callback), 50);
};

webos3Accessory.prototype.setMuteState = function (state, callback) {
    if (this.connected) {
        lgtv.request('ssap://audio/setMute', {mute: !state});
        callback(null, state);
    } else {
        callback(new Error('webOS - is not connected'))
    }
};

webos3Accessory.prototype.getVolume = function (callback) {
    setTimeout(this.checkVolumeLevel.bind(this, callback), 50);
};

webos3Accessory.prototype.setVolume = function (level, callback) {
    if (this.connected) {
        lgtv.request('ssap://audio/setVolume', {volume: level});
        callback(null, level);
    } else {
        callback(new Error('webOS - is not connected'))
    }
};

webos3Accessory.prototype.getAppSwitchState = function (callback, appId) {
    if (!this.connected) {
        callback(null, false);
    } else {
        setTimeout(this.checkForegroundApp.bind(this, callback, appId), 50);
    }
};

webos3Accessory.prototype.setAppSwitchState = function (state, callback, appId) {
    if (this.connected) {
        if (state) {
            lgtv.request('ssap://system.launcher/launch', {id: appId});
			this.setAppSwitchManually(null, true, appId);
        } else {
            lgtv.request('ssap://system.launcher/launch', {id: "com.webos.app.livetv"});
        }
        callback(null, state);
    } else {
        callback(new Error('webOS - is not connected'))
    }
};

webos3Accessory.prototype.getServices = function () {
    return this.enabledServices;
};

<|endoftext|>


<commit_before>var preprocessor = require('suitcss-preprocessor');

module.exports = function(content) {
  this.cacheable(true);
  return preprocessor(content);
};
<commit_msg>correctly set source path<commit_after>var preprocessor = require('suitcss-preprocessor');

module.exports = function(content) {
  this.cacheable(true);
  return preprocessor(content, {
    source: this.resourcePath
  });
};
<|endoftext|>


<commit_before>global.require = require;
const assert = require('chai').assert;
const jsdom = require('jsdom').jsdom;
const NeovimStore = require('../../src/out/neovim/store').default;
const NeovimInput = require('../../src/out/neovim/input').default;

function keydownEvent(opts) {
    const o = opts || {};
    if (o.keyCode) {
        o.charCode = o.which = o.keyCode;
    } else if (o.key) {
        o.keyCode = o.charCode = o.which = o.key.charCodeAt(0);
    } else {
        throw 'Invalid options for KeyboardEvent';
    }
    return new window.KeyboardEvent('keydown', o);
}

function dispatchKeydown(opts) {
    return global.input_element.dispatchEvent(keydownEvent(opts));
}

function inputByKeydown(opts) {
    global.last_input = '';
    dispatchKeydown(opts);
    const i = global.last_input;
    global.last_input = '';
    return i;
}

function dispatchInputEvent() {
    global.input_element.dispatchEvent(
        new window.Event('input', {
            bubbles: true,
            cancelable: false
        })
    );
}

function catchInputOnInputEvent(i) {
    global.last_input = '';
    global.input_element.value = i;
    dispatchInputEvent();
    const tmp = global.last_input;
    global.last_input = '';
    return tmp;
}

describe('NeovimInput', () => {
    before(() => {
        /* global document input_element window input last_input */
        global.document = jsdom('<input class="neovim-input"/>');
        global.input_element = document.querySelector('.neovim-input');
        input_element.value = '';
        global.window = document.defaultView;
        const s = new NeovimStore();
        /* eslint no-unused-vars:0 */
        global.input = new NeovimInput(s);
        global.last_input = '';
        s.on('input', i => {
            global.last_input = i;
        });
    });

    after(() => {
        delete global.document;
        delete global.window;
        delete global.input;
        delete global.input_element;
    });

    it('focuses on an input element on initialization', () => {
        assert.equal(document.activeElement.className, 'neovim-input');
    });

    context("on 'keydown' event", () => {
        it('ignores normal char input without modifiers', () => {
            global.last_input = '';

            dispatchKeydown({key: 'a'});
            assert.equal(last_input, '');

            dispatchKeydown({key: '['});
            assert.equal(last_input, '');

            dispatchKeydown({key: '3', shiftKey: true});
            assert.equal(last_input, '');

            dispatchKeydown({key: 'あ'});
            assert.equal(last_input, '');
        });

        it('accepts input with ctrl key', () => {
            assert.equal(inputByKeydown({key: '\u0001', ctrlKey: true}), '\u0001');  // <C-a>
            assert.equal(inputByKeydown({key: '3', ctrlKey: true}), '3');
        });

        it('accepts input with alt key', () => {
            assert.equal(inputByKeydown({key: ',', altKey: true}), '');
            // assert.equal(inputByKeydown('r', {altKey: true}), '');
            assert.equal(inputByKeydown({key: '4', altKey: true}), '');
            // assert.equal(inputByKeydown('.', {altKey: true, shiftKey: true}), '');
        });

        it('accepts input with alt and ctrl key', () => {
            assert.equal(inputByKeydown({key: ',', altKey: true, ctrlKey: true}), '<C-A-,>');
            assert.equal(inputByKeydown({key: 'a', altKey: true, ctrlKey: true}), '<C-A-a>');
            assert.equal(inputByKeydown({key: 'a', altKey: true, ctrlKey: true, shiftKey: true}), '<C-A-S-a>');
        });

        it('accepts special keys', () => {
            assert.equal(inputByKeydown({keyCode: 9}), '<Tab>');
            assert.equal(inputByKeydown({keyCode: 9, altKey: true}), '');
            assert.equal(inputByKeydown({keyCode: 9, ctrlKey: true, shiftKey: true}), '<C-S-Tab>');
            assert.equal(inputByKeydown({keyCode: 13}), '<CR>');
            assert.equal(inputByKeydown({keyCode: 13, altKey: true}), '');
            assert.equal(inputByKeydown({keyCode: 13, ctrlKey: true, shiftKey: true}), '<C-S-CR>');
            assert.equal(inputByKeydown({keyCode: 37}), '<Left>');
            assert.equal(inputByKeydown({keyCode: 37, altKey: true}), '');
            assert.equal(inputByKeydown({keyCode: 37, ctrlKey: true, shiftKey: true}), '<C-S-Left>');
            assert.equal(inputByKeydown({keyCode: 92}), '<Bslash>');
            assert.equal(inputByKeydown({keyCode: 92, altKey: true}), '');
            assert.equal(inputByKeydown({keyCode: 92, ctrlKey: true, shiftKey: true}), '<C-S-Bslash>');
            assert.equal(inputByKeydown({keyCode: 188, shiftKey: true}), '<LT>');
            assert.equal(inputByKeydown({keyCode: 188, ctrlKey: true, shiftKey: true}), '<C-LT>');
            assert.equal(inputByKeydown({keyCode: 188}), '');
        });

        it('handles <CR>, <Esc>, <BS> and <C-m>, <C-[>, <C-h> edge cases', () => {
            assert.equal(inputByKeydown({key: 'Enter', keyCode: 13}), '<CR>');
            assert.equal(inputByKeydown({key: 'Enter', keyCode: 77, ctrlKey: true}), '<C-m>');
            assert.equal(inputByKeydown({key: 'Enter', keyCode: 13, ctrlKey: true}), '<C-CR>');

            assert.equal(inputByKeydown({key: 'Escape', keyCode: 27}), '<Esc>');
            assert.equal(inputByKeydown({key: 'Escape', keyCode: 219, ctrlKey: true}), '<C-[>');
            assert.equal(inputByKeydown({key: 'Escape', keyCode: 27, ctrlKey: true}), '<C-Esc>');

            assert.equal(inputByKeydown({key: 'Backspace', keyCode: 8}), '<BS>');
            assert.equal(inputByKeydown({key: 'Backspace', keyCode: 72, ctrlKey: true}), '<C-h>');
            assert.equal(inputByKeydown({key: 'Backspace', keyCode: 8, ctrlKey: true}), '<C-BS>');

            assert.equal(inputByKeydown({key: 'Tab', keyCode: 9}), '<Tab>');
            assert.equal(inputByKeydown({key: 'Tab', keyCode: 73, ctrlKey: true}), '<C-i>');
            assert.equal(inputByKeydown({key: 'Tab', keyCode: 9, ctrlKey: true}), '<C-Tab>');
        });
    });

    context("on 'input' event", () => {
        it('sends input character to Neovim', () => {
            assert.equal(catchInputOnInputEvent('a'), 'a');
            assert.equal(catchInputOnInputEvent('3'), '3');
            assert.equal(catchInputOnInputEvent(';'), ';');
            assert.equal(catchInputOnInputEvent('^'), '^');
        });

        it('clears value in <input>', () => {
            global.input_element.value = 'a';
            dispatchInputEvent();
            assert.equal(global.input_element.value, '');
        });
    });
});

<commit_msg>fix and add tests for improved key handling with KeyboardEvent.key<commit_after>global.require = require;
const assert = require('chai').assert;
const jsdom = require('jsdom').jsdom;
const NeovimStore = require('../../src/out/neovim/store').default;
const NeovimInput = require('../../src/out/neovim/input').default;

function keydownEvent(opts) {
    const o = opts || {};
    if (o.keyCode) {
        o.charCode = o.which = o.keyCode;
    } else if (o.key !== undefined) {
        o.keyCode = o.charCode = o.which = o.key.charCodeAt(0);
    } else {
        throw 'Invalid options for KeyboardEvent';
    }
    return new window.KeyboardEvent('keydown', o);
}

function dispatchKeydown(opts) {
    return global.input_element.dispatchEvent(keydownEvent(opts));
}

function inputByKeydown(opts) {
    global.last_input = '';
    dispatchKeydown(opts);
    const i = global.last_input;
    global.last_input = '';
    return i;
}

function dispatchInputEvent() {
    global.input_element.dispatchEvent(
        new window.Event('input', {
            bubbles: true,
            cancelable: false
        })
    );
}

function catchInputOnInputEvent(i) {
    global.last_input = '';
    global.input_element.value = i;
    dispatchInputEvent();
    const tmp = global.last_input;
    global.last_input = '';
    return tmp;
}

describe('NeovimInput', () => {
    before(() => {
        /* global document input_element window input last_input */
        global.document = jsdom('<input class="neovim-input"/>');
        global.input_element = document.querySelector('.neovim-input');
        input_element.value = '';
        global.window = document.defaultView;
        const s = new NeovimStore();
        /* eslint no-unused-vars:0 */
        global.input = new NeovimInput(s);
        global.last_input = '';
        s.on('input', i => {
            global.last_input = i;
        });
    });

    after(() => {
        delete global.document;
        delete global.window;
        delete global.input;
        delete global.input_element;
    });

    it('focuses on an input element on initialization', () => {
        assert.equal(document.activeElement.className, 'neovim-input');
    });

    context("on 'keydown' event", () => {
        it('ignores normal char input without modifiers', () => {
            global.last_input = '';

            dispatchKeydown({key: 'a'});
            assert.equal(last_input, '');

            dispatchKeydown({key: '['});
            assert.equal(last_input, '');

            dispatchKeydown({key: '3', shiftKey: true});
            assert.equal(last_input, '');

            dispatchKeydown({key: 'あ'});
            assert.equal(last_input, '');
        });

        it('accepts input with ctrl key', () => {
            assert.equal(inputByKeydown({key: '\u0001', ctrlKey: true}), '\u0001');  // <C-a>
            assert.equal(inputByKeydown({key: '\u000f', ctrlKey: true}), '\u000f');  // <C-o>
            // Note: Ctrl key input is omitted in many Ctrl+{number} or Ctrl+{sign} sequences.
            assert.equal(inputByKeydown({key: '3', ctrlKey: true}), '3');
        });

        it('accepts input with alt key', () => {
            assert.equal(inputByKeydown({key: ',', altKey: true}), '');
            assert.equal(inputByKeydown({key: 'r', altKey: true}), '');
            assert.equal(inputByKeydown({key: '4', altKey: true}), '');
        });

        it('accepts input with alt+ctrl keys', () => {
            if (process.platform === 'darwin') {
                assert.equal(inputByKeydown({key: 'a', ctrlKey: true, altKey: 'true'}), '<C-A-a>');
                assert.equal(inputByKeydown({key: 'o', ctrlKey: true, altKey: 'true'}), '<C-A-o>');
            } else {
                assert.equal(inputByKeydown({key: '\u0001', ctrlKey: true, altKey: 'true'}), '<C-A-\u0001>');
                assert.equal(inputByKeydown({key: '\u000f', ctrlKey: true, altKey: 'true'}), '<C-A-\u000f>');
            }
        });

        it('accepts special keys', () => {
            assert.equal(inputByKeydown({key: 'Tab'}), '<Tab>');
            assert.equal(inputByKeydown({key: 'Tab', altKey: true}), '');
            assert.equal(inputByKeydown({key: 'Tab', ctrlKey: true, shiftKey: true}), '<C-S-Tab>');
            assert.equal(inputByKeydown({key: 'Enter'}), '<CR>');
            assert.equal(inputByKeydown({key: 'Enter', altKey: true}), '');
            assert.equal(inputByKeydown({key: 'Enter', ctrlKey: true, shiftKey: true}), '<C-S-CR>');
            assert.equal(inputByKeydown({key: 'ArrowLeft'}), '<Left>');
            assert.equal(inputByKeydown({key: 'ArrowLeft', altKey: true}), '');
            assert.equal(inputByKeydown({key: 'ArrowLeft', ctrlKey: true, shiftKey: true}), '<C-S-Left>');
            assert.equal(inputByKeydown({key: '<', shiftKey: true}), '<LT>');
            assert.equal(inputByKeydown({key: '<', ctrlKey: true, shiftKey: true}), '<C-LT>');
            assert.equal(inputByKeydown({key: '\0'}), '<Nul>');
        });

        it('handles <CR>, <Esc>, <BS> and <C-m>, <C-[>, <C-h> edge cases', () => {
            assert.equal(inputByKeydown({key: 'Enter', keyCode: 13}), '<CR>');
            assert.equal(inputByKeydown({key: 'Enter', keyCode: 77, ctrlKey: true}), '<C-m>');
            assert.equal(inputByKeydown({key: 'Enter', keyCode: 13, ctrlKey: true}), '<C-CR>');

            assert.equal(inputByKeydown({key: 'Escape', keyCode: 27}), '<Esc>');
            assert.equal(inputByKeydown({key: 'Escape', keyCode: 219, ctrlKey: true}), '<C-[>');
            assert.equal(inputByKeydown({key: 'Escape', keyCode: 27, ctrlKey: true}), '<C-Esc>');

            assert.equal(inputByKeydown({key: 'Backspace', keyCode: 8}), '<BS>');
            assert.equal(inputByKeydown({key: 'Backspace', keyCode: 72, ctrlKey: true}), '<C-h>');
            assert.equal(inputByKeydown({key: 'Backspace', keyCode: 8, ctrlKey: true}), '<C-BS>');

            assert.equal(inputByKeydown({key: 'Tab', keyCode: 9}), '<Tab>');
            assert.equal(inputByKeydown({key: 'Tab', keyCode: 73, ctrlKey: true}), '<C-i>');
            assert.equal(inputByKeydown({key: 'Tab', keyCode: 9, ctrlKey: true}), '<C-Tab>');
        });
    });

    context("on 'input' event", () => {
        it('sends input character to Neovim', () => {
            assert.equal(catchInputOnInputEvent('a'), 'a');
            assert.equal(catchInputOnInputEvent('3'), '3');
            assert.equal(catchInputOnInputEvent(';'), ';');
            assert.equal(catchInputOnInputEvent('^'), '^');
        });

        it('clears value in <input>', () => {
            global.input_element.value = 'a';
            dispatchInputEvent();
            assert.equal(global.input_element.value, '');
        });
    });
});

<|endoftext|>


<commit_before>var parseArgs = require('minimist');
var argv = parseArgs( process.argv , {
  number: [ 'port' ],
  string: [ 'comport' ],
  boolean: [ 'debug']
});

var port = argv.port || 'NULL';
var debug = argv.debug || false;
var comport = argv.comport;

console.log('Starting water rower.');
console.log('\tPort: ', argv.port);
console.log('\tCom port: ', argv.comport);
console.log('\tDebug: ', debug);

var server = require('http').createServer()
var ws = require('ws');
var url = require('url')
var WebSocketServer = ws.Server
var wsServer = new WebSocketServer({ server: server });

var wr = require('./water_rower.js')({ debug: debug} );
var express = require('express');
var app = express();
app.get('/', express.static(__dirname+'/public') );

var rower;

function broadcastClients(msg){
  wsServer.clients.forEach( function(client) {
    client.send( JSON.stringify(msg), function(err) {
      if (err) {
        console.log(err.toString());
      }
    });
  });
}

function setupRower() {
  rower = new wr.WaterRower({ port: comport });
  rower.on('readings', function(msg) {
    broadcastClients( {
      type: 'readings',
      data: msg
    });
  });
  rower.on('keypad', function(msg){
    broadcastClients( {
      type: 'keypad',
      data: msg
    });
  });
}

wsServer.on('connection', function connection(sock) {
  var location = url.parse(sock.upgradeReq.url, true);

  sock.on('close', function _accounceDisconnect(){
    console.log("Socket disconnect.");
  });

  sock.on('message', function _socketMessage(msg){
    console.log("Socket sent message: ", msg.toString() );
    try {
      var parsedMsg = JSON.parse(msg);
      if (parsedMsg.msg === 'reset rower') {
        setupRower();
      }
    } catch (e) {
      console.log('Error handling message: ', e.toString());
    }
  });

  console.log("Socket connect.");
});

server.on('request', app);
server.listen(port, function () {
  console.log('Listening on ' + server.address().port)
});

process.on('SIGINT', function() {
  console.log("\nSIGINT (Ctrl+C)");
  if (rower) {
    rower.shutdown();
  }
  process.exit(0);
});
<commit_msg>Add throttling of readings broadcast messages. ~250ms<commit_after>var parseArgs = require('minimist');
var argv = parseArgs( process.argv , {
  number: [ 'port' ],
  string: [ 'comport' ],
  boolean: [ 'debug']
});

var port = argv.port || 'NULL';
var debug = argv.debug || false;
var comport = argv.comport;

console.log('Starting water rower.');
console.log('\tPort: ', argv.port);
console.log('\tCom port: ', argv.comport);
console.log('\tDebug: ', debug);

var server = require('http').createServer();
var ws = require('ws');
var url = require('url');
var WebSocketServer = ws.Server;
var wsServer = new WebSocketServer({ server: server });

var wr = require('./water_rower.js')({ debug: debug} );
var express = require('express');
var app = express();
app.get('/', express.static(__dirname+'/public') );

var rower;

var lastBroadcast = Date.now();
function broadcastClients(msg, cooldown){
  var currTime = Date.now();
  if ( (currTime - lastBroadcast) >= (cooldown || 0) ) {
    lastBroadcast = currTime;
    wsServer.clients.forEach( function(client) {
      client.send( JSON.stringify(msg), function(err) {
        if (err) {
          console.log(err.toString());
        }
      });
    });
  }
}

function setupRower() {
  rower = new wr.WaterRower({ port: comport });
  rower.on('readings', function(msg) {
    broadcastClients( {
      type: 'readings',
      data: msg
    },100);
  });
  rower.on('keypad', function(msg){
    broadcastClients( {
      type: 'keypad',
      data: msg
    });
  });
}

wsServer.on('connection', function connection(sock) {
  var location = url.parse(sock.upgradeReq.url, true);

  sock.on('close', function _accounceDisconnect(){
    console.log("Socket disconnect.");
  });

  sock.on('message', function _socketMessage(msg){
    console.log("Socket sent message: ", msg.toString() );
    try {
      var parsedMsg = JSON.parse(msg);
      if (parsedMsg.msg === 'reset rower') {
        setupRower();
      }
    } catch (e) {
      console.log('Error handling message: ', e.toString());
    }
  });

  console.log("Socket connect.");
});

server.on('request', app);
server.listen(port, function () {
  console.log('Listening on ' + server.address().port)
});

process.on('SIGINT', function() {
  console.log("\nSIGINT (Ctrl+C)");
  if (rower) {
    rower.shutdown();
  }
  process.exit(0);
});
<|endoftext|>


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

var through = require('through2');
var Promise = require('promise-polyfill');
var mime = require('mime-types');
var Tributary = require('tributary');
var collect = require('stream-collect');

var findFile = require('./lib/find-file.js');

var debug = require('debug')('gulp-tributary');

var PLUGIN_NAME = 'gulp-tributary';

// Default placeholders based on the mimetype of the document
var placeholders = {
    'text/plain': ['<!-- include ',' -->'],
    'text/html': ['<!-- include ',' -->'],
    'application/xml': ['<!-- include ',' -->'],
    'text/css': ['/* include ',' */'],
    'application/javascript': ['/* include ',' */']
};

['less','x-less','sass','x-sass','scss','x-scss'].forEach( function(synoymn) {
    placeholders[ 'text/' + synoymn ] = placeholders['text/css'];
} );

/**
 *
 *  @param {stream.Readable} includes Object stream of vinyl files to include
 *  @param {String[]} [options.placeholder] The placeholder template to use
 *  @param {String} [options.mediaType] The media type used to choose the placeholder
 *  @returns {stream.Transform}
 */
function include( includes, options ) {

    options = options || {};
    var forceMediaType = options.mediaType;
    var ignoreMissingFiles = options.ignoreMissingFiles === true;
    var delimiter = options.delimiter;
    var files;
    if (includes ) {
        files = collect(includes);
    } else {
        files = Promise.resolve(null);
    }

    function getStream( filename, cb ) {

        files
            .then( function(files) {

                if ( !files ) {
                    cb();
                    return;
                }

                var file = findFile( filename, files );

                debug( 'found file', file );
                if ( file === null ) {
                    if ( !ignoreMissingFiles ) {
                        var error = new Error( PLUGIN_NAME + ': ' + filename + ' not found' );
                        stream.emit( 'error', error );
                    }
                    cb();
                    return;
                }
                cb( file.pipe(through()) );
            } );

    }

    function getInjector( mediaType ) {
        var placeholder = options.placeholder || placeholders[mediaType] || placeholders['text/plain'];
        var injector = new Tributary( {
            placeholderStart: placeholder[0],
            placeholderEnd: placeholder[1],
            getStream: getStream,
            delimiter: delimiter
        } );
        return injector;
    }

    var stream = through.obj( function(file, enc, cb) {

        if ( file.isNull() ) {
            cb(null, file);
            return;
        }

        var mediaType = forceMediaType || mime.lookup(file.path);

        if ( file.isBuffer() ) {
            // Process the buffer as a stream
            // and then write back as a buffer
            var buffer = new Buffer(0);

            var stream = through();
            stream.end(file.contents);
            stream.pipe( getInjector(mediaType) )
                .on( 'data', function(chunk) {
                    if ( chunk !== null ) {
                        buffer = Buffer.concat( [ buffer, chunk ] );
                    }
                } )
                .on( 'end', function() {
                    file.contents = buffer;
                    cb(null, file);
                } );

            return;
        }

        if ( file.isStream() ) {
            // Replace the stream with our piped stream
            file.contents = file.contents.pipe( getInjector(mediaType) );
        }

        cb(null, file);

    } );

    return stream;

}

module.exports = include;
include.placeholders = placeholders;
<commit_msg>Only load promise-polyfill if required<commit_after>/* jshint node:true */
/* global Promise: false */
"use strict";

var through = require('through2');

if ( typeof Promise === 'undefined' ) {
    global.Promise = require('promise-polyfill');
}

var mime = require('mime-types');
var Tributary = require('tributary');
var collect = require('stream-collect');

var findFile = require('./lib/find-file.js');

var debug = require('debug')('gulp-tributary');

var PLUGIN_NAME = 'gulp-tributary';

// Default placeholders based on the mimetype of the document
var placeholders = {
    'text/plain': ['<!-- include ',' -->'],
    'text/html': ['<!-- include ',' -->'],
    'application/xml': ['<!-- include ',' -->'],
    'text/css': ['/* include ',' */'],
    'application/javascript': ['/* include ',' */']
};

['less','x-less','sass','x-sass','scss','x-scss'].forEach( function(synoymn) {
    placeholders[ 'text/' + synoymn ] = placeholders['text/css'];
} );

/**
 *
 *  @param {stream.Readable} includes Object stream of vinyl files to include
 *  @param {String[]} [options.placeholder] The placeholder template to use
 *  @param {String} [options.mediaType] The media type used to choose the placeholder
 *  @returns {stream.Transform}
 */
function include( includes, options ) {

    options = options || {};
    var forceMediaType = options.mediaType;
    var ignoreMissingFiles = options.ignoreMissingFiles === true;
    var delimiter = options.delimiter;
    var files;
    if (includes ) {
        files = collect(includes);
    } else {
        files = Promise.resolve(null);
    }

    function getStream( filename, cb ) {

        files
            .then( function(files) {

                if ( !files ) {
                    cb();
                    return;
                }

                var file = findFile( filename, files );

                debug( 'found file', file );
                if ( file === null ) {
                    if ( !ignoreMissingFiles ) {
                        var error = new Error( PLUGIN_NAME + ': ' + filename + ' not found' );
                        stream.emit( 'error', error );
                    }
                    cb();
                    return;
                }
                cb( file.pipe(through()) );
            } );

    }

    function getInjector( mediaType ) {
        var placeholder = options.placeholder || placeholders[mediaType] || placeholders['text/plain'];
        var injector = new Tributary( {
            placeholderStart: placeholder[0],
            placeholderEnd: placeholder[1],
            getStream: getStream,
            delimiter: delimiter
        } );
        return injector;
    }

    var stream = through.obj( function(file, enc, cb) {

        if ( file.isNull() ) {
            cb(null, file);
            return;
        }

        var mediaType = forceMediaType || mime.lookup(file.path);

        if ( file.isBuffer() ) {
            // Process the buffer as a stream
            // and then write back as a buffer
            var buffer = new Buffer(0);

            var stream = through();
            stream.end(file.contents);
            stream.pipe( getInjector(mediaType) )
                .on( 'data', function(chunk) {
                    if ( chunk !== null ) {
                        buffer = Buffer.concat( [ buffer, chunk ] );
                    }
                } )
                .on( 'end', function() {
                    file.contents = buffer;
                    cb(null, file);
                } );

            return;
        }

        if ( file.isStream() ) {
            // Replace the stream with our piped stream
            file.contents = file.contents.pipe( getInjector(mediaType) );
        }

        cb(null, file);

    } );

    return stream;

}

module.exports = include;
include.placeholders = placeholders;
<|endoftext|>


<commit_before>let assert = require('chai').assert;

import {tail} from '../../src/index.es6';

describe('tail', () => {
  it('should return all the elements of an array minus the first one', () => {
    assert.deepEqual(tail([1,2,3]), [2,3]);
  });
});
<commit_msg>test(tail): add more use cases<commit_after>let assert = require('chai').assert;

import {tail} from '../../src/index.es6';

describe('tail', () => {
  it('returns the tail of an ordered collection', function() {
    assert.deepEqual(tail([1, 2, 3]), [2, 3]);
    assert.deepEqual(tail([2, 3]), [3]);
    assert.deepEqual(tail([3]), []);
    assert.deepEqual(tail([]), []);

    assert.strictEqual(tail('abc'), 'bc');
    assert.strictEqual(tail('bc'), 'c');
    assert.strictEqual(tail('c'), '');
    assert.strictEqual(tail(''), '');
  });

  it('throws if applied to null or undefined', function() {
    assert.throws(function() { tail(null); }, TypeError);
    assert.throws(function() { tail(undefined); }, TypeError);
  });
});
<|endoftext|>


<commit_before>// Example express application adding the parse-server module to expose Parse
// compatible API routes.

var express = require('express');
var ParseServer = require('parse-server').ParseServer;

var databaseUri = process.env.DATABASE_URI || process.env.MONGOLAB_URI

if (!databaseUri) {
  console.log('DATABASE_URI not specified, falling back to localhost.');
}

var api = new ParseServer({
  databaseURI: databaseUri || 'mongodb://localhost:27017/dev',
  cloud: process.env.CLOUD_CODE_MAIN || __dirname + '/cloud/main.js',
  appId: process.env.APP_ID || 'myAppId',
  masterKey: process.env.MASTER_KEY || 'myMasterKey'
});
// Client-keys like the javascript key or the .NET key are not necessary with parse-server
// If you wish you require them, you can set them as options in the initialization above:
// javascriptKey, restAPIKey, dotNetKey, clientKey

var app = express();

// Serve the Parse API on the /parse URL prefix
var mountPath = process.env.PARSE_MOUNT || '/parse';
app.use(mountPath, api);

// Parse Server plays nicely with the rest of your web routes
app.get('/', function(req, res) {
  res.status(200).send('I dream of being a web site.');
});

var port = process.env.PORT || 1337;
app.listen(port, function() {
    console.log('parse-server-example running on port ' + port + '.');
});
<commit_msg>Remove default master key<commit_after>// Example express application adding the parse-server module to expose Parse
// compatible API routes.

var express = require('express');
var ParseServer = require('parse-server').ParseServer;

var databaseUri = process.env.DATABASE_URI || process.env.MONGOLAB_URI

if (!databaseUri) {
  console.log('DATABASE_URI not specified, falling back to localhost.');
}

var api = new ParseServer({
  databaseURI: databaseUri || 'mongodb://localhost:27017/dev',
  cloud: process.env.CLOUD_CODE_MAIN || __dirname + '/cloud/main.js',
  appId: process.env.APP_ID || 'myAppId',
  masterKey: process.env.MASTER_KEY || '' //Add your master key here. Keep it secret!
});
// Client-keys like the javascript key or the .NET key are not necessary with parse-server
// If you wish you require them, you can set them as options in the initialization above:
// javascriptKey, restAPIKey, dotNetKey, clientKey

var app = express();

// Serve the Parse API on the /parse URL prefix
var mountPath = process.env.PARSE_MOUNT || '/parse';
app.use(mountPath, api);

// Parse Server plays nicely with the rest of your web routes
app.get('/', function(req, res) {
  res.status(200).send('I dream of being a web site.');
});

var port = process.env.PORT || 1337;
app.listen(port, function() {
    console.log('parse-server-example running on port ' + port + '.');
});
<|endoftext|>


<commit_before>var app = require('express')();
var _PORT_ = 8080;

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();
});

app.get('/', function(req,res) {
	if (req.query.callback !== undefined)
	{
		console.log(req.query.callback);
		res.jsonp([{"title":"Anamazingtitle","description":"Somethingaboutthiscard","ctas":[{"text":"125for1","value":"c1cta1"},{"text":"200for2","value":"c1cta2"}],"important":"yes"},{"title":"Anamazingtitle2","description":"Somethingaboutthisothercard","ctas":[{"text":"300","value":"c2"}]},{"title":"Didn'tlikeanything?","description":"Searchforsomethingelse","ctas":[{"text":"Search","value":"search"}],"important":"max"}]);
	}
	else
	{
		console.log("no callback");
		res.json([{"title":"Anamazingtitle","description":"Somethingaboutthiscard","ctas":[{"text":"125for1","value":"c1cta1"},{"text":"200for2","value":"c1cta2"}],"important":"yes"},{"title":"Anamazingtitle2","description":"Somethingaboutthisothercard","ctas":[{"text":"300","value":"c2"}]},{"title":"Didn'tlikeanything?","description":"Searchforsomethingelse","ctas":[{"text":"Search","value":"search"}],"important":"max"}]);
	}
});

app.listen(_PORT_, function() {
	console.log(`Server started on port: ${_PORT_}`);
});

<commit_msg>o pedro lembrou-se de mudar as coisas outra vez<commit_after>var app = require('express')();
var _PORT_ = 8080;

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();
});

app.get('/', function(req,res) {
	if (req.query.callback !== undefined)
	{
		console.log(req.query.callback);
		res.jsonp({"cards":[{"image":"http://i.imgur.com/eM2Uena.jpg","title":"Anamazingtitle","description":"Somethingaboutthiscard","ctas":[{"text":"125for1","value":"c1cta1"},{"text":"200for2","value":"c1cta2"}],"important":"yes"},{"title":"Anamazingtitle2","description":"Somethingaboutthisothercard","ctas":[{"text":"300","value":"c2"}]},{"title":"Didn'tlikeanything?","description":"Searchforsomethingelse","ctas":[{"text":"Search","value":"search"}],"important":"max"}]});
	}
	else
	{
		console.log("no callback");
		res.json({"cards":[{"image":"http://i.imgur.com/eM2Uena.jpg","title":"Anamazingtitle","description":"Somethingaboutthiscard","ctas":[{"text":"125for1","value":"c1cta1"},{"text":"200for2","value":"c1cta2"}],"important":"yes"},{"title":"Anamazingtitle2","description":"Somethingaboutthisothercard","ctas":[{"text":"300","value":"c2"}]},{"title":"Didn'tlikeanything?","description":"Searchforsomethingelse","ctas":[{"text":"Search","value":"search"}],"important":"max"}]});
	}
});

app.listen(_PORT_, function() {
	console.log(`Server started on port: ${_PORT_}`);
});

<|endoftext|>


<commit_before>
/**
 * http post request
 * @param url    {String} Newsletter2Go API URL
 * @param params {String} serialized parameter string
 * @return       {Object} API response object
 */

var post = function(url, params) {
	try {
		var http = new XMLHttpRequest(),
			result;

		http.open("POST", url, true);
		// send the proper header information along with the request
		http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
		http.setRequestHeader("Content-length", params.length);
		http.setRequestHeader("Connection", "close");
		// callback function when the state changes.
		http.onreadystatechange = function() {
		    if (http.readyState == 4 && http.status == 200) {
		        result = JSON.parse(http.response);
		    }
		};
		http.send(params);

		return result;
	} catch (e) {
		WAKTOOLS.log(e);
		return e;
	}
};

/**
 * serialize parameter object
 * @param params {Object} parameter object
 * @return       {String} serialized parameter string
 */

var serialize = function(obj) {
	try {
		var str = [];

		// traverse object
		for(var p in obj) {
			if (obj.hasOwnProperty(p)) {
				str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
			}
		}

		return str.join("&");
	} catch (e) {
		WAKTOOLS.log(e);
		return e;
	}
}

/**
 * send email
 * @param params {Object} parameter object
 * @return       {Object} API response object
 */

var sendEmail = function(params) {
	try {
		return post('https://www.newsletter2go.de/de/api/send/email/', serialize(params))
	} catch (e) {
		WAKTOOLS.log(e);
		return e;
	}
};

exports.sendEmail = sendEmail;<commit_msg>add misc functions<commit_after>
var baseURL = 'https://www.newsletter2go.de/de/api/';

/**
 * http post request
 * @param url    {String} Newsletter2Go API URL
 * @param params {String} serialized parameter string
 * @return       {Object} API response object
 */

var post = function(url, params) {
	try {
		var http = new XMLHttpRequest(),
			result;

		http.open("POST", url, true);
		// send the proper header information along with the request
		http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
		http.setRequestHeader("Content-length", params.length);
		http.setRequestHeader("Connection", "close");
		// callback function when the state changes.
		http.onreadystatechange = function() {
		    if (http.readyState == 4 && http.status == 200) {
		        result = JSON.parse(http.response);
		    }
		};
		http.send(params);

		return result;
	} catch (e) {
		WAKTOOLS.log(e);
		return e;
	}
};

/**
 * serialize parameter object
 * @param params {Object} parameter object
 * @return       {String} serialized parameter string
 */

var serialize = function(obj) {
	try {
		var str = [];

		// traverse object
		for(var p in obj) {
			if (obj.hasOwnProperty(p)) {
				str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
			}
		}

		return str.join("&");
	} catch (e) {
		WAKTOOLS.log(e);
		return e;
	}
}

/**
 * send email
 * @param params {Object} parameter object
 * @return       {Object} API response object
 */

var sendEmail = function(params) {
	try {
		return post(baseURL + 'send/email/', serialize(params))
	} catch (e) {
		WAKTOOLS.log(e);
		return e;
	}
};

/**
 * create newsletter
 * @param params {Object} parameter object
 * @return       {Object} API response object
 */

var createNewsletter = function(params) {
	try {
		return post(baseURL + 'create/newsletter/', serialize(params))
	} catch (e) {
		WAKTOOLS.log(e);
		return e;
	}
};

/**
 * set group to newsletter
 * @param params {Object} parameter object
 * @return       {Object} API response object
 */

var setGroupToNewsletter = function(params) {
	try {
		return post(baseURL + 'set/grouptonewsletter/', serialize(params))
	} catch (e) {
		WAKTOOLS.log(e);
		return e;
	}
};

/**
 * get groups by newsletter
 * @param params {Object} parameter object
 * @return       {Object} API response object
 */

var getGroupsByNewsletter = function(params) {
	try {
		return post(baseURL + 'get/groupsbynewsletter/', serialize(params))
	} catch (e) {
		WAKTOOLS.log(e);
		return e;
	}
};

/**
 * delete all groups from newsletter
 * @param params {Object} parameter object
 * @return       {Object} API response object
 */

var deleteAllGroupsFromNewsletter = function(params) {
	try {
		return post(baseURL + 'delete/allgroupsfromnewsletter/', serialize(params))
	} catch (e) {
		WAKTOOLS.log(e);
		return e;
	}
};

/**
 * send newsletter
 * @param params {Object} parameter object
 * @return       {Object} API response object
 */

var sendNewsletter = function(params) {
	try {
		return post(baseURL + 'send/newsletter/', serialize(params))
	} catch (e) {
		WAKTOOLS.log(e);
		return e;
	}
};

/**
 * get statistics
 * @param params {Object} parameter object
 * @return       {Object} API response object
 */

var getStatistics = function(params) {
	try {
		return post(baseURL + 'get/statistics/', serialize(params))
	} catch (e) {
		WAKTOOLS.log(e);
		return e;
	}
};

/**
 * create receipent
 * @param params {Object} parameter object
 * @return       {Object} API response object
 */

var createRecipient = function(params) {
	try {
		return post(baseURL + 'create/recipient/', serialize(params))
	} catch (e) {
		WAKTOOLS.log(e);
		return e;
	}
};

/**
 * delete receipent
 * @param params {Object} parameter object
 * @return       {Object} API response object
 */

var deleteRecipient = function(params) {
	try {
		return post(baseURL + 'create/recipient/', serialize(params))
	} catch (e) {
		WAKTOOLS.log(e);
		return e;
	}
};

exports.sendEmail                     = sendEmail;
exports.createNewsletter              = createNewsletter;
exports.setGroupToNewsletter          = setGroupToNewsletter;
exports.getGroupsByNewsletter         = getGroupsByNewsletter;
exports.deleteAllGroupsFromNewsletter = deleteAllGroupsFromNewsletter;
exports.sendNewsletter                = sendNewsletter;
exports.getStatistics                 = getStatistics;
exports.createRecipient               = createRecipient;
exports.deleteRecipient               = deleteRecipient;<|endoftext|>


<commit_before>/*jslint vars: false, browser: true, nomen: true */
/*global MOOC: true, Backbone, $, _ */

// Copyright 2012 Rooter Analysis S.L.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

if (_.isUndefined(window.MOOC)) {
    window.MOOC = {};
}

MOOC.models = {};

MOOC.models.Activity = Backbone.Model.extend({
    defaults: {
        kqs: []
    },

    url: function () {
        "use strict";
        return MOOC.ajax.getAbsoluteUrl('activity/') + this.get('id') + '/';
    },

    addKQ: function (kq) {
        "use strict";
        if (!_.include(this.get('kqs'), kq)) {
            this.set('kqs', _.union(this.get('kqs'), [kq]));
            this.save();
        }
    },

    hasKQ: function (kq) {
        "use strict";
        return _.include(this.get('kqs'), String(kq));
    }
});

MOOC.models.Option = Backbone.Model.extend({
    defaults: function () {
        "use strict";
        return {
            optiontype: 't',
            x: 0,
            y: 0,
            width: 100,
            height: 12,
            solution: null
        };
    },

    isCorrect: function (reply) {
        "use strict";
        var solution = this.get('solution'),
            optiontype = this.get('optiontype'),
            result;

        if (_.isUndefined(solution) || solution === null) {
            return;
        }

        if (optiontype === 't') {
            result = solution === reply.get('value');
        } else {
            if (solution === 'True') {
                result = reply.get('value') === true;
            } else {
                result = reply.get('value') === false;
            }
        }

        return result;
    }
});

MOOC.models.OptionList  = Backbone.Collection.extend({
    model: MOOC.models.Option
});

MOOC.models.Question = Backbone.Model.extend({
    defaults: function () {
        "use strict";
        return {
            lastFrame: null, // of the KnowledgeQuantum's video
            solution: null,
            optionList: null,
            answer: null
        };
    },

    isCorrect: function () {
        "use strict";
        var answer = this.get("answer");
        if (_.isUndefined(answer) || answer === null) {
            return;
        }

        return this.get('optionList').all(function (opt) {
            return opt.isCorrect(answer.getReply(opt.get('id')));
        });
    }
});

/* An answer is a student submission to a question */
MOOC.models.Answer = Backbone.Model.extend({
    defaults: {
        date: null,
        replyList: null
    },

    /* Return a reply which option is opt_id or null otherwise */
    getReply: function (opt_id) {
        "use strict";
        var replies = this.get('replyList'),
            result = null;
        if (replies) {
            result = replies.find(function (reply) {
                return reply.get('option') === opt_id;
            });
        }
        return result;
    }
});

/* A reply is a student value for an option */
MOOC.models.Reply = Backbone.Model.extend({
    defaults: {
        option: null,
        value: null
    }
});

MOOC.models.ReplyList = Backbone.Collection.extend({
    model: MOOC.models.Reply
});

MOOC.models.Attachment = Backbone.Model.extend({
    defaults: function () {
        "use strict";
        return {
            url: null
        };
    }
});

MOOC.models.AttachmentList = Backbone.Collection.extend({
    model: MOOC.models.Attachment
});

MOOC.models.KnowledgeQuantum = Backbone.Model.extend({
    defaults: function () {
        "use strict";
        return {
            order: -1,
            title: null,
            videoID: null,
            teacher_comments: null,
            supplementary_material: null,
            question: null, // Optional
            questionInstance: null,
            completed: false,
            correct: null,
            attachmentList: null
        };
    }
});

MOOC.models.KnowledgeQuantumList  = Backbone.Collection.extend({
    model: MOOC.models.KnowledgeQuantum,

    comparator: function (kq) {
        "use strict";
        return kq.get("order");
    },

    getByPosition: function (position) {
        "use strict";
        return this.find(function (kq) {
            return position === kq.get("order");
        });
    }
});

MOOC.models.Unit = Backbone.Model.extend({
    defaults: function () {
        "use strict";
        return {
            order: -1,
            knowledgeQuantumList: null,
            title: ""
        };
    },

    calculateProgress: function (conditions) {
        "use strict";
        var kqs = this.get("knowledgeQuantumList").length,
            progress = this.get("knowledgeQuantumList").where(conditions);
        return (progress.length * 100) / kqs;
    }
});

MOOC.models.UnitList = Backbone.Collection.extend({
    model: MOOC.models.Unit,

    getByKQ: function (kqID) {
        "use strict";
        return this.find(function (unit) {
            var kq = unit.get("knowledgeQuantumList");
            if (_.isNull(kq)) {
                return false;
            }
            kq = kq.get(kqID);
            return !_.isUndefined(kq);
        });
    }
});

MOOC.models.course = new MOOC.models.UnitList();
<commit_msg>Bugfix showing progress percentage<commit_after>/*jslint vars: false, browser: true, nomen: true */
/*global MOOC: true, Backbone, $, _ */

// Copyright 2012 Rooter Analysis S.L.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

if (_.isUndefined(window.MOOC)) {
    window.MOOC = {};
}

MOOC.models = {};

MOOC.models.Activity = Backbone.Model.extend({
    defaults: {
        kqs: []
    },

    url: function () {
        "use strict";
        return MOOC.ajax.getAbsoluteUrl('activity/') + this.get('id') + '/';
    },

    addKQ: function (kq) {
        "use strict";
        if (!_.include(this.get('kqs'), kq)) {
            this.set('kqs', _.union(this.get('kqs'), [kq]));
            this.save();
        }
    },

    hasKQ: function (kq) {
        "use strict";
        return _.include(this.get('kqs'), String(kq));
    }
});

MOOC.models.Option = Backbone.Model.extend({
    defaults: function () {
        "use strict";
        return {
            optiontype: 't',
            x: 0,
            y: 0,
            width: 100,
            height: 12,
            solution: null
        };
    },

    isCorrect: function (reply) {
        "use strict";
        var solution = this.get('solution'),
            optiontype = this.get('optiontype'),
            result;

        if (_.isUndefined(solution) || solution === null) {
            return;
        }

        if (optiontype === 't') {
            result = solution === reply.get('value');
        } else {
            if (solution === 'True') {
                result = reply.get('value') === true;
            } else {
                result = reply.get('value') === false;
            }
        }

        return result;
    }
});

MOOC.models.OptionList  = Backbone.Collection.extend({
    model: MOOC.models.Option
});

MOOC.models.Question = Backbone.Model.extend({
    defaults: function () {
        "use strict";
        return {
            lastFrame: null, // of the KnowledgeQuantum's video
            solution: null,
            optionList: null,
            answer: null
        };
    },

    isCorrect: function () {
        "use strict";
        var answer = this.get("answer");
        if (_.isUndefined(answer) || answer === null) {
            return;
        }

        return this.get('optionList').all(function (opt) {
            return opt.isCorrect(answer.getReply(opt.get('id')));
        });
    }
});

/* An answer is a student submission to a question */
MOOC.models.Answer = Backbone.Model.extend({
    defaults: {
        date: null,
        replyList: null
    },

    /* Return a reply which option is opt_id or null otherwise */
    getReply: function (opt_id) {
        "use strict";
        var replies = this.get('replyList'),
            result = null;
        if (replies) {
            result = replies.find(function (reply) {
                return reply.get('option') === opt_id;
            });
        }
        return result;
    }
});

/* A reply is a student value for an option */
MOOC.models.Reply = Backbone.Model.extend({
    defaults: {
        option: null,
        value: null
    }
});

MOOC.models.ReplyList = Backbone.Collection.extend({
    model: MOOC.models.Reply
});

MOOC.models.Attachment = Backbone.Model.extend({
    defaults: function () {
        "use strict";
        return {
            url: null
        };
    }
});

MOOC.models.AttachmentList = Backbone.Collection.extend({
    model: MOOC.models.Attachment
});

MOOC.models.KnowledgeQuantum = Backbone.Model.extend({
    defaults: function () {
        "use strict";
        return {
            order: -1,
            title: null,
            videoID: null,
            teacher_comments: null,
            supplementary_material: null,
            question: null, // Optional
            questionInstance: null,
            completed: false,
            correct: null,
            attachmentList: null
        };
    }
});

MOOC.models.KnowledgeQuantumList  = Backbone.Collection.extend({
    model: MOOC.models.KnowledgeQuantum,

    comparator: function (kq) {
        "use strict";
        return kq.get("order");
    },

    getByPosition: function (position) {
        "use strict";
        return this.find(function (kq) {
            return position === kq.get("order");
        });
    }
});

MOOC.models.Unit = Backbone.Model.extend({
    defaults: function () {
        "use strict";
        return {
            order: -1,
            knowledgeQuantumList: null,
            title: ""
        };
    },

    calculateProgress: function (conditions) {
        "use strict";
        var kqs = this.get("knowledgeQuantumList").length,
            progress = this.get("knowledgeQuantumList").where(conditions),
            result = (progress.length * 100) / kqs;

        if (!_.isNumber(result) || _.isNaN(result)) {
            result = 0;
        }
        return result;
    }
});

MOOC.models.UnitList = Backbone.Collection.extend({
    model: MOOC.models.Unit,

    getByKQ: function (kqID) {
        "use strict";
        return this.find(function (unit) {
            var kq = unit.get("knowledgeQuantumList");
            if (_.isNull(kq)) {
                return false;
            }
            kq = kq.get(kqID);
            return !_.isUndefined(kq);
        });
    }
});

MOOC.models.course = new MOOC.models.UnitList();
<|endoftext|>


<commit_before>var basename = require('path').basename;
var utils = require('cli-util');
var merge = utils.merge, walk = utils.walk;
var native = require('cli-native');
var camelcase = utils.camelcase;
var delimited = utils.delimited;

var defaults = {
  prefix: basename(process.argv[1]),
  delimiter: '_',
  initialize: false,
  match: null,
  transform: {
    key: null,
    value: null,
    name: null
  },
  native: null
}

/**
 *  Helper class for accessing environment variables
 *  using a defined prefix.
 *
 *  @param conf The configuration object.
 */
var Environment = function(conf) {
  Object.defineProperty(this, 'conf',
    {
      enumerable: false,
      configurable: false,
      writable: false,
      value: conf || {}
    }
  );
  if(this.conf.initialize) {
    this.load();
  }
}

/**
 *  Retrieve a suitable key for setting an environment
 *  variable.
 *
 *  @api private
 *
 *  @param key The candidate key.
 *
 *  @return A converted key.
 */
function getKey(key) {
  if(this.conf.transform) {
    if(typeof(this.conf.transform.key) == 'function') {
      return this.conf.transform.key.call(this, key);
    }
  }
  key = delimited(key, this.conf.delimiter);
  key = key.replace(/- /, this.conf.delimiter);
  key = key.replace(/[^a-zA-Z0-9_]/, '');
  if(this.conf.prefix) {
    return this.conf.prefix + this.conf.delimiter + key.toLowerCase()
  }
  return key.toLowerCase();
}

/**
 *  Retrieve the value of a variable from the environment.
 *
 *  @api private
 *
 *  @param key The key that has already been passed through
 *  the key transformation function.
 *  @param property The name of a property corresponding to the key.
 *  @param raw The raw untouched key.
 *
 *  @return The value of the environment variable.
 */
function getValue (key, name, raw) {
  if(this.conf.transform) {
    if(typeof(this.conf.transform.value) == 'function') {
      return this.conf.transform.value.call(this, key, name, raw);
    }
  }
  var value = process.env[raw] || this[name];
  if(this.conf.native && typeof(value) == 'string') {
    value = native.to(
      value, this.conf.native.delimiter, this.conf.native.json);
  }
  return value;
}

/**
 *  Convert a key into a property name.
 *
 *  @param key The property key.
 *
 *  @return A camel case property name.
 */
function getName(key) {
  if(key == '_') return key;
  if(this.conf.transform) {
    if(typeof(this.conf.transform.name) == 'function') {
      return this.conf.transform.name.call(this, key);
    }
  }
  if(this.conf.prefix) {
    key = key.replace(this.conf.prefix + '_', '');
  }
  // guard against silly variables such as npm_config_cache____foo
  key = key.replace(
    new RegExp(this.conf.delimiter + '+', 'g'), this.conf.delimiter);

  return camelcase(key, this.conf.delimiter)
}

/**
 *  Set an environment variable using the transform
 *  set function.
 *
 *  @param key The variable key.
 *  @param value The variable value.
 */
function set(key, value) {
  var k = this.getKey(key);
  var name = this.getName(key);
  if(this.conf.native && typeof(value) == 'string') {
    try {
      value = native.to(
        value, this.conf.native.delimiter, this.conf.native.json);
    }catch(e){}
  }
  this[name] = process.env[k] = value;
}

/**
 *  Get an environment variable using the transform
 *  get function.
 *
 *  @param key The variable key.
 *
 *  @return The variable value.
 */
function get(key) {
  var k = this.getKey(key);
  var name = this.getName(key);
  var value = this.getValue(k, name, key);
  return value;
}

/**
 *  Load variables from the environment into this
 *  instance.
 *
 *  @param match A regular expression test determining
 *  which variables to load.
 */
function load(match) {
  match = match || this.conf.match;
  for(var z in process.env) {
    if(match instanceof RegExp) {
      if(match.test(z)) {
        this.set(z.toLowerCase(), process.env[z]);
      }
    }else{
      this.set(z.toLowerCase(), process.env[z]);
    }
  }
}

/**
 *  Performs substitution of environment variables within
 *  strings. The substitution format is:
 *
 *    $variable or ${variable}
 *
 *  @param str The target string value.
 *  @param env An object containing environment variables
 *  default is process.env.
 *  @param escaping Whether escaped dollars indicate replacement
 *  should be ignored.
 */
function replace(str, env, escaping) {
  if(!str) return str;
  var re = /(\\?)(\$\{?)(\w+)(\}?)/g;
  if(!re.test(str)) return str;
  env = env || process.env;
  escaping = escaping === undefined ? true : escaping;
  str = str.replace(re, function(
    match, backslash, prefix, name, suffix, offset, string) {
    //console.log(name);
    if(backslash && env[name]) {
      if(escaping) {
        return prefix + name + suffix;
      }
    }
    if(env[name]) {
      if(!escaping && backslash) {
        return backslash + env[name];
      }
      return env[name];
    }
    return match;
  })
  return str;
}

/**
 *  Performs recursive substitution of environment variables within
 *  complex objects.
 *
 *  @param root The root object.
 *  @param env An object containing environment variables
 *  default is process.env.
 *  @param escaping Whether escaped dollars indicate replacement
 *  should be ignored.
 */
function env(root, env, escaping) {
  walk(root, function visit(props) {
    return (props.value instanceof String) || typeof props.value === 'string';
  }, function transform(props) {
    props.parent[props.name] = replace(props.value, env, escaping);
  })
}

var methods = {
  getKey: getKey,
  getValue: getValue,
  getName: getName,
  get: get,
  set: set,
  load: load
}

for(var z in methods) {
  Object.defineProperty(Environment.prototype, z,
    {
      enumerable: false,
      configurable: false,
      writable: false,
      value: methods[z]
    }
  )
}

module.exports = function(conf) {
  var c = merge(defaults, {});
  var env = new Environment(merge(conf, c));
  return env;
}

module.exports.Environment = Environment;
module.exports.replace = replace;
module.exports.env = env;
<commit_msg>Strip backslash when escaping and no variable.<commit_after>var basename = require('path').basename;
var utils = require('cli-util');
var merge = utils.merge, walk = utils.walk;
var native = require('cli-native');
var camelcase = utils.camelcase;
var delimited = utils.delimited;

var defaults = {
  prefix: basename(process.argv[1]),
  delimiter: '_',
  initialize: false,
  match: null,
  transform: {
    key: null,
    value: null,
    name: null
  },
  native: null
}

/**
 *  Helper class for accessing environment variables
 *  using a defined prefix.
 *
 *  @param conf The configuration object.
 */
var Environment = function(conf) {
  Object.defineProperty(this, 'conf',
    {
      enumerable: false,
      configurable: false,
      writable: false,
      value: conf || {}
    }
  );
  if(this.conf.initialize) {
    this.load();
  }
}

/**
 *  Retrieve a suitable key for setting an environment
 *  variable.
 *
 *  @api private
 *
 *  @param key The candidate key.
 *
 *  @return A converted key.
 */
function getKey(key) {
  if(this.conf.transform) {
    if(typeof(this.conf.transform.key) == 'function') {
      return this.conf.transform.key.call(this, key);
    }
  }
  key = delimited(key, this.conf.delimiter);
  key = key.replace(/- /, this.conf.delimiter);
  key = key.replace(/[^a-zA-Z0-9_]/, '');
  if(this.conf.prefix) {
    return this.conf.prefix + this.conf.delimiter + key.toLowerCase()
  }
  return key.toLowerCase();
}

/**
 *  Retrieve the value of a variable from the environment.
 *
 *  @api private
 *
 *  @param key The key that has already been passed through
 *  the key transformation function.
 *  @param property The name of a property corresponding to the key.
 *  @param raw The raw untouched key.
 *
 *  @return The value of the environment variable.
 */
function getValue (key, name, raw) {
  if(this.conf.transform) {
    if(typeof(this.conf.transform.value) == 'function') {
      return this.conf.transform.value.call(this, key, name, raw);
    }
  }
  var value = process.env[raw] || this[name];
  if(this.conf.native && typeof(value) == 'string') {
    value = native.to(
      value, this.conf.native.delimiter, this.conf.native.json);
  }
  return value;
}

/**
 *  Convert a key into a property name.
 *
 *  @param key The property key.
 *
 *  @return A camel case property name.
 */
function getName(key) {
  if(key == '_') return key;
  if(this.conf.transform) {
    if(typeof(this.conf.transform.name) == 'function') {
      return this.conf.transform.name.call(this, key);
    }
  }
  if(this.conf.prefix) {
    key = key.replace(this.conf.prefix + '_', '');
  }
  // guard against silly variables such as npm_config_cache____foo
  key = key.replace(
    new RegExp(this.conf.delimiter + '+', 'g'), this.conf.delimiter);

  return camelcase(key, this.conf.delimiter)
}

/**
 *  Set an environment variable using the transform
 *  set function.
 *
 *  @param key The variable key.
 *  @param value The variable value.
 */
function set(key, value) {
  var k = this.getKey(key);
  var name = this.getName(key);
  if(this.conf.native && typeof(value) == 'string') {
    try {
      value = native.to(
        value, this.conf.native.delimiter, this.conf.native.json);
    }catch(e){}
  }
  this[name] = process.env[k] = value;
}

/**
 *  Get an environment variable using the transform
 *  get function.
 *
 *  @param key The variable key.
 *
 *  @return The variable value.
 */
function get(key) {
  var k = this.getKey(key);
  var name = this.getName(key);
  var value = this.getValue(k, name, key);
  return value;
}

/**
 *  Load variables from the environment into this
 *  instance.
 *
 *  @param match A regular expression test determining
 *  which variables to load.
 */
function load(match) {
  match = match || this.conf.match;
  for(var z in process.env) {
    if(match instanceof RegExp) {
      if(match.test(z)) {
        this.set(z.toLowerCase(), process.env[z]);
      }
    }else{
      this.set(z.toLowerCase(), process.env[z]);
    }
  }
}

/**
 *  Performs substitution of environment variables within
 *  strings. The substitution format is:
 *
 *    $variable or ${variable}
 *
 *  @param str The target string value.
 *  @param env An object containing environment variables
 *  default is process.env.
 *  @param escaping Whether escaped dollars indicate replacement
 *  should be ignored.
 *  @param convert A conversion function used to convert the value
 *  to a string value before replacement.
 */
function replace(str, env, escaping, convert) {
  if(!str) return str;
  var re = /(\\?)(\$\{?)(\w+)(\}?)/g;
  if(!re.test(str)) return str;
  env = env || process.env;
  escaping = escaping === undefined ? true : escaping;
  if(!convert) {
    convert = function(value){return value};
  }
  str = str.replace(re, function(
    match, backslash, prefix, name, suffix, offset, string) {
    //console.log(name);
    if(backslash && env[name]) {
      if(escaping) {
        return prefix + name + suffix;
      }
    }
    if(!env[name] && backslash && escaping) {
      return prefix + name + suffix;
    }
    if(env[name]) {
      if(!escaping && backslash) {
        return backslash + convert(env[name]);
      }
      return convert(env[name]);
    }
    return match;
  })
  return str;
}

/**
 *  Performs recursive substitution of environment variables within
 *  complex objects.
 *
 *  @param root The root object.
 *  @param env An object containing environment variables
 *  default is process.env.
 *  @param escaping Whether escaped dollars indicate replacement
 *  should be ignored.
 */
function env(root, env, escaping) {
  walk(root, function visit(props) {
    return (props.value instanceof String) || typeof props.value === 'string';
  }, function transform(props) {
    props.parent[props.name] = replace(props.value, env, escaping);
  })
}

var methods = {
  getKey: getKey,
  getValue: getValue,
  getName: getName,
  get: get,
  set: set,
  load: load
}

for(var z in methods) {
  Object.defineProperty(Environment.prototype, z,
    {
      enumerable: false,
      configurable: false,
      writable: false,
      value: methods[z]
    }
  )
}

module.exports = function(conf) {
  var c = merge(defaults, {});
  var env = new Environment(merge(conf, c));
  return env;
}

module.exports.Environment = Environment;
module.exports.replace = replace;
module.exports.env = env;
<|endoftext|>