<commit_before>'use strict';
var path = require('path');
var gulp = require('gulp');
var eslint = require('gulp-eslint');
var excludeGitignore = require('gulp-exclude-gitignore');
var mocha = require('gulp-mocha');
var istanbul = require('gulp-istanbul');
var nsp = require('gulp-nsp');
var plumber = require('gulp-plumber');
var coveralls = require('gulp-coveralls');
var codeclimate = require('gulp-codeclimate-reporter');

gulp.task('lint', function () {
    return gulp.src(['**/*.js', '!node_modules/**'])
        .pipe(excludeGitignore())
        .pipe(eslint())
        .pipe(eslint.format())
        .pipe(eslint.failAfterError());
});

gulp.task('nsp', function (cb) {
    nsp({package: path.resolve('package.json')}, cb);
});

gulp.task('pre-test', function () {
    return gulp.src('lib/**/*.js')
        .pipe(istanbul({
            includeUntested: true
        }))
        .pipe(istanbul.hookRequire());
});

gulp.task('test', ['pre-test'], function (cb) {
    var mochaErr;

    process.env.OP_TEST = 1;

    gulp.src('test/**/*.js')
        .pipe(plumber())
        .pipe(mocha({reporter: 'spec'}))
        .on('error', function (err) {
            mochaErr = err;
        })
        .pipe(istanbul.writeReports())
        .on('end', function () {
            cb(mochaErr);
        });
});

gulp.task('coveralls', ['test'], function () {
    if (!process.env.COVERAGE_REPORT) {
        return;
    }

    return gulp.src(path.join(__dirname, 'coverage/lcov.info'))
        .pipe(coveralls());
});

gulp.task('codeclimate', ['test'], function () {
    if (!process.env.COVERAGE_REPORT) {
        return;
    }

    return gulp.src(path.join(__dirname, 'coverage/lcov.info'))
        .pipe(codeclimate({
            token: process.env.CODECLIMATE_REPO_TOKEN
        }));
});

console.log(process.env.TRAVIS_NODE_VERSION);
console.log(process.env.COVERAGE_REPORT);

gulp.task('stats', ['coveralls', 'codeclimate']);
gulp.task('prepublish', ['nsp']);
gulp.task('default', ['prepublish', 'lint', 'test']);
<commit_msg>Removed redundant console logs<commit_after>'use strict';
var path = require('path');
var gulp = require('gulp');
var eslint = require('gulp-eslint');
var excludeGitignore = require('gulp-exclude-gitignore');
var mocha = require('gulp-mocha');
var istanbul = require('gulp-istanbul');
var nsp = require('gulp-nsp');
var plumber = require('gulp-plumber');
var coveralls = require('gulp-coveralls');
var codeclimate = require('gulp-codeclimate-reporter');

gulp.task('lint', function () {
    return gulp.src(['**/*.js', '!node_modules/**'])
        .pipe(excludeGitignore())
        .pipe(eslint())
        .pipe(eslint.format())
        .pipe(eslint.failAfterError());
});

gulp.task('nsp', function (cb) {
    nsp({package: path.resolve('package.json')}, cb);
});

gulp.task('pre-test', function () {
    return gulp.src('lib/**/*.js')
        .pipe(istanbul({
            includeUntested: true
        }))
        .pipe(istanbul.hookRequire());
});

gulp.task('test', ['pre-test'], function (cb) {
    var mochaErr;

    process.env.OP_TEST = 1;

    gulp.src('test/**/*.js')
        .pipe(plumber())
        .pipe(mocha({reporter: 'spec'}))
        .on('error', function (err) {
            mochaErr = err;
        })
        .pipe(istanbul.writeReports())
        .on('end', function () {
            cb(mochaErr);
        });
});

gulp.task('coveralls', ['test'], function () {
    if (!process.env.COVERAGE_REPORT) {
        return;
    }

    return gulp.src(path.join(__dirname, 'coverage/lcov.info'))
        .pipe(coveralls());
});

gulp.task('codeclimate', ['test'], function () {
    if (!process.env.COVERAGE_REPORT) {
        return;
    }

    return gulp.src(path.join(__dirname, 'coverage/lcov.info'))
        .pipe(codeclimate({
            token: process.env.CODECLIMATE_REPO_TOKEN
        }));
});

gulp.task('stats', ['coveralls', 'codeclimate']);
gulp.task('prepublish', ['nsp']);
gulp.task('default', ['prepublish', 'lint', 'test']);
<|endoftext|>


<commit_before>'use strict';
var Querystring = require('querystring');
var Faketoe = require('faketoe');
var Hoek = require('hoek');
var Wreck = require('wreck');

var _defaults = {
  host: 'http://production.shippingapis.com/ShippingAPI.dll',
  secureHost: 'https://secure.shippingapis.com/ShippingAPI.dll'
};

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

  Hoek.assert(options !== null && typeof options === 'object',
              'options must be an object');
  Hoek.assert(typeof options.userId === 'string', 'user ID must be a string');

  var settings = Hoek.applyToDefaults(_defaults, options);

  this._userId = settings.userId;
  this._host = settings.host;
  this._secureHost = settings.secureHost;
}

module.exports = Usps;

Usps.prototype.cityStateLookup = function (options, callback) {
  Hoek.assert(options !== null && typeof options === 'object',
              'options must be an object');
  Hoek.assert(typeof options.zip === 'string' ||
              (Array.isArray(options.zip) && options.zip.length > 0),
              'zips must be a string or array with at least one element');

  var zips = [].concat(options.zip);
  var xml = '<CityStateLookupRequest USERID="' + this._userId + '">';

  for (var i = 0, il = zips.length; i < il; ++i) {
    xml += '<ZipCode ID="' + i + '"><Zip5>' + zips[i] + '</Zip5></ZipCode>';
  }

  xml += '</CityStateLookupRequest>';

  _request('CityStateLookup', xml, this._host, function (err, result) {
    if (err) {
      return callback(err);
    }

    var lookups = result.CityStateLookupResponse.ZipCode;
    var serviceErr = null;
    var response;

    if (Array.isArray(lookups)) {
      response = [];

      for (var i = 0, il = lookups.length; i < il; ++i) {
        response[i] = _formatCityStateLookupResponse(lookups[i]);

        if (response[i] instanceof Error) {
          serviceErr = response[i];
        }
      }
    } else {
      response = _formatCityStateLookupResponse(lookups);

      if (response instanceof Error) {
        serviceErr = response;
      }
    }

    callback(serviceErr, response);
  });
};

function _formatCityStateLookupResponse (response) {

  if (response.Error) {
    return new Error(response.Error.Description);
  }

  return {
    zip: response.Zip5,
    city: response.City,
    state: response.State
  };
}

function _request (api, xml, host, callback) {
  var url = host + '?' + Querystring.stringify({
    API: api,
    XML: xml
  });

  Wreck.request('GET', url, null, function (err, response) {
    if (err) {
      return callback(err);
    }

    response.pipe(Faketoe.createParser(callback));
  });
}
<commit_msg>Remove blank line to pass linting<commit_after>'use strict';
var Querystring = require('querystring');
var Faketoe = require('faketoe');
var Hoek = require('hoek');
var Wreck = require('wreck');

var _defaults = {
  host: 'http://production.shippingapis.com/ShippingAPI.dll',
  secureHost: 'https://secure.shippingapis.com/ShippingAPI.dll'
};

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

  Hoek.assert(options !== null && typeof options === 'object',
              'options must be an object');
  Hoek.assert(typeof options.userId === 'string', 'user ID must be a string');

  var settings = Hoek.applyToDefaults(_defaults, options);

  this._userId = settings.userId;
  this._host = settings.host;
  this._secureHost = settings.secureHost;
}

module.exports = Usps;

Usps.prototype.cityStateLookup = function (options, callback) {
  Hoek.assert(options !== null && typeof options === 'object',
              'options must be an object');
  Hoek.assert(typeof options.zip === 'string' ||
              (Array.isArray(options.zip) && options.zip.length > 0),
              'zips must be a string or array with at least one element');

  var zips = [].concat(options.zip);
  var xml = '<CityStateLookupRequest USERID="' + this._userId + '">';

  for (var i = 0, il = zips.length; i < il; ++i) {
    xml += '<ZipCode ID="' + i + '"><Zip5>' + zips[i] + '</Zip5></ZipCode>';
  }

  xml += '</CityStateLookupRequest>';

  _request('CityStateLookup', xml, this._host, function (err, result) {
    if (err) {
      return callback(err);
    }

    var lookups = result.CityStateLookupResponse.ZipCode;
    var serviceErr = null;
    var response;

    if (Array.isArray(lookups)) {
      response = [];

      for (var i = 0, il = lookups.length; i < il; ++i) {
        response[i] = _formatCityStateLookupResponse(lookups[i]);

        if (response[i] instanceof Error) {
          serviceErr = response[i];
        }
      }
    } else {
      response = _formatCityStateLookupResponse(lookups);

      if (response instanceof Error) {
        serviceErr = response;
      }
    }

    callback(serviceErr, response);
  });
};

function _formatCityStateLookupResponse (response) {
  if (response.Error) {
    return new Error(response.Error.Description);
  }

  return {
    zip: response.Zip5,
    city: response.City,
    state: response.State
  };
}

function _request (api, xml, host, callback) {
  var url = host + '?' + Querystring.stringify({
    API: api,
    XML: xml
  });

  Wreck.request('GET', url, null, function (err, response) {
    if (err) {
      return callback(err);
    }

    response.pipe(Faketoe.createParser(callback));
  });
}
<|endoftext|>


<commit_before>'use strict';

var Steppy = require('twostep').Steppy,
	_ = require('underscore'),
	querystring = require('querystring');
/*
 * Pure rest api on pure nodejs follows below
 */

var router = {};
router.routes = {};

_(['get', 'post', 'patch', 'delete']).each(function(method) {
	router[method] = function(path, handler) {
		this.routes[method] = this.routes[method] || [];
		var keys = [],
			regExpStr = path.replace(/:(\w+)/g, function(match, name) {
				keys.push(name);
				return '(.+)';
			});

		this.routes[method].push({
			regExp: new RegExp('^' + regExpStr + '$'),
			handler: handler,
			keys: keys
		});
	};
});

router.del = router['delete'];

router.getRoute = function(req) {
	var parts,
		route = _(this.routes[req.method.toLowerCase()]).find(function(route) {
			parts = route.regExp.exec(req.path);
			return parts;
		});

	if (route && route.keys.length) {
		route.params = {};
		_(route.keys).each(function(key, index) {
			route.params[key] = parts[index + 1];
		});
	}

	return route;
};

