<commit_before>'use strict';

var util = require('util');
var pathLib = require('path');
var _ = require('underscore');
var BBPromise = require('bluebird');
var yaml = require('js-yaml');

var fs = require("fs");
BBPromise.promisifyAll(fs);

// constant
var sourceByRefProtocol = 'sourceref:';

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

function Sources(app, tilelive, npmResolver, serviceRootDir) {
    this.log = app.logger.log.bind(app.logger);
    this.tilelive = tilelive;
    this.npmResolver = npmResolver;
    this.serviceRootDir = serviceRootDir;
    this.sources = {};
    this.variables = {};

    // Set up a ref protocol resolver - this way its enough to specify the source
    // by sourceref:///?ref=sourceID URL, instead of a full source URL.
    var self = this;
    tilelive.protocols[sourceByRefProtocol] = function (uri, callback) {
        BBPromise.try(function () {
            uri = core.normalizeUri(uri);
            if (!uri.query.ref) {
                throw new Err('ref uri parameter is not set');
            }
            return self.getSourceById(uri.query.ref).handler;
        }).nodeify(callback);
    };
}

/**
 * Source ID must begin with a letter, and may contain letters, digits, and underscores
 */
Sources.isValidSourceId = function (sourceId) {
    return typeof sourceId === 'string' && sourceId.length > 0 && /^[A-Za-z]\w*$/.test(sourceId);
};

Sources.prototype.loadAsync = function(conf) {
    var self = this;
    // Load variables (e.g. passwords, etc)
    return self._localOrExternalDataAsync(conf.variables, self.serviceRootDir, 'variables')
        .then(function (variables) {
            self.variables = _.extend(self.variables, variables);
            // Load sources
            return self._localOrExternalDataAsync(conf.sources, self.serviceRootDir, 'sources');
        }).then(function (sources) {
            self.sources = _.extend(self.sources, sources);

            // Parse sources in parallel (loading on the other hand has to done in order)
            return core.mapSequentialAsync(self.sources, function (src, key) {
                if (!Sources.isValidSourceId(key))
                    throw new Err('sources.%s key must only contain letters and digits', key);
                if (typeof src !== 'object')
                    throw new Err('sources.%s must be an object', key);

                core.checkType(src, 'uri', 'string', true, 1);
                src.uri = core.normalizeUri(src.uri);

                // These params are stored, but not acted on within core
                // Kartotherian service uses them when handling user's requests
                // If public is not set or false, the rest of the values are unused
                core.checkType(src, 'public', 'boolean');
                core.checkType(src, 'minzoom', 'zoom');
                core.checkType(src, 'maxzoom', 'zoom');
                core.checkType(src, 'defaultHeaders', 'object');
                core.checkType(src, 'headers', 'object');

                // Add URI query values, e.g.  ?password=...
                if (core.checkType(src, 'params', 'object')) {
                    _.each(src.params, function (v, k) {
                        src.uri.query[k] = self._resolveValue(v);
                    });
                }
                // Set URI's path, e.g. /srv/data/mydir
                if (core.checkType(src, 'pathname', 'object')) {
                    src.uri.pathname = self._resolveValue(src.pathname);
                }
                if (src.xml) {
                    return self._loadXmlAsync(src);
                }
            });
        }).then(function () {
            return core.mapSequentialAsync(self.sources, function (src) {
                return self.tilelive
                    .loadAsync(src.uri)
                    .then(function (handler) {
                        src.handler = BBPromise.promisifyAll(handler);
                        return true;
                    });
            });
        });
};

Sources.prototype._localOrExternalDataAsync = function(values, rootDir, name) {
    var self = this;
    return BBPromise.try(function () {
        if (values === undefined) {
            self.log('info', name + ' is not set in the config file');
            return {};
        }
        if (!Array.isArray(values)) {
            values = [values];
        }
        values = _.map(values, function (value) {
            if (typeof value === 'string') {
                var path = pathLib.resolve(rootDir, value);
                self.log('info', 'Loading ' + name + ' from external file ' + path);
                return fs
                    .readFileAsync(path)
                    .then(yaml.safeLoad);
            } else if (typeof value === 'object' && !Array.isArray(value)) {
                self.log('info', 'Loading ' + name + ' from the config file');
                return value;
            } else {
                throw new Err('config.%s must be an object or filename, or an array of objects and/or filenames', name);
            }
        });

        return BBPromise.reduce(values, _.extend, {});
    });
};

Sources.prototype._xmlParamByName = function(xmlParams, name, xmlFile) {
    var param = xmlParams.childWithAttribute('name', name);
    if (!param || param.name !== 'Parameter') {
        throw new Err('<Parameter name="%s"> xml element was not found in %s', name, xmlFile);
    }
    return param;
};

Sources.prototype._setXmlParameters = function(newValues, xmlParams, xmlFile) {
    var self = this;
    _.each(newValues, function (value, name) {
        var param = self._xmlParamByName(xmlParams, name, xmlFile);
        param.val = self._resolveValue(value);
    });
};

Sources.prototype._loadXmlAsync = function(src) {
    var self = this;
    src.xml = self._resolveValue(src.xml);
    var promise = fs.readFileAsync(src.xml, 'utf8');

    if (src.xmlSetParams || src.xmlLayers || src.xmlExceptLayers || src.xmlSetDataSource) {
        promise = promise.then(function (xml) {
            var xmldoc = require('xmldoc');
            var doc = new xmldoc.XmlDocument(xml);

            // 'xmlSetParams' overrides root parameter values. Usage:
            //    xmlSetParams: { 'maxzoom':20, 'source': {'ref':'v1gen'} }
            if (core.checkType(src, 'xmlSetParams', 'object')) {
                var xmlParams = doc.childNamed('Parameters');
                if (!xmlParams) {
                    throw new Err('<Parameters> xml element was not found in %s', src.xml);
                }
                self._setXmlParameters(src.xmlSetParams, xmlParams, src.xml);
            }

            // 'xmlLayers' selects just the layers specified by a list (could be just one string instead)
            // Remove layers that were not listed in the layer parameter. Keep all non-layer elements.
            // Alternatively, use 'xmlExceptLayers' to exclude a list of layers.
            //    layers: ['waterway', 'building']
            var layerFunc = getLayerFilter(src);
            if (layerFunc) {
                var result = [];
                doc.eachChild(function (xmlChild) {
                    if (xmlChild.name !== 'Layer' || layerFunc(xmlChild)) {
                        result.push(xmlChild);
                    }
                });
                doc.children = result;
            }

            // 'xmlSetDataSource' allows alterations to the datasource parameters in each layer.
            // could be an object or an array of objects
            // use 'if' to provide a set of values to match, and 'set' to change values, xmlLayers/xmlExceptLayers filters
            if (core.checkType(src, 'xmlSetDataSource', 'object')) {
                var dataSources = src.xmlSetDataSource;
                if (typeof dataSources === 'object' && !Array.isArray(dataSources)) {
                    dataSources = [dataSources];
                }
                _.each(dataSources, function(ds) {
                    if (typeof ds !== 'object' || Array.isArray(ds)) {
                        throw new Err('XmlLoader: xmlSetDataSource must be an object');
                    }
                    var layerFunc = getLayerFilter(ds);
                    var conditions = false;
                    if (core.checkType(ds, 'if', 'object')) {
                        conditions = _.mapObject(ds.if, function (value) {
                            return self._resolveValue(value);
                        });
                    }
                    doc.eachChild(function (xmlLayer) {
                        if (xmlLayer.name !== 'Layer' || (layerFunc && !layerFunc(xmlLayer))) {
                            return;
                        }
                        var xmlParams = xmlLayer.childNamed('Datasource');
                        if (!xmlParams) {
                            self.log('warn', '<Datasource> xml element was not found in layer %s in %s', xmlLayer.attr.name, src.xml);
                            return;
                        }
                        if (conditions) {
                            if (!_.all(conditions, function (val, key) {
                                    return self._xmlParamByName(xmlParams, key, src.xml).val === val;
                                })) return;
                        }
                        self.log('info', 'Updating layer ' + xmlLayer.attr.name);
                        core.checkType(ds, 'set', 'object', true);
                        self._setXmlParameters(ds.set, xmlParams, src.xml);
                    });
                });
            }

            return doc.toString({cdata: true});
        });
    }

    return promise.then(function(xml) {
        // override all query params except protocol
        src.uri = {
            protocol: src.uri.protocol,
            xml: xml,
            base: pathLib.dirname(src.xml)
        };
    });
};

Sources.prototype._resolveValue = function(value) {
    if (typeof value !== 'object') {
        return value;
    }
    var key = _.keys(value);
    if (key.length !== 1) {
        throw new Err('Value must be an object with one key-value pair');
    }
    key = key[0];
    value = value[key];

    switch (key) {
        case 'npm':
            return this.getModulePath(value);
        case 'ref':
            return this._getSourceUri(value);
        case 'var':
            return this._getVariable(value);
        default:
            throw new Err('Value type %s is not recognized', key);
    }
};

Sources.prototype.getSourceById = function(sourceId, dontThrow) {
    if (!Sources.isValidSourceId(sourceId) || !this.sources.hasOwnProperty(sourceId))
        throw new Err('Unknown source %s', sourceId);
    return this.sources[sourceId];
};

Sources.prototype._getSourceUri = function(sourceId) {
    this.getSourceById(sourceId); // assert it exists
    return sourceByRefProtocol + '///?ref=' + sourceId;
};

Sources.prototype._getVariable = function(name) {
    core.checkType(this.variables, name, 'string', true);
    return this.variables[name];
};

Sources.prototype.getModulePath = function(moduleName) {
    var params;
    if (Array.isArray(moduleName)) {
        params = moduleName;
        moduleName = moduleName.shift();
    } else if ( typeof moduleName === 'string' ) {
        if (moduleName.indexOf("/") > -1) {
            return moduleName; // Already resolved path
        }
        params = [];
    } else {
        throw new Err('npm module name key must be a string or an array');
    }
    // remove the name of the startup js file, and use it as path
    params.unshift(pathLib.dirname(this.npmResolver(moduleName)));
    return pathLib.resolve.apply(undefined, params);
};

