<commit_before>'use strict';

var mongoose = require('mongoose'),
	Q = require('q'),
	path = require('path'),
	thunkify = require('thunkify'),
	glob = thunkify(require("glob")),
	S = require('string');

var databaseHandler = {};
var cacheDatabaseSchema = {};
var cacheConnection = {};
var wasInitialize = false;

/**
 * connection to mongodb
 */
function connect(dbConfig) {
	var deferred = Q.defer();
	var connection = mongoose.createConnection(makeConnectionString(dbConfig));
	connection.on('connected', function (){
		deferred.resolve(connection);
	});
	connection.on('error', function(error) {
		deferred.reject(new Error(error));
	})
	return deferred.promise;
}

function *createConnections(dbConfig) {
	for (var name in dbConfig.connections) {
		var connectionConfig = dbConfig.connections[name];
		if ("mongoose" !== connectionConfig.driver) continue;
		cacheConnection[name] = yield connect(dbConfig.connections[name]);
	}

	if (Object.keys(cacheConnection).length === 0) {
		Log.warn("no connection with mongoose driver. Delete gaia-driver-mongoose dependencie");
	}
}

/**
 * Inject databse middleware
 */
exports.injectDatabase = function() {
	return function *(next) {
		this.getDatabaseHandler = getDatabaseHandler;
		yield next;
	}
}

exports.mongoose = mongoose;

/**
 * Initialize database
 */
exports.init = function *(gaia) {
	var dbConfig = gaia.config.database;
	if (wasInitialize) return;
	yield createConnections(dbConfig);

	for (var databaseSchemaName in dbConfig.databaseSchema) {
		var databaseSchema = dbConfig.databaseSchema[databaseSchemaName];

		cacheDatabaseSchema[databaseSchemaName] = {
			models: yield getModels(gaia.config.appDir, databaseSchema.dir),
			connection: cacheConnection[databaseSchema.connection]
		};

		var handler = databaseHandler[databaseSchema.databaseName] = createDatabaseHandler(databaseSchema.databaseName, cacheDatabaseSchema[databaseSchemaName]);
		if (databaseSchema.default) {
			databaseHandler["default"] = handler;
		}

		if (databaseSchema.collectionTestForFirstRun && databaseSchema.onFirstRun) {
			var obj = handler[databaseSchema.collectionTestForFirstRun];
			var objs = yield obj.find().exec();
			if (!objs || !objs.length) yield databaseSchema.onFirstRun(handler);
		}
	}
	wasInitialize = true;
}

/**
 * Return the database handler
 *
 */
var getDatabaseHandler = exports.getDatabaseHandler = function (databaseName, schemaName) {
	if (!databaseName) databaseName = "default";

	if (databaseHandler[databaseName]) {
		return databaseHandler[databaseName];
	}

	if (schemaName && cacheDatabaseSchema[schemaName]) {
		return databaseHandler[databaseName] = createDatabaseHandler(databaseName, cacheDatabaseSchema[schemaName]);
	}
	return null;
}

/**
 * Generate connection string
 */
function makeConnectionString(dbConfig) {
	var buff = [ 'mongodb://' ];
	if (dbConfig.user) {
		buff.push(dbConfig.user, ':', dbConfig.password, '@');
	}
	buff.push(dbConfig.server, '/');

	if (dbConfig.database){
		buff.push(dbConfig.databse);
	}

	return buff.join('');
}

/**
 * Create a database handler with specific shema
 */
function createDatabaseHandler(databaseName, schema) {
	var databaseHandler = {};
	var handler = schema.connection.useDb(databaseName);
	var models = schema.models;
	models.forEach(function(model) {
		databaseHandler[model.name] = handler.model(model.name, model.schema);
	});

	return databaseHandler;
}

/**
 * Get all models
 */
function *getModels(appDir, modelsDir) {
	var modelsPath = path.resolve(appDir, 'app/models', modelsDir) + '/**/*.js';

	var files = yield glob(modelsPath, {});
	var models = [];
	files.forEach(function (file) {
		if (~file.indexOf('.js')) {
			models.push({
				name: S(path.basename(file, '.js')).capitalize().camelize().s,
				schema: require(path.resolve(appDir, 'app/models', modelsDir, file))
			});
		}
	});
	return models;
}
<commit_msg>no creation of handler if schema is not a generator<commit_after>'use strict';

var mongoose = require('mongoose'),
	Q = require('q'),
	path = require('path'),
	thunkify = require('thunkify'),
	glob = thunkify(require("glob")),
	S = require('string');

var databaseHandler = {};
var cacheDatabaseSchema = {};
var cacheConnection = {};
var wasInitialize = false;

/**
 * connection to mongodb
 */
function connect(dbConfig) {
	var deferred = Q.defer();
	var connection = mongoose.createConnection(makeConnectionString(dbConfig));
	connection.on('connected', function (){
		deferred.resolve(connection);
	});
	connection.on('error', function(error) {
		deferred.reject(new Error(error));
	})
	return deferred.promise;
}

function *createConnections(dbConfig) {
	for (var name in dbConfig.connections) {
		var connectionConfig = dbConfig.connections[name];
		if ("mongoose" !== connectionConfig.driver) continue;
		cacheConnection[name] = yield connect(dbConfig.connections[name]);
	}

	if (Object.keys(cacheConnection).length === 0) {
		Log.warn("no connection with mongoose driver. Delete gaia-driver-mongoose dependencie");
	}
}

/**
 * Inject databse middleware
 */
exports.injectDatabase = function() {
	return function *(next) {
		this.getDatabaseHandler = getDatabaseHandler;
		yield next;
	}
}

exports.mongoose = mongoose;

/**
 * Initialize database
 */
exports.init = function *(gaia) {
	var dbConfig = gaia.config.database;
	if (wasInitialize) return;
	yield createConnections(dbConfig);

	for (var databaseSchemaName in dbConfig.databaseSchema) {
		var databaseSchema = dbConfig.databaseSchema[databaseSchemaName];

		cacheDatabaseSchema[databaseSchemaName] = {
			models: yield getModels(gaia.config.appDir, databaseSchema.dir),
			connection: cacheConnection[databaseSchema.connection]
		};

		var handler = databaseHandler[databaseSchema.databaseName] = createDatabaseHandler(databaseSchema.databaseName, cacheDatabaseSchema[databaseSchemaName]);
		if (databaseSchema.default) {
			databaseHandler["default"] = handler;
		}

		if (databaseSchema.collectionTestForFirstRun && databaseSchema.onFirstRun) {
			var obj = handler[databaseSchema.collectionTestForFirstRun];
			var objs = yield obj.find().exec();
			if (!objs || !objs.length) yield databaseSchema.onFirstRun(handler);
		}
	}
	wasInitialize = true;
}

/**
 * Return the database handler
 *
 */
var getDatabaseHandler = exports.getDatabaseHandler = function (databaseName, schemaName) {
	if (!databaseName) databaseName = "default";

	if (databaseHandler[databaseName]) {
		return databaseHandler[databaseName];
	}

	if (schemaName && cacheDatabaseSchema[schemaName] && cacheDatabaseSchema[schemaName].isGenerator) {
		return databaseHandler[databaseName] = createDatabaseHandler(databaseName, cacheDatabaseSchema[schemaName]);
	}
	return null;
}

/**
 * Generate connection string
 */
function makeConnectionString(dbConfig) {
	var buff = [ 'mongodb://' ];
	if (dbConfig.user) {
		buff.push(dbConfig.user, ':', dbConfig.password, '@');
	}
	buff.push(dbConfig.server, '/');

	if (dbConfig.database){
		buff.push(dbConfig.databse);
	}

	return buff.join('');
}

/**
 * Create a database handler with specific shema
 */
function createDatabaseHandler(databaseName, schema) {
	var databaseHandler = {};
	var handler = schema.connection.useDb(databaseName);
	var models = schema.models;
	models.forEach(function(model) {
		databaseHandler[model.name] = handler.model(model.name, model.schema);
	});

	return databaseHandler;
}

/**
 * Get all models
 */
function *getModels(appDir, modelsDir) {
	var modelsPath = path.resolve(appDir, 'app/models', modelsDir) + '/**/*.js';

	var files = yield glob(modelsPath, {});
	var models = [];
	files.forEach(function (file) {
		if (~file.indexOf('.js')) {
			models.push({
				name: S(path.basename(file, '.js')).capitalize().camelize().s,
				schema: require(path.resolve(appDir, 'app/models', modelsDir, file))
			});
		}
	});
	return models;
}
<|endoftext|>


<commit_before>try {
	var Config = require('./config.json');
} catch (e){
	Config.debug = false;
	Config.respondToInvalid = false;
}

try {
	var AuthDetails = require('./auth.json');
} catch (e){
	console.log("Please create an auth.json file with account email and password"+e.stack);
	process.exit();
}

try {
	var Discord = require("discord.js");
} catch (e){
	console.log("Please run npm install discord.js"+e.stack);
	process.exit();
}

try {
	var d20 = require('d20');
} catch (e){
	console.log("Please run npm install d20"+e.stack);
}

try {
	var Stats = require("./user_stats.json");
} catch (e){
	console.log("Unable to provide stats: Missing file");
	Stats.debug = true;
}

var bot = new Discord.Client();