var createRequestListener = function(app) {
	var logger = app.lib.logger('rest api'),
		accessToken = (Math.random() * Math.random()).toString(36).substring(2);

	logger.log('access token is: %s', accessToken);

	router.get('/api/0.1/builds', function(req, res, next) {
		Steppy(
			function() {
				var getParams = {limit: Number(req.query.limit)};

				if (req.query.project) {
					getParams.projectName = req.query.project;
				}

				app.builds.getRecent(getParams, this.slot());
			},
			function(err, builds) {
				res.json({builds: builds});
			},
			next
		);
	});

	router.get('/api/0.1/builds/:id', function(req, res, next) {
		Steppy(
			function() {
				var id = Number(req.params.id);

				app.builds.get(id, this.slot());
			},
			function(err, build) {
				if (build) {
					res.json({build: build});
				} else {
					res.statusCode = 404;
					res.end();
				}
			},
			next
		);
	});

	// run building of a project
	router.post('/api/0.1/builds', function(req, res, next) {
		Steppy(
			function() {
				var projectName = req.body.project,
					project = app.projects.get(projectName);

				if (project) {
					res.statusCode = 204;
					logger.log('Run project "%s"', projectName);
					app.builds.create({
						projectName: projectName,
						withScmChangesOnly: req.body.withScmChangesOnly,
						queueQueued: req.body.queueQueued,
						initiator: {type: 'httpApi'}
					});
				} else {
					res.statusCode = 404;
				}

				res.end();
			},
			next
		);
	});

	router.get('/api/0.1/projects', function(req, res, next) {
		Steppy(
			function() {
				res.json({projects: app.projects.getAll()});
			},
			next
		);
	});

	router.get('/api/0.1/projects/:name', function(req, res, next) {
		Steppy(
			function() {
				var project = app.projects.get(req.params.name);
				if (project) {
					res.json({project: project});
				} else {
					res.statusCode = 404;
					res.end();
				}
			},
			next
		);
	});

	router.del('/api/0.1/projects/:name', function(req, res, next) {
		var token = req.body.token,
			projectName = req.params.name;

		Steppy(
			function() {
				logger.log('Cleaning up project "%s"', projectName);

				if (token !== accessToken) {
					throw new Error('Access token doesn`t match');
				}

				app.projects.remove(projectName, this.slot());
			},
			function() {
				logger.log('Project "%s" cleaned up', projectName);
				res.statusCode = 204;
				res.end();
			},
			next
		);
	});

	router.patch('/api/0.1/projects/:name', function(req, res, next) {
		var token = req.body.token,
			projectName = req.params.name,
			newProjectName = req.body.name;

		Steppy(
			function() {
				logger.log(
					'Rename project "%s" to "%s"', projectName, newProjectName
				);

				if (token !== accessToken) {
					throw new Error('Access token doesn`t match');
				}

				if (!newProjectName) throw new Error('new project name is not set');

				var curProject = app.projects.get(projectName);
				if (!curProject) {
					throw new Error('Project "' + projectName + '" not found');
				}
				this.pass(curProject);

				var newProject = app.projects.get(newProjectName);
				if (newProject) {
					throw new Error(
						'Project name "' + newProjectName + '" already used'
					);
				}

				app.projects.rename(projectName, newProjectName, this.slot());
			},
			function(err) {
				res.statusCode = 204;
				res.end();
			},
			next
		);
	});

	return function(req, res, next) {

		res.json = function(data) {
			res.end(JSON.stringify(data, null, 4));
		};

		Steppy(
			function() {
				var stepCallback = this.slot();

				var urlParts = req.url.split('?');
				req.path = urlParts[0];
				req.query = querystring.parse(urlParts[1]);

				req.setEncoding('utf-8');
				var bodyString = '';
				req.on('data', function(data) {
					bodyString += data;
				});
				req.on('end', function() {
					var body = bodyString ? JSON.parse(bodyString) : {};
					stepCallback(null, body);
				});
				req.on('error', stepCallback);
			},
			function(err, body) {
				req.body = body;

				var route = router.getRoute(req);
				if (route) {
					req.params = route.params;
					route.handler(req, res, this.slot());
				} else {
					res.statusCode = 404;
					res.end();
				}
			},
			next
		);
	};

};

exports.register = function(app) {
	var requestListener = createRequestListener(app);

	app.httpServer.addRequestListener(function(req, res, next) {
		if (req.url.indexOf('/api/') === 0) {
			return requestListener(req, res, next);
		} else {
			next();
		}
	});
};
<commit_msg>set default limit when get builds<commit_after>'use strict';

var Steppy = require('twostep').Steppy,
	_ = require('underscore'),
	querystring = require('querystring');
/*
 * Pure rest api on pure nodejs follows below
 */

var router = {};
router.routes = {};

_(['get', 'post', 'patch', 'delete']).each(function(method) {
	router[method] = function(path, handler) {
		this.routes[method] = this.routes[method] || [];
		var keys = [],
			regExpStr = path.replace(/:(\w+)/g, function(match, name) {
				keys.push(name);
				return '(.+)';
			});

		this.routes[method].push({
			regExp: new RegExp('^' + regExpStr + '$'),
			handler: handler,
			keys: keys
		});
	};
});

router.del = router['delete'];

router.getRoute = function(req) {
	var parts,
		route = _(this.routes[req.method.toLowerCase()]).find(function(route) {
			parts = route.regExp.exec(req.path);
			return parts;
		});

	if (route && route.keys.length) {
		route.params = {};
		_(route.keys).each(function(key, index) {
			route.params[key] = parts[index + 1];
		});
	}

	return route;
};

var createRequestListener = function(app) {
	var logger = app.lib.logger('rest api'),
		accessToken = (Math.random() * Math.random()).toString(36).substring(2);

	logger.log('access token is: %s', accessToken);

	router.get('/api/0.1/builds', function(req, res, next) {
		Steppy(
			function() {
				var getParams = {limit: Number(req.query.limit) || 20};

				if (req.query.project) {
					getParams.projectName = req.query.project;
				}

				app.builds.getRecent(getParams, this.slot());
			},
			function(err, builds) {
				res.json({builds: builds});
			},
			next
		);
	});

	router.get('/api/0.1/builds/:id', function(req, res, next) {
		Steppy(
			function() {
				var id = Number(req.params.id);

				app.builds.get(id, this.slot());
			},
			function(err, build) {
				if (build) {
					res.json({build: build});
				} else {
					res.statusCode = 404;
					res.end();
				}
			},
			next
		);
	});

	// run building of a project
	router.post('/api/0.1/builds', function(req, res, next) {
		Steppy(
			function() {
				var projectName = req.body.project,
					project = app.projects.get(projectName);

				if (project) {
					res.statusCode = 204;
					logger.log('Run project "%s"', projectName);
					app.builds.create({
						projectName: projectName,
						withScmChangesOnly: req.body.withScmChangesOnly,
						queueQueued: req.body.queueQueued,
						initiator: {type: 'httpApi'}
					});
				} else {
					res.statusCode = 404;
				}

				res.end();
			},
			next
		);
	});

	router.get('/api/0.1/projects', function(req, res, next) {
		Steppy(
			function() {
				res.json({projects: app.projects.getAll()});
			},
			next
		);
	});

	router.get('/api/0.1/projects/:name', function(req, res, next) {
		Steppy(
			function() {
				var project = app.projects.get(req.params.name);
				if (project) {
					res.json({project: project});
				} else {
					res.statusCode = 404;
					res.end();
				}
			},
			next
		);
	});

	router.del('/api/0.1/projects/:name', function(req, res, next) {
		var token = req.body.token,
			projectName = req.params.name;

		Steppy(
			function() {
				logger.log('Cleaning up project "%s"', projectName);

				if (token !== accessToken) {
					throw new Error('Access token doesn`t match');
				}

				app.projects.remove(projectName, this.slot());
			},
			function() {
				logger.log('Project "%s" cleaned up', projectName);
				res.statusCode = 204;
				res.end();
			},
			next
		);
	});

	router.patch('/api/0.1/projects/:name', function(req, res, next) {
		var token = req.body.token,
			projectName = req.params.name,
			newProjectName = req.body.name;

		Steppy(
			function() {
				logger.log(
					'Rename project "%s" to "%s"', projectName, newProjectName
				);

				if (token !== accessToken) {
					throw new Error('Access token doesn`t match');
				}

				if (!newProjectName) throw new Error('new project name is not set');

				var curProject = app.projects.get(projectName);
				if (!curProject) {
					throw new Error('Project "' + projectName + '" not found');
				}
				this.pass(curProject);

				var newProject = app.projects.get(newProjectName);
				if (newProject) {
					throw new Error(
						'Project name "' + newProjectName + '" already used'
					);
				}

				app.projects.rename(projectName, newProjectName, this.slot());
			},
			function(err) {
				res.statusCode = 204;
				res.end();
			},
			next
		);
	});

	return function(req, res, next) {

		res.json = function(data) {
			res.end(JSON.stringify(data, null, 4));
		};

		Steppy(
			function() {
				var stepCallback = this.slot();

				var urlParts = req.url.split('?');
				req.path = urlParts[0];
				req.query = querystring.parse(urlParts[1]);

				req.setEncoding('utf-8');
				var bodyString = '';
				req.on('data', function(data) {
					bodyString += data;
				});
				req.on('end', function() {
					var body = bodyString ? JSON.parse(bodyString) : {};
					stepCallback(null, body);
				});
				req.on('error', stepCallback);
			},
			function(err, body) {
				req.body = body;

				var route = router.getRoute(req);
				if (route) {
					req.params = route.params;
					route.handler(req, res, this.slot());
				} else {
					res.statusCode = 404;
					res.end();
				}
			},
			next
		);
	};

};

exports.register = function(app) {
	var requestListener = createRequestListener(app);

	app.httpServer.addRequestListener(function(req, res, next) {
		if (req.url.indexOf('/api/') === 0) {
			return requestListener(req, res, next);
		} else {
			next();
		}
	});
};
<|endoftext|>