// returns a function that will test a layer for being in a list (or not in a list)
function getLayerFilter(src) {
    var include = core.checkType(src, 'xmlLayers', 'string-array');
    var exclude = core.checkType(src, 'xmlExceptLayers', 'string-array');
    if (!include && !exclude) {
        return undefined;
    }
    if (include && exclude) {
        throw new Err('XmlLoader: it may be either xmlLayers or xmlExceptLayers, not both');
    }
    var layers = include ? src.xmlLayers : src.xmlExceptLayers;
    if (!Array.isArray(layers)) {
        throw new Err('XmlLoader xmlLayers/xmlExceptLayers must be a string or an array of strings');
    }
    return function (xmlChild) {
        return _.contains(layers, xmlChild.attr.name) === include;
    };
}

module.exports = Sources;
<commit_msg>Added param-array handling, error reporting<commit_after>'use strict';

var util = require('util');
var pathLib = require('path');
var _ = require('underscore');
var BBPromise = require('bluebird');
var yaml = require('js-yaml');

var fs = require("fs");
BBPromise.promisifyAll(fs);

// constant
var sourceByRefProtocol = 'sourceref:';

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

function Sources(app, tilelive, npmResolver, serviceRootDir) {
    this.log = app.logger.log.bind(app.logger);
    this.tilelive = tilelive;
    this.npmResolver = npmResolver;
    this.serviceRootDir = serviceRootDir;
    this.sources = {};
    this.variables = {};

    // Set up a ref protocol resolver - this way its enough to specify the source
    // by sourceref:///?ref=sourceID URL, instead of a full source URL.
    var self = this;
    tilelive.protocols[sourceByRefProtocol] = function (uri, callback) {
        BBPromise.try(function () {
            uri = core.normalizeUri(uri);
            if (!uri.query.ref) {
                throw new Err('ref uri parameter is not set');
            }
            return self.getSourceById(uri.query.ref).handler;
        }).nodeify(callback);
    };
}

/**
 * Source ID must begin with a letter, and may contain letters, digits, and underscores
 */
Sources.isValidSourceId = function (sourceId) {
    return typeof sourceId === 'string' && sourceId.length > 0 && /^[A-Za-z]\w*$/.test(sourceId);
};

Sources.prototype.loadAsync = function(conf) {
    var self = this;
    // Load variables (e.g. passwords, etc)
    return self._localOrExternalDataAsync(conf.variables, self.serviceRootDir, 'variables')
        .then(function (variables) {
            self.variables = _.extend(self.variables, variables);
            // Load sources
            return self._localOrExternalDataAsync(conf.sources, self.serviceRootDir, 'sources');
        }).then(function (sources) {
            self.sources = _.extend(self.sources, sources);

            // Parse sources in parallel (loading on the other hand has to done in order)
            return core.mapSequentialAsync(self.sources, function (src, key) {
                if (!Sources.isValidSourceId(key))
                    throw new Err('sources.%s key must only contain letters and digits', key);
                if (typeof src !== 'object')
                    throw new Err('sources.%s must be an object', key);

                core.checkType(src, 'uri', 'string', true, 1);
                src.uri = core.normalizeUri(src.uri);

                // These params are stored, but not acted on within core
                // Kartotherian service uses them when handling user's requests
                // If public is not set or false, the rest of the values are unused
                core.checkType(src, 'public', 'boolean');
                core.checkType(src, 'minzoom', 'zoom');
                core.checkType(src, 'maxzoom', 'zoom');
                core.checkType(src, 'defaultHeaders', 'object');
                core.checkType(src, 'headers', 'object');

                // Add URI query values, e.g.  ?password=...
                if (core.checkType(src, 'params', 'object')) {
                    _.each(src.params, function (v, k) {
                        src.uri.query[k] = self._resolveValue(v, k);
                    });
                }
                // Set URI's path, e.g. /srv/data/mydir
                if (core.checkType(src, 'pathname', 'object')) {
                    src.uri.pathname = self._resolveValue(src.pathname, 'pathname');
                }
                if (src.xml) {
                    return self._loadXmlAsync(src);
                }
            });
        }).then(function () {
            return core.mapSequentialAsync(self.sources, function (src) {
                return self.tilelive
                    .loadAsync(src.uri)
                    .then(function (handler) {
                        src.handler = BBPromise.promisifyAll(handler);
                        return true;
                    });
            });
        });
};

Sources.prototype._localOrExternalDataAsync = function(values, rootDir, name) {
    var self = this;
    return BBPromise.try(function () {
        if (values === undefined) {
            self.log('info', name + ' is not set in the config file');
            return {};
        }
        if (!Array.isArray(values)) {
            values = [values];
        }
        values = _.map(values, function (value) {
            if (typeof value === 'string') {
                var path = pathLib.resolve(rootDir, value);
                self.log('info', 'Loading ' + name + ' from external file ' + path);
                return fs
                    .readFileAsync(path)
                    .then(yaml.safeLoad);
            } else if (typeof value === 'object' && !Array.isArray(value)) {
                self.log('info', 'Loading ' + name + ' from the config file');
                return value;
            } else {
                throw new Err('config.%s must be an object or filename, or an array of objects and/or filenames', name);
            }
        });

        return BBPromise.reduce(values, _.extend, {});
    });
};

Sources.prototype._xmlParamByName = function(xmlParams, name, xmlFile) {
    var param = xmlParams.childWithAttribute('name', name);
    if (!param || param.name !== 'Parameter') {
        throw new Err('<Parameter name="%s"> xml element was not found in %s', name, xmlFile);
    }
    return param;
};

Sources.prototype._setXmlParameters = function(newValues, xmlParams, xmlFile) {
    var self = this;
    _.each(newValues, function (value, name) {
        var param = self._xmlParamByName(xmlParams, name, xmlFile);
        param.val = self._resolveValue(value, name);
    });
};

Sources.prototype._loadXmlAsync = function(src) {
    var self = this;
    src.xml = self._resolveValue(src.xml, 'xml');
    var promise = fs.readFileAsync(src.xml, 'utf8');

    if (src.xmlSetParams || src.xmlLayers || src.xmlExceptLayers || src.xmlSetDataSource) {
        promise = promise.then(function (xml) {
            var xmldoc = require('xmldoc');
            var doc = new xmldoc.XmlDocument(xml);

            // 'xmlSetParams' overrides root parameter values. Usage:
            //    xmlSetParams: { 'maxzoom':20, 'source': {'ref':'v1gen'} }
            if (core.checkType(src, 'xmlSetParams', 'object')) {
                var xmlParams = doc.childNamed('Parameters');
                if (!xmlParams) {
                    throw new Err('<Parameters> xml element was not found in %s', src.xml);
                }
                self._setXmlParameters(src.xmlSetParams, xmlParams, src.xml);
            }

            // 'xmlLayers' selects just the layers specified by a list (could be just one string instead)
            // Remove layers that were not listed in the layer parameter. Keep all non-layer elements.
            // Alternatively, use 'xmlExceptLayers' to exclude a list of layers.
            //    layers: ['waterway', 'building']
            var layerFunc = getLayerFilter(src);
            if (layerFunc) {
                var result = [];
                doc.eachChild(function (xmlChild) {
                    if (xmlChild.name !== 'Layer' || layerFunc(xmlChild)) {
                        result.push(xmlChild);
                    }
                });
                doc.children = result;
            }

            // 'xmlSetDataSource' allows alterations to the datasource parameters in each layer.
            // could be an object or an array of objects
            // use 'if' to provide a set of values to match, and 'set' to change values, xmlLayers/xmlExceptLayers filters
            if (core.checkType(src, 'xmlSetDataSource', 'object')) {
                var dataSources = src.xmlSetDataSource;
                if (typeof dataSources === 'object' && !Array.isArray(dataSources)) {
                    dataSources = [dataSources];
                }
                _.each(dataSources, function(ds) {
                    if (typeof ds !== 'object' || Array.isArray(ds)) {
                        throw new Err('XmlLoader: xmlSetDataSource must be an object');
                    }
                    var layerFunc = getLayerFilter(ds);
                    var conditions = false;
                    if (core.checkType(ds, 'if', 'object')) {
                        conditions = _.mapObject(ds.if, function (value, key) {
                            return self._resolveValue(value, key);
                        });
                    }
                    doc.eachChild(function (xmlLayer) {
                        if (xmlLayer.name !== 'Layer' || (layerFunc && !layerFunc(xmlLayer))) {
                            return;
                        }
                        var xmlParams = xmlLayer.childNamed('Datasource');
                        if (!xmlParams) {
                            self.log('warn', '<Datasource> xml element was not found in layer %s in %s', xmlLayer.attr.name, src.xml);
                            return;
                        }
                        if (conditions) {
                            if (!_.all(conditions, function (val, key) {
                                    return self._xmlParamByName(xmlParams, key, src.xml).val === val;
                                })) return;
                        }
                        self.log('info', 'Updating layer ' + xmlLayer.attr.name);
                        core.checkType(ds, 'set', 'object', true);
                        self._setXmlParameters(ds.set, xmlParams, src.xml);
                    });
                });
            }

            return doc.toString({cdata: true});
        });
    }

    return promise.then(function(xml) {
        // override all query params except protocol
        src.uri = {
            protocol: src.uri.protocol,
            xml: xml,
            base: pathLib.dirname(src.xml)
        };
    });
};

Sources.prototype._resolveValue = function(value, valueName) {
    if (typeof value !== 'object' || Array.isArray(value)) {
        return value;
    }
    var key = _.keys(value);
    if (key.length !== 1) {
        throw new Err('Value %s must be an object with one key-value pair', valueName);
    }
    key = key[0];
    value = value[key];

    switch (key) {
        case 'npm':
            return this.getModulePath(value);
        case 'ref':
            return this._getSourceUri(value);
        case 'var':
            return this._getVariable(value);
        default:
            throw new Err('Value %s of type %s is not recognized', valueName, key);
    }
};

Sources.prototype.getSourceById = function(sourceId, dontThrow) {
    if (!Sources.isValidSourceId(sourceId) || !this.sources.hasOwnProperty(sourceId))
        throw new Err('Unknown source %s', sourceId);
    return this.sources[sourceId];
};