var commands = {
	"dam": {
		usage: "",
		description: "returns image/gif",
		process: function(bot, msg) {
			var rand = Math.floor(Math.random() * 2) + 1;
			var result;
			if (rand == 1)
				result = 'https://i.imgur.com/7lZwLKc.jpg';
			else
				result = 'https://i.imgur.com/VQLGJOL.gif';
			bot.sendMessage(msg.channel, result);
		}
	},
	"happy": {
		usage: "",
		description: "returns image/gif",
		process: function(bot, msg) {
			var rand = Math.floor(Math.random() * 2) + 1;
			var result;
			if (rand == 1)
			  result = 'https://i.imgur.com/gbXzYXp.gif';
			else
			  result = 'https://i.imgur.com/OxwIC0O.gif';
			bot.sendMessage(msg.channel, result);
		}
	},
	"horse": {
		usage: "",
		description: "returns horse gif",
		process: function(bot, msg) {
			var result = 'https://i.giphy.com/S4mOmgmpBQAXm.gif';
			bot.sendMessage(msg.channel, result);
		}
	},
	"img": {
		usage: "<search term>",
		description: "searches imgur based on keyword",
		process: function(bot, msg, suffix) {
			var key = AuthDetails;
			var Imgur = require('imgur-search');

			var term = suffix;
			if(!term) {
				bot.sendMessage(msg.channel, "No Search Term");
				return;
			}
			new Imgur(key.imgur_key).search(term).then(function(result) {
				if (result.length == 0 || result == undefined) {
					bot.sendMessage(msg.channel, "You suck at searching");
				} else {
					var img = result[Math.floor(Math.random() * result.length)];
					bot.sendMessage(msg.channel, img.link);
				}
			});
		}
	},
	"why": {
		usage: "",
		description: "returns gif",
		process: function(bot, msg) {
			var result = 'https://media.giphy.com/media/1M9fmo1WAFVK0/giphy.gif';
			bot.sendMessage(msg.channel, result);
		}
	},
	"roll": {
		usage: "[#d#]",
		description: "dice roller, with single command giving a d20, otherwise rolling specified combo",
		process: function(bot, msg, suffix) {
			if(!suffix) {
				bot.sendMessage(msg.channel, msg.author + " rolled: " + d20.roll(20));
			} else {
				var result;
				try {
					result = d20.roll(suffix);
				} catch (e){
					bot.sendMessage(msg.channel, msg.author + " Could not roll " + suffix);
					return;
				}
				if (!isNaN(result) && result != 'NaN')
					bot.sendMessage(msg.channel, msg.author + " rolled: " + d20.verboseRoll(suffix));
				else
					bot.sendMessage(msg.channel, msg.author + " Not a valid number");
			}
		}
	},
	"rolloff": {
		usage: "",
		description: "Roll off against the bot (D20)",
		process: function(bot, msg) {
			var roll_bot;
			var roll_usr;

			roll_bot = d20.roll(20);
			roll_usr = d20.roll(20);
			bot.sendMessage(msg.channel, "A CHALLENGER APPROACHES!");

			if (roll_bot > roll_usr)
				bot.sendMessage(msg.channel, "MUAHAHAHA. My " + roll_bot + " p4wned your " + roll_usr + ". Get rekt.");
			else if (roll_usr > roll_bot)
				bot.sendMessage(msg.channel, "Lucky roll... My " + roll_bot + " can't beat your " + roll_usr + ".");
			else
				bot.sendMessage(msg.channel, "Tie? TIE!? Let's call that a win for me...");
		}
	},
	"blown": {
		usage: "",
		description: "returns gif",
		process: function(bot, msg) {
			var result;
			var rand = Math.floor(Math.random() * 6);
			var gifs = [	'https://i.imgur.com/YGsTG5o.gif',
					'https://i.imgur.com/pBTk7MC.gif',
					'https://i.imgur.com/rfFWukr.gif',
					'https://media.giphy.com/media/OK27wINdQS5YQ/giphy.gif',
					'https://i.imgur.com/l4bJmRE.gif',
					'https://i.imgur.com/3WB3L.gif',
					'https://giphy.com/gifs/cheezburger-robots-screaming-NfEQd1ge1kP04']

			bot.sendMessage(msg.channel, gifs[rand]);
		}
	},
	"weekend": {
		usage: "",
		description: "returns days until weekend",
		process: function(bot, msg) {
			var date = new Date();
			var day = date.getDay();
			var result = [  "Last day, Enjoy it!",
					"4 Days Left. Got a case of the Mondays?",
					"3 Days Left",
					"2 Days Left.  Hump Day!",
					"1 Day Left",
					"FRIDAY!",
					"Less than 48 hours left..."];

			bot.sendMessage(msg.channel, result[day]);
		}
	},
	"test": {
		usage: "Admin only",
		description: "",
		process: function(bot, msg) {
			var roles = msg.channel.server.roles;
			if (msg.author.hasRole(roles[1])) {
				bot.sendMessage(msg.channel, "Good afternoon, Mr. Admin.  JSON test below");
				var tmp = JSON.parse(Stats);
 +				bot.sendMessage(msg.channel, tmp);
			} else
				bot.sendMessage(msg.channel, "Frig off...")
		}
	}
};

bot.on("ready", function () {
	console.log("Ready to start. Channels: " + bot.channels.length);
});

bot.on('disconnected', function() {
	console.log('Disconnected.');
	process.exit(1);
});

bot.on("message", function (msg) {
	if(msg.author.id != bot.user.id && msg.content[0] === '!'){
		var cmdTxt = msg.content.split(" ")[0].substring(1);
        	var suffix = msg.content.substring(cmdTxt.length+2);
		var cmd = commands[cmdTxt];

        	if(cmdTxt === "help"){
			var info = "Available Commands: ";
			for(var cmd in commands) {
				info += "!" + cmd;
				var usage = commands[cmd].usage;
				if(usage){
					info += " " + usage + "; ";
				}
				else
					info += "; ";
			}
			bot.sendMessage(msg.author,info);
        	} else if(cmd) {
			try{
				cmd.process(bot,msg,suffix);
			} catch(e){
				if(Config.debug){
					bot.sendMessage(msg.channel, "command " + cmdTxt + " failed :(\n" + e.stack);
				}
			}
		} else {
			bot.sendMessage(msg.author, " Invalid command, Sucka!  Maybe you need some !help");
		}
	} else if (msg.author != bot.user && msg.isMentioned(bot.user)) {
		bot.sendMessage(msg.channel,msg.author + ", you called?");
    	}
});

exports.addCommand = function(commandName, commandObject){
    try {
        commands[commandName] = commandObject;
    } catch(err){
        console.log(err);
    }
}
exports.commandCount = function(){
    return Object.keys(commands).length;
}