<commit_before>var
	cheerio = require('cheerio'),
	request = require('./request'),

	baseHost = '.craigslist.org',
	defaultRequestOptions = {
		hostname : '',
		path : '',
	    secure : true
	},
	searchMaxAsk = '&maxAsk=',
	searchMinAsk = '&minAsk=';
	// searchPath = '/search/sss?sort=rel&query=';

    var initialize = (function (self) {
	'use strict';

	self = self || {};
	self.options = {};
	/*
		Accepts string of HTML and parses that string to find all pertinent listings.
	*/
	function getRssListings (options, html) {
		var
			$ = cheerio.load(html),
			listing = {},
			listings = [];

        var category = $('channel').find('title').text()

		$('item').each(function (i, element) {
			var children = $(element).children()

            var url = $(element).attr('rdf:about')
			var idx = url.lastIndexOf('/')
			var idx1 = url.indexOf('.', idx)
			var listing = { category : category, url: url, pid: url.substring(idx + 1, idx1) }

			for (var i=0; i<children.length; i++) {
				var t, child = children[i]
				var name = child.name

				if (name == 'title') {
					t = child.children[0].data.trim()
					if (t.indexOf('[CDATA[') != -1)
						t = t.substring(7, t.length - 2).trim().replace(/^\&\#x0024\;/g, '').replace(/&\#x0024\;/g, '$')

					var priceIdx = t.indexOf('$')
					if (priceIdx != -1)  {
						var sidx = t.indexOf(' ', priceIdx)
						listing.price = sidx == -1 ? t.substring(priceIdx) : t.substring(priceIdx, sidx)
					}
					var idx = t.lastIndexOf('(')
				    if (idx == -1)
				    	listing.title = t
				    else {
				    	var idx1 = t.indexOf(')', idx)
				    	listing.location = t.substring(idx + 1, idx1).trim()
				    	listing.title = t.substring(0, idx).trim()
				    }

				}
				else if (name == 'description') {
					t = child.children[0].data.trim()
					if (t.indexOf('[CDATA[') != -1)
						t = t.substring(7, t.length - 2)
					listing.description = t.replace(/&\#x0024\;/g, '$').trim()
				}
				else if (name == 'dc:date')
					listing.date = $(child).text().trim()
				else if (name == 'enc:enclosure') {
					listing.hasPic = true
					listing.img = $(child).attr('resource')
				}
			}

					// coordinates : {
					// 	lat : $(element).attr('data-latitude'),
					// 	lon : $(element).attr('data-longitude')
					// },

				// make sure lat / lon is valid
				// if (typeof listing.coordinates.lat === 'undefined' ||
				// 	typeof listing.coordinates.lon === 'undefined') {
				// 	delete listing.coordinates;
				// }
			listings.push(listing);
		});

		return listings;
	}

	/*
		Accepts string of HTML and parses that string to find all pertinent listings.
	*/
	function getListings (options, html) {
		var
			$ = cheerio.load(html),
			listing = {},
			listings = [];

		$('div.content').find('p.row').each(function (i, element) {
			listing = {
				category : $(element)
					.find('span.l2 a.gc')
					.text(),
				coordinates : {
					lat : $(element).attr('data-latitude'),
					lon : $(element).attr('data-longitude')
				},
				date : $(element)
					.find('span.date')
					.text(),
				hasPic : ($(element)
					.find('span.l2 span.p')
					.text()
					.trim()) !== '',
				location : $(element)
					.find('span.pnr small')
					.text()
					.replace(/[\(,\)]/g, '').trim(), // santize
				pid : $(element)
					.attr('data-pid'),
				price : $(element)
					.find('span.l2 span.price')
					.text()
					.replace(/^\&\#x0024\;/g, ''), // sanitize
				title : $(element)
					.find('span.pl a')
					.text().trim()
				// url :
				// 	(options.secure ? 'https://' : 'http://') +
				// 	options.hostname + $(element).find('span.pl a').attr('href')
			}
			var rurl = $(element).find('span.pl a').attr('href');
			if (rurl) {
			  if (rurl.indexOf('http') == -1)
			    rurl = (options.secure ? 'https://' : 'http://') + options.hostname + rurl
			  listing.url = rurl;
		    }
		    if (options.cityName)
		      listing.cityName = options.cityName

			if (listing.hasPic) {
				var dId = $(element).find('a.i').attr('data-id')
				if (dId)
				  listing.pic = 'http://images.craigslist.org/' + dId.substring(2) + '_300x300.jpg'
			}
			var d = $(element).find('span.date')
			if (!d  ||  d.text().length == 0) {
				d = $(element).find('time').attr('datetime')
				listing.date = d;
			}

			// make sure lat / lon is valid
			if (typeof listing.coordinates.lat === 'undefined' ||
				typeof listing.coordinates.lon === 'undefined') {
				delete listing.coordinates;
			}

			listings.push(listing);
		});

		return listings;
	}

	function getCities(options, html) {
		var
			$ = cheerio.load(html),
			listing = {},
			listings = [];

		$('div.colmask').find('a').each(function (i, element) {
           var href = $(element).attr('href')
           var idx = href.indexOf('//'), idx1 = href.indexOf('.');

           listing = { city: href.substring(idx + 2, idx1),
           			   href: href,
                       cityName: $(element).text().replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()})
                     }

           listings.push(listing)
		})
		return listings
	}

    // Extracting full description for the listing
	function getDescription(options, html) {
		var
			$ = cheerio.load(html);

		var description = $('section#postingbody').html()
		if (description)
	  	  return  { description: description.trim() }
	}
	/*
		Accepts options, iterates through the known acceptable keys from defaultOptions
		and if found in input options, uses that. If not found in input options to method,
		falls back to the options specified when the module was initialized. If not found
		in initialization options, uses the default options setting. All keys provided in
		the input options variable are retained.
	*/
	function getRequestOptions (options, query) {
		var requestOptions = JSON.parse(JSON.stringify(defaultRequestOptions));
		if (options.fullListing  &&  options.path) {
			requestOptions.path = options.path
			requestOptions.hostname = options.hostname
			return requestOptions
		}

		// ensure default options are set, even if omitted from input options
		requestOptions.hostname = options.allCities? 'www' + baseHost : (options.city || self.options.city || '') + baseHost;

		// preserve any extraneous input option keys (may have addition instructions for underlying request object)
		Object.keys(options).forEach(function (key) {
			if (
				// key !== 'maxAsk'   &&
				// key !== 'minAsk'   &&
				// key !== 'category' &&
				// key !== 'hasPic'   &&
				// key !== 's'        &&
				typeof requestOptions[key] === 'undefined' &&
				typeof defaultRequestOptions[key] === 'undefined') {
				requestOptions[key] = options[key];
			}
		});

		// set path
		if (options.allCities)
		  requestOptions.path = '/about/sites'
		else
		  requestOptions.path = '/search/' + (options.category ? options.category : 'sss') + '?sort=rel' +
  		                                     (options.s ? '&s=' + options.s : '') +
		                                     (options.hasPic && (options.hasPic == '1' || options.hasPic == 'true') ? '&hasPic=1' : '') +
		                                     (options.rss ? '&format=rss' : '') +
		                                     (options.query ? '&query=' + encodeURIComponent(options.query) : '');

		// add min and max asking price
		if (typeof options.minAsk !== 'undefined') {
			requestOptions.path += searchMinAsk + options.minAsk;
		}

		if (typeof options.maxAsk !== 'undefined') {
			requestOptions.path += searchMaxAsk + options.maxAsk;
		}

		return requestOptions;
	}

	/*
		options = {
			city : '',
			maxAsk : '',
			minAsk : '',
		}
	*/
	self.search = function (options, query, callback) {
		if (typeof query === 'function' && typeof callback === 'undefined') {
			callback = query;
			query = options;
			options = {};
		}

        // The original set of options will have something like
        //
        //  options = {
		//   city: 'newyork',
		//   category: 'tia',
		//   hasPic: 1,
		//   fullListing: true,
		//   offset: 100,
		//   query: 'concert'
		// }
		//
		// only if particular listing's url is known then we can request the listing's page and description
		//
        var isFullDescriptionRequest = options.fullListing  &&  options.path
		options = getRequestOptions(options, query);

		self.request.get(options, function (err, data) {
			if (err)
			  return callback(err)

            if (options.allCities)
              return callback(null, getCities(options, data))
            if (options.rss)
              return callback(null, getRssListings(options, data))
            if (isFullDescriptionRequest)
              return callback(null, getDescription(options, data))

			return callback(null, getListings(options, data))
		});
	};

	/*
		the module
	*/
	return function (options) {
		options = options || {};

		self.options = options;
		self.request = request.initialize();
		return self;
	};
}({}));

exports = module.exports = initialize;
exports.initialize = initialize;
<commit_msg>getting props from single listing<commit_after>var
	cheerio = require('cheerio'),
	request = require('./request'),

	baseHost = '.craigslist.org',
	defaultRequestOptions = {
		hostname : '',
		path : '',
	    secure : true
	},
	searchMaxAsk = '&maxAsk=',
	searchMinAsk = '&minAsk=';
	// searchPath = '/search/sss?sort=rel&query=';

    var initialize = (function (self) {
	'use strict';

	self = self || {};
	self.options = {};
	/*
		Accepts string of HTML and parses that string to find all pertinent listings.
	*/
	function getRssListings (options, html) {
		var
			$ = cheerio.load(html),
			listing = {},
			listings = [];

        var category = $('channel').find('title').text()

		$('item').each(function (i, element) {
			var children = $(element).children()

            var url = $(element).attr('rdf:about')
			var idx = url.lastIndexOf('/')
			var idx1 = url.indexOf('.', idx)
			var listing = { category : category, url: url, pid: url.substring(idx + 1, idx1) }

			for (var i=0; i<children.length; i++) {
				var t, child = children[i]
				var name = child.name

				if (name == 'title') {
					t = child.children[0].data.trim()
					if (t.indexOf('[CDATA[') != -1)
						t = t.substring(7, t.length - 2).trim().replace(/^\&\#x0024\;/g, '').replace(/&\#x0024\;/g, '$')

					var priceIdx = t.indexOf('$')
					if (priceIdx != -1)  {
						var sidx = t.indexOf(' ', priceIdx)
						listing.price = sidx == -1 ? t.substring(priceIdx) : t.substring(priceIdx, sidx)
					}
					var idx = t.lastIndexOf('(')
				    if (idx == -1)
				    	listing.title = t
				    else {
				    	var idx1 = t.indexOf(')', idx)
				    	listing.location = t.substring(idx + 1, idx1).trim()
				    	listing.title = t.substring(0, idx).trim()
				    }

				}
				else if (name == 'description') {
					t = child.children[0].data.trim()
					if (t.indexOf('[CDATA[') != -1)
						t = t.substring(7, t.length - 2)
					listing.description = t.replace(/&\#x0024\;/g, '$').trim()
				}
				else if (name == 'dc:date')
					listing.date = $(child).text().trim()
				else if (name == 'enc:enclosure') {
					listing.hasPic = true
					listing.img = $(child).attr('resource')
				}
			}

					// coordinates : {
					// 	lat : $(element).attr('data-latitude'),
					// 	lon : $(element).attr('data-longitude')
					// },

				// make sure lat / lon is valid
				// if (typeof listing.coordinates.lat === 'undefined' ||
				// 	typeof listing.coordinates.lon === 'undefined') {
				// 	delete listing.coordinates;
				// }
			listings.push(listing);
		});

		return listings;
	}

	/*
		Accepts string of HTML and parses that string to find all pertinent listings.
	*/
	function getListings (options, html) {
		var
			$ = cheerio.load(html),
			listing = {},
			listings = [];

		$('div.content').find('p.row').each(function (i, element) {
			listing = {
				category : $(element)
					.find('span.l2 a.gc')
					.text(),
				coordinates : {
					lat : $(element).attr('data-latitude'),
					lon : $(element).attr('data-longitude')
				},
				date : $(element)
					.find('span.date')
					.text(),
				hasPic : ($(element)
					.find('span.l2 span.p')
					.text()
					.trim()) !== '',
				location : $(element)
					.find('span.pnr small')
					.text()
					.replace(/[\(,\)]/g, '').trim(), // santize
				pid : $(element)
					.attr('data-pid'),
				price : $(element)
					.find('span.l2 span.price')
					.text()
					.replace(/^\&\#x0024\;/g, ''), // sanitize
				title : $(element)
					.find('span.pl a')
					.text().trim()
				// url :
				// 	(options.secure ? 'https://' : 'http://') +
				// 	options.hostname + $(element).find('span.pl a').attr('href')
			}
			var rurl = $(element).find('span.pl a').attr('href');
			if (rurl) {
			  if (rurl.indexOf('http') == -1)
			    rurl = (options.secure ? 'https://' : 'http://') + options.hostname + rurl
			  listing.url = rurl;
		    }
		    if (options.cityName)
		      listing.cityName = options.cityName

			if (listing.hasPic) {
				var dId = $(element).find('a.i').attr('data-id')
				if (dId)
				  listing.pic = 'http://images.craigslist.org/' + dId.substring(2) + '_300x300.jpg'
			}
			var d = $(element).find('span.date')
			if (!d  ||  d.text().length == 0) {
				d = $(element).find('time').attr('datetime')
				listing.date = d;
			}

			// make sure lat / lon is valid
			if (typeof listing.coordinates.lat === 'undefined' ||
				typeof listing.coordinates.lon === 'undefined') {
				delete listing.coordinates;
			}

			listings.push(listing);
		});

		return listings;
	}

	function getCities(options, html) {
		var
			$ = cheerio.load(html),
			listing = {},
			listings = [];

		$('div.colmask').find('a').each(function (i, element) {
           var href = $(element).attr('href')
           var idx = href.indexOf('//'), idx1 = href.indexOf('.');

           listing = { city: href.substring(idx + 2, idx1),
           			   href: href,
                       cityName: $(element).text().replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()})
                     }

           listings.push(listing)
		})
		return listings
	}

    // Extracting full description for the listing
	function getDescription(options, html) {
		var
			$ = cheerio.load(html);

		var description = $('section#postingbody').html()
		var listing = {}
		if (description)
	  	  listing.description = description.trim()
		var attr = $('section.attrgroup')
        if (!attr)
          return
    	var props = attr.find('span')
    	for (var p in props) {
    	  if (p.class)
    	  	listing.date = p.text()
    	  else {
    	  	var pName = p.text().trim()
    	  	pName = pName.replace(':', '').trim()
    	  	if (pName.charAt(0) == '#')
    	  	  pName = ('Number_of' + pName.substring(1)).replace(' ', '_')
    	  	listing[pName] = p.find('b').text()
    	  }
        }

	}
	/*
		Accepts options, iterates through the known acceptable keys from defaultOptions
		and if found in input options, uses that. If not found in input options to method,
		falls back to the options specified when the module was initialized. If not found
		in initialization options, uses the default options setting. All keys provided in
		the input options variable are retained.
	*/
	function getRequestOptions (options, query) {
		var requestOptions = JSON.parse(JSON.stringify(defaultRequestOptions));
		if (options.fullListing  &&  options.path) {
			requestOptions.path = options.path
			requestOptions.hostname = options.hostname
			return requestOptions
		}

		// ensure default options are set, even if omitted from input options
		requestOptions.hostname = options.allCities? 'www' + baseHost : (options.city || self.options.city || '') + baseHost;

		// preserve any extraneous input option keys (may have addition instructions for underlying request object)
		Object.keys(options).forEach(function (key) {
			if (
				// key !== 'maxAsk'   &&
				// key !== 'minAsk'   &&
				// key !== 'category' &&
				// key !== 'hasPic'   &&
				// key !== 's'        &&
				typeof requestOptions[key] === 'undefined' &&
				typeof defaultRequestOptions[key] === 'undefined') {
				requestOptions[key] = options[key];
			}
		});

		// set path
		if (options.allCities)
		  requestOptions.path = '/about/sites'
		else
		  requestOptions.path = '/search/' + (options.category ? options.category : 'sss') + '?sort=rel' +
  		                                     (options.s ? '&s=' + options.s : '') +
		                                     (options.hasPic && (options.hasPic == '1' || options.hasPic == 'true') ? '&hasPic=1' : '') +
		                                     (options.rss ? '&format=rss' : '') +
		                                     (options.query ? '&query=' + encodeURIComponent(options.query) : '');

		// add min and max asking price
		if (typeof options.minAsk !== 'undefined') {
			requestOptions.path += searchMinAsk + options.minAsk;
		}

		if (typeof options.maxAsk !== 'undefined') {
			requestOptions.path += searchMaxAsk + options.maxAsk;
		}

		return requestOptions;
	}

	/*
		options = {
			city : '',
			maxAsk : '',
			minAsk : '',
		}
	*/
	self.search = function (options, query, callback) {
		if (typeof query === 'function' && typeof callback === 'undefined') {
			callback = query;
			query = options;
			options = {};
		}

        // The original set of options will have something like
        //
        //  options = {
		//   city: 'newyork',
		//   category: 'tia',
		//   hasPic: 1,
		//   fullListing: true,
		//   offset: 100,
		//   query: 'concert'
		// }
		//
		// only if particular listing's url is known then we can request the listing's page and description
		//
        var isFullDescriptionRequest = options.fullListing  &&  options.path
		options = getRequestOptions(options, query);

		self.request.get(options, function (err, data) {
			if (err)
			  return callback(err)

            if (options.allCities)
              return callback(null, getCities(options, data))
            if (options.rss)
              return callback(null, getRssListings(options, data))
            if (isFullDescriptionRequest)
              return callback(null, getDescription(options, data))

			return callback(null, getListings(options, data))
		});
	};

	/*
		the module
	*/
	return function (options) {
		options = options || {};

		self.options = options;
		self.request = request.initialize();
		return self;
	};
}({}));

exports = module.exports = initialize;
exports.initialize = initialize;
<|endoftext|>


<commit_before>var FNCart = Class.create({
  initialize: function() {
    // Intercept the product add form submit event.

    this.boundAddSubmit = this.onAddFormSubmit.bind(this);
    $$('.product_add').each(function(form, index) {
      form.observe('submit', this.boundAddSubmit);
    }, this);

    this._prepareCart();
  },

  /**
   *  Handler for the Submit event on the product add forms.
   */
  onAddFormSubmit: function(ev){
    this.updateCart(ev.target.action);
    ev.stop();
  },

  /**
   *  Handler for the click event on the empty list link.
   */
  onEmptyList: function(ev) {
    this.updateCart("/cart/empty/");
    ev.stop();
  },

  /**
   *  Updates the cart with a given url.
   */
  updateCart: function(url) {

    this._prepareCartForUpdate();

    $('cart_load_indicator').show();

    new Ajax.Updater({success:'cart_content'}, url, {
      method:'post',
      onComplete: function() {
        this._prepareCart();
      }.bind(this)
    });
  },

  /**
   *  Prepares the cart at initialize and each time it's been updated.
   */
  _prepareCart: function() {
    this.boundEmptyList =this.onEmptyList.bind(this);
    $('empty_list').observe('click', this.boundEmptyList);
  },

  /**
   *  Prepares cart for update by removing listeners.
   */
  _prepareCartForUpdate: function() {
    this.boundEmptyList =this.onEmptyList.bind(this);
    $('empty_list').stopObserving('click', this.boundEmptyList);
  }

});
<commit_msg>Javascript for quantity selection in the list.<commit_after>var FNCart = Class.create({
  initialize: function() {
    // Intercept the product add form submit event.

    this.boundAddSubmit = this.onAddFormSubmit.bind(this);
    $$('.product_add').each(function(form, index) {
      form.observe('submit', this.boundAddSubmit);
    }, this);

    this._prepareCart();
  },

  /**
   *  Handler for the Submit event on the product add forms.
   */
  onAddFormSubmit: function(ev){
    ev.stop();

    // Get the quantity to add.
    quantity = $(ev.target).down('.quantity').value;
    // Bail out if the quantity isn't a number.
    if(/^\d+$/.test(quantity) == false) return false;

    url = ev.target.action+quantity+"/";
    this.updateCart(url);
  },

  /**
   *  Handler for the click event on the empty list link.
   */
  onEmptyList: function(ev) {
    this.updateCart("/cart/empty/");
    ev.stop();
  },

  /**
   *  Updates the cart with a given url.
   */
  updateCart: function(url) {

    this._prepareCartForUpdate();

    $('cart_load_indicator').show();

    new Ajax.Updater({success:'cart_content', failure:'cart_notice'}, url, {
      method:'post',
      onComplete: function() {
        this._prepareCart();
      }.bind(this)
    });
  },

  /**
   *  Prepares the cart at initialize and each time it's been updated.
   */
  _prepareCart: function() {
    this.boundEmptyList =this.onEmptyList.bind(this);
    $('empty_list').observe('click', this.boundEmptyList);
  },

  /**
   *  Prepares cart for update by removing listeners.
   */
  _prepareCartForUpdate: function() {
    this.boundEmptyList =this.onEmptyList.bind(this);
    $('empty_list').stopObserving('click', this.boundEmptyList);
  }

});
<|endoftext|>


<commit_before>M.wrap('github/jillix/bind-filter/dev/filter.js', function (require, module, exports) {
var Bind = require('github/jillix/bind');
var Events = require('github/jillix/events');

var find = require('./find');
var ui = require('./ui');
var validate = require('./validate');

var firstTemplate = true;
var templateCache = {};
var defaultOptions = {
    limit: 17
};
var operatorConfig = {
    '=':        ['',        'mixed'],                                       // or
    '!=':       ['$ne',     ['number', 'string', 'array']],                 // and
    '>':        ['$gt',     ['number']],                                    // and
    '<':        ['$lt',     ['number']],                                    // and
    '>=':       ['$gte',    ['number']],                                    // and
    '<=':       ['$lte',    ['number']],                                    // and
    'in':       ['$in',     ['number', 'string', 'array'],    'split'],     // and ('or' can be achieved by concatenating the arrays)
    'notin':    ['$nin',    ['number', 'string', 'array'],    'split'],     // or ('and' can be achieved by concatenating the arrays)
    'all':      ['$all',    ['array'],    'split'],                         // or ('and' can be achieved by concatenating the arrays)
    'regExp':   ['$regex',  ['string']],                                    // and ('or' is built in the regex syntax)
    'exists':   ['$exists', 'mixed',    'boolean']                          // makes no sense
};

function MergeRecursive(obj1, obj2) {

    for (var p in obj2) {
        if (!obj2.hasOwnProperty(p)) return;
        try {
            // Property in destination object set; update its value.
            if (obj2[p].constructor == Object) {
                obj1[p] = MergeRecursive(obj1[p], obj2[p]);
            } else {
                obj1[p] = obj2[p];
            }
        } catch(e) {
            // Property in destination object not set; create it and set its value.
            obj1[p] = obj2[p];
        }
    }

    return obj1;
}

function uid (len, uid) {
    uid = "";
    for (var i = 0, l = len || 24; i < l; ++i) {
        uid += "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"[0 | Math.random() * 62];
    }
    return uid;
};

function setFilters (filters, reset, dontFetchData) {
    var self = this;

    if (!filters || typeof filters !== "object") {
        return;
    }

    // we always reset the skip option
    self.emit('resetSkip');

    // reset filters if reset is true
    if (reset) {
        self.filters = {};
    }

    // create and buffer filters
    for (var i = 0, l = filters.length; i < l; ++i) {

        // validate field
        if (validate.validate.call(self, filters[i])) {

            // convert value from value field
            filters[i] = validate.convert.call(self, filters[i]);

            var hash = filters[i].hash || uid(4);
            self.filters[hash] = self.filters[hash] || {};

            // merge filter
            for (var key in filters[i]) {
                if (!filters[i].hasOwnProperty(key)) return;
                self.filters[hash][key] = filters[i][key];
            }

            // if field type is string and operator is 'regexp' make it case insensitive
            var field = self.templates[self.template].schema[filters[i].field];
            if (filters[i].operator === 'regExp' && field && field.type === 'string' && !field.casesensitive) {
                self.filters[hash].originalValue = filters[i].value;
                self.filters[hash].value = '(?i)' + filters[i].value;
            } else {
                delete self.filters[hash].originalValue;
            }
        }
    }

    // emit filters cached event
    self.emit('filtersCached', self.filters, reset);

    // find data in db
    if (!dontFetchData) {
        find.call(self);
    }
}

function getTemplates (templates, reset, callback) {
    var self = this;

    // get templates to fetch from server
    self.emit('getTemplates', templates, function (err, templates) {

        self.emit('templateResult', err, templates);

        if (err) {
            return callback(err);
        }

        // merge fetched templates into result templates
        for (var template in templates) {
            if (!templates.hasOwnProperty(template)) return;

           self.templates[template] = templates[template];
        }

        // reset cache
        if (reset) {
            self.templates = templates;
        }

        callback(null);
    });
}

function setTemplates (templates, callback) {
    var self = this;

    if (templates instanceof Array) {
        getTemplates.call(self, templates, true, function (err) {
            if (err) {
                // TODO handle error
                return console.error(err);
            }

            // select a template
            if (!self.template) {
                for (template in self.templates) {
                    if (!self.templates.hasOwnProperty(template)) return;

                    self.template = template;
                    self.emit('template', self.templates[template]);
                    break;
                }
            }

            if (callback) {
                callback();
            }

            // emit the templates
            self.emit('templates', self.templates);
        });
    }
}

// TODO swap force and dontFetchData parameter to have the same syntax everywhere
function setTemplate (template, dontFetchData, force) {
    var self = this;

    // TODO this is a hack until bind knows how select keys in parameters
    var template = typeof template === 'string' ? template : (template.id || template._id);
    if (!template) {
        // TODO handle error
        return;
    }

    // nothing to do if the same template
    if (!force && self.template === template) {
        return;
    }

    // get template from server or cache
    getTemplates.call(self, [template], false, function (err) {

        if (err || !self.templates[template]) {
            // TODO handle error
            return console.error(err || 'template ' + template + ' not found.');
        }

        // set current template (this is only the id)
        self.template = template;

        // set sort options
        if (self.templates[template].sort) {
            self.emit("setOptions", { sort: self.templates[template].sort });
        }

        setFilters.call(self, (self.config.setFilters || []).concat(self.templates[template].options.filters || []), true, dontFetchData);

        // emit the template
        self.emit('template', self.templates[template]);
    });
}

function resetSkip () {
    var self = this;

    self.options = self.options || {};
    self.options.skip = 0;
}

function setOptions (options, reset, callFind) {
    var self = this;

    if (typeof options !== 'object') {
        return;
    }

    // when no skip is received, we force it to 0
    //options.skip = options.skip || 0;

    // reset options
    if (reset) {
        self.options = options;
    }
    // merge options
    else {
        for (var option in options) {
            if (!options.hasOwnProperty(option)) return;
            var value = options[option];

            // option is an array
            //var optionValue = self.options[option];

            // TODO How do we merge objects?
            // switch ((optionValue || {}).constructor) {
            //     case Array:
            //         optionValue.push(value);
            //         break;
            //     case Object:
            //         switch (value.constructor) {
            //             case Object:
            //                 optionValue = MergeRecursive(self.options[option], value);
            //                 break;
            //             default:
            //                 optionValue = value;
            //                 break;
            //         }
            //         break;
            //     default:
            //         optionValue = value;
            // }

            self.options[option] = value;
        }
    }

    // emit options cahed event
    self.emit('optionsCached', self.options, reset);

    // find data in db
    if (callFind) {
        find.call(self);
    }
}

function getFilters (callback) {
    var self = this;
    var filters = [];
    for (var id in self.filters) {
        if (!self.filters.hasOwnProperty(id)) return;
        var filter = self.filters[id];
        filter.item = null;
        filters.push(filter);
    }
    callback(filters);
}

function getItem (dataItem, callback) {
    var self = this;

    if (!self.query) { return; }

    if (self.crudFindBusy) {
        return callback('Find is busy.');
    }

    self.crudFindBusy = true;

    var queryWithoutLimit = JSON.parse(JSON.stringify(self.query));
    delete queryWithoutLimit.o.limit;

    self.emit('find', queryWithoutLimit, function (err, data) {
        self.crudFindBusy = false;

        for (var i = 0, l = data.length; i < l; ++i) {
            if (data[i]._id === dataItem._id) {
                data[i].bindFilterMessage = 'show';
                return callback(null, data[i]);
            }
        }

        // TODO How must this look?
        callback(null, {_id: dataItem._id, bindFilterMessage: 'hide'});
    });
}

function initInterface () {
    var self = this;

    // listen to interface events
    self.on('setFilters', setFilters);
    self.on('setTemplate', setTemplate);
    self.on('setTemplates', setTemplates);
    self.on('setOptions', setOptions);
    self.on('getFilters', getFilters);
    self.on('getItem', getItem);
    self.on('refresh', find);
    self.on('resetSkip', resetSkip);
}

function init (config) {

    var self = this;
    // prepare module
    self.config = config;
    self.filters = {};
    self.templates = templateCache;

    // TODO merge options
    self.options = config.options || defaultOptions;
    self.config.operators = operatorConfig;

    if (!config.waitFor) {
        return console.error('At least a CRUD mofule must be a dependency of the bind-filter module.');
    }

    // setup interface
    initInterface.call(self);

    // listen to external events
    Events.call(self, config);

    // run the binds
    for (var i in config.binds) {
        if (!config.binds.hasOwnProperty(i)) return;

        Bind.call(self, config.binds[i]);
    }

    // i18n for operators
    if (self.config.i18n === true) {

        // cache for translated operators
        self.config.i18n = {};

        var operators = self.config.operators;
        for (var operator in operators) {
            if (!operators.hasOwnProperty(operator)) return;

            (function (op) {
                self.emit("message", op, function (err, newOperator) {
                    if (err) { return; }
                    // cache the translated operator
                    self.config.i18n[op] = newOperator.message;
                });
            })(operator);
        }
    }

    if (self.config.ui) {
        ui.call(self);
    }

    // init templates
    if (self.config.setTemplates) {
        self.emit('setTemplates', self.config.setTemplates, function () {
            // init template
            if (self.config.template) {
                self.emit('setTemplate', self.config.template);
            }
        });
    // init template
    } else if (self.config.template) {
        self.emit('setTemplate', self.config.template);
    }

    self.emit('ready');
}

module.exports = init;

return module; });
<commit_msg>Updated deprecated getTemplates to new CRUD read method<commit_after>M.wrap('github/jillix/bind-filter/dev/filter.js', function (require, module, exports) {
var Bind = require('github/jillix/bind');
var Events = require('github/jillix/events');

var find = require('./find');
var ui = require('./ui');
var validate = require('./validate');

var firstTemplate = true;
var templateCache = {};
var defaultOptions = {
    limit: 17
};
var operatorConfig = {
    '=':        ['',        'mixed'],                                       // or
    '!=':       ['$ne',     ['number', 'string', 'array']],                 // and
    '>':        ['$gt',     ['number']],                                    // and
    '<':        ['$lt',     ['number']],                                    // and
    '>=':       ['$gte',    ['number']],                                    // and
    '<=':       ['$lte',    ['number']],                                    // and
    'in':       ['$in',     ['number', 'string', 'array'],    'split'],     // and ('or' can be achieved by concatenating the arrays)
    'notin':    ['$nin',    ['number', 'string', 'array'],    'split'],     // or ('and' can be achieved by concatenating the arrays)
    'all':      ['$all',    ['array'],    'split'],                         // or ('and' can be achieved by concatenating the arrays)
    'regExp':   ['$regex',  ['string']],                                    // and ('or' is built in the regex syntax)
    'exists':   ['$exists', 'mixed',    'boolean']                          // makes no sense
};

function MergeRecursive(obj1, obj2) {

    for (var p in obj2) {
        if (!obj2.hasOwnProperty(p)) return;
        try {
            // Property in destination object set; update its value.
            if (obj2[p].constructor == Object) {
                obj1[p] = MergeRecursive(obj1[p], obj2[p]);
            } else {
                obj1[p] = obj2[p];
            }
        } catch(e) {
            // Property in destination object not set; create it and set its value.
            obj1[p] = obj2[p];
        }
    }

    return obj1;
}

function uid (len, uid) {
    uid = "";
    for (var i = 0, l = len || 24; i < l; ++i) {
        uid += "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"[0 | Math.random() * 62];
    }
    return uid;
};

function setFilters (filters, reset, dontFetchData) {
    var self = this;

    if (!filters || typeof filters !== "object") {
        return;
    }

    // we always reset the skip option
    self.emit('resetSkip');

    // reset filters if reset is true
    if (reset) {
        self.filters = {};
    }

    // create and buffer filters
    for (var i = 0, l = filters.length; i < l; ++i) {

        // validate field
        if (validate.validate.call(self, filters[i])) {

            // convert value from value field
            filters[i] = validate.convert.call(self, filters[i]);

            var hash = filters[i].hash || uid(4);
            self.filters[hash] = self.filters[hash] || {};

            // merge filter
            for (var key in filters[i]) {
                if (!filters[i].hasOwnProperty(key)) return;
                self.filters[hash][key] = filters[i][key];
            }

            // if field type is string and operator is 'regexp' make it case insensitive
            var field = self.templates[self.template].schema[filters[i].field];
            if (filters[i].operator === 'regExp' && field && field.type === 'string' && !field.casesensitive) {
                self.filters[hash].originalValue = filters[i].value;
                self.filters[hash].value = '(?i)' + filters[i].value;
            } else {
                delete self.filters[hash].originalValue;
            }
        }
    }

    // emit filters cached event
    self.emit('filtersCached', self.filters, reset);

    // find data in db
    if (!dontFetchData) {
        find.call(self);
    }
}

function getTemplates (templates, reset, callback) {
    var self = this;

    // get templates to fetch from server
    self.emit('find', [templates], function (err, templates) {

        self.emit('templateResult', err, templates);

        if (err) {
            return callback(err);
        }

        // merge fetched templates into result templates
        for (var template in templates) {
            if (!templates.hasOwnProperty(template)) return;

           self.templates[templates[template]._id] = templates[template];
        }

        // reset cache
        if (reset) {
            for (var template in templates) {
                if (!templates.hasOwnProperty(template)) return;
               self.templates[templates[template]._id] = templates[template];
          }
        }

        callback(null);
    });
}

function setTemplates (templates, callback) {
    var self = this;

    if (templates instanceof Array) {
        getTemplates.call(self, templates, true, function (err) {
            if (err) {
                // TODO handle error
                return console.error(err);
            }

            // select a template
            if (!self.template) {
                for (template in self.templates) {
                    if (!self.templates.hasOwnProperty(template)) return;

                    self.template = template;
                    self.emit('template', self.templates[template]);
                    break;
                }
            }

            if (callback) {
                callback();
            }

            // emit the templates
            self.emit('templates', self.templates);
        });
    }
}

// TODO swap force and dontFetchData parameter to have the same syntax everywhere
function setTemplate (template, dontFetchData, force) {
    var self = this;

    // TODO this is a hack until bind knows how select keys in parameters
    var template = typeof template === 'string' ? template : (template.id || template._id);
    if (!template) {
        // TODO handle error
        return;
    }

    // nothing to do if the same template
    if (!force && self.template === template) {
        return;
    }

    // get template from server or cache
    getTemplates.call(self, template, false, function (err) {

        if (err || !self.templates[template]) {
            // TODO handle error
            return console.error(err || 'template ' + template + ' not found.');
        }

        // set current template (this is only the id)
        self.template = template;

        // set sort options
        if (self.templates[template].sort) {
            self.emit("setOptions", { sort: self.templates[template].sort });
        }

        setFilters.call(self, (self.config.setFilters || []).concat(self.templates[template].options.filters || []), true, dontFetchData);

        // emit the template
        self.emit('template', self.templates[template]);
    });
}

function resetSkip () {
    var self = this;

    self.options = self.options || {};
    self.options.skip = 0;
}

function setOptions (options, reset, callFind) {
    var self = this;

    if (typeof options !== 'object') {
        return;
    }

    // when no skip is received, we force it to 0
    //options.skip = options.skip || 0;

    // reset options
    if (reset) {
        self.options = options;
    }
    // merge options
    else {
        for (var option in options) {
            if (!options.hasOwnProperty(option)) return;
            var value = options[option];

            // option is an array
            //var optionValue = self.options[option];

            // TODO How do we merge objects?
            // switch ((optionValue || {}).constructor) {
            //     case Array:
            //         optionValue.push(value);
            //         break;
            //     case Object:
            //         switch (value.constructor) {
            //             case Object:
            //                 optionValue = MergeRecursive(self.options[option], value);
            //                 break;
            //             default:
            //                 optionValue = value;
            //                 break;
            //         }
            //         break;
            //     default:
            //         optionValue = value;
            // }

            self.options[option] = value;
        }
    }

    // emit options cahed event
    self.emit('optionsCached', self.options, reset);

    // find data in db
    if (callFind) {
        find.call(self);
    }
}

function getFilters (callback) {
    var self = this;
    var filters = [];
    for (var id in self.filters) {
        if (!self.filters.hasOwnProperty(id)) return;
        var filter = self.filters[id];
        filter.item = null;
        filters.push(filter);
    }
    callback(filters);
}

function getItem (dataItem, callback) {
    var self = this;

    if (!self.query) { return; }

    if (self.crudFindBusy) {
        return callback('Find is busy.');
    }

    self.crudFindBusy = true;

    var queryWithoutLimit = JSON.parse(JSON.stringify(self.query));
    delete queryWithoutLimit.o.limit;

    self.emit('find', queryWithoutLimit, function (err, data) {
        self.crudFindBusy = false;

        for (var i = 0, l = data.length; i < l; ++i) {
            if (data[i]._id === dataItem._id) {
                data[i].bindFilterMessage = 'show';
                return callback(null, data[i]);
            }
        }

        // TODO How must this look?
        callback(null, {_id: dataItem._id, bindFilterMessage: 'hide'});
    });
}

function initInterface () {
    var self = this;

    // listen to interface events
    self.on('setFilters', setFilters);
    self.on('setTemplate', setTemplate);
    self.on('setTemplates', setTemplates);
    self.on('setOptions', setOptions);
    self.on('getFilters', getFilters);
    self.on('getItem', getItem);
    self.on('refresh', find);
    self.on('resetSkip', resetSkip);
}

function init (config) {

    var self = this;
    // prepare module
    self.config = config;
    self.filters = {};
    self.templates = templateCache;

    // TODO merge options
    self.options = config.options || defaultOptions;
    self.config.operators = operatorConfig;

    if (!config.waitFor) {
        return console.error('At least a CRUD mofule must be a dependency of the bind-filter module.');
    }

    // setup interface
    initInterface.call(self);

    // listen to external events
    Events.call(self, config);

    // run the binds
    for (var i in config.binds) {
        if (!config.binds.hasOwnProperty(i)) return;

        Bind.call(self, config.binds[i]);
    }

    // i18n for operators
    if (self.config.i18n === true) {

        // cache for translated operators
        self.config.i18n = {};

        var operators = self.config.operators;
        for (var operator in operators) {
            if (!operators.hasOwnProperty(operator)) return;

            (function (op) {
                self.emit("message", op, function (err, newOperator) {
                    if (err) { return; }
                    // cache the translated operator
                    self.config.i18n[op] = newOperator.message;
                });
            })(operator);
        }
    }

    if (self.config.ui) {
        ui.call(self);
    }

    // init templates
    if (self.config.setTemplates) {
        self.emit('setTemplates', self.config.setTemplates, function () {
            // init template
            if (self.config.template) {
                self.emit('setTemplate', self.config.template);
            }
        });
    // init template
    } else if (self.config.template) {
        self.emit('setTemplate', self.config.template);
    }

    self.emit('ready');
}

module.exports = init;

return module; });
<|endoftext|>


<commit_before>//INCLUDE GULP PLUGINS

var gulp 			= require('gulp'),
	sass			= require('gulp-sass'),
	concat			= require('gulp-concat'),
	jshint			= require('gulp-jshint'),
	uglify			= require('gulp-uglify'),
	rename			= require('gulp-rename'),
	stylish			= require('jshint-stylish'),
	browserSync		= require('browser-sync').create(),
	sourcemaps		= require('gulp-sourcemaps'),
	plumber			= require('gulp-plumber'),
	iconfont		= require('gulp-iconfont'),
	iconfontCss		= require('gulp-iconfont-css'),
	imageop			= require('gulp-image-optimization'),
	minifyCss		= require('gulp-minify-css'),
	postcss 		= require('gulp-postcss'),
	autoprefixer	= require('autoprefixer'),
	cssgrace 		= require('cssgrace'),
	cssnano			= require('gulp-cssnano');

var runTimestamp 	= Math.round(Date.now()/1000);
var fontName 		= 'zuyderland-icons';

//PATHS TO WATCH

var paths = {
	sass: [
		'./source/modules/**/scss/*.scss',
		'./source/objects/**/scss/*.scss',
		'./source/main.scss',
		'./source/**/*.scss'
	],
	js: [
		'./source/modules/**/js/*.js',
		'./source/objects/**/js/*.js'
	],
	vendors: [
		'./public/library/js/vendor/bootstrap.min.js',
	],
	images: [
		'./public/library/img/*'
	],
	templates: [
		'./source/templates/'
	]
};

//SASS BUILD
gulp.task('sass', function() {
	var processors = [
		autoprefixer({browsers: ['last 2 version', '> 5%', 'ie 8', 'ie 9', 'ios 6', 'android 4']}),
	];

	gulp.src('./source/main.scss')
		.pipe(sass().on('error', sass.logError))
		.pipe(postcss(processors))
		// .pipe(cssnano())
		// Initialize sourcemaps
		.pipe(sourcemaps.init())
		.pipe(plumber())
        .pipe(sass({outputStyle: 'compressed'}))
        // Write sourcemaps to /library/css/maps
        .pipe(sourcemaps.write('./maps'))
        // CSS destination
        .pipe(gulp.dest('./public/library/css'));
});

//SASS DEPLOY
gulp.task('sass_deploy', function() {
	gulp.src('./source/main.scss')
		// Initialize sourcemaps
		.pipe(sourcemaps.init())
		// Prevent pipe breaking caused by errors from gulp plugins
		.pipe(plumber())
		// Compiles sass to css
        .pipe(sass())
        // Minify CSS
        .pipe(minifyCss({compatibility: 'ie8'}))
        // CSS destination
        .pipe(gulp.dest('./public/library/css'));
});

//JAVASCRIPT BUILD
gulp.task('js', function(){
	gulp.src(paths.js)
		// Initialize Sourcemaps
		.pipe(sourcemaps.init())
		// Check for errors
		.pipe(plumber())
		// Check on syntax errors
		.pipe(jshint())
		.pipe(jshint.reporter(stylish))
		// Concatenate all JS files into one
		.pipe(concat('main.min.js'))
		//Stop plumber errors
		.pipe(plumber.stop())
		// will write the source maps to ./Library/scripts/maps
		.pipe(sourcemaps.write('./maps'))
		// Where to store the finalized JS
		.pipe(gulp.dest('./public/library/js'));
});

//JAVASCRIPT DEPLOY
gulp.task('js_deploy', function(){
	gulp.src(paths.js)
		// Initialize Sourcemaps
		.pipe(sourcemaps.init())
		// Check for errors
		.pipe(plumber())
		// Check on syntax errors
		.pipe(jshint())
		.pipe(jshint.reporter(stylish))
		// Concatenate all JS files into one
		.pipe(concat('main.min.js'))
		// Minify JS
		.pipe(uglify())
		//Stop plumber errors
		.pipe(plumber.stop())
		// will write the source maps to ./Library/scripts/maps
		.pipe(sourcemaps.write('./maps'))
		// Where to store the finalized JS
		.pipe(gulp.dest('./public/library/js'));
});

//JS VENDORS BUILD
gulp.task('vendors', function(){
	gulp.src(paths.vendors)
		// Concatenate all JS files into one
		.pipe(concat('vendors.min.js'))
		// Minify JS
		.pipe(uglify())
		// Where to store the finalized JS
		.pipe(gulp.dest('./public/library/js'));
});

//ICONFONT
gulp.task('iconfont', function(){
	gulp.src(['./public/library/icons/*.svg'])
	.pipe(iconfontCss({
  		fontName: fontName,
		path: paths.templates + '_icons.scss',
		targetPath: '../../../../source/base/fonts/_icons.scss',
		fontPath: '../fonts/icons/'
	}))
    .pipe(iconfont({
    	fontName: fontName, // Name of the font
		appendUnicode: true, // recommended option
		formats: ['svg', 'ttf', 'eot', 'woff', 'woff2'], // default, 'woff2' and 'svg' are available
		timestamp: runTimestamp, // recommended to get consistent builds when watching files
    }))
    .pipe(gulp.dest('./public/library/fonts/icons/'));
});

//IMAGE OPTIM.
gulp.task('images', function(){
	gulp.src(paths.images)
		.pipe(imageop({
			optimizationLevel: 5,
			progressive: true,
			interlaced: true
		}))
		.pipe(gulp.dest('./public/library/img'));
});

//BROWSER SYNC
gulp.task('browser-sync', function() {
    browserSync.init({
        server: {
            baseDir: "./"
        }

        // proxy: "http://localhost/"

    });

	gulp.watch(paths.sass, ['sass']).on('change', browserSync.reload);
    gulp.watch(paths.js, ['js']).on('change', browserSync.reload);
    gulp.watch("*.html").on('change', browserSync.reload);
});

//GULP WATCH TASK
gulp.task('watch', ['browser-sync'], function(){
	gulp.watch(paths.sass, ['sass']);
	gulp.watch(paths.js, ['js']);
});

//GULP SETUP TASK
gulp.task('setup', ['sass', 'js', 'vendors', 'images']);

//GULP BUILD TASK
gulp.task('build', ['watch', 'browser-sync']);

//DEFAULT GULP TASK
gulp.task('default', ['watch', 'browser-sync']);

//DEPLOY GULP TASK
gulp.task('deploy', ['sass_deploy', 'js_deploy', 'vendors', 'images']);
<commit_msg>Update gulpfile for development<commit_after>//INCLUDE GULP PLUGINS

var gulp 			= require('gulp'),
	sass			= require('gulp-sass'),
	concat			= require('gulp-concat'),
	jshint			= require('gulp-jshint'),
	uglify			= require('gulp-uglify'),
	rename			= require('gulp-rename'),
	stylish			= require('jshint-stylish'),
	browserSync		= require('browser-sync').create(),
	sourcemaps		= require('gulp-sourcemaps'),
	plumber			= require('gulp-plumber'),
	iconfont		= require('gulp-iconfont'),
	iconfontCss		= require('gulp-iconfont-css'),
	imageop			= require('gulp-image-optimization'),
	minifyCss		= require('gulp-minify-css'),
	postcss 		= require('gulp-postcss'),
	autoprefixer	= require('autoprefixer'),
	cssgrace 		= require('cssgrace'),
	cssnano			= require('gulp-cssnano');

var runTimestamp 	= Math.round(Date.now()/1000);
var fontName 		= 'xxx-icons';

//PATHS TO WATCH

var paths = {
	sass: [
		'./source/modules/**/scss/*.scss',
		'./source/objects/**/scss/*.scss',
		'./source/main.scss',
		'./source/**/*.scss'
	],
	js: [
		'./source/modules/**/js/*.js',
		'./source/objects/**/js/*.js'
	],
	vendors: [
		// './public/library/js/vendor/bootstrap.min.js',
	],
	images: [
		'./public/library/img/*'
	],
	templates: [
		'./source/templates/'
	]
};

//SASS BUILD
gulp.task('sass', function() {
	var processors = [
		autoprefixer({browsers: ['last 2 version', '> 5%', 'ie 9', 'ios 7', 'android 4']}),
	];

	gulp.src('./source/main.scss')
		.pipe(sass().on('error', sass.logError))
		.pipe(postcss(processors))
		// .pipe(cssnano())
		// Initialize sourcemaps
		.pipe(sourcemaps.init())
		.pipe(plumber())
        .pipe(sass({outputStyle: 'uncompressed'}))
        // Write sourcemaps to /library/css/maps
        .pipe(sourcemaps.write('./maps'))
        // CSS destination
        .pipe(gulp.dest('./public/library/css'));
});

//SASS DEPLOY
gulp.task('sass_deploy', function() {
	gulp.src('./source/main.scss')
		// Initialize sourcemaps
		.pipe(sourcemaps.init())
		// Prevent pipe breaking caused by errors from gulp plugins
		.pipe(plumber())
		// Compiles sass to css
        .pipe(sass())
        // Minify CSS
        .pipe(minifyCss({compatibility: 'ie8'}))
        // CSS destination
        .pipe(gulp.dest('./public/library/css'));
});

//JAVASCRIPT BUILD
gulp.task('js', function(){
	gulp.src(paths.js)
		// Initialize Sourcemaps
		.pipe(sourcemaps.init())
		// Check for errors
		.pipe(plumber())
		// Check on syntax errors
		.pipe(jshint())
		.pipe(jshint.reporter(stylish))
		// Concatenate all JS files into one
		.pipe(concat('main.min.js'))
		//Stop plumber errors
		.pipe(plumber.stop())
		// will write the source maps to ./Library/scripts/maps
		.pipe(sourcemaps.write('./maps'))
		// Where to store the finalized JS
		.pipe(gulp.dest('./public/library/js'));
});

//JAVASCRIPT DEPLOY
gulp.task('js_deploy', function(){
	gulp.src(paths.js)
		// Initialize Sourcemaps
		.pipe(sourcemaps.init())
		// Check for errors
		.pipe(plumber())
		// Check on syntax errors
		.pipe(jshint())
		.pipe(jshint.reporter(stylish))
		// Concatenate all JS files into one
		.pipe(concat('main.min.js'))
		// Minify JS
		.pipe(uglify())
		//Stop plumber errors
		.pipe(plumber.stop())
		// will write the source maps to ./Library/scripts/maps
		.pipe(sourcemaps.write('./maps'))
		// Where to store the finalized JS
		.pipe(gulp.dest('./public/library/js'));
});

//JS VENDORS BUILD
gulp.task('vendors', function(){
	gulp.src(paths.vendors)
		// Concatenate all JS files into one
		.pipe(concat('vendors.min.js'))
		// Minify JS
		.pipe(uglify())
		// Where to store the finalized JS
		.pipe(gulp.dest('./public/library/js'));
});

//ICONFONT
gulp.task('iconfont', function(){
	gulp.src(['./public/library/icons/*.svg'])
	.pipe(iconfontCss({
  		fontName: fontName,
		path: paths.templates + '_icons.scss',
		targetPath: '../../../../source/base/fonts/_icons.scss',
		fontPath: '../fonts/icons/'
	}))
    .pipe(iconfont({
    	fontName: fontName, // Name of the font
		appendUnicode: true, // recommended option
		formats: ['svg', 'ttf', 'eot', 'woff', 'woff2'], // default, 'woff2' and 'svg' are available
		timestamp: runTimestamp, // recommended to get consistent builds when watching files
    }))
    .pipe(gulp.dest('./public/library/fonts/icons/'));
});

//IMAGE OPTIM.
gulp.task('images', function(){
	gulp.src(paths.images)
		.pipe(imageop({
			optimizationLevel: 5,
			progressive: true,
			interlaced: true
		}))
		.pipe(gulp.dest('./public/library/img'));
});

//BROWSER SYNC
gulp.task('browser-sync', function() {
    browserSync.init({
        server: {
            baseDir: "./"
        }

        // proxy: "http://localhost/"

    });

	gulp.watch(paths.sass, ['sass']).on('change', browserSync.reload);
    gulp.watch(paths.js, ['js']).on('change', browserSync.reload);
    gulp.watch("*.html").on('change', browserSync.reload);
});

//GULP WATCH TASK
gulp.task('watch', ['browser-sync'], function(){
	gulp.watch(paths.sass, ['sass']);
	gulp.watch(paths.js, ['js']);
});

//GULP SETUP TASK
gulp.task('setup', ['sass', 'js', 'vendors', 'images']);

//GULP BUILD TASK
gulp.task('build', ['watch', 'browser-sync']);

//DEFAULT GULP TASK
gulp.task('default', ['watch', 'browser-sync']);

//DEPLOY GULP TASK
gulp.task('deploy', ['sass_deploy', 'js_deploy', 'vendors', 'images']);
<|endoftext|>


<commit_before>var recursive = require('recursive-readdir');
var tinify = require('tinify');
var path = require('path');
var Promise = require('bluebird');
var nodegit = require('nodegit');
var fs = require('fs');
var filesize = require('filesize');
var ProgressBar = require('progress');
var logger = require('loglevel');

function run (key, rootPath, force, verbose) {
  tinify.key = key;

  logger.setDefaultLevel(logger.levels.INFO);

  if (verbose) {
    logger.setLevel(logger.levels.DEBUG);
  }

  if (!fs.existsSync(rootPath)) {
    logger.info("Root directory doesn't exists");
    process.exit(1);
  }

  // Do the magic!
  recursive(rootPath, function (err, files) {

    var imageExtensions = ['.png', '.jpg', '.jpeg'];

    // We filter the files by type
    var imageFiles = files.filter(function (file) { return imageExtensions.indexOf(path.extname(file)) > -1; });
    var imageFilesPromise;

    // If force option is passed, we use the original list
    if (force) {
      imageFilesPromise = new Promise(function(resolve, reject) { resolve(imageFiles); });
    } else {
      imageFilesPromise = filterNonStagedImages(imageFiles, rootPath);
    }

    imageFilesPromise
      .then(function(files) {

        // If there are no files to convert, we exit
        if (files.length === 0) {
          logger.info('\nNo files to convert');
          process.exit(0);
        }

        // Get the current files size
        var filesSizeBeforeTinifying = getFilesSize(files);

        // Start tinifying the files
        logger.info('');
        var bar = new ProgressBar('Replacing... [:bar] :percent :etas', {
          complete: '=', incomplete: ' ', width: 60, total: files.length
        });
        var imagesProcessed = 0;
        var filesWithError = [];
        files.forEach(function (file) {
          tinify.fromFile(file).toFile(file, function(err) {
            if (err) {
              if (err instanceof tinify.AccountError) {
                logger.error('You have reached the Tinify API limit, please request a new key');
                logResults(filesSizeBeforeTinifying, files, filesWithError);
                process.exit(1);
              } else {
                logger.debug('Error on ' + file + ': ' + err.message);
                filesWithError.push(file);
              }
            }
            imagesProcessed++;
            bar.tick();
            if (imagesProcessed === files.length) {
              logResults(filesSizeBeforeTinifying, files, filesWithError);
            }
          });
        });
      });
  });

  // Shows the results of the processing
  var logResults = function(filesSizeBeforeTinifying, files, filesWithError) {
    logger.info('\nSize\n------------------');
    logger.info('' + filesize(filesSizeBeforeTinifying) + '\tBefore\n------------------');

    // After all, get the size after tinifying
    var filesSizeAfterTinifying = getFilesSize(files);
    logger.info('' + filesize(filesSizeAfterTinifying) + '\tAfter\n------------------');

    var difference = filesSizeBeforeTinifying - filesSizeAfterTinifying;
    logger.info('' + filesize(difference) + '\tDifference\n------------------');

    if (filesWithError.length > 0) {
      logger.info("\n" + filesWithError.length + " files couldn't be replaced");
      logger.debug('\n' + filesWithError.join('\n'));
    }
  };

  // Returns the total size of an array of files
  var getFilesSize = function(files) {
    var size =  files.map(function (file) { return fs.statSync(file).size; })
                     .reduce(function (size, total) { return size + total; }, 0);
    return Math.floor(size);
  };

  // Filters the files that aren't staged with git
  var filterNonStagedImages = function(imageFiles, rootPath) {
    var gitFolder = findGitFolder(rootPath);
    if (!gitFolder) {
      // If there is no .git folder, we return the original list
      return new Promise(function(resolve, reject) { resolve(imageFiles); });
    }

    return nodegit.Repository.open(gitFolder)
      .then(function(repo) {
        return repo.getStatus().then(function(statusFiles) {
          // statusFiles is an array of files that aren't staged
          var projectFolder = gitFolder.substring(0, gitFolder.lastIndexOf(path.sep));

          // Convert them to a full path file
          var statusFilesPaths = statusFiles.map(function(statusFile) {
            return path.join(projectFolder, statusFile.path());
          });

          // And then filter those imageFiles which aren't staged
          return imageFiles.filter(function(imageFile) {
            var index = statusFilesPaths.indexOf(imageFile);
            if (index > -1) {
              logger.debug("Filtering " + imageFile + " because isn't staged");
            }
            return index === -1;
          });
        });
      });
  };

  // Iterates recursively backwards through the folders
  var findGitFolder = function(currentPath) {
    var files = fs.readdirSync(currentPath);
    for (var i = 0; i < files.length; i++) {

      // For each file in the directory, we search those which are folders
      var filename = path.join(currentPath, files[i]);
      var stat = fs.lstatSync(filename);
      if (stat.isDirectory()) {

        // The .git folder path
        if (filename.indexOf('.git') > -1) {
          return filename;
        } else {
          var lastIndexOfSep = currentPath.lastIndexOf(path.sep);
          if (lastIndexOfSep > -1) {
            if (lastIndexOfSep === currentPath.length - 1) {
              // The case where the path has the separator at the end
              currentPath = currentPath.substring(0, currentPath.length - 1);
              lastIndexOfSep = currentPath.lastIndexOf(path.sep);
            }
            // Finds the parent folder and calls it again
            var parentPath = currentPath.substring(0, lastIndexOfSep);
            return findGitFolder(parentPath);
          } else {
            // We didn't find the git folder
            return null;
          }
        }
      }
    };
  };
};

module.exports = {
  run: run
};
<commit_msg>Checking if recursive returns a valid array<commit_after>var recursive = require('recursive-readdir');
var tinify = require('tinify');
var path = require('path');
var Promise = require('bluebird');
var nodegit = require('nodegit');
var fs = require('fs');
var filesize = require('filesize');
var ProgressBar = require('progress');
var logger = require('loglevel');

function run (key, rootPath, force, verbose) {
  tinify.key = key;

  logger.setDefaultLevel(logger.levels.INFO);

  if (verbose) {
    logger.setLevel(logger.levels.DEBUG);
  }

  if (!fs.existsSync(rootPath)) {
    logger.info("Root directory doesn't exists");
    process.exit(1);
  }

  // Do the magic!
  recursive(rootPath, function (err, files) {

    // If there are no files to convert, we exit
    if (!files || files.length === 0) {
      logger.info('\nNo files to convert');
      process.exit(0);
    }

    var imageExtensions = ['.png', '.jpg', '.jpeg'];

    // We filter the files by type
    var imageFiles = files.filter(function (file) { return imageExtensions.indexOf(path.extname(file)) > -1; });
    var imageFilesPromise;

    // If force option is passed, we use the original list
    if (force) {
      imageFilesPromise = new Promise(function(resolve, reject) { resolve(imageFiles); });
    } else {
      imageFilesPromise = filterNonStagedImages(imageFiles, rootPath);
    }

    imageFilesPromise
      .then(function(files) {

        // If there are no files to convert, we exit
        if (files.length === 0) {
          logger.info('\nNo files to convert');
          process.exit(0);
        }

        // Get the current files size
        var filesSizeBeforeTinifying = getFilesSize(files);

        // Start tinifying the files
        logger.info('');
        var bar = new ProgressBar('Replacing... [:bar] :percent :etas', {
          complete: '=', incomplete: ' ', width: 60, total: files.length
        });
        var imagesProcessed = 0;
        var filesWithError = [];
        files.forEach(function (file) {
          tinify.fromFile(file).toFile(file, function(err) {
            if (err) {
              if (err instanceof tinify.AccountError) {
                logger.error('You have reached the Tinify API limit, please request a new key');
                logResults(filesSizeBeforeTinifying, files, filesWithError);
                process.exit(1);
              } else {
                logger.debug('Error on ' + file + ': ' + err.message);
                filesWithError.push(file);
              }
            }
            imagesProcessed++;
            bar.tick();
            if (imagesProcessed === files.length) {
              logResults(filesSizeBeforeTinifying, files, filesWithError);
            }
          });
        });
      });
  });

  // Shows the results of the processing
  var logResults = function(filesSizeBeforeTinifying, files, filesWithError) {
    logger.info('\nSize\n------------------');
    logger.info('' + filesize(filesSizeBeforeTinifying) + '\tBefore\n------------------');

    // After all, get the size after tinifying
    var filesSizeAfterTinifying = getFilesSize(files);
    logger.info('' + filesize(filesSizeAfterTinifying) + '\tAfter\n------------------');

    var difference = filesSizeBeforeTinifying - filesSizeAfterTinifying;
    logger.info('' + filesize(difference) + '\tDifference\n------------------');

    if (filesWithError.length > 0) {
      logger.info("\n" + filesWithError.length + " files couldn't be replaced");
      logger.debug('\n' + filesWithError.join('\n'));
    }
  };

  // Returns the total size of an array of files
  var getFilesSize = function(files) {
    var size =  files.map(function (file) { return fs.statSync(file).size; })
                     .reduce(function (size, total) { return size + total; }, 0);
    return Math.floor(size);
  };

  // Filters the files that aren't staged with git
  var filterNonStagedImages = function(imageFiles, rootPath) {
    var gitFolder = findGitFolder(rootPath);
    if (!gitFolder) {
      // If there is no .git folder, we return the original list
      return new Promise(function(resolve, reject) { resolve(imageFiles); });
    }

    return nodegit.Repository.open(gitFolder)
      .then(function(repo) {
        return repo.getStatus().then(function(statusFiles) {
          // statusFiles is an array of files that aren't staged
          var projectFolder = gitFolder.substring(0, gitFolder.lastIndexOf(path.sep));

          // Convert them to a full path file
          var statusFilesPaths = statusFiles.map(function(statusFile) {
            return path.join(projectFolder, statusFile.path());
          });

          // And then filter those imageFiles which aren't staged
          return imageFiles.filter(function(imageFile) {
            var index = statusFilesPaths.indexOf(imageFile);
            if (index > -1) {
              logger.debug("Filtering " + imageFile + " because isn't staged");
            }
            return index === -1;
          });
        });
      });
  };

  // Iterates recursively backwards through the folders
  var findGitFolder = function(currentPath) {
    var files = fs.readdirSync(currentPath);
    for (var i = 0; i < files.length; i++) {

      // For each file in the directory, we search those which are folders
      var filename = path.join(currentPath, files[i]);
      var stat = fs.lstatSync(filename);
      if (stat.isDirectory()) {

        // The .git folder path
        if (filename.indexOf('.git') > -1) {
          return filename;
        } else {
          var lastIndexOfSep = currentPath.lastIndexOf(path.sep);
          if (lastIndexOfSep > -1) {
            if (lastIndexOfSep === currentPath.length - 1) {
              // The case where the path has the separator at the end
              currentPath = currentPath.substring(0, currentPath.length - 1);
              lastIndexOfSep = currentPath.lastIndexOf(path.sep);
            }
            // Finds the parent folder and calls it again
            var parentPath = currentPath.substring(0, lastIndexOfSep);
            return findGitFolder(parentPath);
          } else {
            // We didn't find the git folder
            return null;
          }
        }
      }
    };
  };
};

module.exports = {
  run: run
};
<|endoftext|>