Sources.prototype._getSourceUri = function(sourceId) {
    this.getSourceById(sourceId); // assert it exists
    return sourceByRefProtocol + '///?ref=' + sourceId;
};

Sources.prototype._getVariable = function(name) {
    core.checkType(this.variables, name, 'string', true);
    return this.variables[name];
};

Sources.prototype.getModulePath = function(moduleName) {
    var params;
    if (Array.isArray(moduleName)) {
        params = moduleName;
        moduleName = moduleName.shift();
    } else if ( typeof moduleName === 'string' ) {
        if (moduleName.indexOf("/") > -1) {
            return moduleName; // Already resolved path
        }
        params = [];
    } else {
        throw new Err('npm module name key must be a string or an array');
    }
    // remove the name of the startup js file, and use it as path
    params.unshift(pathLib.dirname(this.npmResolver(moduleName)));
    return pathLib.resolve.apply(undefined, params);
};

// returns a function that will test a layer for being in a list (or not in a list)
function getLayerFilter(src) {
    var include = core.checkType(src, 'xmlLayers', 'string-array');
    var exclude = core.checkType(src, 'xmlExceptLayers', 'string-array');
    if (!include && !exclude) {
        return undefined;
    }
    if (include && exclude) {
        throw new Err('XmlLoader: it may be either xmlLayers or xmlExceptLayers, not both');
    }
    var layers = include ? src.xmlLayers : src.xmlExceptLayers;
    if (!Array.isArray(layers)) {
        throw new Err('XmlLoader xmlLayers/xmlExceptLayers must be a string or an array of strings');
    }
    return function (xmlChild) {
        return _.contains(layers, xmlChild.attr.name) === include;
    };
}

module.exports = Sources;
<|endoftext|>


<commit_before>#!/usr/bin/env node
"use strict";

require('pkginfo')(module, ['name', 'description', 'version']);

var child_process = require('child_process'),
	fs = require('fs'),
	path = require('path'),
	mkdirp = require('mkdirp'),
	which = require('which'),
	_ = require('lodash'),
	S = require('string'),
	program = require('commander'),
	datastore = require('./datastore');

program
	.version(exports.version)
	.option('-c, --config <file>', 'use a different user config file, defaults to ~/.config/ssmount.json');

program
	.command('list')
	.description('List the saved sshfs endpoints')
	.action(function() {
		datastore.setup(program);

		_(datastore.mounts.list())
			.sortBy('name')
			.each(function(mount) {
				console.log('%s %s', S(mount.name).padRight(10).s, mount.dest);
			});

		console.log("/ End of list");
	});

program
	.command('add <name> <dest>')
	.description('add new sshfs endpoints')
	.option('-f, --force', 'add endpoint even if name is already in use')
	.action(function(name, dest, o) {
		datastore.setup(program);

		if ( datastore.mounts.get(name) && !o.force ) {
			console.error("A mount by the name %s already exists", name);
			throw { exit: 1 };
		} else {
			datastore.mounts.add(name, dest);
			datastore.save();
		}
	});

program
	.command('rm <name>')
	.description('remove sshfs endpoints')
	.action(function(name) {
		datastore.setup(program);
		datastore.mounts.rm(name);
		datastore.save();
	});

program
	.command('mount <name>')
	.description('mount a sshfs endpoint')
	.action(function(name) {
		datastore.setup(program);

		var mount = datastore.mounts.get(name),
			cmdArgs = [];

		cmdArgs.push(mount.dest);
		cmdArgs.push(mount.point);

		if ( !fs.existsSync(mount.point) ) {
			mkdirp.sync(mount.point);
		}

		child_process.spawn('sshfs', cmdArgs, { stdio: 'inherit' })
			.on('close', function(code) {
				if ( code > 0 ) {
					// Remove the directory if the mount failed
					fs.rmdirSync(mount.point);
					console.error('/ Mount failed');
					throw { exit: 1 };
				} else {
					console.log('/ Mount finished');
				}
			})
			.on('error', function(err) {
				throw err;
			});
	});

program
	.command('unmount <name>')
	.description('unmount a mounted sshfs endpoint')
	.option('-f, --force', 'unmount even if in use')
	.action(function(name, o) {
		datastore.setup(program);

		var mount = datastore.mounts.get(name),
			cmdArgs = ['unmount'];

		if ( o.force ) {
			cmdArgs.push('force');
		}

		cmdArgs.push(mount.point);

		if ( !fs.existsSync(mount.point) ) {
			console.warn('/ %s was not mounted', mount.name);
		} else {
			child_process.spawn('diskutil', cmdArgs, { stdio: 'inherit' })
				.on('close', function(code) {
					if ( code > 0 ) {
						console.error('/ Unmount failed');
						throw { exit: 1 };
					} else {
						fs.rmdirSync(mount.point);
						console.log('/ Unmount finished');
					}
				})
				.on('error', function(err) {
					throw err;
				});
		}
	});

// Only make `ssmount subl` available when the subl command has been installed
if ( which.sync('subl') ) {
	program
		.command('subl <name> [file]')
		.description('open the mount point (or a file within it) in Sublime Edit')
		.action(function(name, file) {
			datastore.setup(program);

			var mount = datastore.mounts.get(name),
				cmdArgs = [];

			if ( file ) {
				cmdArgs.push(path.join(mount.point, file));
			} else {
				cmdArgs.push(mount.point);
			}

			if ( !fs.existsSync(mount.point) ) {
				console.error('/ %s is not mounted', mount.name);
			} else {
				child_process.spawn('subl', cmdArgs, { stdio: 'inherit' })
					.on('close', function(code) {
						if ( code > 0 ) {
							console.error('/ Failed');
							throw { exit: 1 };
						} else {
							console.log('/ Done');
						}
					})
					.on('error', function(err) {
						throw err;
					});
			}
		});
}

program
	.command('help [command]')
	.description('output usage information')
	.action(function(command) {
		if ( command ) {
			console.error("Sorry, per command help hasn't been implemented.");
		} else {
			program.help();
		}
	});

try {
	program.parse(process.argv);
} catch ( e ) {
	if ( e.exit ) {
		process.exit(1);
	} else {
		throw e;
	}
}

if ( process.argv.length <= 2 ) {
	program.help();
}
<commit_msg>When mounting, say where it was mounted to the user.<commit_after>#!/usr/bin/env node
"use strict";

require('pkginfo')(module, ['name', 'description', 'version']);

var child_process = require('child_process'),
	fs = require('fs'),
	path = require('path'),
	mkdirp = require('mkdirp'),
	which = require('which'),
	_ = require('lodash'),
	S = require('string'),
	program = require('commander'),
	datastore = require('./datastore');

program
	.version(exports.version)
	.option('-c, --config <file>', 'use a different user config file, defaults to ~/.config/ssmount.json');

program
	.command('list')
	.description('List the saved sshfs endpoints')
	.action(function() {
		datastore.setup(program);

		_(datastore.mounts.list())
			.sortBy('name')
			.each(function(mount) {
				console.log('%s %s', S(mount.name).padRight(10).s, mount.dest);
			});

		console.log("/ End of list");
	});

program
	.command('add <name> <dest>')
	.description('add new sshfs endpoints')
	.option('-f, --force', 'add endpoint even if name is already in use')
	.action(function(name, dest, o) {
		datastore.setup(program);

		if ( datastore.mounts.get(name) && !o.force ) {
			console.error("A mount by the name %s already exists", name);
			throw { exit: 1 };
		} else {
			datastore.mounts.add(name, dest);
			datastore.save();
		}
	});

program
	.command('rm <name>')
	.description('remove sshfs endpoints')
	.action(function(name) {
		datastore.setup(program);
		datastore.mounts.rm(name);
		datastore.save();
	});

program
	.command('mount <name>')
	.description('mount a sshfs endpoint')
	.action(function(name) {
		datastore.setup(program);

		var mount = datastore.mounts.get(name),
			cmdArgs = [];

		cmdArgs.push(mount.dest);
		cmdArgs.push(mount.point);

		if ( !fs.existsSync(mount.point) ) {
			mkdirp.sync(mount.point);
		}

		child_process.spawn('sshfs', cmdArgs, { stdio: 'inherit' })
			.on('close', function(code) {
				if ( code > 0 ) {
					// Remove the directory if the mount failed
					fs.rmdirSync(mount.point);
					console.error('/ Mount failed');
					throw { exit: 1 };
				} else {
					console.log('/ %s mounted at %s', mount.name, mount.point);
				}
			})
			.on('error', function(err) {
				throw err;
			});
	});

program
	.command('unmount <name>')
	.description('unmount a mounted sshfs endpoint')
	.option('-f, --force', 'unmount even if in use')
	.action(function(name, o) {
		datastore.setup(program);

		var mount = datastore.mounts.get(name),
			cmdArgs = ['unmount'];

		if ( o.force ) {
			cmdArgs.push('force');
		}

		cmdArgs.push(mount.point);

		if ( !fs.existsSync(mount.point) ) {
			console.warn('/ %s was not mounted', mount.name);
		} else {
			child_process.spawn('diskutil', cmdArgs, { stdio: 'inherit' })
				.on('close', function(code) {
					if ( code > 0 ) {
						console.error('/ Unmount failed');
						throw { exit: 1 };
					} else {
						fs.rmdirSync(mount.point);
						console.log('/ Unmount finished');
					}
				})
				.on('error', function(err) {
					throw err;
				});
		}
	});

// Only make `ssmount subl` available when the subl command has been installed
if ( which.sync('subl') ) {
	program
		.command('subl <name> [file]')
		.description('open the mount point (or a file within it) in Sublime Edit')
		.action(function(name, file) {
			datastore.setup(program);

			var mount = datastore.mounts.get(name),
				cmdArgs = [];

			if ( file ) {
				cmdArgs.push(path.join(mount.point, file));
			} else {
				cmdArgs.push(mount.point);
			}

			if ( !fs.existsSync(mount.point) ) {
				console.error('/ %s is not mounted', mount.name);
			} else {
				child_process.spawn('subl', cmdArgs, { stdio: 'inherit' })
					.on('close', function(code) {
						if ( code > 0 ) {
							console.error('/ Failed');
							throw { exit: 1 };
						} else {
							console.log('/ Done');
						}
					})
					.on('error', function(err) {
						throw err;
					});
			}
		});
}