bot.login(AuthDetails.email, AuthDetails.password);
<commit_msg>how do i json =(<commit_after>try {
	var Config = require('./config.json');
} catch (e){
	Config.debug = false;
	Config.respondToInvalid = false;
}

try {
	var AuthDetails = require('./auth.json');
} catch (e){
	console.log("Please create an auth.json file with account email and password"+e.stack);
	process.exit();
}

try {
	var Discord = require("discord.js");
} catch (e){
	console.log("Please run npm install discord.js"+e.stack);
	process.exit();
}

try {
	var d20 = require('d20');
} catch (e){
	console.log("Please run npm install d20"+e.stack);
}

try {
	var Stats = require("./user_stats.json");
} catch (e){
	console.log("Unable to provide stats: Missing file");
	Stats.debug = true;
}

var bot = new Discord.Client();

var commands = {
	"dam": {
		usage: "",
		description: "returns image/gif",
		process: function(bot, msg) {
			var rand = Math.floor(Math.random() * 2) + 1;
			var result;
			if (rand == 1)
				result = 'https://i.imgur.com/7lZwLKc.jpg';
			else
				result = 'https://i.imgur.com/VQLGJOL.gif';
			bot.sendMessage(msg.channel, result);
		}
	},
	"happy": {
		usage: "",
		description: "returns image/gif",
		process: function(bot, msg) {
			var rand = Math.floor(Math.random() * 2) + 1;
			var result;
			if (rand == 1)
			  result = 'https://i.imgur.com/gbXzYXp.gif';
			else
			  result = 'https://i.imgur.com/OxwIC0O.gif';
			bot.sendMessage(msg.channel, result);
		}
	},
	"horse": {
		usage: "",
		description: "returns horse gif",
		process: function(bot, msg) {
			var result = 'https://i.giphy.com/S4mOmgmpBQAXm.gif';
			bot.sendMessage(msg.channel, result);
		}
	},
	"img": {
		usage: "<search term>",
		description: "searches imgur based on keyword",
		process: function(bot, msg, suffix) {
			var key = AuthDetails;
			var Imgur = require('imgur-search');

			var term = suffix;
			if(!term) {
				bot.sendMessage(msg.channel, "No Search Term");
				return;
			}
			new Imgur(key.imgur_key).search(term).then(function(result) {
				if (result.length == 0 || result == undefined) {
					bot.sendMessage(msg.channel, "You suck at searching");
				} else {
					var img = result[Math.floor(Math.random() * result.length)];
					bot.sendMessage(msg.channel, img.link);
				}
			});
		}
	},
	"why": {
		usage: "",
		description: "returns gif",
		process: function(bot, msg) {
			var result = 'https://media.giphy.com/media/1M9fmo1WAFVK0/giphy.gif';
			bot.sendMessage(msg.channel, result);
		}
	},
	"roll": {
		usage: "[#d#]",
		description: "dice roller, with single command giving a d20, otherwise rolling specified combo",
		process: function(bot, msg, suffix) {
			if(!suffix) {
				bot.sendMessage(msg.channel, msg.author + " rolled: " + d20.roll(20));
			} else {
				var result;
				try {
					result = d20.roll(suffix);
				} catch (e){
					bot.sendMessage(msg.channel, msg.author + " Could not roll " + suffix);
					return;
				}
				if (!isNaN(result) && result != 'NaN')
					bot.sendMessage(msg.channel, msg.author + " rolled: " + d20.verboseRoll(suffix));
				else
					bot.sendMessage(msg.channel, msg.author + " Not a valid number");
			}
		}
	},
	"rolloff": {
		usage: "",
		description: "Roll off against the bot (D20)",
		process: function(bot, msg) {
			var roll_bot;
			var roll_usr;

			roll_bot = d20.roll(20);
			roll_usr = d20.roll(20);
			bot.sendMessage(msg.channel, "A CHALLENGER APPROACHES!");

			if (roll_bot > roll_usr)
				bot.sendMessage(msg.channel, "MUAHAHAHA. My " + roll_bot + " p4wned your " + roll_usr + ". Get rekt.");
			else if (roll_usr > roll_bot)
				bot.sendMessage(msg.channel, "Lucky roll... My " + roll_bot + " can't beat your " + roll_usr + ".");
			else
				bot.sendMessage(msg.channel, "Tie? TIE!? Let's call that a win for me...");
		}
	},
	"blown": {
		usage: "",
		description: "returns gif",
		process: function(bot, msg) {
			var result;
			var rand = Math.floor(Math.random() * 6);
			var gifs = [	'https://i.imgur.com/YGsTG5o.gif',
					'https://i.imgur.com/pBTk7MC.gif',
					'https://i.imgur.com/rfFWukr.gif',
					'https://media.giphy.com/media/OK27wINdQS5YQ/giphy.gif',
					'https://i.imgur.com/l4bJmRE.gif',
					'https://i.imgur.com/3WB3L.gif',
					'https://giphy.com/gifs/cheezburger-robots-screaming-NfEQd1ge1kP04']

			bot.sendMessage(msg.channel, gifs[rand]);
		}
	},
	"weekend": {
		usage: "",
		description: "returns days until weekend",
		process: function(bot, msg) {
			var date = new Date();
			var day = date.getDay();
			var result = [  "Last day, Enjoy it!",
					"4 Days Left. Got a case of the Mondays?",
					"3 Days Left",
					"2 Days Left.  Hump Day!",
					"1 Day Left",
					"FRIDAY!",
					"Less than 48 hours left..."];

			bot.sendMessage(msg.channel, result[day]);
		}
	},
	"test": {
		usage: "Admin only",
		description: "",
		process: function(bot, msg) {
			var roles = msg.channel.server.roles;
			if (msg.author.hasRole(roles[1])) {
				bot.sendMessage(msg.channel, "Good afternoon, Mr. Admin.  JSON test below");
				var tmp = Stats;
				bot.sendMessage(msg.channel, tmp);
			} else
				bot.sendMessage(msg.channel, "Frig off...")
		}
	}
};

bot.on("ready", function () {
	console.log("Ready to start. Channels: " + bot.channels.length);
});

bot.on('disconnected', function() {
	console.log('Disconnected.');
	process.exit(1);
});

bot.on("message", function (msg) {
	if(msg.author.id != bot.user.id && msg.content[0] === '!'){
		var cmdTxt = msg.content.split(" ")[0].substring(1);
        	var suffix = msg.content.substring(cmdTxt.length+2);
		var cmd = commands[cmdTxt];

        	if(cmdTxt === "help"){
			var info = "Available Commands: ";
			for(var cmd in commands) {
				info += "!" + cmd;
				var usage = commands[cmd].usage;
				if(usage){
					info += " " + usage + "; ";
				}
				else
					info += "; ";
			}
			bot.sendMessage(msg.author,info);
        	} else if(cmd) {
			try{
				cmd.process(bot,msg,suffix);
			} catch(e){
				if(Config.debug){
					bot.sendMessage(msg.channel, "command " + cmdTxt + " failed :(\n" + e.stack);
				}
			}
		} else {
			bot.sendMessage(msg.author, " Invalid command, Sucka!  Maybe you need some !help");
		}
	} else if (msg.author != bot.user && msg.isMentioned(bot.user)) {
		bot.sendMessage(msg.channel,msg.author + ", you called?");
    	}
});

exports.addCommand = function(commandName, commandObject){
    try {
        commands[commandName] = commandObject;
    } catch(err){
        console.log(err);
    }
}
exports.commandCount = function(){
    return Object.keys(commands).length;
}

bot.login(AuthDetails.email, AuthDetails.password);
<|endoftext|>


<commit_before>// Dependencies
var path = require('path')

// Generate absolute paths
// to styles packages
function pathToModule (style) {
  return path.join(__dirname,
    '..',
    'node_modules',
    style,
    'bin',
    'cmd.js'
  )
}

module.exports = {
  standard: pathToModule('standard'),
  semiStandard: pathToModule('semistandard'),
  happiness: pathToModule('happiness'),
  noStyle: path.join(__dirname, 'no-style.js')
}
<commit_msg>fix bin-path utils<commit_after>// Dependencies
var path = require('path')

// Generate absolute paths
// to styles packages
function pathToModule (style) {
  return path.join(__dirname,
    '..',
    '..',
    'node_modules',
    style,
    'bin',
    'cmd.js'
  )
}

module.exports = {
  standard: pathToModule('standard'),
  semiStandard: pathToModule('semistandard'),
  happiness: pathToModule('happiness'),
  noStyle: path.join(__dirname, 'no-style.js')
}
<|endoftext|>


<commit_before>'use strict';

var common = require('./rules/common.json');
var sMarketplace = require('./rules/marketplace.json');

var Warning = function (message) {
  this.name = 'Warning';
  this.message = message || '';
};

Warning.prototype = Error.prototype;

var clean = function (word) {
  return word.toString().trim();
};

var camelCase = function (word) {
  var words = word.split('_');
  var finalWord = [];

  words.forEach(function (w) {
    finalWord.push(clean(w.charAt(0).toUpperCase() + w.slice(1)));
  });

  return finalWord.join('');
};

var Manifest = function () {
  this.manifest;
  this.appType = 'mkt';

  var errors = {};
  var warnings = {};

  var self = this;

  var hasValidJSON = function (content) {
    try {
      if (typeof content !== 'object') {
        self.manifest = JSON.parse(content);
      } else {
        self.manifest = content;
      }
    } catch (err) {
      throw new Error('Manifest is not in a valid JSON format or has invalid properties');
    }
  };

  var hasMandatoryKeys = function () {
    var missingKeys = [];
    var keys = common.required;

    if (self.appType === 'mkt') {
      keys = keys.concat(sMarketplace.required);
    }

    for (var i = 0; i < keys.length; i ++) {
      var currKey = camelCase(keys[i]);

      if (!self.manifest || !self.manifest[keys[i]]) {
        errors['MandatoryField' + currKey] = new Error('Mandatory field ' + keys[i] + ' is missing');
      }
    }
  };

  var hasValidPropertyTypes = function () {
    for (var k in self.manifest) {
      if (typeof self.manifest[k] !== common.properties[k].type) {
        errors['InvalidPropertyType' + camelCase(k)] = new Error('`' + k +
               '` must be of type `' + common.properties[k].type + '`');
      }
    }
  };

  var hasValidStringItem = function () {
    for (var k in self.manifest) {
      if (common.properties[k].oneOf && common.properties[k].oneOf.indexOf(self.manifest[k]) === -1) {
        errors['InvalidStringType' + camelCase(k)] = new Error('`' + k +
               '` must be one of the following: ' + common.properties[k].oneOf.toString());
      } else if (common.properties[k].anyOf) {
        self.manifest[k].split(',').forEach(function (v) {
          if (common.properties[k].anyOf.indexOf(v.trim()) === -1) {
            errors['InvalidStringType' + camelCase(k)] = new Error('`' + k +
               '` must be any of the following: ' + common.properties[k].anyOf.toString());
          }
        });
      }
    }
  };

  var hasRequiredLength = function () {
    for (var k in self.manifest) {
      if (common.properties[k].minLength && self.manifest[k].toString().length < common.properties[k].minLength) {
        errors['InvalidPropertyLength' + camelCase(k)] = new Error(
          '`' + k + '` must not be empty');
      }

      if (common.properties[k].maxLength && self.manifest[k].toString().length > common.properties[k].maxLength) {
        errors['InvalidPropertyLength' + camelCase(k)] = new Error(
          '`' + k + '` must not exceed length ' + common.properties[k].maxLength);
      }
    }
  };

  var hasValidLaunchPath = function () {
    if (self.manifest.launch_path) {
      var pattern = new RegExp(common.properties.launch_path.pattern);
      var launchPath = clean(self.manifest.launch_path);

      if (launchPath.length > 0) {
        if (pattern.test(launchPath) === false) {
          errors['InvalidLaunchPath'] = new Error("`launch_path` must be a path relative to app's origin");
        }
      }
    }
  };

  // Icon validation is based on a key name that is tied to the icon size. Can't set this in the schema
  // if it is an arbitrary natural number, so this will suffice for current manifest validation.
  var hasValidIconSizeAndPath = function () {
    if (self.manifest.icons) {
      for (var k in self.manifest.icons) {
        var key = parseInt(k, 10);

        if (isNaN(key) || key < 1) {
          errors['InvalidIconSize' + camelCase(k)] = new Error('Icon size must be a natural number');
        }

        if (!self.manifest.icons[k] || self.manifest.icons[k].length < 1) {
          errors['InvalidIconPath' + camelCase(k)] = new Error('Paths to icons must be absolute paths, relative URIs, or data URIs');
        }
      }
    }
  };

  var hasValidVersion = function () {
    if (self.manifest.version) {
      var pattern = new RegExp(common.properties.version.pattern);

      if (pattern.test(clean(self.manifest.version)) === false) {
        errors['InvalidVersion'] = new Error('`version` is in an invalid format.');
      }
    }
  };

  var hasValidDefaultLocale = function () {
    // only relevant if locales property is not empty
    var error = new Error('`default_locale` must match one of the keys in `locales`');

    if (self.manifest.locales) {
      if (!self.manifest.default_locale) {
        errors['InvalidDefaultLocale'] = error;
      } else {
        var languages = [];

        for (var i in self.manifest.locales) {
          languages.push(i);
        }

        if (languages.indexOf(self.manifest.default_locale) === -1) {
          errors['InvalidDefaultLocale'] = error;
        }
      }
    }
  };

  this.validate = function (content) {
    errors = {};
    warnings = {};

    hasValidJSON(content);
    hasMandatoryKeys();
    hasValidPropertyTypes();
    hasRequiredLength();
    hasValidLaunchPath();
    hasValidIconSizeAndPath();
    hasValidVersion();
    hasValidStringItem();
    hasValidDefaultLocale();

    return {
      errors: errors,
      warnings: warnings
    };
  };
};

module.exports = Warning;
module.exports = Manifest;

/*

Actual rules here https://github.com/mozilla/app-validator/blob/master/appvalidator/specs/webapps.py

var RULES = {
  "required_nodes_when": {"default_locale": lambda n: "locales" in n},
  "disallowed_nodes": ["widget"],
  "child_nodes": {
    "developer":
        {"expected_type": "object",
         "child_nodes": {"name": {"expected_type": "string",
                                  "not_empty": true},
                         "url": {"expected_type": "string",
                                 "not_empty": true,
                                 "process":
                                     lambda s: s.process_dev_url}},
         "required_nodes": ["name"],
         "allowed_once_nodes": ["url", "email"]},
    "installs_allowed_from": {"expected_type": "object",
                              "process": lambda s: s.process_iaf,
                              "not_empty": true},
    "screen_size":
        {"expected_type": "object",
         "allowed_once_nodes": ["min_height", "min_width"],
         "not_empty": true,
         "child_nodes":
             {"min_height":
                  {"expected_type": "number",
                   "process": lambda s: s.process_screen_size},
              "min_width":
                  {"expected_type": "number",
                   "process": lambda s: s.process_screen_size}}},
    "required_features": {"expected_type": "object"},
    "fullscreen": {"expected_type": "string",
                   "values": ["true", "false"]},
    "appcache_path": {"expected_type": "string",
                      "process": lambda s: s.process_appcache_path},
    "type": {"expected_type": "string",
             "process": lambda s: s.process_type},
    "activities": {
        "expected_type": "object",
        "allowed_nodes": ["*"],
        "child_nodes": {
            "*": {
                "expected_type": "object",
                "required_nodes": ["href"],
                "allowed_once_nodes": [
                    "disposition", "filters", "returnValue"
                ],
                "child_nodes": WEB_ACTIVITY_HANDLER,
            }
        }
    },
    "inputs": {
        "expected_type": "object",
        "allowed_nodes": ["*"],
        "not_empty": true,
        "child_nodes": {
            "*": {
                "expected_type": "object",
                "required_nodes": ["launch_path", "name", "description",
                                   "types"],
                "allowed_once_nodes": ["locales"],
                "child_nodes": INPUT_DEF_OBJ
            }
        }
    },
    "permissions": {
        "allowed_nodes": PERMISSIONS['web'] |
                         PERMISSIONS['privileged'] |
                         PERMISSIONS['certified'],
        "expected_type": "object",
        "unknown_node_level": "error",
        "child_nodes": {
            "*": {
                "expected_type": "object",
                "required_nodes": ["description"],
                "allowed_once_nodes": ["access"],
                "child_nodes": {
                    "description": {"expected_type": "string",
                                    "not_empty": true},
                    "access": {"expected_type": "string",
                               "not_empty": true}
                }
            }
        },
        "process": lambda s: s.process_permissions
    },
    "messages": {
        "expected_type": "object",
        "process": lambda s: s.process_messages,
    },
    "redirects": {
        "expected_type": "object",
        "child_nodes": {
            "expected_type": "object",
            "required_nodes": ["to", "from"],
            "child_nodes": {
                "to": {"expected_type": "string",
                       "not_empty": true},
                "from": {"expected_type": "string",
                         "not_empty": true},
            }
        },
    },
    "origin": {
        "expected_type": "string",
        "value_matches": /^app:\/\/[a-z0-9]+([-.{1}[a-z0-9]+)/
                         /\.[a-z]{2,5}$/]
        "process": lambda s: s.process_origin,
    },
    "chrome": {
        "expected_type": "object",
        "unknown_node_level": "error",
        "allowed_nodes": ["navigation"],
        "child_nodes": {
            "navigation": {"expected_type": "boolean"}
        }
    }
  }
};
*/
<commit_msg>remove stub comment for default_locale<commit_after>'use strict';

var common = require('./rules/common.json');
var sMarketplace = require('./rules/marketplace.json');

var Warning = function (message) {
  this.name = 'Warning';
  this.message = message || '';
};

Warning.prototype = Error.prototype;

var clean = function (word) {
  return word.toString().trim();
};

var camelCase = function (word) {
  var words = word.split('_');
  var finalWord = [];

  words.forEach(function (w) {
    finalWord.push(clean(w.charAt(0).toUpperCase() + w.slice(1)));
  });

  return finalWord.join('');
};

var Manifest = function () {
  this.manifest;
  this.appType = 'mkt';

  var errors = {};
  var warnings = {};

  var self = this;

  var hasValidJSON = function (content) {
    try {
      if (typeof content !== 'object') {
        self.manifest = JSON.parse(content);
      } else {
        self.manifest = content;
      }
    } catch (err) {
      throw new Error('Manifest is not in a valid JSON format or has invalid properties');
    }
  };

  var hasMandatoryKeys = function () {
    var missingKeys = [];
    var keys = common.required;

    if (self.appType === 'mkt') {
      keys = keys.concat(sMarketplace.required);
    }

    for (var i = 0; i < keys.length; i ++) {
      var currKey = camelCase(keys[i]);

      if (!self.manifest || !self.manifest[keys[i]]) {
        errors['MandatoryField' + currKey] = new Error('Mandatory field ' + keys[i] + ' is missing');
      }
    }
  };

  var hasValidPropertyTypes = function () {
    for (var k in self.manifest) {
      if (typeof self.manifest[k] !== common.properties[k].type) {
        errors['InvalidPropertyType' + camelCase(k)] = new Error('`' + k +
               '` must be of type `' + common.properties[k].type + '`');
      }
    }
  };

  var hasValidStringItem = function () {
    for (var k in self.manifest) {
      if (common.properties[k].oneOf && common.properties[k].oneOf.indexOf(self.manifest[k]) === -1) {
        errors['InvalidStringType' + camelCase(k)] = new Error('`' + k +
               '` must be one of the following: ' + common.properties[k].oneOf.toString());
      } else if (common.properties[k].anyOf) {
        self.manifest[k].split(',').forEach(function (v) {
          if (common.properties[k].anyOf.indexOf(v.trim()) === -1) {
            errors['InvalidStringType' + camelCase(k)] = new Error('`' + k +
               '` must be any of the following: ' + common.properties[k].anyOf.toString());
          }
        });
      }
    }
  };

  var hasRequiredLength = function () {
    for (var k in self.manifest) {
      if (common.properties[k].minLength && self.manifest[k].toString().length < common.properties[k].minLength) {
        errors['InvalidPropertyLength' + camelCase(k)] = new Error(
          '`' + k + '` must not be empty');
      }

      if (common.properties[k].maxLength && self.manifest[k].toString().length > common.properties[k].maxLength) {
        errors['InvalidPropertyLength' + camelCase(k)] = new Error(
          '`' + k + '` must not exceed length ' + common.properties[k].maxLength);
      }
    }
  };

  var hasValidLaunchPath = function () {
    if (self.manifest.launch_path) {
      var pattern = new RegExp(common.properties.launch_path.pattern);
      var launchPath = clean(self.manifest.launch_path);

      if (launchPath.length > 0) {
        if (pattern.test(launchPath) === false) {
          errors['InvalidLaunchPath'] = new Error("`launch_path` must be a path relative to app's origin");
        }
      }
    }
  };

  // Icon validation is based on a key name that is tied to the icon size. Can't set this in the schema
  // if it is an arbitrary natural number, so this will suffice for current manifest validation.
  var hasValidIconSizeAndPath = function () {
    if (self.manifest.icons) {
      for (var k in self.manifest.icons) {
        var key = parseInt(k, 10);

        if (isNaN(key) || key < 1) {
          errors['InvalidIconSize' + camelCase(k)] = new Error('Icon size must be a natural number');
        }

        if (!self.manifest.icons[k] || self.manifest.icons[k].length < 1) {
          errors['InvalidIconPath' + camelCase(k)] = new Error('Paths to icons must be absolute paths, relative URIs, or data URIs');
        }
      }
    }
  };

  var hasValidVersion = function () {
    if (self.manifest.version) {
      var pattern = new RegExp(common.properties.version.pattern);

      if (pattern.test(clean(self.manifest.version)) === false) {
        errors['InvalidVersion'] = new Error('`version` is in an invalid format.');
      }
    }
  };

  var hasValidDefaultLocale = function () {
    // only relevant if locales property is not empty
    var error = new Error('`default_locale` must match one of the keys in `locales`');

    if (self.manifest.locales) {
      if (!self.manifest.default_locale) {
        errors['InvalidDefaultLocale'] = error;
      } else {
        var languages = [];

        for (var i in self.manifest.locales) {
          languages.push(i);
        }

        if (languages.indexOf(self.manifest.default_locale) === -1) {
          errors['InvalidDefaultLocale'] = error;
        }
      }
    }
  };

  this.validate = function (content) {
    errors = {};
    warnings = {};

    hasValidJSON(content);
    hasMandatoryKeys();
    hasValidPropertyTypes();
    hasRequiredLength();
    hasValidLaunchPath();
    hasValidIconSizeAndPath();
    hasValidVersion();
    hasValidStringItem();
    hasValidDefaultLocale();

    return {
      errors: errors,
      warnings: warnings
    };
  };
};

module.exports = Warning;
module.exports = Manifest;

/*

Actual rules here https://github.com/mozilla/app-validator/blob/master/appvalidator/specs/webapps.py

var RULES = {
  "disallowed_nodes": ["widget"],
  "child_nodes": {
    "developer":
        {"expected_type": "object",
         "child_nodes": {"name": {"expected_type": "string",
                                  "not_empty": true},
                         "url": {"expected_type": "string",
                                 "not_empty": true,
                                 "process":
                                     lambda s: s.process_dev_url}},
         "required_nodes": ["name"],
         "allowed_once_nodes": ["url", "email"]},
    "installs_allowed_from": {"expected_type": "object",
                              "process": lambda s: s.process_iaf,
                              "not_empty": true},
    "screen_size":
        {"expected_type": "object",
         "allowed_once_nodes": ["min_height", "min_width"],
         "not_empty": true,
         "child_nodes":
             {"min_height":
                  {"expected_type": "number",
                   "process": lambda s: s.process_screen_size},
              "min_width":
                  {"expected_type": "number",
                   "process": lambda s: s.process_screen_size}}},
    "required_features": {"expected_type": "object"},
    "fullscreen": {"expected_type": "string",
                   "values": ["true", "false"]},
    "appcache_path": {"expected_type": "string",
                      "process": lambda s: s.process_appcache_path},
    "type": {"expected_type": "string",
             "process": lambda s: s.process_type},
    "activities": {
        "expected_type": "object",
        "allowed_nodes": ["*"],
        "child_nodes": {
            "*": {
                "expected_type": "object",
                "required_nodes": ["href"],
                "allowed_once_nodes": [
                    "disposition", "filters", "returnValue"
                ],
                "child_nodes": WEB_ACTIVITY_HANDLER,
            }
        }
    },
    "inputs": {
        "expected_type": "object",
        "allowed_nodes": ["*"],
        "not_empty": true,
        "child_nodes": {
            "*": {
                "expected_type": "object",
                "required_nodes": ["launch_path", "name", "description",
                                   "types"],
                "allowed_once_nodes": ["locales"],
                "child_nodes": INPUT_DEF_OBJ
            }
        }
    },
    "permissions": {
        "allowed_nodes": PERMISSIONS['web'] |
                         PERMISSIONS['privileged'] |
                         PERMISSIONS['certified'],
        "expected_type": "object",
        "unknown_node_level": "error",
        "child_nodes": {
            "*": {
                "expected_type": "object",
                "required_nodes": ["description"],
                "allowed_once_nodes": ["access"],
                "child_nodes": {
                    "description": {"expected_type": "string",
                                    "not_empty": true},
                    "access": {"expected_type": "string",
                               "not_empty": true}
                }
            }
        },
        "process": lambda s: s.process_permissions
    },
    "messages": {
        "expected_type": "object",
        "process": lambda s: s.process_messages,
    },
    "redirects": {
        "expected_type": "object",
        "child_nodes": {
            "expected_type": "object",
            "required_nodes": ["to", "from"],
            "child_nodes": {
                "to": {"expected_type": "string",
                       "not_empty": true},
                "from": {"expected_type": "string",
                         "not_empty": true},
            }
        },
    },
    "origin": {
        "expected_type": "string",
        "value_matches": /^app:\/\/[a-z0-9]+([-.{1}[a-z0-9]+)/
                         /\.[a-z]{2,5}$/]
        "process": lambda s: s.process_origin,
    },
    "chrome": {
        "expected_type": "object",
        "unknown_node_level": "error",
        "allowed_nodes": ["navigation"],
        "child_nodes": {
            "navigation": {"expected_type": "boolean"}
        }
    }
  }
};
*/
<|endoftext|>


<commit_before>
// utilities for working with the js-registry site.

exports.publish = publish;
exports.tag = tag;
exports.adduser = adduser;

var npm = require("../../npm"),
  http = require("http"),
  url = require("url"),
  log = require("./log"),
  uuid = require("./uuid"),
  sha = require("./sha"),
  sys = require("sys"),
  ini = require("./ini");

function tag (project, version, tag, cb) { PUT(project+"/"+tag, version, cb) }

function publish (data, tarball, cb) {
  // add the dist-url to the data, pointing at the tarball.
  // if the {name} isn't there, then create it.
  // if the {version} is already there, then fail.
  // then:
  // PUT the data to {config.registry}/{data.name}/{data.version}
  try {
    reg();
  } catch (ex) {
    return cb(ex);
  }
  var fullData =
    { "_id" : data.name
    , "name" : data.name
    , "description" : data.description
    , "dist-tags" : {}
    , "versions" : {}
    };
  fullData.versions[ data.version ] = data;
  data._id = data.name+"-"+data.version;
  data.dist = { "tarball" : tarball };

  // first try to just PUT the whole fullData, and this will fail if it's
  // already there, because it'll be lacking a _rev, so couch'll bounce it.
  PUT(data.name, fullData, function (er) {
    if (!er) return cb();
    // there was an error, so assume the fullData is already there.
    // now try to create just this version.  This time, failure
    // is not ok.
    // Note: the first might've failed for a down host or something,
    // in which case this will likely fail, too.  If the host was down for the
    // first req, but now it's up, then this may fail for not having the
    // project created yet, or because the user doesn't have access to it.
    PUT(data.name+"/"+data.version, data, function (er) {
      if (er) return cb(er);
      cb();
    });
  });
}

function PUT (where, what, cb) {
  reg();
  what = JSON.stringify(what);
  log(where, "registryPUT");
  where = ini.config.registry + where;

  var u = url.parse(where),
    headers = { "content-type" : "application/json"
              , "host" : u.host
              , "content-length" : what.length
              };
  if (ini.config.auth) {
    headers.authorization = 'Basic ' + ini.config.auth;
  }
  log(sys.inspect(u), "registryPUT");
  log(u.port || (u.protocol === "https:" ? 443 : 80), "registryPUT port");

  var client = http.createClient(u.port || (u.protocol === "https:" ? 443 : 80), u.hostname);
  if (u.protocol === "https:") {
    client.setSecure("X509_PEM");
  }
  var request = client.request("PUT", u.pathname, headers);

  log(sys.inspect(headers), "registryPUT headers");

  request.addListener("response", function (response) {
    // if (response.statusCode !== 200) return cb(new Error(
    //   "Status code " + response.statusCode + " from PUT "+where));
    var data = "";
    response.setBodyEncoding("utf8");
    response.addListener("data", function (chunk) { data += chunk });
    response.addListener("end", function () {
      log(data, "registryPUT");
      try {
        data = JSON.parse(data);
      } catch (ex) {
        ex.message += "\n" + data;
        return cb(ex, data, response);
      }
      if (data.error) return cb(new Error(
        data.error + (" "+data.reason || "")));
      cb(null, data, response);
    });
  });
  request.write(what, "utf8");
  request.end();
}

function reg () {
  if (!ini.config.registry) throw new Error(
    "Must define registry before publishing.");
  log(ini.config.registry, "registry");
  if (ini.config.registry.substr(-1) !== "/") {
    ini.config.registry += "/";
  }
}

function adduser (username, password, email, callback) {
  var salt = uuid.generate()
  var userobj =
    { '_id' : 'org.couchdb.user:'+username
    , name : username
    , type : "user"
    , roles : []
    , salt : salt
    , password_sha : sha.hex_sha1(password + salt)
    , email : email
    };
  PUT('/adduser/org.couchdb.user:'+username, userobj, function (error, data, response) {
    // if the error is a 409, then update the rev.
    if (error || response.statusCode !== 201) {
      callback(new Error(
        "Could not create user "+error+'\n'+data));
    } else {
      callback();
    }
  });
}
<commit_msg>comma first<commit_after>
// utilities for working with the js-registry site.

exports.publish = publish
exports.tag = tag
exports.adduser = adduser

var npm = require("../../npm")
  , http = require("http")
  , url = require("url")
  , log = require("./log")
  , uuid = require("./uuid")
  , sha = require("./sha")
  , sys = require("sys")
  , ini = require("./ini")

function tag (project, version, tag, cb) { PUT(project+"/"+tag, version, cb) }

function publish (data, tarball, cb) {
  // add the dist-url to the data, pointing at the tarball.
  // if the {name} isn't there, then create it.
  // if the {version} is already there, then fail.
  // then:
  // PUT the data to {config.registry}/{data.name}/{data.version}
  try {
    reg()
  } catch (ex) {
    return cb(ex)
  }
  var fullData =
    { "_id" : data.name
    , "name" : data.name
    , "description" : data.description
    , "dist-tags" : {}
    , "versions" : {}
    }
  fullData.versions[ data.version ] = data
  data._id = data.name+"-"+data.version
  data.dist = { "tarball" : tarball }

  // first try to just PUT the whole fullData, and this will fail if it's
  // already there, because it'll be lacking a _rev, so couch'll bounce it.
  PUT(data.name, fullData, function (er) {
    if (!er) return cb()
    // there was an error, so assume the fullData is already there.
    // now try to create just this version.  This time, failure
    // is not ok.
    // Note: the first might've failed for a down host or something,
    // in which case this will likely fail, too.  If the host was down for the
    // first req, but now it's up, then this may fail for not having the
    // project created yet, or because the user doesn't have access to it.
    PUT(data.name+"/"+data.version, data, function (er) {
      if (er) return cb(er)
      cb()
    })
  })
}

function PUT (where, what, cb) {
  reg()
  what = JSON.stringify(what)
  log(where, "registryPUT")
  where = ini.config.registry + where

  var u = url.parse(where)
    , headers = { "content-type" : "application/json"
                , "host" : u.host
                , "content-length" : what.length
                }
  if (ini.config.auth) {
    headers.authorization = 'Basic ' + ini.config.auth
  }
  log(sys.inspect(u), "registryPUT")
  log(u.port || (u.protocol === "https:" ? 443 : 80), "registryPUT port")

  var client = http.createClient(u.port || (u.protocol === "https:" ? 443 : 80), u.hostname)
  if (u.protocol === "https:") {
    client.setSecure("X509_PEM")
  }
  var request = client.request("PUT", u.pathname, headers)

  log(sys.inspect(headers), "registryPUT headers")

  request.addListener("response", function (response) {
    // if (response.statusCode !== 200) return cb(new Error(
    //   "Status code " + response.statusCode + " from PUT "+where))
    var data = ""
    response.setBodyEncoding("utf8")
    response.addListener("data", function (chunk) { data += chunk })
    response.addListener("end", function () {
      log(data, "registryPUT")
      try {
        data = JSON.parse(data)
      } catch (ex) {
        ex.message += "\n" + data
        return cb(ex, data, response)
      }
      if (data.error) return cb(new Error(
        data.error + (" "+data.reason || "")))
      cb(null, data, response)
    })
  })
  request.write(what, "utf8")
  request.end()
}

function reg () {
  if (!ini.config.registry) throw new Error(
    "Must define registry before publishing.")
  log(ini.config.registry, "registry")
  if (ini.config.registry.substr(-1) !== "/") {
    ini.config.registry += "/"
  }
}

function adduser (username, password, email, callback) {
  var salt = uuid.generate()
  var userobj =
    { '_id' : 'org.couchdb.user:'+username
    , name : username
    , type : "user"
    , roles : []
    , salt : salt
    , password_sha : sha.hex_sha1(password + salt)
    , email : email
    }
  PUT('/adduser/org.couchdb.user:'+username, userobj, function (error, data, response) {
    // if the error is a 409, then update the rev.
    if (error || response.statusCode !== 201) {
      callback(new Error(
        "Could not create user "+error+'\n'+data))
    } else {
      callback()
    }
  })
}
<|endoftext|>


<commit_before>(function () {
    'use strict';

    var d       = require('dejavu'),
        utils   = require('amd-utils'),
        colors  = require('colors'),
        fs      = require('fs'),
        util    = require('util'),
        async   = require('async'),
        path    = require('path'),
        inter   = require('./lib/castInterpolate')
    ;

    // set up a useful set of formats
    colors.setTheme({
        input:   'grey',
        info:    'green',
        data:    'grey',
        help:    'cyan',
        warning: 'yellow',
        debug:   'blue',
        error:   'red'
    });

    var Automaton = d.Class.declare({
        $name: 'Automaton',

        _tasks: [],
        _verbosity: 1,

        /**
         * Constructor.
         */
        initialize: function () {
            // load core tasks
            this.loadTasks(__dirname + '/tasks');
        },

        /**
         * Set verbosity level.
         *
         * 0 means no logging.
         * 1 means only 1 deep level tasks are able to log.
         * 2 means 1 and 2 deep level tasks are able to log.
         * And so on..
         *
         * @param {Number} depth The depth
         *
         * @return {Automaton} Chainable!
         */
        setVerbosity: function (depth) {
            this._verbosity = depth;

            return this;
        },

        /**
         * Add new task.
         * If the task already exists, it will be replaced.
         * The task will be validaded only when its run.
         *
         * @param {Object} task The task definition
         *
         * @return {Automaton} Chainable!
         */
        addTask: function (task) {
            this._assertIsObject(task, 'Invalid task provided');
            this._assertIsString(task.id, 'You have provided a task without ID');

            this._tasks[task.id] = task;

            return this;
        },

        /**
         * Remove task.
         *
         * @param {String} id The task id
         *
         * @return {Automaton} Chainable!
         */
        removeTask: function (id) {
            this._assertIsString(id, 'Invalid task id provided \'' + id + '\'');
            delete this._tasks[id];

            return this;
        },

        /**
         * Run a task.
         * The callback follows the node style.
         *
         * @param {String|Object} task        The task id or definition
         * @param {Object}        [$options]  The task options
         * @param {Function}      [$callback] A callback to be called when the task completes
         *
         * @return {Automaton} Chainable!
         */
        run: function (task, $options, $callback) {
            var batch  = this._batchTask(task, $options);

            async.waterfall(batch, function (err) {
                if (err) {
                    if (utils.lang.isFunction($callback)) {
                        $callback(new Error(err));
                    } else {
                        this._throwError(err);
                    }
                } else {
                    // TODO: all done, should we output something?
                    if (utils.lang.isFunction($callback)) {
                        $callback();
                    }
                }
            }.$bind(this));

            return this;
        },

        /**
         * Load tasks in a given folder.
         *
         * @param {String} folder The folder to search for tasks
         *
         * @return {Automaton} Chainable!
         */
        loadTasks: function (folder) {
            this._assertIsString(folder, 'Expected folder to be a string');

            folder = fs.realpathSync(folder) + '/';

            var filenames = fs.readdirSync(folder),
                i
            ;

            for (i = filenames.length - 1; i >= 0; --i) {
                this.addTask(require(folder + path.basename(filenames[i], '.js')));
            }

            return this;
        },

        /**
         * Retrieve a task definition by its id.
         *
         * @param {String} taskId The task id
         *
         * @return {Object} The task definition
         */
        getTask: function (taskId) {
            this._assertTaskLoaded(taskId);

            return this._tasks[taskId];
        },

        /**
         * Create a batch for a task.
         * The batch is a sequence of functions that form the task.
         *
         * @param {String|Object} task       The task id or definition
         * @param {Object}        [$options] The task options
         */
        _batchTask: function (task, options) {
            var batch = [],       // batch of tasks
                option,
                filter,
                parentOptions = arguments[2] || {},
                depth = arguments[3] != null ? arguments[3] : 1
            ;

            options = options || {};

            ///////////////////////////////////////////////////////////////

            // if task is an id, grab its real definition
            if (utils.lang.isString(task)) {
                this._assertTaskLoaded(task);
                task = this._tasks[task];
            }

            this._assertIsObject(task, 'Expected task to be an object');

            if (!utils.lang.isString(task.id)) {
                task.id = 'Non-identified task';
            }

            if (task.filter) {
                this._assertIsFunction(task.filter, 'Expected filter to be a function in \'' + task.id + '\' task');
            }

            // check if all the task required options were provided
            // if task has a definition of the options
            if (task.options) {
                this._assertIsObject(task.options, 'Expected options to be an object in \'' + task.id + '\' task');

                // fill in the options with default values where the option was not provided
                for (option in task.options) {
                    if (options[option] === undefined && task.options[option]['default'] !== undefined) {
                        options[option] = task.options[option]['default'];
                    }
                }

                if (task.filter) {
                    // besides calling the filter, we need to validate the required options afterwards.
                    filter = function (next) {
                        // replace the options
                        this._replaceOptions(options, parentOptions);
                        // run the filter and validate task afterwards
                        async.waterfall([
                            // TODO: this could be a loggin interface
                            task.filter.$bind(this, options),
                            function (next) {
                                this._validateTaskOptions(task, options);
                                next();
                            }.$bind(this)
                        ], next);
                    }.$bind(this);
                } else {
                    filter = function (next) {
                        // no filter, so we only replace options and validate the required options
                        this._replaceOptions(options, parentOptions);
                        this._validateTaskOptions(task, options);
                        next();
                    }.$bind(this);
                }
            } else if (task.filter) {
                // TODO: this could be a loggin interface
                filter = task.filter.$bind(this, options);
            }

            if (filter) {
                batch.push(filter);
            }

            ///////////////////////////////////////////////////////////////

            this._assertIsArray(task.tasks, 'Expected subtasks to be an array in \'' + task.id + '\' task');
            task.tasks.forEach(function (currentSubtask, i) {
                var subtaskBatch;

                this._assertIsObject(currentSubtask, 'Invalid subtask specified at index \'' + i + '\' in \'' + task.id + '\' task');

                if (!utils.lang.isString(currentSubtask.task) && !utils.lang.isFunction(currentSubtask.task)) {
                    this._throwError('Subtask type at index \'' + i + '\' must be a function or an object in \'' + task.id + '\' task');
                }

                // if it's a function, just add it to the batch
                if (utils.lang.isFunction(currentSubtask.task)) {
                    batch.push(function (subtask, next) {
                        // we need to replace options again because parent filter might have
                        // added options that are placeholders
                        this._replaceOptions(options, parentOptions);
                        // skip task if disabled
                        if (!this._isTaskEnabled(subtask, options)) {
                            return next();
                        }
                        this._reportNextTask(this, subtask, options, depth);
                        // TODO: the this of the task could be a logging interface
                        subtask.task.call(this, options, next);
                    }.$bind(this, currentSubtask));
                // it's not a function, then it must be another task, check if it is loaded, and batch it
                } else if (utils.lang.isString(currentSubtask.task)) {
                    this._assertTaskLoaded(currentSubtask.task);

                    subtaskBatch = this._batchTask(currentSubtask.task, currentSubtask.options, options, depth + 1);
                    batch.push(function (subtask, subtaskBatch, next) {
                        // we need to replace options again because parent filter might have
                        // added options that are placeholders
                        this._replaceOptions(options, parentOptions);
                        // skip task if disabled
                        if (!this._isTaskEnabled(subtask, options)) {
                            return next();
                        }
                        this._reportNextTask(this, subtask, options, depth);
                        async.waterfall(subtaskBatch, next);
                    }.$bind(this, currentSubtask, subtaskBatch));
                }
            }, this);

            return batch;
        },

        /**
         * Validate the task options.
         * Detects if a task is missing required options.
         *
         * @param {Object} task    The task definition
         * @param {Object} options The task options
         */
        _validateTaskOptions: function (task, options) {
            // for each option in the definition
            for (var option in task.options) {
                // if option was not provided to the task, abort
                if (options[option] === undefined) {
                    this._throwError('Missing option \'' + option + '\' in \'' + task.id + '\' task');
                }
            }
        },

        /**
         * Check if a task is enabled.
         * Disabled tasks should be skipped.
         *
         * Detects if a task is missing required options.
         *
         * @param {Object} task    The task definition
         * @param {Object} options The task options
         */
        _isTaskEnabled: function (task, options) {
            if (task.hasOwnProperty('on')) {
                return utils.lang.isString(task.on) ?
                    !!this._replacePlaceholders(task.on, options, true)
                    : task.on;
            }

            return true;
        },

        /**
         * Replace target placeholders with their correspondent options value.
         * If the target is an array or an object, it will replace them
         * recursively.
         *
         * @param {Mixed}  target  The target which will get its values replaced
         * @param {Object} options The options
         *
         * @return {Mixed} The passed target
         */
        _replaceOptions: function (target, options) {
            var k;

            if (utils.lang.isObject(target)) {
                for (k in target) {
                    target[k] = this._replaceOptions(target[k], options);
                }
            } else if (utils.lang.isArray(target)) {
                for (k = target.length - 1; k >= 0; --k) {
                    target[k] = this._replaceOptions(target[k], options);
                }
            } else if (utils.lang.isString(target)) {
                target = this._replacePlaceholders(target, options);
            }

            return target;
        },

        /**
         * Replace placeholders in a string with their correspondent options value
         *
         * @param {String} str                   The string
         * @param {Object} options               The options
         * @param {Boolean} [$removeUnspecified] True to remove placeholders that had no matchings, false otherwise
         *
         * @return {String} The replaced string
         */
        _replacePlaceholders: function (str, options, removeUnspecified) {
            return inter(str, options, removeUnspecified);
        },

        /**
         * Parses a task description.
         * The description can be a string or a function.
         * If it's a function, it will be called with the options, in order to
         * generate a complex description based on the them.
         * Placeholders will be handled with Automaton#_replacePlaceholders.
         *
         * @param  {String|Function} description The task description
         * @param  {Object}          options     The task options
         *
         * @return {String} The parsed description
         */
        _parseDescription: function (description, options) {
            if (utils.lang.isFunction(description)) {
                description = description(options);
            } else if (description != null) {
                description = this._replacePlaceholders(description, options, true);
            }

            return description;
        },

        /**
         * Report the next task that will run.
         *
         * @param {Object} task    The task definition
         * @param {Object} options The task options
         * @param {Number} depth   The task depth
         */
        _reportNextTask: function (task, options, depth) {
            if (task.description) {
                this._log('  - ' + task.description.cyan, depth, false);
            }
        },

        /**
         * Assert task is loaded.
         *
         * @param {String} taskId The task id
         */
        _assertTaskLoaded: function (taskId) {
            if (!utils.lang.isObject(this._tasks[taskId])) {
                this._throwError('Could not find any task handler suitable for \'' + taskId + '\'');
            }
        },

        /**
         * Assert string.
         *
         * @param {Mixed} variable  The target to assert
         * @param {String} errorMsg The error message to show if the assert fails
         */
        _assertIsString: function (variable, errorMsg) {
            if (!utils.lang.isString(variable)) {
                this._throwError(errorMsg);
            }
        },

        /**
         * Assert object.
         *
         * @param {Mixed} variable  The target to assert
         * @param {String} errorMsg The error message to show if the assert fails
         */
        _assertIsObject: function (variable, errorMsg) {
            if (!utils.lang.isObject(variable)) {
                this._throwError(errorMsg);
            }
        },

        /**
         * Assert function.
         *
         * @param {Mixed} variable  The target to assert
         * @param {String} errorMsg The error message to show if the assert fails
         */
        _assertIsFunction: function (variable, errorMsg) {
            if (!utils.lang.isFunction(variable)) {
                this._throwError(errorMsg);
            }
        },

        /**
         * Assert array.
         *
         * @param {Mixed} variable  The target to assert
         * @param {String} errorMsg The error message to show if the assert fails
         */
        _assertIsArray: function (variable, errorMsg) {
            if (!utils.lang.isArray(variable)) {
                this._throwError(errorMsg);
            }
        },

        // TODO: the functions bellow are not stable..
        //       streams will be added and we can't do
        //       process.exit() because we won't be able to use
        //       automaton programatically
        _throwError: function (errorMsg, $verbose) {
            if (!$verbose) {
                console.error('\n' + errorMsg.error + '\n');
                process.exit(1);
            }

            throw new Error(errorMsg);
        },

        _log: function (msg, $depth, $newLine) {
            $depth   = $depth || 0;
            $newLine = $newLine || true;

            if ($depth <= this._verbosity) {
                if ($newLine) {
                    util.puts(msg);
                } else {
                    util.print(msg);
                }
            }
        },

        _error: function (msg, $depth) {
            $depth = $depth || 0;
            if ($depth <= this._verbosity) {
                console.error(msg);
            }
        }
    });

    module.exports = new Automaton();

})();<commit_msg>Fix reporting.<commit_after>(function () {
    'use strict';

    var d       = require('dejavu'),
        utils   = require('amd-utils'),
        colors  = require('colors'),
        fs      = require('fs'),
        util    = require('util'),
        async   = require('async'),
        path    = require('path'),
        inter   = require('./lib/castInterpolate')
    ;

    // set up a useful set of formats
    colors.setTheme({
        input:   'grey',
        info:    'green',
        data:    'grey',
        help:    'cyan',
        warning: 'yellow',
        debug:   'blue',
        error:   'red'
    });

    var Automaton = d.Class.declare({
        $name: 'Automaton',

        _tasks: [],
        _verbosity: 1,

        /**
         * Constructor.
         */
        initialize: function () {
            // load core tasks
            this.loadTasks(__dirname + '/tasks');
        },

        /**
         * Set verbosity level.
         *
         * 0 means no logging.
         * 1 means only 1 deep level tasks are able to log.
         * 2 means 1 and 2 deep level tasks are able to log.
         * And so on..
         *
         * @param {Number} depth The depth
         *
         * @return {Automaton} Chainable!
         */
        setVerbosity: function (depth) {
            this._verbosity = depth;

            return this;
        },

        /**
         * Add new task.
         * If the task already exists, it will be replaced.
         * The task will be validaded only when its run.
         *
         * @param {Object} task The task definition
         *
         * @return {Automaton} Chainable!
         */
        addTask: function (task) {
            this._assertIsObject(task, 'Invalid task provided');
            this._assertIsString(task.id, 'You have provided a task without ID');

            this._tasks[task.id] = task;

            return this;
        },

        /**
         * Remove task.
         *
         * @param {String} id The task id
         *
         * @return {Automaton} Chainable!
         */
        removeTask: function (id) {
            this._assertIsString(id, 'Invalid task id provided \'' + id + '\'');
            delete this._tasks[id];

            return this;
        },

        /**
         * Run a task.
         * The callback follows the node style.
         *
         * @param {String|Object} task        The task id or definition
         * @param {Object}        [$options]  The task options
         * @param {Function}      [$callback] A callback to be called when the task completes
         *
         * @return {Automaton} Chainable!
         */
        run: function (task, $options, $callback) {
            var batch  = this._batchTask(task, $options);

            async.waterfall(batch, function (err) {
                if (err) {
                    if (utils.lang.isFunction($callback)) {
                        $callback(new Error(err));
                    } else {
                        this._throwError(err);
                    }
                } else {
                    // TODO: all done, should we output something?
                    if (utils.lang.isFunction($callback)) {
                        $callback();
                    }
                }
            }.$bind(this));

            return this;
        },

        /**
         * Load tasks in a given folder.
         *
         * @param {String} folder The folder to search for tasks
         *
         * @return {Automaton} Chainable!
         */
        loadTasks: function (folder) {
            this._assertIsString(folder, 'Expected folder to be a string');

            folder = fs.realpathSync(folder) + '/';

            var filenames = fs.readdirSync(folder),
                i
            ;

            for (i = filenames.length - 1; i >= 0; --i) {
                this.addTask(require(folder + path.basename(filenames[i], '.js')));
            }

            return this;
        },

        /**
         * Retrieve a task definition by its id.
         *
         * @param {String} taskId The task id
         *
         * @return {Object} The task definition
         */
        getTask: function (taskId) {
            this._assertTaskLoaded(taskId);

            return this._tasks[taskId];
        },

        /**
         * Create a batch for a task.
         * The batch is a sequence of functions that form the task.
         *
         * @param {String|Object} task       The task id or definition
         * @param {Object}        [$options] The task options
         */
        _batchTask: function (task, options) {
            var batch = [],       // batch of tasks
                option,
                filter,
                parentOptions = arguments[2] || {},
                depth = arguments[3] != null ? arguments[3] : 1
            ;

            options = options || {};

            ///////////////////////////////////////////////////////////////

            // if task is an id, grab its real definition
            if (utils.lang.isString(task)) {
                this._assertTaskLoaded(task);
                task = this._tasks[task];
            }

            this._assertIsObject(task, 'Expected task to be an object');

            if (!utils.lang.isString(task.id)) {
                task.id = 'Non-identified task';
            }

            if (task.filter) {
                this._assertIsFunction(task.filter, 'Expected filter to be a function in \'' + task.id + '\' task');
            }

            // check if all the task required options were provided
            // if task has a definition of the options
            if (task.options) {
                this._assertIsObject(task.options, 'Expected options to be an object in \'' + task.id + '\' task');

                // fill in the options with default values where the option was not provided
                for (option in task.options) {
                    if (options[option] === undefined && task.options[option]['default'] !== undefined) {
                        options[option] = task.options[option]['default'];
                    }
                }

                if (task.filter) {
                    // besides calling the filter, we need to validate the required options afterwards.
                    filter = function (next) {
                        // replace the options
                        this._replaceOptions(options, parentOptions);
                        // run the filter and validate task afterwards
                        async.waterfall([
                            // TODO: this could be a loggin interface
                            task.filter.$bind(this, options),
                            function (next) {
                                this._validateTaskOptions(task, options);
                                next();
                            }.$bind(this)
                        ], next);
                    }.$bind(this);
                } else {
                    filter = function (next) {
                        // no filter, so we only replace options and validate the required options
                        this._replaceOptions(options, parentOptions);
                        this._validateTaskOptions(task, options);
                        next();
                    }.$bind(this);
                }
            } else if (task.filter) {
                // TODO: this could be a loggin interface
                filter = task.filter.$bind(this, options);
            }

            if (filter) {
                batch.push(filter);
            }

            ///////////////////////////////////////////////////////////////

            this._assertIsArray(task.tasks, 'Expected subtasks to be an array in \'' + task.id + '\' task');
            task.tasks.forEach(function (currentSubtask, i) {
                var subtaskBatch;

                this._assertIsObject(currentSubtask, 'Invalid subtask specified at index \'' + i + '\' in \'' + task.id + '\' task');

                if (!utils.lang.isString(currentSubtask.task) && !utils.lang.isFunction(currentSubtask.task)) {
                    this._throwError('Subtask type at index \'' + i + '\' must be a function or an object in \'' + task.id + '\' task');
                }

                // if it's a function, just add it to the batch
                if (utils.lang.isFunction(currentSubtask.task)) {
                    batch.push(function (subtask, next) {
                        // we need to replace options again because parent filter might have
                        // added options that are placeholders
                        this._replaceOptions(options, parentOptions);
                        // skip task if disabled
                        if (!this._isTaskEnabled(subtask, options)) {
                            return next();
                        }
                        this._reportNextTask(subtask, options, depth);
                        // TODO: the this of the task could be a logging interface
                        subtask.task.call(this, options, next);
                    }.$bind(this, currentSubtask));
                // it's not a function, then it must be another task, check if it is loaded, and batch it
                } else if (utils.lang.isString(currentSubtask.task)) {
                    this._assertTaskLoaded(currentSubtask.task);

                    subtaskBatch = this._batchTask(currentSubtask.task, currentSubtask.options, options, depth + 1);
                    batch.push(function (subtask, subtaskBatch, next) {
                        // we need to replace options again because parent filter might have
                        // added options that are placeholders
                        this._replaceOptions(options, parentOptions);
                        // skip task if disabled
                        if (!this._isTaskEnabled(subtask, options)) {
                            return next();
                        }
                        this._reportNextTask(subtask, options, depth);
                        async.waterfall(subtaskBatch, next);
                    }.$bind(this, currentSubtask, subtaskBatch));
                }
            }, this);

            return batch;
        },

        /**
         * Validate the task options.
         * Detects if a task is missing required options.
         *
         * @param {Object} task    The task definition
         * @param {Object} options The task options
         */
        _validateTaskOptions: function (task, options) {
            // for each option in the definition
            for (var option in task.options) {
                // if option was not provided to the task, abort
                if (options[option] === undefined) {
                    this._throwError('Missing option \'' + option + '\' in \'' + task.id + '\' task');
                }
            }
        },

        /**
         * Check if a task is enabled.
         * Disabled tasks should be skipped.
         *
         * Detects if a task is missing required options.
         *
         * @param {Object} task    The task definition
         * @param {Object} options The task options
         */
        _isTaskEnabled: function (task, options) {
            if (task.hasOwnProperty('on')) {
                return utils.lang.isString(task.on) ?
                    !!this._replacePlaceholders(task.on, options, true)
                    : task.on;
            }

            return true;
        },

        /**
         * Replace target placeholders with their correspondent options value.
         * If the target is an array or an object, it will replace them
         * recursively.
         *
         * @param {Mixed}  target  The target which will get its values replaced
         * @param {Object} options The options
         *
         * @return {Mixed} The passed target
         */
        _replaceOptions: function (target, options) {
            var k;

            if (utils.lang.isObject(target)) {
                for (k in target) {
                    target[k] = this._replaceOptions(target[k], options);
                }
            } else if (utils.lang.isArray(target)) {
                for (k = target.length - 1; k >= 0; --k) {
                    target[k] = this._replaceOptions(target[k], options);
                }
            } else if (utils.lang.isString(target)) {
                target = this._replacePlaceholders(target, options);
            }

            return target;
        },

        /**
         * Replace placeholders in a string with their correspondent options value
         *
         * @param {String} str                   The string
         * @param {Object} options               The options
         * @param {Boolean} [$removeUnspecified] True to remove placeholders that had no matchings, false otherwise
         *
         * @return {String} The replaced string
         */
        _replacePlaceholders: function (str, options, removeUnspecified) {
            return inter(str, options, removeUnspecified);
        },

        /**
         * Parses a task description.
         * The description can be a string or a function.
         * If it's a function, it will be called with the options, in order to
         * generate a complex description based on the them.
         * Placeholders will be handled with Automaton#_replacePlaceholders.
         *
         * @param  {String|Function} description The task description
         * @param  {Object}          options     The task options
         *
         * @return {String} The parsed description
         */
        _parseDescription: function (description, options) {
            if (utils.lang.isFunction(description)) {
                description = description(options);
            } else if (description != null) {
                description = this._replacePlaceholders(description, options, true);
            }

            return description;
        },

        /**
         * Report the next task that will run.
         *
         * @param {Object} task    The task definition
         * @param {Object} options The task options
         * @param {Number} depth   The task depth
         */
        _reportNextTask: function (task, options, depth) {
            if (task.description) {
                this._log('  - ' + task.description.cyan, depth, false);
            }
        },

        /**
         * Assert task is loaded.
         *
         * @param {String} taskId The task id
         */
        _assertTaskLoaded: function (taskId) {
            if (!utils.lang.isObject(this._tasks[taskId])) {
                this._throwError('Could not find any task handler suitable for \'' + taskId + '\'');
            }
        },

        /**
         * Assert string.
         *
         * @param {Mixed} variable  The target to assert
         * @param {String} errorMsg The error message to show if the assert fails
         */
        _assertIsString: function (variable, errorMsg) {
            if (!utils.lang.isString(variable)) {
                this._throwError(errorMsg);
            }
        },

        /**
         * Assert object.
         *
         * @param {Mixed} variable  The target to assert
         * @param {String} errorMsg The error message to show if the assert fails
         */
        _assertIsObject: function (variable, errorMsg) {
            if (!utils.lang.isObject(variable)) {
                this._throwError(errorMsg);
            }
        },

        /**
         * Assert function.
         *
         * @param {Mixed} variable  The target to assert
         * @param {String} errorMsg The error message to show if the assert fails
         */
        _assertIsFunction: function (variable, errorMsg) {
            if (!utils.lang.isFunction(variable)) {
                this._throwError(errorMsg);
            }
        },

        /**
         * Assert array.
         *
         * @param {Mixed} variable  The target to assert
         * @param {String} errorMsg The error message to show if the assert fails
         */
        _assertIsArray: function (variable, errorMsg) {
            if (!utils.lang.isArray(variable)) {
                this._throwError(errorMsg);
            }
        },

        // TODO: the functions bellow are not stable..
        //       streams will be added and we can't do
        //       process.exit() because we won't be able to use
        //       automaton programatically
        _throwError: function (errorMsg, $verbose) {
            if (!$verbose) {
                console.error('\n' + errorMsg.error + '\n');
                process.exit(1);
            }

            throw new Error(errorMsg);
        },

        _log: function (msg, $depth, $newLine) {
            $depth   = $depth || 0;
            $newLine = $newLine || true;

            if ($depth <= this._verbosity) {
                if ($newLine) {
                    util.puts(msg);
                } else {
                    util.print(msg);
                }
            }
        },

        _error: function (msg, $depth) {
            $depth = $depth || 0;
            if ($depth <= this._verbosity) {
                console.error(msg);
            }
        }
    });

    module.exports = new Automaton();

})();<|endoftext|>


<commit_before>
var parserx = require('parse-regexp')
var EventEmitter = require('events').EventEmitter
var MuxDemux = require('mux-demux')

module.exports = Rumours

function Rumours (schema) {
  var emitter = new EventEmitter()
  var rules = []
  var live = {}

  /* schema must be of form:
  { '/regexp/f': function (key) {
      //must return a scuttlebutt instance
    } }
  */

  for (var p in schema) {
    rules.push({rx: parserx(p) || p, fn: schema[p]})
  }

  function match (key) {
    for (i in rules) {
      var r = rules[i]
      var m = key.match(r.rx)
      console.log(m, r, key)
      if(m && m.index === 0)
        return r.fn
    }
  }

  //TRANSCEIVE
  emitter.transceive =
  emitter.trx = function (key, cb) {
    if(live[key]) return live[key]
    var fn = match(key)
    if(!fn) throw new Error('no schema for:'+key)
    var doc = fn(key) //create instance.
    doc.key = key
    live[key] = doc //remeber what is open.
    emitter.emit('trx', doc) //attach to any open streams.

    if(cb) doc.once('sync', cb)
    return doc
  }

  emitter.untransceive =
  emitter.untrx = function (doc) {
    delete live[doc.key]
    emitter.emit('untrx', doc)
    return this
  }

  /*
    So, right now, I'm just replicating each live document.

    When you connect, it's necessary to sync all documents.
    use a Merkle Tree.

    do this is parallel with the open documents
    (because that is much more important, we want that to work live)

    only do disk IO when something changes.

    that is a little bit more complicated than just reading
    and writing.

    also, you may want to compact.
    maybe we want some smart compacting on the client,
    to optimize for localStorage.

    next: TESTS?, persistence, efficient-replication?

    this is basically integration, so don't go overboard with tests.
    test the modules. just lite integration testing.
  */

  emitter.createStream = function (mode) {
    var streams = {}
    function onConnection (stream, local) {
      //when a stream comes in, replicate it to the local
      var doc
      try {
        doc = emitter.trx(stream.meta)
      } catch (err) {
        return stream.error(stream)
      }
      //this works for in-memory stuff.
      stream.pipe(doc.createStream()).pipe(stream)
      //TODO: if document is not live,
      //pause the incoming, until the document is synced.
      //that will need a refactor of mux-demux to use duplex instead of through...
    }
    var mx = MuxDemux(onConnection)
    //replicate all live documents.
    process.nextTick(function () {
      for (var key in live) {
        onConnection(mx.createStream(key), true)
      }
    })
    return mx
  }
  return emitter
}

<commit_msg>dispose docs from memory when inactive<commit_after>var parserx      = require('parse-regexp')
var EventEmitter = require('events').EventEmitter
var MuxDemux     = require('mux-demux')
var remember     = require('remember')
var idle         = require('idle')
var timestamp    = require('monotonic-timestamp')

module.exports = Rumours

//THIS IS STRICTLY FOR DEBUGGING
var ids = 'ABCDEFHIJKLMNOP'
function createId () {
  var id = ids[0]
  ids = ids.substring(1)
  return id
}

function Rumours (schema) {
  var emitter = new EventEmitter()
  //DEBUG
  emitter.id = createId()
  var rules = []
  var live = {}
  var locals = {}
  /* schema must be of form:
  { '/regexp/g': function (key) {
      //must return a scuttlebutt instance
      //regexes must match from the first character.
      //[else madness would ensue]
    }
  }
  */

  for (var p in schema) {
    rules.push({rx: parserx(p) || p, fn: schema[p]})
  }

  function match (key) {
    for (i in rules) {
      var r = rules[i]
      var m = key.match(r.rx)
      if(m && m.index === 0)
        return r.fn
    }
  }

  //OPEN is a much better verb than TRANSCIEVE
  emitter.open =
  emitter.transceive =
  emitter.trx = function (key, local, cb) {
    if(!cb && 'function' === typeof local)
      cb = local, local = true
    local = (local !== false) //default to true
    if(local) locals[key] = true
    if(live[key]) return live[key]

    var fn = match(key)
    if(!fn) throw new Error('no schema for:'+key)
    var doc = fn(key) //create instance.
    doc.key = key
    live[key] = doc //remeber what is open.
    emitter.emit('open', doc, local) //attach to any open streams.
    emitter.emit('trx', doc, local)
    doc.once('dispose', function () {
      console.log('dispose', emitter.id, doc.key)
      delete locals[doc.key]
      delete live[doc.key]
    })
    if(cb) doc.once('sync', cb)
    return doc
  }

  //CLOSE(doc)
  emitter.close =
  emitter.untransceive =
  emitter.untrx = function (doc) {
    doc.dispose()
    emitter.emit('untrx', doc)
    emitter.emit('close', doc)
    return this
  }

  /*
    so the logic here:
    if open a doc,
      RTR over stream;
      if idle
        close stream
      if change
        reopen stream
  */

  function activations (listen) {
    emitter.on('open', onActive)
    for(var key in live) {
      (onActive)(live[key])
    }

    function onActive (doc, local) {
      local = (local !== false)
      var up = true
      function onUpdate () {
        if(up) return
        up = true
        listen(doc, true)
      }
      function onIdle () {
        up = false
        listen(doc, false)
      }
      idle(doc, 'update', 1e3, onIdle)
      doc.once('dispose', function () {
        if(up) {
          up = false
          listen(doc, false)
        }
        doc.removeListener('update', onIdle)
        doc.removeListener('update', onUpdate)
      })
      doc.on('update', onUpdate)

      listen(doc, true)
    }
  }

  emitter.createStream = function (mode) {

    var streams = {}
    var mx = MuxDemux(function (stream) {
      if(_stream = streams[stream.meta.key]) {
        if(_stream.meta.ts > stream.meta.ts)
          return _stream.end()
        else
          stream.end(), stream = _stream
      }
      streams[stream.meta.key] = stream
      //this will trigger connectIf
      emitter.open(stream.meta.key, false)
    })

    function connectIf(doc) {
        var stream = streams[doc.key]
        console.log(emitter.id, 'connectIf')
        if(!stream) {
          streams[doc.key] = stream =
            mx.createStream({key: doc.key, ts: timestamp()})
        }
        console.log(emitter.id, 'createStream', stream.id)
        stream.pipe(doc.createStream({wrapper:'raw', tail: true})).pipe(stream)
        stream.once('close', function () {
          delete streams[doc.key]
        })
    }

    //replicate all live documents.
    process.nextTick(function () {
      activations(function (doc, up) {
        console.log(emitter.id, 'ACTIVE', up, doc.key)
        if(up) {
          process.nextTick(function () {
            connectIf(doc)
          })
        } else {
          if(streams[doc.key])
            streams[doc.key].end()
          console.log(locals)
          if(!locals[doc.key])
            doc.dispose()
        }
      })
    })

    return mx
  }

  //inject kv object used to persist everything.
  emitter.persist = function (kv) {
    var sync = remember(kv)
    function onActive (doc) {
      sync(doc)
    }
    //check if any docs are already active.
    //start persisting them.
    for (var k in live) {
      onActive(live[k])
    }
    emitter.on('trx', onActive)
  }
  return emitter
}
<|endoftext|>