program
	.command('help [command]')
	.description('output usage information')
	.action(function(command) {
		if ( command ) {
			console.error("Sorry, per command help hasn't been implemented.");
		} else {
			program.help();
		}
	});

try {
	program.parse(process.argv);
} catch ( e ) {
	if ( e.exit ) {
		process.exit(1);
	} else {
		throw e;
	}
}

if ( process.argv.length <= 2 ) {
	program.help();
}
<|endoftext|>


<commit_before>"use strict";

const base = process.cwd();
const contextFile = "context.json";

const path = require("path");
const fs = require("fs-promise");

const contextFilePath = path.resolve(base, contextFile);

exports.getContext = () => {
  return fs.readFile(contextFilePath, "utf8")
    .then(data => JSON.parse(data))
    .then(data => {
      data.date = new Date(data.date);
      return data;
    })
    .catch(() => ({}));
};

exports.saveContext = context => {
  return fs.writeFile(contextFilePath, JSON.stringify(context));
};
<commit_msg>Clean<commit_after>"use strict";

const base = process.cwd();
const contextFile = "context.json";

const path = require("path");
const fs = require("fs-promise");

const contextFilePath = path.resolve(base, contextFile);

exports.getContext = () => {
  return fs.readFile(contextFilePath, "utf8")
    .then(data => JSON.parse(data))
    .catch(() => ({}));
};

exports.saveContext = context => {
  return fs.writeFile(contextFilePath, JSON.stringify(context));
};
<|endoftext|>


<commit_before>//     (c) 2012 Victor Saiz, Michael Aufreiter
//     surface is freely distributable under the MIT license.
//     For all details and documentation:
//     http://github.com/surface/surface

// TODO: Implement updating the range within an annotation
// TODO: create tests

(function() {

  // Backbone.Events
  // -----------------

  // Regular expression used to split event strings
  var eventSplitter = /\s+/;
  // Create a local reference to slice/splice.
  var slice = Array.prototype.slice;
  var splice = Array.prototype.splice;

  // A module that can be mixed in to *any object* in order to provide it with
  // custom events. You may bind with `on` or remove with `off` callback functions
  // to an event; trigger`-ing an event fires all callbacks in succession.
  //
  //     var object = {};
  //     _.extend(object, Backbone.Events);
  //     object.on('expand', function(){ alert('expanded'); });
  //     object.trigger('expand');
  //
    _.Events = window.Backbone ? Backbone.Events : {

    // Bind one or more space separated events, `events`, to a `callback`
    // function. Passing `"all"` will bind the callback to all events fired.
    on: function(events, callback, context) {

      var calls, event, node, tail, list;
      if (!callback) return this;
      events = events.split(eventSplitter);
      calls = this._callbacks || (this._callbacks = {});

      // Create an immutable callback list, allowing traversal during
      // modification.  The tail is an empty object that will always be used
      // as the next node.
      while (event = events.shift()) {
        list = calls[event];
        node = list ? list.tail : {};
        node.next = tail = {};
        node.context = context;
        node.callback = callback;
        calls[event] = {tail: tail, next: list ? list.next : node};
      }

      return this;
    },

    // Remove one or many callbacks. If `context` is null, removes all callbacks
    // with that function. If `callback` is null, removes all callbacks for the
    // event. If `events` is null, removes all bound callbacks for all events.
    off: function(events, callback, context) {
      var event, calls, node, tail, cb, ctx;

      // No events, or removing *all* events.
      if (!(calls = this._callbacks)) return;
      if (!(events || callback || context)) {
        delete this._callbacks;
        return this;
      }

      // Loop through the listed events and contexts, splicing them out of the
      // linked list of callbacks if appropriate.
      events = events ? events.split(eventSplitter) : _.keys(calls);
      while (event = events.shift()) {
        node = calls[event];
        delete calls[event];
        if (!node || !(callback || context)) continue;
        // Create a new list, omitting the indicated callbacks.
        tail = node.tail;
        while ((node = node.next) !== tail) {
          cb = node.callback;
          ctx = node.context;
          if ((callback && cb !== callback) || (context && ctx !== context)) {
            this.on(event, cb, ctx);
          }
        }
      }

      return this;
    },

    // Trigger one or many events, firing all bound callbacks. Callbacks are
    // passed the same arguments as `trigger` is, apart from the event name
    // (unless you're listening on `"all"`, which will cause your callback to
    // receive the true name of the event as the first argument).
    trigger: function(events) {
      var event, node, calls, tail, args, all, rest;
      if (!(calls = this._callbacks)) return this;
      all = calls.all;
      events = events.split(eventSplitter);
      rest = slice.call(arguments, 1);

      // For each event, walk through the linked list of callbacks twice,
      // first to trigger the event, then to trigger any `"all"` callbacks.
      while (event = events.shift()) {
        if (node = calls[event]) {
          tail = node.tail;
          while ((node = node.next) !== tail) {
            node.callback.apply(node.context || this, rest);
          }
        }
        if (node = all) {
          tail = node.tail;
          args = [event].concat(rest);
          while ((node = node.next) !== tail) {
            node.callback.apply(node.context || this, args);
          }
        }
      }

      return this;
    }

  };

  // Substance
  // ---------

  if (!window.Substance) { var Substance = window.Substance = {}; }

  // Surface
  // ---------

  Substance.Surface = function(options) {

    var range
    ,   $el = $(options.el)
    ,   annotationList = []
    ,   events = _.extend({}, _.Events);

    // Modes
    // -------
    var modes = {
          INSERT: 'insertMode',
          SELECT: 'selectMode',
          IDLE: 'idleMode',
          OFF: 'offMode'
    },
    mode = modes.IDLE;

    // manage modes
    // ------
    function setMode(m){
      console.log('currently in:', mode);
      console.log('changing to:', m);
      if(mode !== m){
        switch(m){

          case modes.INSERT:
            cm.focus();
            cm.setOption('readOnly', false);
            $el.addClass('active');
            applyMode(m);
            break;

          case modes.IDLE:
            if(mode !== modes.SELECT && mode !== modes.OFF){
              cm.setOption('readOnly', true);
              $el.removeClass('active');
              applyMode(m);
            }
            break;

          case modes.OFF:
            cm.setOption('readOnly', 'nocursor');
            $el.removeClass('active');
            applyMode(m);
            break;
        }

      }
    }

    function applyMode(m){
        events.trigger('mode:change', m);
        mode = m;
    }

    // Force linewrapping before init
    options.lineWrapping = true;
    // options.autofocus = true;
    options.readOnly = true;

    // Init CodeMirror
    var cm = CodeMirror($el[0], options);

    // CodeMirror events
    // -----------------

    cm.setOption('onUpdate', function(){
      var needupdate = false || insideAnnotation(cm.getCursor());
      if(needupdate !== false){
        var i = _.indexOf(annotationList, needupdate);
        var bmPos = toOffset(annotationList[i].bm.find());
        console.log(bmPos);
        annotationList[i].pos.end = bmPos;
        events.trigger('annotation:update', selection());
      }
    });

    cm.setOption('onCursorActivity', function(cm){
      // Make sure we at least have 1 character selected
      if (cm.getSelection().length > 0){
        // Set the selection range
        var from = cm.getCursor(true)
        ,   to = cm.getCursor(false)
        , str = cm.getSelection();

        range = {'from':from, 'to':to, 'str': str};
        findMatch();
        setMode(modes.SELECT);
        events.trigger('selection:change', selection());
      }
    });

    $el.dblclick(function(){
      if (cm.getSelection().length > 0) {
        setMode(modes.SELECT);
      }else {
        events.trigger('selection:clear');
      }
    });

    // CodeMirror Blurred/Focus events
    // TODO: decide what part of this code is done in Text
    // ..... and what is in CM as in involves the tools

    $(document).mousedown(function(el){
      var $cl = $(el.toElement);
      var clickedClass = $cl.attr('class');
      var classRegex = /.*(CodeMirror|cm|tool).*/;
      var matched = classRegex.exec(clickedClass);

      if(!matched && cm.getSelection().length < 1 ){
        onBlur(clickedClass);
      }else{
        onFocus(clickedClass);
      }
      // console.log('matched', typeof matched !== 'undefined');
    });

    // Focussed
    function onFocus(clss){
      if(mode !== modes.OFF
        // make sure we're not clicking on the tools
        && !clss.match(/tool/g)) {
        setMode(modes.INSERT);
        events.trigger('selection:clear');
      }
    }

    // Blurred
    function onBlur($cl){
        setMode(modes.IDLE);
    }
    // //////////////////////////////////////////////////
    // //////////////////////////////////////////////////

    // Resets the cursor selection to the actual range
    function resetCursor(range) {
      // Reselect trimmed range and string value
      cm.setSelection(range.from, range.to);
    }

    // Unselects left or right blank characters
    function trim(sel) {
      var first = sel.str.charCodeAt(0);
      var last = sel.str.charCodeAt(sel.str.length-1);
      // Check if first character is blank and adjusts the annotation range if it is
      if (first === 32){
        sel.from.ch = sel.from.ch + 1;
      }

      // Check if last character is blank and adjusts the annotation range if it is
      if (last === 32){
        sel.to.ch = sel.to.ch - 1;
      }

      // Get selection string
      sel.str = cm.getRange(sel.from, sel.to);
      return sel;
    }

    // Turns position line objects into a single value representing the offset from character 0
    function toOffset(pos) {
      var offset = 0;
      if (pos.line > 0) {
        var i = 0;
        for(; i < pos.line; i++ ){
          offset += cm.lineInfo(i).text.length + 1;//count the newline character as 1?
        }
      }
      return pos.ch + offset;
    }

    // Returns the current selection
    function selection() {
      if (undefined != range) {
        range = trim(range);
        var start = toOffset(range.from);
        var end = toOffset(range.to);
        return { 'start': start, 'end': end, 'range': range} ;
      }
    }

    // Adds annotation (uses the current selection)
    function annotate(note) {
      if (cm.getSelection().length > 0) {
        note.pos = selection();
        //autogenerated id based on the type and the ofsset position string
        note.id = note.type + '-' + note.pos.start + '' + note.pos.end;
        // set a bookmark to the last char +1 to update ranges
        note.bm = cm.setBookmark({'ch':range.to.ch +1, 'line': range.to.line});
        annotationList.push(note);
        resetCursor(range);
        cm.markText(range.from, range.to, 'surface-' + note.type);
        events.trigger('annotation:change', note);
      }
    }

    // Determines if we are inside an annotation, returns true or false
    function insideAnnotation(pos){
      var currentPos = toOffset(pos);
      var found = _.find(annotationList, function(ann){ return ann.pos.start < currentPos && ann.pos.end > currentPos; });
      if(typeof found !== 'undefined'){
        return found;
      }else{
        return false;
      }
    }

    // Finds matching annotations for the selected range
    function findMatch(){
      var start = toOffset(range.from);
      var end = toOffset(range.to);
      var found = _.find(annotationList, function(ann){ return ann.pos.start === start && ann.pos.end === end; });
      if(typeof found !== 'undefined'){
        found.match = true;
        events.trigger('annotation:match', found);
      }else{
        events.trigger('annotation:nomatch');
      }
    }

    function remove(item){
      cm.replaceSelection(item.pos.range.str);
      annotationList =  _.without(annotationList, item);
    }

    // Returns mathcing annotations if there are
    function annotations(sel){
      if (undefined != sel){
        return _.filter(annotationList , function(ann) {
          return ann.pos.start == sel.start && ann.pos.end == sel.end;
        });
      } else {
        return annotationList;
      }
    }

    // mode getter
    function getMode(){
      return mode;
    }

    function enable(){
      setMode(modes.INSERT);
    }

    function cancel(){
      // TODO: needs to take the already saved surface if there is one before resetting!!
      var content = cm.getValue();
      cm.setValue(content);
      setMode(modes.OFF);
    }

    function disable(){
      setMode(modes.OFF);
    }

    function getModes(){
      return modes;
    }

    // Expose public API
    // -----------------

    return {
      on:          function () { events.on.apply(events, arguments); },
      off:         function () { events.off.apply(events, arguments); },
      trigger:     function () { events.trigger.apply(events, arguments); },
      setValue:    function(value){ cm.setValue(value); },

      annotations:  annotations,
      selection:    selection,
      annotate:     annotate,
      remove:       remove,
      enable:       enable,
      disable:      disable,
      cancel:       cancel,
      getModes:     getModes,

      // just testing what we have so far to render html from the Surface
      compile:      function compile(){
                      var content = cm.getValue();
                      var off = 0;

                      _.each( annotationList, function(annot){
                        var pre = content.substr(0, annot.pos.start + off);
                        var post = content.substr(annot.pos.end + off, content.length +  off);

                        var sub = annot.pos.range.str;

                        var preStyling = '';
                        var postStyling = '';
                        var styling = preStyling + postStyling;
                        content = pre +  preStyling  + sub + postStyling + post;
                        off = styling.length;
                      });
                      // content = content.replace(/\r\n|\r|\n/g,"
");
                      content = content.replace(/\n/g,"
");
                      return content;
                    }
    };
  };
})(window);<commit_msg>Updated todo list for substance in the comments<commit_after>//     (c) 2012 Victor Saiz, Michael Aufreiter
//     surface is freely distributable under the MIT license.
//     For all details and documentation:
//     http://github.com/surface/surface

// TODO: Implement updating the range within an annotation
// ..... Perhaps we should use CM bookmarks to start and end of the ranges
// ..... that way when they move around we can keep track of them
// TODO: when using mookmarks we need a way to determine if/when the bm
// ..... has been deleted and delete the annotation/notify the user before
// ..... taking action
// TODO: the annotation positions are getting messed up right now, must be fixed
// TODO: cancel last changes must be properly implemented
// TODO: Compile in one click has to be properly implemented
// TODO: create tests

(function() {

  // Backbone.Events
  // -----------------

  // Regular expression used to split event strings
  var eventSplitter = /\s+/;
  // Create a local reference to slice/splice.
  var slice = Array.prototype.slice;
  var splice = Array.prototype.splice;

  // A module that can be mixed in to *any object* in order to provide it with
  // custom events. You may bind with `on` or remove with `off` callback functions
  // to an event; trigger`-ing an event fires all callbacks in succession.
  //
  //     var object = {};
  //     _.extend(object, Backbone.Events);
  //     object.on('expand', function(){ alert('expanded'); });
  //     object.trigger('expand');
  //
    _.Events = window.Backbone ? Backbone.Events : {

    // Bind one or more space separated events, `events`, to a `callback`
    // function. Passing `"all"` will bind the callback to all events fired.
    on: function(events, callback, context) {

      var calls, event, node, tail, list;
      if (!callback) return this;
      events = events.split(eventSplitter);
      calls = this._callbacks || (this._callbacks = {});

      // Create an immutable callback list, allowing traversal during
      // modification.  The tail is an empty object that will always be used
      // as the next node.
      while (event = events.shift()) {
        list = calls[event];
        node = list ? list.tail : {};
        node.next = tail = {};
        node.context = context;
        node.callback = callback;
        calls[event] = {tail: tail, next: list ? list.next : node};
      }

      return this;
    },

    // Remove one or many callbacks. If `context` is null, removes all callbacks
    // with that function. If `callback` is null, removes all callbacks for the
    // event. If `events` is null, removes all bound callbacks for all events.
    off: function(events, callback, context) {
      var event, calls, node, tail, cb, ctx;

      // No events, or removing *all* events.
      if (!(calls = this._callbacks)) return;
      if (!(events || callback || context)) {
        delete this._callbacks;
        return this;
      }

      // Loop through the listed events and contexts, splicing them out of the
      // linked list of callbacks if appropriate.
      events = events ? events.split(eventSplitter) : _.keys(calls);
      while (event = events.shift()) {
        node = calls[event];
        delete calls[event];
        if (!node || !(callback || context)) continue;
        // Create a new list, omitting the indicated callbacks.
        tail = node.tail;
        while ((node = node.next) !== tail) {
          cb = node.callback;
          ctx = node.context;
          if ((callback && cb !== callback) || (context && ctx !== context)) {
            this.on(event, cb, ctx);
          }
        }
      }

      return this;
    },

    // Trigger one or many events, firing all bound callbacks. Callbacks are
    // passed the same arguments as `trigger` is, apart from the event name
    // (unless you're listening on `"all"`, which will cause your callback to
    // receive the true name of the event as the first argument).
    trigger: function(events) {
      var event, node, calls, tail, args, all, rest;
      if (!(calls = this._callbacks)) return this;
      all = calls.all;
      events = events.split(eventSplitter);
      rest = slice.call(arguments, 1);

      // For each event, walk through the linked list of callbacks twice,
      // first to trigger the event, then to trigger any `"all"` callbacks.
      while (event = events.shift()) {
        if (node = calls[event]) {
          tail = node.tail;
          while ((node = node.next) !== tail) {
            node.callback.apply(node.context || this, rest);
          }
        }
        if (node = all) {
          tail = node.tail;
          args = [event].concat(rest);
          while ((node = node.next) !== tail) {
            node.callback.apply(node.context || this, args);
          }
        }
      }

      return this;
    }

  };

  // Substance
  // ---------

  if (!window.Substance) { var Substance = window.Substance = {}; }

  // Surface
  // ---------

  Substance.Surface = function(options) {

    var range
    ,   $el = $(options.el)
    ,   annotationList = []
    ,   events = _.extend({}, _.Events);

    // Modes
    // -------
    var modes = {
          INSERT: 'insertMode',
          SELECT: 'selectMode',
          IDLE: 'idleMode',
          OFF: 'offMode'
    },
    mode = modes.IDLE;

    // manage modes
    // ------
    function setMode(m){
      console.log('currently in:', mode);
      console.log('changing to:', m);
      if(mode !== m){
        switch(m){

          case modes.INSERT:
            cm.focus();
            cm.setOption('readOnly', false);
            $el.addClass('active');
            applyMode(m);
            break;

          case modes.IDLE:
            if(mode !== modes.SELECT && mode !== modes.OFF){
              cm.setOption('readOnly', true);
              $el.removeClass('active');
              applyMode(m);
            }
            break;

          case modes.OFF:
            cm.setOption('readOnly', 'nocursor');
            $el.removeClass('active');
            applyMode(m);
            break;
        }

      }
    }

    function applyMode(m){
        events.trigger('mode:change', m);
        mode = m;
    }

    // Force linewrapping before init
    options.lineWrapping = true;
    // options.autofocus = true;
    options.readOnly = true;

    // Init CodeMirror
    var cm = CodeMirror($el[0], options);

    // CodeMirror events
    // -----------------

    cm.setOption('onUpdate', function(){
      var needupdate = false || insideAnnotation(cm.getCursor());
      if(needupdate !== false){
        var i = _.indexOf(annotationList, needupdate);
        var bmPos = toOffset(annotationList[i].bm.find());
        console.log(bmPos);
        annotationList[i].pos.end = bmPos;
        events.trigger('annotation:update', selection());
      }
    });

    cm.setOption('onCursorActivity', function(cm){
      // Make sure we at least have 1 character selected
      if (cm.getSelection().length > 0){
        // Set the selection range
        var from = cm.getCursor(true)
        ,   to = cm.getCursor(false)
        , str = cm.getSelection();

        range = {'from':from, 'to':to, 'str': str};
        findMatch();
        setMode(modes.SELECT);
        events.trigger('selection:change', selection());
      }
    });

    $el.dblclick(function(){
      if (cm.getSelection().length > 0) {
        setMode(modes.SELECT);
      }else {
        events.trigger('selection:clear');
      }
    });

    // CodeMirror Blurred/Focus events
    // TODO: decide what part of this code is done in Text
    // ..... and what is in CM as in involves the tools

    $(document).mousedown(function(el){
      var $cl = $(el.toElement);
      var clickedClass = $cl.attr('class');
      var classRegex = /.*(CodeMirror|cm|tool).*/;
      var matched = classRegex.exec(clickedClass);

      if(!matched && cm.getSelection().length < 1 ){
        onBlur(clickedClass);
      }else{
        onFocus(clickedClass);
      }
      // console.log('matched', typeof matched !== 'undefined');
    });

    // Focussed
    function onFocus(clss){
      if(mode !== modes.OFF
        // make sure we're not clicking on the tools
        && !clss.match(/tool/g)) {
        setMode(modes.INSERT);
        events.trigger('selection:clear');
      }
    }

    // Blurred
    function onBlur($cl){
        setMode(modes.IDLE);
    }
    // //////////////////////////////////////////////////
    // //////////////////////////////////////////////////

    // Resets the cursor selection to the actual range
    function resetCursor(range) {
      // Reselect trimmed range and string value
      cm.setSelection(range.from, range.to);
    }

    // Unselects left or right blank characters
    function trim(sel) {
      var first = sel.str.charCodeAt(0);
      var last = sel.str.charCodeAt(sel.str.length-1);
      // Check if first character is blank and adjusts the annotation range if it is
      if (first === 32){
        sel.from.ch = sel.from.ch + 1;
      }

      // Check if last character is blank and adjusts the annotation range if it is
      if (last === 32){
        sel.to.ch = sel.to.ch - 1;
      }

      // Get selection string
      sel.str = cm.getRange(sel.from, sel.to);
      return sel;
    }

    // Turns position line objects into a single value representing the offset from character 0
    function toOffset(pos) {
      var offset = 0;
      if (pos.line > 0) {
        var i = 0;
        for(; i < pos.line; i++ ){
          offset += cm.lineInfo(i).text.length + 1;//count the newline character as 1?
        }
      }
      return pos.ch + offset;
    }

    // Returns the current selection
    function selection() {
      if (undefined != range) {
        range = trim(range);
        var start = toOffset(range.from);
        var end = toOffset(range.to);
        return { 'start': start, 'end': end, 'range': range} ;
      }
    }

    // Adds annotation (uses the current selection)
    function annotate(note) {
      if (cm.getSelection().length > 0) {
        note.pos = selection();
        //autogenerated id based on the type and the ofsset position string
        note.id = note.type + '-' + note.pos.start + '' + note.pos.end;
        // set a bookmark to the last char +1 to update ranges
        note.bm = cm.setBookmark({'ch':range.to.ch +1, 'line': range.to.line});
        annotationList.push(note);
        resetCursor(range);
        cm.markText(range.from, range.to, 'surface-' + note.type);
        events.trigger('annotation:change', note);
      }
    }

    // Determines if we are inside an annotation, returns true or false
    function insideAnnotation(pos){
      var currentPos = toOffset(pos);
      var found = _.find(annotationList, function(ann){ return ann.pos.start < currentPos && ann.pos.end > currentPos; });
      if(typeof found !== 'undefined'){
        return found;
      }else{
        return false;
      }
    }

    // Finds matching annotations for the selected range
    function findMatch(){
      var start = toOffset(range.from);
      var end = toOffset(range.to);
      var found = _.find(annotationList, function(ann){ return ann.pos.start === start && ann.pos.end === end; });
      if(typeof found !== 'undefined'){
        found.match = true;
        events.trigger('annotation:match', found);
      }else{
        events.trigger('annotation:nomatch');
      }
    }

    function remove(item){
      cm.replaceSelection(item.pos.range.str);
      annotationList =  _.without(annotationList, item);
    }

    // Returns mathcing annotations if there are
    function annotations(sel){
      if (undefined != sel){
        return _.filter(annotationList , function(ann) {
          return ann.pos.start == sel.start && ann.pos.end == sel.end;
        });
      } else {
        return annotationList;
      }
    }

    // mode getter
    function getMode(){
      return mode;
    }

    function enable(){
      setMode(modes.INSERT);
    }

    function cancel(){
      // TODO: needs to take the already saved surface if there is one before resetting!!
      var content = cm.getValue();
      cm.setValue(content);
      setMode(modes.OFF);
    }

    function disable(){
      setMode(modes.OFF);
    }

    function getModes(){
      return modes;
    }

    // Expose public API
    // -----------------

    return {
      on:          function () { events.on.apply(events, arguments); },
      off:         function () { events.off.apply(events, arguments); },
      trigger:     function () { events.trigger.apply(events, arguments); },
      setValue:    function(value){ cm.setValue(value); },

      annotations:  annotations,
      selection:    selection,
      annotate:     annotate,
      remove:       remove,
      enable:       enable,
      disable:      disable,
      cancel:       cancel,
      getModes:     getModes,

      // just testing what we have so far to render html from the Surface
      compile:      function compile(){
                      var content = cm.getValue();
                      var off = 0;

                      _.each( annotationList, function(annot){
                        var pre = content.substr(0, annot.pos.start + off);
                        var post = content.substr(annot.pos.end + off, content.length +  off);

                        var sub = annot.pos.range.str;

                        var preStyling = '';
                        var postStyling = '';
                        var styling = preStyling + postStyling;
                        content = pre +  preStyling  + sub + postStyling + post;
                        off = styling.length;
                      });
                      // content = content.replace(/\r\n|\r|\n/g,"
");
                      content = content.replace(/\n/g,"
");
                      return content;
                    }
    };
  };
})(window);<|endoftext|>


<commit_before>var net = require('net');

var Server = net.Server.prototype;
var Pipe = process.binding('pipe_wrap').Pipe;

var oldListen = Server.listen;
Server.listen = function () {
    var self = this;

    if (arguments.length === 1 && arguments[0] === 'systemd') {
        if (!process.env.LISTEN_FDS || parseInt(process.env.LISTEN_FDS, 10) !== 1) {
            throw(new Error('No or too many file descriptors received.'));
        }

        if (process.binding('pipe_wrap').Pipe.constants && process.binding('pipe_wrap').Pipe.constants.SOCKET) {
            self._handle = new Pipe(process.binding('pipe_wrap').Pipe.constants.SOCKET);
        }
        else {
            self._handle = new Pipe();
        }
        self._handle.open(3);
        self._listen2(null, -1, -1);
    } else {
        oldListen.apply(self, arguments);
    }
    return self;
};
<commit_msg>Fix PR #15 which was merged.<commit_after>var net = require('net');

var Server = net.Server.prototype;
var PipeWrap = process.binding('pipe_wrap');
var Pipe = PipeWrap.Pipe;

var oldListen = Server.listen;
Server.listen = function () {
    var self = this;

    if (arguments.length === 1 && arguments[0] === 'systemd') {
        if (!process.env.LISTEN_FDS || parseInt(process.env.LISTEN_FDS, 10) !== 1) {
            throw(new Error('No or too many file descriptors received.'));
        }

        if (PipeWrap.constants && typeof PipeWrap.constants.SOCKET !== 'undefined') {
          self._handle = new Pipe(PipeWrap.constants.SOCKET);
        }
        else {
          self._handle = new Pipe();
        }
        self._handle.open(3);
        self._listen2(null, -1, -1);
    } else {
        oldListen.apply(self, arguments);
    }
    return self;
};
<|endoftext|>


<commit_before>var net = require('net');

var Server = net.Server.prototype;
var PipeWrap = process.binding('pipe_wrap');
var Pipe = PipeWrap.Pipe;

var oldListen = Server.listen;
Server.listen = function () {
    var self = this;

    if (arguments.length === 1 && arguments[0] === 'systemd') {
        if (!process.env.LISTEN_FDS || parseInt(process.env.LISTEN_FDS, 10) !== 1) {
            throw(new Error('No or too many file descriptors received.'));
        }

        if (PipeWrap.constants && typeof PipeWrap.constants.SOCKET !== 'undefined') {
            self._handle = new Pipe(PipeWrap.constants.SOCKET);
        }
        else {
            self._handle = new Pipe();
        }
        self._handle.open(3);
        self._listen2(null, -1, -1);
    } else {
        oldListen.apply(self, arguments);
    }
    return self;
};
<commit_msg>support standard net.Server.listen args<commit_after>var net = require('net');

var Server = net.Server.prototype;
var PipeWrap = process.binding('pipe_wrap');
var Pipe = PipeWrap.Pipe;

var oldListen = Server.listen;
Server.listen = function () {
    var self = this;
    var backlog;
    var callback;

    if (arguments[0] === 'systemd') {
        if (typeof arguments[1] === 'function') {
            callback = arguments[1];
        } else if (typeof arguments[1] === 'number') {
            backlog = arguments[1];
            callback = arguments[2];
        }

        if (!process.env.LISTEN_FDS || parseInt(process.env.LISTEN_FDS, 10) !== 1) {
            throw(new Error('No or too many file descriptors received.'));
        }

        if (callback) {
            self.once('listening', callback);
        }

        if (PipeWrap.constants && typeof PipeWrap.constants.SOCKET !== 'undefined') {
            self._handle = new Pipe(PipeWrap.constants.SOCKET);
        }
        else {
            self._handle = new Pipe();
        }
        self._handle.open(3);
        self._listen2(null, -1, -1, backlog);
    } else {
        oldListen.apply(self, arguments);
    }
    return self;
};
<|endoftext|>


<commit_before>'use strict';

/**
 * Strip leading alphanumeric characters plus spaces from a string. Useful for
 * converting filenames to more usable strings or IDs.
 *
 * @since v0.0.1
 * @param {String} name
 * @return {String}
 * @example
 *
 *   {{toTitle "01 Introduction"}} //=> "Introduction"
 */

module.exports = function toTitle (name) {
  var pattern = /^[0-9]{0,4}[-_]?\s+?/;
  return name.replace(pattern, '');
};
<commit_msg>Add type coercion to toTitle helper<commit_after>'use strict';

/**
 * Strip leading alphanumeric characters plus spaces from a string. Useful for
 * converting filenames to more usable strings or IDs.
 *
 * @since v0.0.1
 * @param {String} name
 * @return {String}
 * @example
 *
 *   {{toTitle "01 Introduction"}} //=> "Introduction"
 */

module.exports = function toTitle (name) {
  var pattern = /^[0-9]{0,4}[-_]?\s+?/;
  name = name.toString();
  return name.replace(pattern, '');
};
<|endoftext|>


<commit_before>var Perlin = require('./Perlin'),
    Worley = require('./Worley'),
    Simplex = require('./Simplex'),
    Fractal = require('./Fractal');

module.exports = {
    Perlin: {
        noise: Perlin.noise
    },

    Simplex: {
        noise3D: Simplex.noise3D
    },

    Worley: {
        Euclidean: Worley.Euclidean,
        Manhattan: Worley.Manhattan
    },

    Fractal: {
        noise: Fractal.noise
    }
};
<commit_msg>lib/tooloud.js cleanup<commit_after>var Perlin = require('./Perlin'),
    Simplex = require('./Simplex'),
    Worley = require('./Worley'),
    Fractal = require('./Fractal');

module.exports = {
    Perlin: {
        noise: Perlin.noise
    },

    Simplex: {
        noise3D: Simplex.noise3D
    },

    Worley: {
        Euclidean: Worley.Euclidean,
        Manhattan: Worley.Manhattan
    },

    Fractal: {
        noise: Fractal.noise
    }
};
<|endoftext|>


<commit_before>var events = require('events');

var Tracker = function (persister) {
	this.persister = persister;
	this.waitingClientsById = {};
	this.waitingClientsByResource = {};
};

Tracker.prototype.createClient = function (id) {
	console.log('Creating client #' + id);

	var client = {};
	client.id = id;
	client.resources = {};
	client.events = new events.EventEmitter();

	this.waitingClientsById[client.id] = client;

	return client;
};

Tracker.prototype.subscribe = function (client, resource, version) {
	version = ('' + version);

	console.log('Request to subscribe client #' + client.id + ' for: ' + resource + ' (@' + version + ')');

	if (typeof(client.resources[resource]) !== 'undefined') {
		return;
	}

	client.resources[resource] = version;

	if (typeof(this.waitingClientsByResource[resource]) === 'undefined') {
		this.waitingClientsByResource[resource] = {};
	}
	this.waitingClientsByResource[resource][client.id] = client;

	this.catchUpClient(client, resource);
};

Tracker.prototype.unsubscribe = function (client, resource) {
	console.log('Request to unsubscribe client #' + client.id + ' from: ' + resource);

	if (typeof(client.resources[resource]) === 'undefined') {
		//Already unsubscribed to resource.
		return;
	}

	delete this.waitingClientsByResource[resource][client.id];
	delete client.resources[resource];

	console.log('Client #' + client.id + ' now subscribed to resources: ' + JSON.stringify(client.resources));
};

Tracker.prototype.deleteClient = function (client) {
	console.log('Deleting client for #' + client.id);

	for (var resource in client.resources) {
		this.unsubscribe(client, resource);
	}

	delete this.waitingClientsById[client.id];
};

Tracker.prototype.publish = function (resource, version, data) {
	resource = ('' + resource);
	version = ('' + version);

	console.log('Received publish request for resource ' + resource + ', version: ' + version);

	this.persister.set(resource, {"version": version, "data": data});

	if (typeof(this.waitingClientsByResource[resource]) === 'undefined') {
		//No one waiting yet.
		return;
	}

	var clients = this.waitingClientsByResource[resource];
	for (var clientIdx in clients) {
		this.catchUpClient(clients[clientIdx], resource);
	}
};

Tracker.prototype.catchUpClient = function (client, resource) {
	var clientVersion = client.resources[resource];

	this.persister.get(resource, function (result) {
		if (result === null || clientVersion >= result.version) {
			console.log('Client #' + client.id + ' is up to date (' + clientVersion + ') for resource: ' + resource);
			return;
		}

		client.resources[resource] = result.version;
		console.log('Notifying client #' + client.id + ' for version ' + result.version + ' on ' + resource);

		client.events.emit('outdated', resource, result.version, result.data);
	});
};

module.exports = function (persister) {
	return new Tracker(persister);
};
<commit_msg>Fix version comparison code to handle different string lengths<commit_after>var events = require('events');

var Tracker = function (persister) {
	this.persister = persister;
	this.waitingClientsById = {};
	this.waitingClientsByResource = {};
};

Tracker.prototype.createClient = function (id) {
	console.log('Creating client #' + id);

	var client = {};
	client.id = id;
	client.resources = {};
	client.events = new events.EventEmitter();

	this.waitingClientsById[client.id] = client;

	return client;
};

Tracker.prototype.subscribe = function (client, resource, version) {
	version = ('' + version);

	console.log('Request to subscribe client #' + client.id + ' for: ' + resource + ' (@' + version + ')');

	if (typeof(client.resources[resource]) !== 'undefined') {
		return;
	}

	client.resources[resource] = version;

	if (typeof(this.waitingClientsByResource[resource]) === 'undefined') {
		this.waitingClientsByResource[resource] = {};
	}
	this.waitingClientsByResource[resource][client.id] = client;

	this.catchUpClient(client, resource);
};

Tracker.prototype.unsubscribe = function (client, resource) {
	console.log('Request to unsubscribe client #' + client.id + ' from: ' + resource);

	if (typeof(client.resources[resource]) === 'undefined') {
		//Already unsubscribed to resource.
		return;
	}

	delete this.waitingClientsByResource[resource][client.id];
	delete client.resources[resource];

	console.log('Client #' + client.id + ' now subscribed to resources: ' + JSON.stringify(client.resources));
};

Tracker.prototype.deleteClient = function (client) {
	console.log('Deleting client for #' + client.id);

	for (var resource in client.resources) {
		this.unsubscribe(client, resource);
	}

	delete this.waitingClientsById[client.id];
};

Tracker.prototype.publish = function (resource, version, data) {
	resource = ('' + resource);
	version = ('' + version);

	console.log('Received publish request for resource ' + resource + ', version: ' + version);

	this.persister.set(resource, {"version": version, "data": data});

	if (typeof(this.waitingClientsByResource[resource]) === 'undefined') {
		//No one waiting yet.
		return;
	}

	var clients = this.waitingClientsByResource[resource];
	for (var clientIdx in clients) {
		this.catchUpClient(clients[clientIdx], resource);
	}
};

Tracker.prototype.catchUpClient = function (client, resource) {
	var clientVersion = client.resources[resource];

	var isUpToDate = function (current, target) {
		if (target === null) {
			//The target is unknown, so whatever the client has is "up to date".
			return true;
		}

		if (target.length > current.length) {
			//("502" > "51") - this is not so, but we want it to be.
			return false;
		}

		return (current > target);
	};

	this.persister.get(resource, function (result) {
		if (isUpToDate(clientVersion, result)) {
			console.log('Client #' + client.id + ' is up to date (' + clientVersion + ') for resource: ' + resource);
			return;
		}

		client.resources[resource] = result.version;
		console.log('Notifying client #' + client.id + ' for version ' + result.version + ' on ' + resource);

		client.events.emit('outdated', resource, result.version, result.data);
	});
};

module.exports = function (persister) {
	return new Tracker(persister);
};
<|endoftext|>


<commit_before>'use strict';

/**
 * Module dependencies
 */

var url = require('url');
var Streamparser = require('./parser');
var request = require('request');
var extend = require('deep-extend');

// Package version
var VERSION = require('../package.json').version;

function Twitter(options) {
  if (!(this instanceof Twitter)) { return new Twitter(options) }

  this.VERSION = VERSION;

  // Merge the default options with the client submitted options
  this.options = extend({
    consumer_key: null,
    consumer_secret: null,
    access_token_key: null,
    access_token_secret: null,
    bearer_token: null,
    rest_base: 'https://api.twitter.com/1.1',
    stream_base: 'https://stream.twitter.com/1.1',
    user_stream_base: 'https://userstream.twitter.com/1.1',
    site_stream_base: 'https://sitestream.twitter.com/1.1',
    media_base: 'https://upload.twitter.com/1.1',
    request_options: {
      headers: {
        Accept: '*/*',
        Connection: 'close',
        'User-Agent': 'node-twitter/' + VERSION
      }
    }
  }, options);

  // Default to user authentication
  var authentication_options = {
    oauth: {
      consumer_key: this.options.consumer_key,
      consumer_secret: this.options.consumer_secret,
      token: this.options.access_token_key,
      token_secret: this.options.access_token_secret
    }
  };

  // Check to see if we are going to use User Authentication or Application Authetication
  if (this.options.bearer_token) {
    authentication_options = {
      headers: {
        Authorization: 'Bearer ' + this.options.bearer_token
      }
    };
  }

  // Configure default request options
  this.request = request.defaults(
    extend(
      this.options.request_options,
      authentication_options
    )
  );

  // Check if Promise present
  this.allow_promise = (typeof Promise === 'function');
}

Twitter.prototype.__buildEndpoint = function(path, base) {
  var bases = {
    'rest': this.options.rest_base,
    'stream': this.options.stream_base,
    'user_stream': this.options.user_stream_base,
    'site_stream': this.options.site_stream_base,
    'media': this.options.media_base
  };
  var endpoint = (bases.hasOwnProperty(base)) ? bases[base] : bases.rest;

  if (url.parse(path).protocol) {
    endpoint = path;
  }
  else {
    // If the path begins with media or /media
    if (path.match(/^(\/)?media/)) {
      endpoint = bases.media;
    }
    endpoint += (path.charAt(0) === '/') ? path : '/' + path;
  }

  // Remove trailing slash
  endpoint = endpoint.replace(/\/$/, '');

  // Add json extension if not provided in call
  endpoint += (path.split('.').pop() !== 'json') ? '.json' : '';

  return endpoint;
};

Twitter.prototype.__request = function(method, path, params, callback) {
  var base = 'rest';
  var promise = false;

  // Set the callback if no params are passed
  if (typeof params === 'function') {
    callback = params;
    params = {};
  }
  // Return promise if no callback is passed and promises available
  else if (callback === undefined && this.allow_promise) {
    promise = true;
  }

  // Set API base
  if (typeof params.base !== 'undefined') {
    base = params.base;
    delete params.base;
  }

  // Build the options to pass to our custom request object
  var options = {
    method: method.toLowerCase(),  // Request method - get || post
    url: this.__buildEndpoint(path, base) // Generate url
  };

  // Pass url parameters if get
  if (method === 'get') {
    options.qs = params;
  }

  // Pass form data if post
  if (method === 'post') {
    var formKey = 'form';

    if (typeof params.media !== 'undefined') {
      formKey = 'formData';
    }
    options[formKey] = params;
  }

  // Promisified version
  if (promise) {
    var _this = this;
    return new Promise(function (resolve, reject) {
      _this.request(options, function (error, response, data) {
        // request error
        if (error) {
          return reject(error);
        }

        // JSON parse error or empty strings
        try {
          // An empty string is a valid response
          if (data === '') {
            data = {};
          }
          else {
            data = JSON.parse(data);
          }
        }
        catch(parseError) {
          return reject(new Error('JSON parseError with HTTP Status: ' + response.statusCode + ' ' + response.statusMessage));
        }

        // response object errors
        // This should return an error object not an array of errors
        if (data.errors !== undefined) {
          return reject(data.errors);
        }

        // status code errors
        if(response.statusCode < 200 || response.statusCode > 299) {
          return reject(new Error('HTTP Error: ' + response.statusCode + ' ' + response.statusMessage));
        }

        // no errors
        resolve(data);
      });
    });
  }

  // Callback version
  this.request(options, function(error, response, data) {
    // request error
    if (error) {
      return callback(error, data, response);
    }

    // JSON parse error or empty strings
    try {
      // An empty string is a valid response
      if (data === '') {
        data = {};
      }
      else {
        data = JSON.parse(data);
      }
    }
    catch(parseError) {
      return callback(
        new Error('JSON parseError with HTTP Status: ' + response.statusCode + ' ' + response.statusMessage),
        data,
        response
      );
    }

    // response object errors
    // This should return an error object not an array of errors
    if (data.errors !== undefined) {
      return callback(data.errors, data, response);
    }

    // status code errors
    if(response.statusCode < 200 || response.statusCode > 299) {
      return callback(
        new Error('HTTP Error: ' + response.statusCode + ' ' + response.statusMessage),
        data,
        response
      );
    }
    // no errors
    callback(null, data, response);
  });

};

/**
 * GET
 */
Twitter.prototype.get = function(url, params, callback) {
  return this.__request('get', url, params, callback);
};

/**
 * POST
 */
Twitter.prototype.post = function(url, params, callback) {
  return this.__request('post', url, params, callback);
};

/**
 * STREAM
 */
Twitter.prototype.stream = function(method, params, callback) {
  if (typeof params === 'function') {
    callback = params;
    params = {};
  }

  var base = 'stream';

  if (method === 'user' || method === 'site') {
    base = method + '_' + base;
  }

  var url = this.__buildEndpoint(method, base);
  var request = this.request({url: url, qs: params});
  var stream = new Streamparser();

  stream.destroy = function() {
    // FIXME: should we emit end/close on explicit destroy?
    if ( typeof request.abort === 'function' ) {
      request.abort(); // node v0.4.0
    }
    else {
      request.socket.destroy();
    }
  };

  request.on('response', function(response) {
    if(response.statusCode !== 200) {
      stream.emit('error', new Error('Status Code: ' + response.statusCode));
    }
    response.on('data', function(chunk) {
      stream.receive(chunk);
    });

    response.on('error', function(error) {
      stream.emit('error', error);
    });

    response.on('end', function() {
      stream.emit('end', response);
    });
  });

  request.on('error', function(error) {
    stream.emit('error', error);
  });
  request.end();

  if (typeof callback === 'function') {
    callback(stream);
  }
  else {
    return stream;
  }
};

module.exports = Twitter;
<commit_msg>Tiny formatting tweak to take off a couple eslint errors<commit_after>'use strict';

/**
 * Module dependencies
 */

var url = require('url');
var Streamparser = require('./parser');
var request = require('request');
var extend = require('deep-extend');

// Package version
var VERSION = require('../package.json').version;

function Twitter(options) {
  if (!(this instanceof Twitter)) { return new Twitter(options) }

  this.VERSION = VERSION;

  // Merge the default options with the client submitted options
  this.options = extend({
    consumer_key: null,
    consumer_secret: null,
    access_token_key: null,
    access_token_secret: null,
    bearer_token: null,
    rest_base: 'https://api.twitter.com/1.1',
    stream_base: 'https://stream.twitter.com/1.1',
    user_stream_base: 'https://userstream.twitter.com/1.1',
    site_stream_base: 'https://sitestream.twitter.com/1.1',
    media_base: 'https://upload.twitter.com/1.1',
    request_options: {
      headers: {
        Accept: '*/*',
        Connection: 'close',
        'User-Agent': 'node-twitter/' + VERSION
      }
    }
  }, options);

  // Default to user authentication
  var authentication_options = {
    oauth: {
      consumer_key: this.options.consumer_key,
      consumer_secret: this.options.consumer_secret,
      token: this.options.access_token_key,
      token_secret: this.options.access_token_secret
    }
  };

  // Check to see if we are going to use User Authentication or Application Authetication
  if (this.options.bearer_token) {
    authentication_options = {
      headers: {
        Authorization: 'Bearer ' + this.options.bearer_token
      }
    };
  }

  // Configure default request options
  this.request = request.defaults(
    extend(
      this.options.request_options,
      authentication_options
    )
  );

  // Check if Promise present
  this.allow_promise = (typeof Promise === 'function');
}

Twitter.prototype.__buildEndpoint = function(path, base) {
  var bases = {
    'rest': this.options.rest_base,
    'stream': this.options.stream_base,
    'user_stream': this.options.user_stream_base,
    'site_stream': this.options.site_stream_base,
    'media': this.options.media_base
  };
  var endpoint = (bases.hasOwnProperty(base)) ? bases[base] : bases.rest;

  if (url.parse(path).protocol) {
    endpoint = path;
  }
  else {
    // If the path begins with media or /media
    if (path.match(/^(\/)?media/)) {
      endpoint = bases.media;
    }
    endpoint += (path.charAt(0) === '/') ? path : '/' + path;
  }

  // Remove trailing slash
  endpoint = endpoint.replace(/\/$/, '');

  // Add json extension if not provided in call
  endpoint += (path.split('.').pop() !== 'json') ? '.json' : '';

  return endpoint;
};

Twitter.prototype.__request = function(method, path, params, callback) {
  var base = 'rest';
  var promise = false;

  // Set the callback if no params are passed
  if (typeof params === 'function') {
    callback = params;
    params = {};
  }
  // Return promise if no callback is passed and promises available
  else if (callback === undefined && this.allow_promise) {
    promise = true;
  }

  // Set API base
  if (typeof params.base !== 'undefined') {
    base = params.base;
    delete params.base;
  }

  // Build the options to pass to our custom request object
  var options = {
    method: method.toLowerCase(),  // Request method - get || post
    url: this.__buildEndpoint(path, base) // Generate url
  };

  // Pass url parameters if get
  if (method === 'get') {
    options.qs = params;
  }

  // Pass form data if post
  if (method === 'post') {
    var formKey = 'form';

    if (typeof params.media !== 'undefined') {
      formKey = 'formData';
    }
    options[formKey] = params;
  }

  // Promisified version
  if (promise) {
    var _this = this;
    return new Promise(function(resolve, reject) {
      _this.request(options, function(error, response, data) {
        // request error
        if (error) {
          return reject(error);
        }

        // JSON parse error or empty strings
        try {
          // An empty string is a valid response
          if (data === '') {
            data = {};
          }
          else {
            data = JSON.parse(data);
          }
        }
        catch(parseError) {
          return reject(new Error('JSON parseError with HTTP Status: ' + response.statusCode + ' ' + response.statusMessage));
        }

        // response object errors
        // This should return an error object not an array of errors
        if (data.errors !== undefined) {
          return reject(data.errors);
        }

        // status code errors
        if(response.statusCode < 200 || response.statusCode > 299) {
          return reject(new Error('HTTP Error: ' + response.statusCode + ' ' + response.statusMessage));
        }

        // no errors
        resolve(data);
      });
    });
  }

  // Callback version
  this.request(options, function(error, response, data) {
    // request error
    if (error) {
      return callback(error, data, response);
    }

    // JSON parse error or empty strings
    try {
      // An empty string is a valid response
      if (data === '') {
        data = {};
      }
      else {
        data = JSON.parse(data);
      }
    }
    catch(parseError) {
      return callback(
        new Error('JSON parseError with HTTP Status: ' + response.statusCode + ' ' + response.statusMessage),
        data,
        response
      );
    }

    // response object errors
    // This should return an error object not an array of errors
    if (data.errors !== undefined) {
      return callback(data.errors, data, response);
    }

    // status code errors
    if(response.statusCode < 200 || response.statusCode > 299) {
      return callback(
        new Error('HTTP Error: ' + response.statusCode + ' ' + response.statusMessage),
        data,
        response
      );
    }
    // no errors
    callback(null, data, response);
  });

};

/**
 * GET
 */
Twitter.prototype.get = function(url, params, callback) {
  return this.__request('get', url, params, callback);
};

/**
 * POST
 */
Twitter.prototype.post = function(url, params, callback) {
  return this.__request('post', url, params, callback);
};

/**
 * STREAM
 */
Twitter.prototype.stream = function(method, params, callback) {
  if (typeof params === 'function') {
    callback = params;
    params = {};
  }

  var base = 'stream';

  if (method === 'user' || method === 'site') {
    base = method + '_' + base;
  }

  var url = this.__buildEndpoint(method, base);
  var request = this.request({url: url, qs: params});
  var stream = new Streamparser();

  stream.destroy = function() {
    // FIXME: should we emit end/close on explicit destroy?
    if ( typeof request.abort === 'function' ) {
      request.abort(); // node v0.4.0
    }
    else {
      request.socket.destroy();
    }
  };

  request.on('response', function(response) {
    if(response.statusCode !== 200) {
      stream.emit('error', new Error('Status Code: ' + response.statusCode));
    }
    response.on('data', function(chunk) {
      stream.receive(chunk);
    });

    response.on('error', function(error) {
      stream.emit('error', error);
    });

    response.on('end', function() {
      stream.emit('end', response);
    });
  });

  request.on('error', function(error) {
    stream.emit('error', error);
  });
  request.end();

  if (typeof callback === 'function') {
    callback(stream);
  }
  else {
    return stream;
  }
};

module.exports = Twitter;
<|endoftext|>