<commit_before>define(['require', './normalize'], function(req, normalize) {
  var nodePrint = function() {};
  if (requirejs.tools)
    requirejs.tools.useLib(function(req) {
      req(['node/print'], function(_nodePrint) {
        nodePrint = _nodePrint;
      }, function(){});
    });

  var cssAPI = {};

  function compress(css) {
    if (typeof process !== "undefined" && process.versions && !!process.versions.node && require.nodeRequire) {
      try {
        var csso = require.nodeRequire('csso');
        var csslen = css.length;
        css = csso.justDoIt(css);
        nodePrint('Compressed CSS output to ' + Math.round(css.length / csslen * 100) + '%.');
        return css;
      }
      catch(e) {
        nodePrint('Compression module not installed. Use "npm install csso -g" to enable.');
        return css;
      }
    }
    nodePrint('Compression not supported outside of nodejs environments.');
    return css;
  }

  //load file code - stolen from text plugin
  function loadFile(path) {
    if (typeof process !== "undefined" && process.versions && !!process.versions.node && require.nodeRequire) {
      var fs = require.nodeRequire('fs');
      var file = fs.readFileSync(path, 'utf8');
      if (file.indexOf('\uFEFF') === 0)
        return file.substring(1);
      return file;
    }
    else {
      var file = new java.io.File(path),
        lineSeparator = java.lang.System.getProperty("line.separator"),
        input = new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(file), 'utf-8')),
        stringBuffer, line;
      try {
        stringBuffer = new java.lang.StringBuffer();
        line = input.readLine();
        if (line && line.length() && line.charAt(0) === 0xfeff)
          line = line.substring(1);
        stringBuffer.append(line);
        while ((line = input.readLine()) !== null) {
          stringBuffer.append(lineSeparator).append(line);
        }
        return String(stringBuffer.toString());
      }
      finally {
        input.close();
      }
    }
  }

  function saveFile(path, data) {
    if (typeof process !== "undefined" && process.versions && !!process.versions.node && require.nodeRequire) {
      var fs = require.nodeRequire('fs');
      fs.writeFileSync(path, data, 'utf8');
    }
    else {
      var content = new java.lang.String(data);
      var output = new java.io.BufferedWriter(new java.io.OutputStreamWriter(new java.io.FileOutputStream(path), 'utf-8'));

      try {
        output.write(content, 0, content.length());
        output.flush();
      }
      finally {
        output.close();
      }
    }
  }

  //when adding to the link buffer, paths are normalised to the baseUrl
  //when removing from the link buffer, paths are normalised to the output file path
  function escape(content) {
    return content.replace(/(["'\\])/g, '\\$1')
      .replace(/[\f]/g, "\\f")
      .replace(/[\b]/g, "\\b")
      .replace(/[\n]/g, "\\n")
      .replace(/[\t]/g, "\\t")
      .replace(/[\r]/g, "\\r");
  }

  // NB add @media query support for media imports
  var importRegEx = /@import\s*(url)?\s*(('([^']*)'|"([^"]*)")|\(('([^']*)'|"([^"]*)"|([^\)]*))\))\s*;?/g;

  var loadCSSFile = function(fileUrl) {
    var css = loadFile(fileUrl);

    // normalize the css (except import statements)
    css = normalize(css, fileUrl, baseUrl);

    // detect all import statements in the css and normalize
    var importUrls = [];
    var importIndex = [];
    var importLength = [];
    var match;
    while (match = importRegEx.exec(css)) {
      var importUrl = match[4] || match[5] || match[7] || match[8] || match[9];

      // normalize import url
      if (importUrl.substr(importUrl.length - 5, 5) != '.less' && importUrl.substr(importUrl.length - 4, 4) != '.css')
        importUrl += '.css';

      // contains a protocol
      if (importUrl.match(/:\/\//))
        continue;

      // relative to css base
      if (importUrl.substr(0, 1) == '/')
        importUrl = cssBase + importUrl;
      else
        importUrl = baseUrl + importUrl;

      importUrls.push(importUrl);
      importIndex.push(importRegEx.lastIndex - match[0].length);
      importLength.push(match[0].length);
    }

    // load the import stylesheets and substitute into the css
    for (var i = 0; i < importUrls.length; i++)
      (function(i) {
        var importCSS = loadCSSFile(importUrls[i]);
        css = css.substr(0, importIndex[i]) + importCSS + css.substr(importIndex[i] + importLength[i]);
        var lenDiff = importCSS.length - importLength[i];
        for (var j = i + 1; j < importUrls.length; j++)
          importIndex[j] += lenDiff;
      })(i);

    return css;
  }

  var baseUrl;
  var cssBase;
  var curModule;
  cssAPI.load = function(name, req, load, config, parse) {
    if (!baseUrl)
      baseUrl = config.baseUrl;

    if (!cssBase) {
      cssBase = config.cssBase || config.appDir || baseUrl;
      if (cssBase.substr(cssBase.length - 1, 1) != '/')
        cssBase += '/';
    }

    if (config.modules) {
      //run through the module list - the first one without a layer set is the current layer we are in
      //allows to track the current layer number for layer-specific config
      for (var i = 0; i < config.modules.length; i++)
        if (config.modules[i].layer === undefined) {
          curModule = i;
          break;
        }
    }

    //store config
    cssAPI.config = cssAPI.config || config;

    name += !parse ? '.css' : '.less';

    var fileUrl = req.toUrl(name);

    //external URLS don't get added (just like JS requires)
    if (fileUrl.substr(0, 7) == 'http://' || fileUrl.substr(0, 8) == 'https://')
      return;

    //add to the buffer
    _cssBuffer[name] = loadCSSFile(fileUrl);

    // parse if necessary
    if (parse)
      _cssBuffer[name] = parse(_cssBuffer[name]);

    load();
  }

  cssAPI.normalize = function(name, normalize) {
    if (name.substr(name.length - 4, 4) == '.css')
      name = name.substr(0, name.length - 4);
    return normalize(name);
  }

  //list of cssIds included in this layer
  var _layerBuffer = [];
  var _cssBuffer = [];

  cssAPI.write = function(pluginName, moduleName, write, parse) {
    //external URLS don't get added (just like JS requires)
    if (moduleName.substr(0, 7) == 'http://' || moduleName.substr(0, 8) == 'https://')
      return;

    var resourceName = moduleName + (!parse ? '.css' : '.less');
    _layerBuffer.push(_cssBuffer[resourceName]);

    var separateCSS = false;
    if (cssAPI.config.separateCSS)
      separateCSS = true;
    if (typeof curModule == 'number' && cssAPI.config.modules[curModule].separateCSS !== undefined)
      separateCSS = cssAPI.config.modules[curModule].separateCSS;
    if (separateCSS)
      write.asModule(pluginName + '!' + moduleName, 'define(function(){})');
    else
      write("requirejs.s.contexts._.nextTick = function(f){f()}; require(['css'], function(css) { css.addBuffer('" + resourceName + "'); }); requirejs.s.contexts._.nextTick = requirejs.nextTick;");
  }

  cssAPI.onLayerEnd = function(write, data, parser) {
    firstWrite = true;
    //separateCSS parameter set either globally or as a layer setting
    var separateCSS = false;
    if (cssAPI.config.separateCSS)
      separateCSS = true;
    if (typeof curModule == 'number' && cssAPI.config.modules[curModule].separateCSS !== undefined)
      separateCSS = cssAPI.config.modules[curModule].separateCSS;
    curModule = null;

    //calculate layer css
    var css = _layerBuffer.join('');

    if (separateCSS) {
      nodePrint('Writing CSS! file: ' + data.name + '\n');

      //calculate the css output path for this layer
      var path = this.config.appDir ? this.config.baseUrl + data.name + '.css' : cssAPI.config.out.replace(/\.js$/, '.css');

      //renormalize the css to the output path
      var output = compress(normalize(css, baseUrl, path));

      saveFile(path, output);
    }
    else {
      if (css == '')
        return;
      //write the injection and layer index into the layer
      //prepare the css
      css = escape(compress(css));

      //the code below overrides async require functionality to ensure instant buffer injection
      write("requirejs.s.contexts._.nextTick = function(f){f()}; require(['css'], function(css) { css.setBuffer('" + css + (parser ? "', true" : "'") + "); }); requirejs.s.contexts._.nextTick = requirejs.nextTick; ");
    }

    //clear layer buffer for next layer
    _layerBuffer = [];
  }

  return cssAPI;
});
<commit_msg>use normalized plugin name<commit_after>define(['require', './normalize'], function(req, normalize) {
  var nodePrint = function() {};
  if (requirejs.tools)
    requirejs.tools.useLib(function(req) {
      req(['node/print'], function(_nodePrint) {
        nodePrint = _nodePrint;
      }, function(){});
    });

  var cssAPI = {};

  function compress(css) {
    if (typeof process !== "undefined" && process.versions && !!process.versions.node && require.nodeRequire) {
      try {
        var csso = require.nodeRequire('csso');
        var csslen = css.length;
        css = csso.justDoIt(css);
        nodePrint('Compressed CSS output to ' + Math.round(css.length / csslen * 100) + '%.');
        return css;
      }
      catch(e) {
        nodePrint('Compression module not installed. Use "npm install csso -g" to enable.');
        return css;
      }
    }
    nodePrint('Compression not supported outside of nodejs environments.');
    return css;
  }

  //load file code - stolen from text plugin
  function loadFile(path) {
    if (typeof process !== "undefined" && process.versions && !!process.versions.node && require.nodeRequire) {
      var fs = require.nodeRequire('fs');
      var file = fs.readFileSync(path, 'utf8');
      if (file.indexOf('\uFEFF') === 0)
        return file.substring(1);
      return file;
    }
    else {
      var file = new java.io.File(path),
        lineSeparator = java.lang.System.getProperty("line.separator"),
        input = new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(file), 'utf-8')),
        stringBuffer, line;
      try {
        stringBuffer = new java.lang.StringBuffer();
        line = input.readLine();
        if (line && line.length() && line.charAt(0) === 0xfeff)
          line = line.substring(1);
        stringBuffer.append(line);
        while ((line = input.readLine()) !== null) {
          stringBuffer.append(lineSeparator).append(line);
        }
        return String(stringBuffer.toString());
      }
      finally {
        input.close();
      }
    }
  }

  function saveFile(path, data) {
    if (typeof process !== "undefined" && process.versions && !!process.versions.node && require.nodeRequire) {
      var fs = require.nodeRequire('fs');
      fs.writeFileSync(path, data, 'utf8');
    }
    else {
      var content = new java.lang.String(data);
      var output = new java.io.BufferedWriter(new java.io.OutputStreamWriter(new java.io.FileOutputStream(path), 'utf-8'));

      try {
        output.write(content, 0, content.length());
        output.flush();
      }
      finally {
        output.close();
      }
    }
  }

  //when adding to the link buffer, paths are normalised to the baseUrl
  //when removing from the link buffer, paths are normalised to the output file path
  function escape(content) {
    return content.replace(/(["'\\])/g, '\\$1')
      .replace(/[\f]/g, "\\f")
      .replace(/[\b]/g, "\\b")
      .replace(/[\n]/g, "\\n")
      .replace(/[\t]/g, "\\t")
      .replace(/[\r]/g, "\\r");
  }

  // NB add @media query support for media imports
  var importRegEx = /@import\s*(url)?\s*(('([^']*)'|"([^"]*)")|\(('([^']*)'|"([^"]*)"|([^\)]*))\))\s*;?/g;

  var loadCSSFile = function(fileUrl) {
    var css = loadFile(fileUrl);

    // normalize the css (except import statements)
    css = normalize(css, fileUrl, baseUrl);

    // detect all import statements in the css and normalize
    var importUrls = [];
    var importIndex = [];
    var importLength = [];
    var match;
    while (match = importRegEx.exec(css)) {
      var importUrl = match[4] || match[5] || match[7] || match[8] || match[9];

      // normalize import url
      if (importUrl.substr(importUrl.length - 5, 5) != '.less' && importUrl.substr(importUrl.length - 4, 4) != '.css')
        importUrl += '.css';

      // contains a protocol
      if (importUrl.match(/:\/\//))
        continue;

      // relative to css base
      if (importUrl.substr(0, 1) == '/')
        importUrl = cssBase + importUrl;
      else
        importUrl = baseUrl + importUrl;

      importUrls.push(importUrl);
      importIndex.push(importRegEx.lastIndex - match[0].length);
      importLength.push(match[0].length);
    }

    // load the import stylesheets and substitute into the css
    for (var i = 0; i < importUrls.length; i++)
      (function(i) {
        var importCSS = loadCSSFile(importUrls[i]);
        css = css.substr(0, importIndex[i]) + importCSS + css.substr(importIndex[i] + importLength[i]);
        var lenDiff = importCSS.length - importLength[i];
        for (var j = i + 1; j < importUrls.length; j++)
          importIndex[j] += lenDiff;
      })(i);

    return css;
  }

  var baseUrl;
  var cssBase;
  var curModule;
  cssAPI.load = function(name, req, load, config, parse) {
    if (!baseUrl)
      baseUrl = config.baseUrl;

    if (!cssBase) {
      cssBase = config.cssBase || config.appDir || baseUrl;
      if (cssBase.substr(cssBase.length - 1, 1) != '/')
        cssBase += '/';
    }

    if (config.modules) {
      //run through the module list - the first one without a layer set is the current layer we are in
      //allows to track the current layer number for layer-specific config
      for (var i = 0; i < config.modules.length; i++)
        if (config.modules[i].layer === undefined) {
          curModule = i;
          break;
        }
    }

    //store config
    cssAPI.config = cssAPI.config || config;

    name += !parse ? '.css' : '.less';

    var fileUrl = req.toUrl(name);

    //external URLS don't get added (just like JS requires)
    if (fileUrl.substr(0, 7) == 'http://' || fileUrl.substr(0, 8) == 'https://')
      return;

    //add to the buffer
    _cssBuffer[name] = loadCSSFile(fileUrl);

    // parse if necessary
    if (parse)
      _cssBuffer[name] = parse(_cssBuffer[name]);

    load();
  }

  cssAPI.normalize = function(name, normalize) {
    if (name.substr(name.length - 4, 4) == '.css')
      name = name.substr(0, name.length - 4);
    return normalize(name);
  }

  //list of cssIds included in this layer
  var _layerBuffer = [];
  var _cssBuffer = [];
  var _pluginName = '';
  cssAPI.write = function(pluginName, moduleName, write, parse) {
    _pluginName = pluginName;
    //external URLS don't get added (just like JS requires)
    if (moduleName.substr(0, 7) == 'http://' || moduleName.substr(0, 8) == 'https://')
      return;

    var resourceName = moduleName + (!parse ? '.css' : '.less');
    _layerBuffer.push(_cssBuffer[resourceName]);

    var separateCSS = false;
    if (cssAPI.config.separateCSS)
      separateCSS = true;
    if (typeof curModule == 'number' && cssAPI.config.modules[curModule].separateCSS !== undefined)
      separateCSS = cssAPI.config.modules[curModule].separateCSS;
    if (separateCSS)
      write.asModule(pluginName + '!' + moduleName, 'define(function(){})');
    else
      write("requirejs.s.contexts._.nextTick = function(f){f()}; require(['" + pluginName + "'], function(css) { css.addBuffer('" + resourceName + "'); }); requirejs.s.contexts._.nextTick = requirejs.nextTick;");
  }

  cssAPI.onLayerEnd = function(write, data, parser) {
    firstWrite = true;
    //separateCSS parameter set either globally or as a layer setting
    var separateCSS = false;
    if (cssAPI.config.separateCSS)
      separateCSS = true;
    if (typeof curModule == 'number' && cssAPI.config.modules[curModule].separateCSS !== undefined)
      separateCSS = cssAPI.config.modules[curModule].separateCSS;
    curModule = null;

    //calculate layer css
    var css = _layerBuffer.join('');

    if (separateCSS) {
      nodePrint('Writing CSS! file: ' + data.name + '\n');

      //calculate the css output path for this layer
      var path = this.config.appDir ? this.config.baseUrl + data.name + '.css' : cssAPI.config.out.replace(/\.js$/, '.css');

      //renormalize the css to the output path
      var output = compress(normalize(css, baseUrl, path));

      saveFile(path, output);
    }
    else {
      if (css == '')
        return;
      //write the injection and layer index into the layer
      //prepare the css
      css = escape(compress(css));

      //the code below overrides async require functionality to ensure instant buffer injection
      write("requirejs.s.contexts._.nextTick = function(f){f()}; require(['" + _pluginName + "'], function(css) { css.setBuffer('" + css + (parser ? "', true" : "'") + "); }); requirejs.s.contexts._.nextTick = requirejs.nextTick; ");
    }

    //clear layer buffer for next layer
    _layerBuffer = [];
  }

  return cssAPI;
});
<|endoftext|>


<commit_before>/* jshint node: true, camelcase: false */
/* globals task: false, fail: false, namespace: false, complete: false */ // Globals exposed by jake
/* globals cat: false, config: false, echo: false, mkdir: false, find: false*/ // Globals exposed by shelljs

var path = require('path');
var fs = require('fs');
require('shelljs/global');
config.fatal = true; //tell shelljs to fail on errors

var EXIT_CODES = {
	lintFailed: 101,
	sauceConnect: 102
};

/**
 * Jake addition. Allows to easily run async tasks without tons of code
 * @param {String} name  name of the task to run
 * @param {Array} params parameters to pass to the task (optional)
 * @param {Function} cb  Callback when the task is finished (optional)
 */
jake.invokeTask = function(name, params, cb) {
	var t = jake.Task[name];

	if (typeof params === 'function') {
		cb = params;
		params = [];
	}

	if (cb) {
		t.addListener('complete', function () {
			cb();
		});
	}
	t.invoke.apply(t, params);
};

namespace('smpl-build-test', function() {
	task('coverage', [], {async: true}, function(config) {
		var istanbul = require('istanbul');

		config.baseDir = config.baseDir || process.cwd();
		config.src = config.src || path.join(config.baseDir, 'src');
		config.coverageDir = config.coverageDir || path.join(config.baseDir, 'coverage');
		var coverageDirSrc = path.join(config.coverageDir, 'src');
		var dataDir = path.join(config.coverageDir, 'data');
		mkdir('-p', dataDir);

		var files = find(config.src).filter(function (file) {
			return file.match(/\.js$/);
		});
		var instrumenter = new istanbul.Instrumenter();
		var collector = new istanbul.Collector();

		files.forEach(function(file) {
			var dest = path.resolve(coverageDirSrc, path.relative(config.src, file));
			var destDir = path.dirname(dest);

			mkdir('-p', destDir);
			var data = fs.readFileSync(file, 'utf8');
			var instrumented = instrumenter.instrumentSync(data, file);
			fs.writeFileSync(dest, instrumented, 'utf8');

			var baseline = instrumenter.lastFileCoverage();
			var coverage = {};
			coverage[baseline.path] = baseline;
			collector.add(coverage);
		}, this);
		fs.writeFileSync(dataDir + '/baseline.json', JSON.stringify(collector.getFinalCoverage()), 'utf8');
		console.log('Instrumented ' + files.length + ' files');

		process.env.SMPL_COVERAGE = '1';

		var reporter = require('./coverageReporter');
		reporter.setBaseDir(config.coverageDir);

		var Mocha = require('mocha');

		var mocha = new Mocha({
			ui: 'tdd',
			reporter: reporter
		});
		config.tests.forEach(function(file) {
			mocha.addFile(file);
		}, this);

		// Now, you can run the tests.
		mocha.run(function(failures) {
			if (failures) fail();
			process.env.SMPL_COVERAGE = '';
			complete();
		});
	});

	task('lint', [], function(files, globals) {
		globals = globals || {};
		var jshint = require('jshint').JSHINT;
		var options = JSON.parse(cat(path.join(__dirname, 'jshint.json')));

		echo('Linting ' + files.length + ' files...');

		var hasErrors = false;
		files.forEach(function (file) {
			jshint(cat(file), options, globals);
			var passed = true;
			var errors = jshint.data().errors;
			if (errors) {
				errors.forEach(function(err) {
					if (!err) {
						return;
					}
					// Tabs are counted as 4 spaces for column number.
					// Lets replace them to get correct results
					var line = err.evidence.replace(/\t/g, '    ');

					// ignore trailing spaces on indentation only lines
					if (err.code === 'W102' && err.evidence.match(/^\t+$/)) {
						return;
					}
					// Do no require {} for one line blocks
					if (err.code === 'W116' && err.a === '{' &&
						err.evidence.match(/^\t* *(?:(?:(?:if|for|while) ?\()|else).*;(?:\s*\/\/.*)?$/)) {
						return;
					}

					// Allow double quote string if they contain single quotes
					if (err.code === 'W109') {
						// https://github.com/jshint/jshint/issues/824
						if (err.character === 0) {
							return;
						}
						var i = err.character - 2; //JSHINT use base 1 for column and return the char after the end
						var singleQuotes = 0;
						var doubleQuotes = 0;
						while (i-- > 0) {
							if (line[i] === "'") singleQuotes++;
							else if (line[i] === '"') {
								var nb = 0;
								while (i-- && line[i] === '\\') nb++;
								if (nb % 2) doubleQuotes++;
								else break;
							}
						}
						if (singleQuotes > doubleQuotes) {
							return;
						}
					}

					// Do not require a space after `function`
					if (err.code === 'W013' && err.a === 'function') {
						return;
					}

					// bug in jslint: `while(i--);` require a space before `;`
					if (err.code === 'W013' && line[err.character - 1] === ';') {
						return;
					}

					// Indentation. White option turn this on. Need to fix indentation for switch case before activating
					if (err.code === 'W015') return;

					if (passed) {
						// First error in the file. Display filename
						echo('\n', file);
						passed = false;
						hasErrors = true;
					}
					line = '[L' + err.line + ':' + err.code + ']';
					while (line.length < 15) {
						line += ' ';
					}

					echo(line, err.reason);
					console.log(err.evidence.replace(/\t/g, '    '));
					console.log(new Array(err.character).join(' ') + '^');
				});
			}
		});
		if (hasErrors) {
			fail('FAIL !!!', EXIT_CODES.lintFailed);
		} else {
			echo('ok');
		}
	});
});

/**
 * Class used to run test on SauceLabs
 * @param {Object} config
 * @param {String} config.user username on sauceLabs
 * @param {String} config.key key on sauceLabs
 * @param {String} config.path Path to use as root web folder when calling `Remote.startServer`
 * @param {Number} config.port Port to use for web server when calling `Remote.startServer`
 * @param {Boolean} config.sauceConnect use Sauce Connect for the tests
 * @param {String} config.url Url to point the browser to before test start
 * @param {String} config.name Name of the test on SauceLabs
 * @param {Array} config.browsers
 * @param {String} config.browsers.os
 * @param {String} config.browsers.name
 * @param {String|Number} config.browsers.version
 * @param {Function} onEnd
 * @param {Function} onTest
 */
var Remote = function(config) {
	this.config = config;
	this.id = process.env.TRAVIS_BUILD_NUMBER;
	this.tags = [];
	if (this.id) {
		this.tags.push('travis');
	} else {
		this.tags.push('custom', '' + Math.floor(Math.random() * 100000000));
	}
	this.status = {};
};

Remote.prototype.startServer = function() {
	if (!this.server && this.config.port) {
		var nodeStatic = require('node-static');
		var file = new nodeStatic.Server(this.config.path);
		this.server = require('http').createServer(function (request, response) {
			request.addListener('end', function () {
				file.serve(request, response);
			});
		});
		this.server.listen(this.config.port);

		if (this.config.url) {
			console.log('server ready: ' + this.config.url);
		}
	}
};

Remote.prototype.stopServer = function() {
	if (this.server) {
		this.server.close();
		delete this.server;
		console.log('server stoped');
	}
};

Remote.prototype.startSauceConnect = function(cb) {
	if (!this.config.sauceConnect) {
		cb();
		return;
	}
	var options = {
		username: this.config.user,
		accessKey: this.config.key,
		verbose: false,
		logger: console.log,
		no_progress: true // optionally hide progress bar
	};
	var sauceConnectLauncher = require('sauce-connect-launcher');
	var self = this;
	sauceConnectLauncher(options, function (err, sauceConnectProcess) {
		if (err) {
			if (!(err + '').match(/Exit code 143/)) {
				console.log(err);
				fail('Error launching sauce connect', EXIT_CODES.sauceConnect);
			}
			return;
		}
		self.sauceConnect = sauceConnectProcess;
		cb();
	});
};

Remote.prototype.stopSauceConnect = function() {
	if (this.sauceConnect) {
		this.sauceConnect.close();
		delete this.sauceConnect;
	}
};

Remote.prototype.run = function() {
	this.startServer();
	var self = this;
	this.startSauceConnect(function() {
		self.nbTests = self.config.browsers.length;
		self.startBrowser(0);
	});
};

Remote.prototype.startBrowser = function(index) {
	var webdriver = require('wd');

	var b = this.config.browsers[index];
	var browser = webdriver.remote('ondemand.saucelabs.com', 80, this.config.user, this.config.key);
	var name = this.getBrowserName(b);
	var desired = {
		name: this.config.name + ' - ' + name,
		browserName: b.name,
		platform: b.os,
		version: b.version,
		build: this.id,
		tags: this.tags
	};
	browser.on('status', function(info) {
		console.log('%s : \x1b[36m%s\x1b[0m', name, info.trim());
	});

	browser.on('command', function(meth, path) {
		console.log('%s : > \x1b[33m%s\x1b[0m: %s', name, meth, path);
	});

	var self = this;
	browser.init(desired, function(err, sessionID) {
		var testDone = self.testDone.bind(self, browser, name, sessionID);
		var onTest = self.config.onTest.bind(null, browser, testDone);
		if (self.config.url) {
			self.loadUrl(browser, self.config.url, onTest);
		} else {
			onTest();
		}
		if (self.config.browsers[index + 1]) {
			process.nextTick(function() {
				self.startBrowser(index + 1);
			});
		}
	});
};

Remote.prototype.loadUrl = function(browser, url, cb) {
	browser.get(url, cb);
};

Remote.prototype.getBrowserName = function(browser) {
	var name = browser.name;
	if (browser.version) name += ' ' + browser.version;
	if (browser.os) name += ' (' + browser.os + ')';
	return name;
};

Remote.prototype.testDone = function(browser, name, id, status) {
	browser.quit();
	this.status[name] = status;
	this.report(id, status, name, this.finish.bind(this));
};

Remote.prototype.finish = function() {
	if (0 === --this.nbTests) {
		this.stopSauceConnect();
		this.stopServer();
		var self = this;
		setTimeout(function() {
			self.displayResults();
		}, 1000);
	}
};

Remote.prototype.report = function(jobId, status, name, done) {
	var request = require('request');

	var success = !!(status && status.ok && status.failed && status.ok.length && !status.failed.length);
	var user = this.config.user;
	var key = this.config.key;
	var httpOpts = {
		url: 'http://' + user + ':' + key + '@saucelabs.com/rest/v1/' + user + '/jobs/' + jobId,
		method: 'PUT',
		headers: {
			'Content-Type': 'text/json'
		},
		body: JSON.stringify({
			passed: success
		}),
		jar: false /* disable cookies: they break next request */
	};

	request(httpOpts, function(err) {
		if (err) {
			console.log('%s : > job %s: unable to set status:', name, jobId, err);
		} else {
			console.log('%s : > job %s marked as %s', name, jobId, success ? 'passed' : 'failed');
		}
		done();
	});
};

Remote.prototype.displayResults = function() {
	var failures = 0;
	var self = this;
	console.log();
	console.log();
	console.log('**********************************');
	console.log('*             Status             *');
	console.log('**********************************');
	console.log();
	console.log();
	this.config.browsers.forEach(function(browser) {
		var name = self.getBrowserName(browser);
		var status = self.status[name];

		var ok = status && status.ok && status.ok.length;
		var failed = status && status.failed && status.failed.length;

		if (!ok && !failed) {
			console.log('    %s: \033[31mno results\033[m', name);
			failures++;
		} else if (failed) {
			console.log('    %s: \033[31m%d/%d failed\033[m', name, failed, ok + failed);
			failures++;
		} else {
			console.log('    %s: \033[32m%d passed\033[m', name, ok);
		}

		if (failed) {
			failed = status.failed;
			var n = 0;
			failed.forEach(function(test) {
				var err = test.error;
				var msg = err.message || '';
				var stack = err.stack || msg;
				var i = stack.indexOf(msg) + msg.length;
				msg = stack.slice(0, i);
				console.log();
				console.log('      %d) %s', ++n, test.fullTitle);
				console.log('\033[31m%s\033[m', stack.replace(/^/gm, '        '));
			});
			console.log();
			console.log();
		}
	});
	console.log();
	console.log();
	if (this.config.onEnd) this.config.onEnd(failures);
};

exports.Remote = Remote;
<commit_msg>New `test` task<commit_after>/* jshint node: true, camelcase: false */
/* globals jake: false, task: false, fail: false, namespace: false, complete: false */ // Globals exposed by jake
/* globals cat: false, config: false, echo: false, mkdir: false, find: false*/ // Globals exposed by shelljs

var path = require('path');
var fs = require('fs');
require('shelljs/global');
config.fatal = true; //tell shelljs to fail on errors

var EXIT_CODES = {
	lintFailed: 101,
	sauceConnect: 102
};

/**
 * Jake addition. Allows to easily run async tasks without tons of code
 * @param {String} name  name of the task to run
 * @param {Array} params parameters to pass to the task (optional)
 * @param {Function} cb  Callback when the task is finished (optional)
 */
jake.invokeTask = function(name, params, cb) {
	var t = jake.Task[name];

	if (typeof params === 'function') {
		cb = params;
		params = [];
	}

	if (cb) {
		t.addListener('complete', function () {
			cb();
		});
	}
	t.invoke.apply(t, params);
};

namespace('smpl-build-test', function() {
	task('coverage', [], {async: true}, function(config) {
		var istanbul = require('istanbul');

		config.baseDir = config.baseDir || process.cwd();
		config.src = config.src || path.join(config.baseDir, 'src');
		config.coverageDir = config.coverageDir || path.join(config.baseDir, 'coverage');
		var coverageDirSrc = path.join(config.coverageDir, 'src');
		var dataDir = path.join(config.coverageDir, 'data');
		mkdir('-p', dataDir);

		var files = find(config.src).filter(function (file) {
			return file.match(/\.js$/);
		});
		var instrumenter = new istanbul.Instrumenter();
		var collector = new istanbul.Collector();

		files.forEach(function(file) {
			var dest = path.resolve(coverageDirSrc, path.relative(config.src, file));
			var destDir = path.dirname(dest);

			mkdir('-p', destDir);
			var data = fs.readFileSync(file, 'utf8');
			var instrumented = instrumenter.instrumentSync(data, file);
			fs.writeFileSync(dest, instrumented, 'utf8');

			var baseline = instrumenter.lastFileCoverage();
			var coverage = {};
			coverage[baseline.path] = baseline;
			collector.add(coverage);
		}, this);
		fs.writeFileSync(dataDir + '/baseline.json', JSON.stringify(collector.getFinalCoverage()), 'utf8');
		console.log('Instrumented ' + files.length + ' files');

		process.env.SMPL_COVERAGE = '1';

		var reporter = require('./coverageReporter');
		reporter.setBaseDir(config.coverageDir);

		var Mocha = require('mocha');

		var mocha = new Mocha({
			ui: 'tdd',
			reporter: reporter
		});
		config.tests.forEach(function(file) {
			mocha.addFile(file);
		}, this);

		// Now, you can run the tests.
		mocha.run(function(failures) {
			if (failures) fail();
			process.env.SMPL_COVERAGE = '';
			complete();
		});
	});

	task('test', [], {async: true}, function(config) {
		var Mocha = require('mocha');

		var mocha = new Mocha({
			ui: 'tdd',
			reporter: config.reporter
		});

		config.tests.forEach(function(file) {
			mocha.addFile(file);
		}, this);

		// Now, you can run the tests.
		mocha.run(function(failures) {
			if (failures) fail();
			complete();
		});
	});

	task('lint', [], function(files, globals) {
		globals = globals || {};
		var jshint = require('jshint').JSHINT;
		var options = JSON.parse(cat(path.join(__dirname, 'jshint.json')));

		echo('Linting ' + files.length + ' files...');

		var hasErrors = false;
		files.forEach(function (file) {
			jshint(cat(file), options, globals);
			var passed = true;
			var errors = jshint.data().errors;
			if (errors) {
				errors.forEach(function(err) {
					if (!err) {
						return;
					}
					// Tabs are counted as 4 spaces for column number.
					// Lets replace them to get correct results
					var line = err.evidence.replace(/\t/g, '    ');

					// ignore trailing spaces on indentation only lines
					if (err.code === 'W102' && err.evidence.match(/^\t+$/)) {
						return;
					}
					// Do no require {} for one line blocks
					if (err.code === 'W116' && err.a === '{' &&
						err.evidence.match(/^\t* *(?:(?:(?:if|for|while) ?\()|else).*;(?:\s*\/\/.*)?$/)) {
						return;
					}

					// Allow double quote string if they contain single quotes
					if (err.code === 'W109') {
						// https://github.com/jshint/jshint/issues/824
						if (err.character === 0) {
							return;
						}
						var i = err.character - 2; //JSHINT use base 1 for column and return the char after the end
						var singleQuotes = 0;
						var doubleQuotes = 0;
						while (i-- > 0) {
							if (line[i] === "'") singleQuotes++;
							else if (line[i] === '"') {
								var nb = 0;
								while (i-- && line[i] === '\\') nb++;
								if (nb % 2) doubleQuotes++;
								else break;
							}
						}
						if (singleQuotes > doubleQuotes) {
							return;
						}
					}

					// Do not require a space after `function`
					if (err.code === 'W013' && err.a === 'function') {
						return;
					}

					// bug in jslint: `while(i--);` require a space before `;`
					if (err.code === 'W013' && line[err.character - 1] === ';') {
						return;
					}

					// Indentation. White option turn this on. Need to fix indentation for switch case before activating
					if (err.code === 'W015') return;

					if (passed) {
						// First error in the file. Display filename
						echo('\n', file);
						passed = false;
						hasErrors = true;
					}
					line = '[L' + err.line + ':' + err.code + ']';
					while (line.length < 15) {
						line += ' ';
					}

					echo(line, err.reason);
					console.log(err.evidence.replace(/\t/g, '    '));
					console.log(new Array(err.character).join(' ') + '^');
				});
			}
		});
		if (hasErrors) {
			fail('FAIL !!!', EXIT_CODES.lintFailed);
		} else {
			echo('ok');
		}
	});
});

/**
 * Class used to run test on SauceLabs
 * @param {Object} config
 * @param {String} config.user username on sauceLabs
 * @param {String} config.key key on sauceLabs
 * @param {String} config.path Path to use as root web folder when calling `Remote.startServer`
 * @param {Number} config.port Port to use for web server when calling `Remote.startServer`
 * @param {Boolean} config.sauceConnect use Sauce Connect for the tests
 * @param {String} config.url Url to point the browser to before test start
 * @param {String} config.name Name of the test on SauceLabs
 * @param {Array} config.browsers
 * @param {String} config.browsers.os
 * @param {String} config.browsers.name
 * @param {String|Number} config.browsers.version
 * @param {Function} onEnd
 * @param {Function} onTest
 */
var Remote = function(config) {
	this.config = config;
	this.id = process.env.TRAVIS_BUILD_NUMBER;
	this.tags = [];
	if (this.id) {
		this.tags.push('travis');
	} else {
		this.tags.push('custom', '' + Math.floor(Math.random() * 100000000));
	}
	this.status = {};
};

Remote.prototype.startServer = function() {
	if (!this.server && this.config.port) {
		var nodeStatic = require('node-static');
		var file = new nodeStatic.Server(this.config.path);
		this.server = require('http').createServer(function (request, response) {
			request.addListener('end', function () {
				file.serve(request, response);
			});
		});
		this.server.listen(this.config.port);

		if (this.config.url) {
			console.log('server ready: ' + this.config.url);
		}
	}
};

Remote.prototype.stopServer = function() {
	if (this.server) {
		this.server.close();
		delete this.server;
		console.log('server stoped');
	}
};

Remote.prototype.startSauceConnect = function(cb) {
	if (!this.config.sauceConnect) {
		cb();
		return;
	}
	var options = {
		username: this.config.user,
		accessKey: this.config.key,
		verbose: false,
		logger: console.log,
		no_progress: true // optionally hide progress bar
	};
	var sauceConnectLauncher = require('sauce-connect-launcher');
	var self = this;
	sauceConnectLauncher(options, function (err, sauceConnectProcess) {
		if (err) {
			if (!(err + '').match(/Exit code 143/)) {
				console.log(err);
				fail('Error launching sauce connect', EXIT_CODES.sauceConnect);
			}
			return;
		}
		self.sauceConnect = sauceConnectProcess;
		cb();
	});
};

Remote.prototype.stopSauceConnect = function() {
	if (this.sauceConnect) {
		this.sauceConnect.close();
		delete this.sauceConnect;
	}
};

Remote.prototype.run = function() {
	this.startServer();
	var self = this;
	this.startSauceConnect(function() {
		self.nbTests = self.config.browsers.length;
		self.startBrowser(0);
	});
};

Remote.prototype.startBrowser = function(index) {
	var webdriver = require('wd');

	var b = this.config.browsers[index];
	var browser = webdriver.remote('ondemand.saucelabs.com', 80, this.config.user, this.config.key);
	var name = this.getBrowserName(b);
	var desired = {
		name: this.config.name + ' - ' + name,
		browserName: b.name,
		platform: b.os,
		version: b.version,
		build: this.id,
		tags: this.tags
	};
	browser.on('status', function(info) {
		console.log('%s : \x1b[36m%s\x1b[0m', name, info.trim());
	});

	browser.on('command', function(meth, path) {
		console.log('%s : > \x1b[33m%s\x1b[0m: %s', name, meth, path);
	});

	var self = this;
	browser.init(desired, function(err, sessionID) {
		var testDone = self.testDone.bind(self, browser, name, sessionID);
		var onTest = self.config.onTest.bind(null, browser, testDone);
		if (self.config.url) {
			self.loadUrl(browser, self.config.url, onTest);
		} else {
			onTest();
		}
		if (self.config.browsers[index + 1]) {
			process.nextTick(function() {
				self.startBrowser(index + 1);
			});
		}
	});
};

Remote.prototype.loadUrl = function(browser, url, cb) {
	browser.get(url, cb);
};

Remote.prototype.getBrowserName = function(browser) {
	var name = browser.name;
	if (browser.version) name += ' ' + browser.version;
	if (browser.os) name += ' (' + browser.os + ')';
	return name;
};

Remote.prototype.testDone = function(browser, name, id, status) {
	browser.quit();
	this.status[name] = status;
	this.report(id, status, name, this.finish.bind(this));
};

Remote.prototype.finish = function() {
	if (0 === --this.nbTests) {
		this.stopSauceConnect();
		this.stopServer();
		var self = this;
		setTimeout(function() {
			self.displayResults();
		}, 1000);
	}
};

Remote.prototype.report = function(jobId, status, name, done) {
	var request = require('request');

	var success = !!(status && status.ok && status.failed && status.ok.length && !status.failed.length);
	var user = this.config.user;
	var key = this.config.key;
	var httpOpts = {
		url: 'http://' + user + ':' + key + '@saucelabs.com/rest/v1/' + user + '/jobs/' + jobId,
		method: 'PUT',
		headers: {
			'Content-Type': 'text/json'
		},
		body: JSON.stringify({
			passed: success
		}),
		jar: false /* disable cookies: they break next request */
	};

	request(httpOpts, function(err) {
		if (err) {
			console.log('%s : > job %s: unable to set status:', name, jobId, err);
		} else {
			console.log('%s : > job %s marked as %s', name, jobId, success ? 'passed' : 'failed');
		}
		done();
	});
};

Remote.prototype.displayResults = function() {
	var failures = 0;
	var self = this;
	console.log();
	console.log();
	console.log('**********************************');
	console.log('*             Status             *');
	console.log('**********************************');
	console.log();
	console.log();
	this.config.browsers.forEach(function(browser) {
		var name = self.getBrowserName(browser);
		var status = self.status[name];

		var ok = status && status.ok && status.ok.length;
		var failed = status && status.failed && status.failed.length;

		if (!ok && !failed) {
			console.log('    %s: \033[31mno results\033[m', name);
			failures++;
		} else if (failed) {
			console.log('    %s: \033[31m%d/%d failed\033[m', name, failed, ok + failed);
			failures++;
		} else {
			console.log('    %s: \033[32m%d passed\033[m', name, ok);
		}

		if (failed) {
			failed = status.failed;
			var n = 0;
			failed.forEach(function(test) {
				var err = test.error;
				var msg = err.message || '';
				var stack = err.stack || msg;
				var i = stack.indexOf(msg) + msg.length;
				msg = stack.slice(0, i);
				console.log();
				console.log('      %d) %s', ++n, test.fullTitle);
				console.log('\033[31m%s\033[m', stack.replace(/^/gm, '        '));
			});
			console.log();
			console.log();
		}
	});
	console.log();
	console.log();
	if (this.config.onEnd) this.config.onEnd(failures);
};

exports.Remote = Remote;
<|endoftext|>


<commit_before>// Brunch build tasks
var execute = require('./lib').execute;
var localBinCommand = require('./lib').localBinCommand;

namespace('build', function() {
  desc('Build project for development');
  task('dev', ['bower:install', 'clean:web'], function() {
    return execute(localBinCommand('brunch', 'b -e web:dev'));
  });

  desc('Build project for production');
  task('prod', ['bower:install', 'clean:web'], function() {
    return execute(localBinCommand('brunch', 'b -e web:prod'));
  });
});

namespace('watch', function() {
  desc('Build project for development and rebuild on changes');
  task('dev', ['bower:install', 'clean:web'], function() {
    return execute(localBinCommand('brunch', 'w -e web:dev'));
  });

  desc('Build project for production and rebuild on changes');
  task('prod', ['bower:install', 'clean:web'], function() {
    return execute(localBinCommand('brunch', 'w -e web:prod'));
  });
});

namespace('server', function() {
  desc('Build project for development, rebuild on changes, and host locally');
  task('dev', ['bower:install', 'clean:web'], function() {
    return execute(localBinCommand('brunch', 'w -s -e web:dev'));
  });

  desc('Build project for production, rebuild on changes, and host locally');
  task('prod', ['bower:install', 'clean:web'], function() {
    return execute(localBinCommand('brunch', 'w -s -e web:dev'));
  });
});
<commit_msg>Use prod for server:prod<commit_after>// Brunch build tasks
var execute = require('./lib').execute;
var localBinCommand = require('./lib').localBinCommand;

namespace('build', function() {
  desc('Build project for development');
  task('dev', ['bower:install', 'clean:web'], function() {
    return execute(localBinCommand('brunch', 'b -e web:dev'));
  });

  desc('Build project for production');
  task('prod', ['bower:install', 'clean:web'], function() {
    return execute(localBinCommand('brunch', 'b -e web:prod'));
  });
});

namespace('watch', function() {
  desc('Build project for development and rebuild on changes');
  task('dev', ['bower:install', 'clean:web'], function() {
    return execute(localBinCommand('brunch', 'w -e web:dev'));
  });

  desc('Build project for production and rebuild on changes');
  task('prod', ['bower:install', 'clean:web'], function() {
    return execute(localBinCommand('brunch', 'w -e web:prod'));
  });
});

namespace('server', function() {
  desc('Build project for development, rebuild on changes, and host locally');
  task('dev', ['bower:install', 'clean:web'], function() {
    return execute(localBinCommand('brunch', 'w -s -e web:dev'));
  });

  desc('Build project for production, rebuild on changes, and host locally');
  task('prod', ['bower:install', 'clean:web'], function() {
    return execute(localBinCommand('brunch', 'w -s -e web:prod'));
  });
});
<|endoftext|>


<commit_before>/**
 * This contains a listing of all contributors. Entries are used in configs and changelogs.
 * Feel free to add yourself if you're not yet in the list.
 *
 * Using `require` for avatars so we don't have to keep a seperate list of imports disconnected from the maintainer definition.
 *
 EXAMPLE

 export const NICKNAME = {
    nickname: 'NICKNAME',
    github: 'GITHUB_NAME',
    discord: 'DISCORD_NAME INCL #xxxx',
    avatar: require('./Images/IMAGE'),
    desc: 'DESC',
    mains: [{
      name: 'CHARNAME',
      spec: SPECS.BLOOD_DEATH_KNIGHT,
      link: 'ARMOR/WCL-LINK',
    }],
    alts: [{
      name: 'CHARNAME',
      spec: SPECS.BLOOD_DEATH_KNIGHT,
      link: 'ARMOR/WCL-LINK',
    }],
    others: {
      'Custom Item': 'normal text',
      'Custom Item': [
        'Item 1',
        'Item 2',
      ],
    },
    links: {
      'Link No1': 'https://www.google.com',
    },
  };
*/

import SPECS from 'common/SPECS';

// For testing purposes because I am too lazy to work out a solution for testing that does not involve adding regular code
export const Dummy = {
  nickname: 'Dummy',
  github: 'DummyHub',
  twitter: '@Dummy',
  avatar: require('./Images/zerotorescue-avatar.png'),
};
export const Zerotorescue = {
  nickname: 'Zerotorescue',
  github: 'MartijnHols',
  twitter: 'Zerotorescue',
  discord: 'Zerotorescue#0724',
  avatar: require('./Images/zerotorescue-avatar.png'),
  desc: 'WoWAnalyzer founder, Holy Paladin theorycrafter',
  mains: [{
    name: 'Zerotorescue',
    spec: SPECS.HOLY_PALADIN,
    link: 'https://worldofwarcraft.com/en-gb/character/alakir/zerotorescue',
  }],
  alts: [{
    name: 'Zerotorescue',
    spec: SPECS.BREWMASTER_MONK,
    link: 'https://worldofwarcraft.com/en-gb/character/skullcrusher/zerotorescue',
  }, {
    name: 'Zerotorescue',
    spec: SPECS.DISCIPLINE_PRIEST,
    link: 'https://worldofwarcraft.com/en-gb/character/xavius/zerotorescue',
  }, {
    name: 'Leukenaam',
    spec: SPECS.RESTORATION_SHAMAN,
    link: 'https://worldofwarcraft.com/en-gb/character/alakir/leukenaam',
  }, {
    name: 'Zerotohunt',
    spec: SPECS.MARKSMANSHIP_HUNTER,
    link: 'https://worldofwarcraft.com/en-gb/character/alakir/Zerotohunt',
  }],
};
export const blazyb = {
  nickname: 'blazyb',
  github: 'buimichael',
};
export const sref = {
  nickname: 'sref',
  github: 'kfinch',
  avatar: require('./Images/sref-avatar.png'),
};
export const Iskalla = {
  nickname: 'Iskalla',
  github: 'Iskalla',
  avatar: require('./Images/iskalla-avatar.png'),
};
export const enragednuke = {
  nickname: 'enragednuke',
  github: 'enragednuke',
};
export const Skamer = {
  nickname: 'Skamer',
  github: 'Skamer',
};
export const Salarissia = {
  nickname: 'Salarissia',
  github: 'Salarissia',
};
export const WOPR = {
  nickname: 'WOPR',
  github: 'shighman',
};
export const Yajinni = {
  nickname: 'Yajinni',
  github: 'yajinni',
};
export const Bonebasher = {
  nickname: 'Bonebasher',
};
export const Sharrq = {
  nickname: 'Sharrq',
  github: 'Sharrq',
  avatar: require('./Images/Sharrq_avatar.jpg'),
};
export const Khazak = {
  nickname: 'Khazak',
  github: 'jjs451',
};
export const Bicepspump = {
  nickname: 'Bicepspump',
  github: 'Bicepspump',
};
export const Mamtooth = {
  nickname: 'Mamtooth',
  github: 'ronaldpereira',
  avatar: require('./Images/mamtooth-avatar.png'),
};
export const Thieseract = {
  nickname: 'Thieseract',
  github: 'Thieseract',
};
export const Putro = {
  nickname: 'Putro',
  github: 'Pewtro',
  discord: 'Putro#6093',
  avatar: require('./Images/putro-avatar.png'),
  mains: [
    {
      name: 'Putro',
      spec: SPECS.MARKSMANSHIP_HUNTER,
      link: 'https://worldofwarcraft.com/en-gb/character/ragnaros/putro',
    },
  ],
};
export const Blazballs = {
  nickname: 'Blazballs',
  github: 'leapis',
};
export const faide = {
  nickname: 'faide',
  github: 'FaideWW',
  avatar: require('./Images/faide-avatar.png'),
};
export const Fyruna = {
  nickname: 'Fyruna',
  github: 'Fyruna',
  avatar: require('./Images/Fyruna_avatar.jpg'),
};
export const Anomoly = {
  nickname: 'Anomoly',
  github: 'anom0ly',
  avatar: require('./Images/anomoly-avatar.jpg'),
};
export const Juko8 = {
  nickname: 'Juko8',
  github: 'Juko8',
  avatar: require('./Images/juko8-avatar.jpg'),
};
export const Noichxd = {
  nickname: 'Noichxd',
  github: 'Noichxd',
};
export const Hewhosmites = {
  nickname: 'Hewhosmites',
  github: 'CollCrom',
  avatar: require('./Images/hewhosmites-avatar.png'),
};
export const Reglitch = {
  nickname: 'Reglitch',
  github: 'rp4rk',
};
export const Gao = {
  nickname: 'Gao',
  github: 'awlego',
};
export const Oratio = {
  nickname: 'Oratio',
  github: 'karlpralow',
};
export const hassebewlen = {
  nickname: 'hassebewlen',
  github: 'hasseboulen',
};
export const tsabo = {
  nickname: 'tsabo',
  github: 'TsaboTavok',
};
export const zealk = {
  nickname: 'zealk',
  github: 'zealk',
};
export const fasib = {
  nickname: 'fasib',
  github: 'fasib',
};
export const janvavra = {
  nickname: 'janvavra',
  github: 'janvavra',
};
export const Nighteyez07 = {
  nickname: 'Nighteyez07',
  github: 'Nighteyez07',
};
export const Versaya = {
  nickname: 'Versaya',
  github: 'versaya',
};
export const Chizu = {
  nickname: 'Chizu',
  github: 'sMteX',
  avatar: require('./Images/Chizu_avatar.jpg'),
};
export const Hordehobbs = {
  nickname: 'Hordehobbs',
  github: 'hpabst',
};
export const TheBadBossy = {
  nickname: 'TheBadBossy',
  avatar: require('./Images/thebadbossy_avatar.jpg'),
};
export const JLassie82 = {
  nickname: 'JLassie82',
  github: 'JLassie82',
};
export const aryu = {
  nickname: 'aryu',
  github: 'Yuyz0112',
};
export const Dyspho = {
  nickname: 'Dyspho',
};
export const Gurupitka = {
  nickname: 'Gurupitka',
};
export const Hartra344 = {
  nickname: 'hartra344',
  github: 'hartra344',
};
export const strel = {
  nickname: 'strel',
};
export const Maldark = {
  nickname: 'Maldark',
  github: 'Maldark',
};
export const hatra344 = {
  nickname: 'hatra344',
  github: 'hatra344',
};
export const emallson = {
  nickname: 'emallson',
  github: 'emallson',
};
export const Gebuz = {
  nickname: 'Gebuz',
  github: 'Gebuz',
  avatar: require('./Images/gebuz-avatar.png'),
};
export const milesoldenburg = {
  nickname: 'milesoldenburg',
  github: 'milesoldenburg',
};
export const mwwscott0 = {
  nickname: 'mwwscott0',
  github: 'mwwscott0',
};
export const Talby = {
  nickname: 'Talby',
  github: 'Talby223',
};
export const Coryn = {
  nickname: 'Coryn',
  github: 'terndrup',
};
export const AttilioLH = {
  nickname: 'AttilioLH',
  github: 'AttilioLH',
};
export const poneria = {
  nickname: 'poneria',
  github: 'poneria',
};
export const Nekorsis = {
  nickname: 'Nekorsis',
  github: 'Nekorsis',
};
export const greatman = {
  nickname: 'greatman',
  github: 'greatman',
};
export const rubensayshi = {
  nickname: 'rubensayshi',
  github: 'rubensayshi',
};
export const nutspanther = {
  nickname: 'nutspanther',
  github: 'nutspanther',
};
export const Riglerr = {
  nickname: 'Riglerr',
  github: 'Riglerr',
};
export const BlokyKappa = {
  nickname: 'BlokyKappa',
  github: 'BlokyKappa',
};
export const kyleglick = {
  nickname: 'kyle-glick',
  github: 'kyle-glick',
};
export const Zeboot = {
  nickname: 'Zeboot',
  github: 'Zeboot',
};
export const HawkCorrigan = {
  nickname: 'HawkCorrigan',
  github: 'HawkCorrigan',
};
export const Anatta336 = {
  nickname: 'Anatta336',
  github: 'Anatta336',
  discord: 'Anatta#5878',
};
export const Herusx = {
  nickname: 'Herusx',
};
export const Scaleable = {
  nickname: 'Scaleable',
  github: 'wkrueger',
  avatar: require('./Images/scaleable-avatar.png'),
};
export const Cloake = {
  nickname: 'Cloake',
  github: 'adilasif',
  discord: 'Cloake#9930',
  mains: [{
    name: 'Trixx',
    spec: SPECS.ASSASSINATION_ROGUE,
    link: 'https://worldofwarcraft.com/en-us/character/kelthuzad/Trixx',
  }]
};
export const joshinator = {
  nickname: 'joshinator',
  github: 'joshinat0r',
  discord: 'joshinator#7267',
  mains: [{
    name: 'Êxtêndêd',
    spec: SPECS.BLOOD_DEATH_KNIGHT,
    link: 'https://worldofwarcraft.com/en-gb/character/eredar/Êxtêndêd',
  }],
};
export const niseko = {
  nickname: 'niseko',
  github: 'niseko',
  discord: 'niseko#4130',
  avatar: require('./Images/niseko-avatar.jpg'),
  mains: [{
    name: 'Niseko',
    spec: SPECS.RESTORATION_SHAMAN,
    link: 'https://worldofwarcraft.com/en-gb/character/stormscale/niseko',
  },
    {
      name: 'Nisefy',
      spec: SPECS.MISTWEAVER_MONK,
      link: 'https://worldofwarcraft.com/en-gb/character/stormscale/nisefy',
    }],
  links: {
    'Ancestral Guidance': 'https://ancestralguidance.com/',
  },
};
export const Aelexe = {
  nickname: 'Aelexe',
  github: 'Aelexe',
  avatar: require('./Images/Aelexe-avatar.jpg'),
  maintainer: [
    SPECS.ARMS_WARRIOR,
  ],
  mains: [{
    name: 'Aelexe',
    spec: SPECS.HOLY_PALADIN,
    link: 'https://worldofwarcraft.com/en-us/character/frostmourne/aelexe',
  }],
};
export const CubeLuke = {
  nickname: 'CubeLuke',
  github: 'CubeLuke',
  discord: 'CubeLuke#8595',
  avatar: require('./Images/CubeLuke-avatar.jpg'),
  mains: [{
    name: 'Monachi',
    spec: SPECS.MISTWEAVER_MONK,
    link: 'https://worldofwarcraft.com/en-us/character/bleeding-hollow/monachi',
  }],
};
export const ackwell = {
  nickname: 'ackwell',
  github: 'ackwell',
  discord: 'ackwell#3835',
  avatar: require('./Images/ackwell-avatar.png'),
};
export const regret = {
  nickname: 'regret',
  github: 'remyaraya',
  discord: "regret#8633",
  mains: [{
    name: 'Ratchrat',
    spec: SPECS.HOLY_PALADIN,
    link: 'https://worldofwarcraft.com/en-us/character/thrall/ratchrat',
  }],
};
<commit_msg>Updated contributors list<commit_after>/**
 * This contains a listing of all contributors. Entries are used in configs and changelogs.
 * Feel free to add yourself if you're not yet in the list.
 *
 * Using `require` for avatars so we don't have to keep a seperate list of imports disconnected from the maintainer definition.
 *
 EXAMPLE

 export const NICKNAME = {
    nickname: 'NICKNAME',
    github: 'GITHUB_NAME',
    discord: 'DISCORD_NAME INCL #xxxx',
    avatar: require('./Images/IMAGE'),
    desc: 'DESC',
    mains: [{
      name: 'CHARNAME',
      spec: SPECS.BLOOD_DEATH_KNIGHT,
      link: 'ARMOR/WCL-LINK',
    }],
    alts: [{
      name: 'CHARNAME',
      spec: SPECS.BLOOD_DEATH_KNIGHT,
      link: 'ARMOR/WCL-LINK',
    }],
    others: {
      'Custom Item': 'normal text',
      'Custom Item': [
        'Item 1',
        'Item 2',
      ],
    },
    links: {
      'Link No1': 'https://www.google.com',
    },
  };
*/

import SPECS from 'common/SPECS';

// For testing purposes because I am too lazy to work out a solution for testing that does not involve adding regular code
export const Dummy = {
  nickname: 'Dummy',
  github: 'DummyHub',
  twitter: '@Dummy',
  avatar: require('./Images/zerotorescue-avatar.png'),
};
export const Zerotorescue = {
  nickname: 'Zerotorescue',
  github: 'MartijnHols',
  twitter: 'Zerotorescue',
  discord: 'Zerotorescue#0724',
  avatar: require('./Images/zerotorescue-avatar.png'),
  desc: 'WoWAnalyzer founder, Holy Paladin theorycrafter',
  mains: [{
    name: 'Zerotorescue',
    spec: SPECS.HOLY_PALADIN,
    link: 'https://worldofwarcraft.com/en-gb/character/alakir/zerotorescue',
  }],
  alts: [{
    name: 'Zerotorescue',
    spec: SPECS.BREWMASTER_MONK,
    link: 'https://worldofwarcraft.com/en-gb/character/skullcrusher/zerotorescue',
  }, {
    name: 'Zerotorescue',
    spec: SPECS.DISCIPLINE_PRIEST,
    link: 'https://worldofwarcraft.com/en-gb/character/xavius/zerotorescue',
  }, {
    name: 'Leukenaam',
    spec: SPECS.RESTORATION_SHAMAN,
    link: 'https://worldofwarcraft.com/en-gb/character/alakir/leukenaam',
  }, {
    name: 'Zerotohunt',
    spec: SPECS.MARKSMANSHIP_HUNTER,
    link: 'https://worldofwarcraft.com/en-gb/character/alakir/Zerotohunt',
  }],
};
export const blazyb = {
  nickname: 'blazyb',
  github: 'buimichael',
};
export const sref = {
  nickname: 'sref',
  github: 'kfinch',
  avatar: require('./Images/sref-avatar.png'),
};
export const Iskalla = {
  nickname: 'Iskalla',
  github: 'Iskalla',
  avatar: require('./Images/iskalla-avatar.png'),
};
export const enragednuke = {
  nickname: 'enragednuke',
  github: 'enragednuke',
};
export const Skamer = {
  nickname: 'Skamer',
  github: 'Skamer',
};
export const Salarissia = {
  nickname: 'Salarissia',
  github: 'Salarissia',
};
export const WOPR = {
  nickname: 'WOPR',
  github: 'shighman',
};
export const Yajinni = {
  nickname: 'Yajinni',
  github: 'yajinni',
};
export const Bonebasher = {
  nickname: 'Bonebasher',
};
export const Sharrq = {
  nickname: 'Sharrq',
  github: 'Sharrq',
  avatar: require('./Images/Sharrq_avatar.jpg'),
};
export const Khazak = {
  nickname: 'Khazak',
  github: 'jjs451',
};
export const Bicepspump = {
  nickname: 'Bicepspump',
  github: 'Bicepspump',
};
export const Mamtooth = {
  nickname: 'Mamtooth',
  github: 'ronaldpereira',
  avatar: require('./Images/mamtooth-avatar.png'),
};
export const Thieseract = {
  nickname: 'Thieseract',
  github: 'Thieseract',
};
export const Putro = {
  nickname: 'Putro',
  github: 'Pewtro',
  discord: 'Putro#6093',
  avatar: require('./Images/putro-avatar.png'),
  mains: [
    {
      name: 'Putro',
      spec: SPECS.MARKSMANSHIP_HUNTER,
      link: 'https://worldofwarcraft.com/en-gb/character/ragnaros/putro',
    },
  ],
};
export const Blazballs = {
  nickname: 'Blazballs',
  github: 'leapis',
};
export const faide = {
  nickname: 'faide',
  github: 'FaideWW',
  avatar: require('./Images/faide-avatar.png'),
};
export const Fyruna = {
  nickname: 'Fyruna',
  github: 'Fyruna',
  avatar: require('./Images/Fyruna_avatar.jpg'),
};
export const Anomoly = {
  nickname: 'Anomoly',
  github: 'anom0ly',
  avatar: require('./Images/anomoly-avatar.jpg'),
};
export const Juko8 = {
  nickname: 'Juko8',
  github: 'Juko8',
  avatar: require('./Images/juko8-avatar.jpg'),
};
export const Noichxd = {
  nickname: 'Noichxd',
  github: 'Noichxd',
};
export const Hewhosmites = {
  nickname: 'Hewhosmites',
  github: 'CollCrom',
  avatar: require('./Images/hewhosmites-avatar.png'),
};
export const Reglitch = {
  nickname: 'Reglitch',
  github: 'rp4rk',
};
export const Gao = {
  nickname: 'Gao',
  github: 'awlego',
};
export const Oratio = {
  nickname: 'Oratio',
  github: 'karlpralow',
};
export const hassebewlen = {
  nickname: 'hassebewlen',
  github: 'hasseboulen',
};
export const tsabo = {
  nickname: 'tsabo',
  github: 'TsaboTavok',
};
export const zealk = {
  nickname: 'zealk',
  github: 'zealk',
};
export const fasib = {
  nickname: 'fasib',
  github: 'fasib',
};
export const janvavra = {
  nickname: 'janvavra',
  github: 'janvavra',
};
export const Nighteyez07 = {
  nickname: 'Nighteyez07',
  github: 'Nighteyez07',
};
export const Versaya = {
  nickname: 'Versaya',
  github: 'versaya',
};
export const Chizu = {
  nickname: 'Chizu',
  github: 'sMteX',
  avatar: require('./Images/Chizu_avatar.jpg'),
};
export const Hordehobbs = {
  nickname: 'Hordehobbs',
  github: 'hpabst',
};
export const TheBadBossy = {
  nickname: 'TheBadBossy',
  avatar: require('./Images/thebadbossy_avatar.jpg'),
};
export const JLassie82 = {
  nickname: 'JLassie82',
  github: 'JLassie82',
};
export const aryu = {
  nickname: 'aryu',
  github: 'Yuyz0112',
};
export const Dyspho = {
  nickname: 'Dyspho',
};
export const Gurupitka = {
  nickname: 'Gurupitka',
};
export const Hartra344 = {
  nickname: 'hartra344',
  github: 'hartra344',
};
export const strel = {
  nickname: 'strel',
};
export const Maldark = {
  nickname: 'Maldark',
  github: 'Maldark',
};
export const hatra344 = {
  nickname: 'hatra344',
  github: 'hatra344',
};
export const emallson = {
  nickname: 'emallson',
  github: 'emallson',
};
export const Gebuz = {
  nickname: 'Gebuz',
  github: 'Gebuz',
  avatar: require('./Images/gebuz-avatar.png'),
};
export const milesoldenburg = {
  nickname: 'milesoldenburg',
  github: 'milesoldenburg',
};
export const mwwscott0 = {
  nickname: 'mwwscott0',
  github: 'mwwscott0',
};
export const Talby = {
  nickname: 'Talby',
  github: 'Talby223',
};
export const Coryn = {
  nickname: 'Coryn',
  github: 'terndrup',
};
export const AttilioLH = {
  nickname: 'AttilioLH',
  github: 'AttilioLH',
};
export const poneria = {
  nickname: 'poneria',
  github: 'poneria',
};
export const Nekorsis = {
  nickname: 'Nekorsis',
  github: 'Nekorsis',
};
export const greatman = {
  nickname: 'greatman',
  github: 'greatman',
};
export const rubensayshi = {
  nickname: 'rubensayshi',
  github: 'rubensayshi',
};
export const nutspanther = {
  nickname: 'nutspanther',
  github: 'nutspanther',
};
export const Riglerr = {
  nickname: 'Riglerr',
  github: 'Riglerr',
};
export const BlokyKappa = {
  nickname: 'BlokyKappa',
  github: 'BlokyKappa',
};
export const kyleglick = {
  nickname: 'kyle-glick',
  github: 'kyle-glick',
};
export const Zeboot = {
  nickname: 'Zeboot',
  github: 'Zeboot',
};
export const HawkCorrigan = {
  nickname: 'HawkCorrigan',
  github: 'HawkCorrigan',
};
export const Anatta336 = {
  nickname: 'Anatta336',
  github: 'Anatta336',
  discord: 'Anatta#5878',
};
export const Herusx = {
  nickname: 'Herusx',
};
export const Scaleable = {
  nickname: 'Scaleable',
  github: 'wkrueger',
  avatar: require('./Images/scaleable-avatar.png'),
};
export const Cloake = {
  nickname: 'Cloake',
  github: 'adilasif',
  discord: 'Cloake#9930',
  mains: [{
    name: 'Trixx',
    spec: SPECS.ASSASSINATION_ROGUE,
    link: 'https://worldofwarcraft.com/en-us/character/kelthuzad/Trixx',
  }],
};
export const joshinator = {
  nickname: 'joshinator',
  github: 'joshinat0r',
  discord: 'joshinator#7267',
  mains: [{
    name: 'Êxtêndêd',
    spec: SPECS.BLOOD_DEATH_KNIGHT,
    link: 'https://worldofwarcraft.com/en-gb/character/eredar/Êxtêndêd',
  }],
};
export const niseko = {
  nickname: 'niseko',
  github: 'niseko',
  discord: 'niseko#4130',
  avatar: require('./Images/niseko-avatar.jpg'),
  mains: [{
    name: 'Niseko',
    spec: SPECS.RESTORATION_SHAMAN,
    link: 'https://worldofwarcraft.com/en-gb/character/stormscale/niseko',
  },
    {
      name: 'Nisefy',
      spec: SPECS.MISTWEAVER_MONK,
      link: 'https://worldofwarcraft.com/en-gb/character/stormscale/nisefy',
    }],
  links: {
    'Ancestral Guidance': 'https://ancestralguidance.com/',
  },
};
export const Aelexe = {
  nickname: 'Aelexe',
  github: 'Aelexe',
  avatar: require('./Images/Aelexe-avatar.jpg'),
  maintainer: [
    SPECS.ARMS_WARRIOR,
  ],
  mains: [{
    name: 'Aelexe',
    spec: SPECS.HOLY_PALADIN,
    link: 'https://worldofwarcraft.com/en-us/character/frostmourne/aelexe',
  }],
};
export const CubeLuke = {
  nickname: 'CubeLuke',
  github: 'CubeLuke',
  discord: 'CubeLuke#8595',
  avatar: require('./Images/CubeLuke-avatar.jpg'),
  mains: [{
    name: 'Monachi',
    spec: SPECS.MISTWEAVER_MONK,
    link: 'https://worldofwarcraft.com/en-us/character/bleeding-hollow/monachi',
  }],
};
export const ackwell = {
  nickname: 'ackwell',
  github: 'ackwell',
  discord: 'ackwell#3835',
  avatar: require('./Images/ackwell-avatar.png'),
};
export const regret = {
  nickname: 'regret',
  github: 'remyaraya',
  discord: "regret#8633",
  mains: [{
    name: 'Ratchrat',
    spec: SPECS.HOLY_PALADIN,
    link: 'https://worldofwarcraft.com/en-us/character/thrall/ratchrat',
  }],
};
<|endoftext|>


<commit_before>/*
Copyright (C) 2013  Daniel Reid

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

function QArrowsAmmt(attrs) {
	this.type = 'QArrowsAmmt';
	this.wallHandle = attrs.wallInfo;
	this.wall = walls[this.wallHandle];
	this.handle = attrs.handle;
	var wall = this.wall;
	wall.recordQ();
	var qMax = (1 / attrs.scale) * 3;
	this.qArrowAmmtMax = isNaN(qMax) ? 3 : qMax;
	this.qArrowAmmtMax = defaultTo(3, (1 / attrs.scale) * 3);

	addListener(wall, 'cleanUp', 'qArrowsAmmt', function() {
		for (var arrowIdx=0; arrowIdx<this.qArrowsAmmt.length; arrowIdx++) {
			this.qArrowsAmmt[arrowIdx].remove();
		}
		removeListener(curLevel, 'update', this.listenerName);

	}, this)

	this.init(this.wall, this.qArrowAmmtMax);

	this.setupStd();
}

_.extend(QArrowsAmmt.prototype, objectFuncs, {
	init: function(wall, qMax) {
		var lengthMin = 15;
		var lengthMax = 80;
		var widthMin = 70
		var widthMax = 90;
		var col = Col(175, 0, 0);
		var width = 40;
		var fracFromEdge = .25;
		var startingDims = V(30, 10);
		var pos1 = wall[3].copy().fracMoveTo(wall[2], fracFromEdge);
		var pos2 = wall[3].copy().fracMoveTo(wall[2], 1-fracFromEdge);
		var UV = pos2.VTo(pos1).perp('cw').UV();
		pos1.movePt(UV.copy().mult(5));
		pos2.movePt(UV.copy().mult(5));
		var arrow1 = new ArrowStatic({pos:pos1, dims:startingDims, fill: Col(175,0,0), stroke: Col(0,0,0), label:'Q', UV:UV, handle: this.handle + 'Left'});
		var arrow2 = new ArrowStatic({pos:pos2, dims:startingDims, fill: Col(175,0,0), stroke: Col(0,0,0), label:'Q', UV:UV, handle: this.handle + 'Right'});

		var redrawThreshold = qMax/(lengthMax-lengthMin);
		this.qArrowsAmmt = [arrow1, arrow2];
		var dirLast = 'out';
		var dir;
		var turn = 0;
		var qLast = wall.q;
		this.setAmmtArrowDims(this.qArrowsAmmt, lengthMin, lengthMax, widthMin, widthMax, wall.q, qMax);
		if (wall.q >= 0) {
			dirLast = 'in';
			this.flipAmmtArrows(this.qArrowsAmmt);
		}
		this.listenerName = this.handle + 'updateQAmmtArrows';
		addListener(curLevel, 'update', this.listenerName,
			function() {
				console.log(wall.q - qLast);
				if (turn == 20 || (Math.abs(wall.q - qLast) > 0.05 && Math.round(turn/6) == turn/6)) {
					if (Math.abs(wall.q - qLast) > 0.025) {
						if (wall.q - qLast< 0) {
							dir = 'out';
						} else {
							dir = 'in';
						}
						this.setAmmtArrowDims(this.qArrowsAmmt, 0, lengthMax, widthMin, widthMax, 10 * (wall.q - qLast), qMax);
						if (dirLast != dir) {
							this.flipAmmtArrows(this.qArrowsAmmt);
							dirLast = dir;
						}
						qLast = wall.q;
					} else {
						this.setAmmtArrowDims(this.qArrowsAmmt, 0, lengthMax, widthMin, widthMax, 0.00002, 0.2);
						qLast = wall.q;
					}
					turn = -1;
				}
				turn++;
			},
		this);
	},

	flipAmmtArrows: function(arrows) {
		for (var arrowIdx=0; arrowIdx<arrows.length; arrowIdx++) {
			var arrow = arrows[arrowIdx];
			var UV = angleToUV(arrow.getAngle()).mult(1);
			arrow.move(UV.mult(arrow.getDims().dx));
			arrow.rotate(Math.PI);
		}
	},
	setAmmtArrowDims: function(arrows, lMin, lMax, wMin, wMax, q, qMax) {
		for (var arrowIdx=0; arrowIdx<arrows.length; arrowIdx++) {
			var arrow = arrows[arrowIdx];
			var dimso = arrow.getDims();
			var percent = Math.abs(q)/qMax;
			var l = lMin + (lMax-lMin)*percent;
			var w = wMin + (wMax-wMin)*percent;
			arrow.size(V(l, w));
			if (q>0) {
				// arrow.move(V(0, l-dimso.dx));
			}
		}
	},
	remove: function() {
		removeListener(curLevel, 'update', this.listenerName);
	},

})<commit_msg>Q arrows have rate attribute<commit_after>/*
Copyright (C) 2013  Daniel Reid

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

function QArrowsAmmt(attrs) {
	this.type = 'QArrowsAmmt';
	this.rate = attrs.rate != undefined ? attrs.rate : true;
	this.wallHandle = attrs.wallInfo;
	this.wall = walls[this.wallHandle];
	this.handle = attrs.handle;
	var wall = this.wall;
	wall.recordQ();
	var qMax = (1 / attrs.scale) * 3;
	this.qArrowAmmtMax = isNaN(qMax) ? 3 : qMax;
	this.qArrowAmmtMax = defaultTo(3, (1 / attrs.scale) * 3);

	addListener(wall, 'cleanUp', 'qArrowsAmmt', function() {
		for (var arrowIdx=0; arrowIdx<this.qArrowsAmmt.length; arrowIdx++) {
			this.qArrowsAmmt[arrowIdx].remove();
		}
		removeListener(curLevel, 'update', this.listenerName);

	}, this)

	this.init(this.wall, this.qArrowAmmtMax);

	this.setupStd();
}

_.extend(QArrowsAmmt.prototype, objectFuncs, {
	init: function(wall, qMax) {
		var lengthMin = 15;
		var lengthMax = 80;
		var widthMin = 70
		var widthMax = 90;
		var col = Col(175, 0, 0);
		var width = 40;
		var fracFromEdge = .25;
		var startingDims = V(30, 10);
		var pos1 = wall[3].copy().fracMoveTo(wall[2], fracFromEdge);
		var pos2 = wall[3].copy().fracMoveTo(wall[2], 1-fracFromEdge);
		var UV = pos2.VTo(pos1).perp('cw').UV();
		pos1.movePt(UV.copy().mult(5));
		pos2.movePt(UV.copy().mult(5));
		var arrow1 = new ArrowStatic({pos:pos1, dims:startingDims, fill: Col(175,0,0), stroke: Col(0,0,0), label:'Q', UV:UV, handle: this.handle + 'Left'});
		var arrow2 = new ArrowStatic({pos:pos2, dims:startingDims, fill: Col(175,0,0), stroke: Col(0,0,0), label:'Q', UV:UV, handle: this.handle + 'Right'});

		var redrawThreshold = qMax/(lengthMax-lengthMin);
		this.qArrowsAmmt = [arrow1, arrow2];
		var dirLast = 'out';
		var dir;
		var turn = 0;
		var qLast = wall.q;
		this.setAmmtArrowDims(this.qArrowsAmmt, lengthMin, lengthMax, widthMin, widthMax, wall.q, qMax);
		if (wall.q >= 0) {
			dirLast = 'in';
			this.flipAmmtArrows(this.qArrowsAmmt);
		}
		this.listenerName = this.handle + 'updateQAmmtArrows';
		if (this.rate) {
			console.log('true');
			addListener(curLevel, 'update', this.listenerName,
				function() {
					if (turn == 30 || (Math.abs(wall.q - qLast) > 0.025 && Math.round(turn/6) == turn/6)) {
						if (Math.abs(wall.q - qLast) > 0.025) {
							if (wall.q - qLast< 0) {
								dir = 'out';
							} else {
								dir = 'in';
							}
							this.setAmmtArrowDims(this.qArrowsAmmt, 0, lengthMax, widthMin, widthMax, 10 * (wall.q - qLast), qMax);
							if (dirLast != dir) {
								this.flipAmmtArrows(this.qArrowsAmmt);
								dirLast = dir;
							}
							qLast = wall.q;
						} else {
							this.setAmmtArrowDims(this.qArrowsAmmt, 0, lengthMax, widthMin, widthMax, 0, 0.2);
							qLast = wall.q;
						}
						turn = -1;
					}
					turn++;
				},
			this);
		} else {
			console.log('false');
			addListener(curLevel, 'update', this.listenerName,
				function() {
					if (Math.abs(wall.q - qLast) > redrawThreshold) {
						if (wall.q < 0) {
							dir = 'out';
						} else {
							dir = 'in';
						}
						this.setAmmtArrowDims(this.qArrowsAmmt, lengthMin, lengthMax, widthMin, widthMax, wall.q, qMax);
						if (dirLast != dir) {
							this.flipAmmtArrows(this.qArrowsAmmt);
							dirLast = dir;
						}
						qLast = wall.q;
					}
				},
			this);
		}
	},

	flipAmmtArrows: function(arrows) {
		for (var arrowIdx=0; arrowIdx<arrows.length; arrowIdx++) {
			var arrow = arrows[arrowIdx];
			var UV = angleToUV(arrow.getAngle()).mult(1);
			// arrow.move(UV.mult(arrow.getDims().dx));
			arrow.rotate(Math.PI);
		}
	},
	setAmmtArrowDims: function(arrows, lMin, lMax, wMin, wMax, q, qMax) {
		for (var arrowIdx=0; arrowIdx<arrows.length; arrowIdx++) {
			var arrow = arrows[arrowIdx];
			var dimso = arrow.getDims();
			var percent = Math.abs(q)/qMax;
			var l = lMin + (lMax-lMin)*percent;
			var w = wMin + (wMax-wMin)*percent;
			arrow.size(V(l, w));
			if (q>0) {
				//arrow.move(V(0, dimso.dx));
			}
		}
	},
	remove: function() {
		removeListener(curLevel, 'update', this.listenerName);
	},

})<|endoftext|>


<commit_before>//to access filesystem
const fs = require('fs');
//to format filepath
const path = require('path');

var commonUtil = require("./commonUtil");
var commonSearchUtil = require("./commonSearchUtil");

const commandHeader = commonUtil.commandHeader;

function extractKeywords(formattedQuery){
	//search candidates to complement
	var splittedQuery =
		commonSearchUtil.separatePath(formattedQuery,false);

	var currentDirectory;
	var searchKeyword;
	var lengthOfSplittedQuery = splittedQuery.length;
	if(lengthOfSplittedQuery == 0){
		currentDirectory = "";
		searchKeyword = "";
	}else if(lengthOfSplittedQuery == 1){
		currentDirectory = "";
		searchKeyword = splittedQuery[0];
	}else{// length >= 2
		currentDirectory = "";
		for(index = 0 ; index < lengthOfSplittedQuery - 1 ; index++){
			currentDirectory = currentDirectory + splittedQuery[index] + "\\";
		}
		//currentDirectory.pop();
		searchKeyword = splittedQuery[lengthOfSplittedQuery - 1];
	}
	return [currentDirectory, searchKeyword];
}

exports.searchCandidates = function(formattedQuery, availableDrives, res){
	var keywords = extractKeywords(formattedQuery);
	var currentDirectory = keywords[0];
	var searchKeyword = keywords[1];
	//search available path to complement
	var foundCandidates = [];

	//check the existence of currentDirectory.
	if(currentDirectory == "" || commonSearchUtil.checkFileOrFolder(currentDirectory) == 2){
		//find candidates
		if(currentDirectory == ""){
			for (var index = 0, len = availableDrives.length ; index < len ; index++){
				if(availableDrives[index].isAvailable == true){
					foundCandidates.push(
						{
							path: availableDrives[index].driveName,
							state: "drive"
						}
					);
				}
			}
		}else{// when checkFileOrFolder(currentDirectory) == 2
			//(todo) make async (use fs.readdir)
			var foundList = fs.readdirSync(currentDirectory);
			//foundCandidates = fs.readdirSync(currentDirectory);
			for(var index = 0, len = foundList.length ; index < len ; index++){
				switch(commonSearchUtil.checkFileOrFolder(currentDirectory + foundList[index])){
					case 1://file
						foundCandidates.push(
							{
								path:foundList[index],
								state:"file"
							}
						);
						break;
					case 2://folder
						foundCandidates.push(
							{
								path:foundList[index],
								state:"folder"
							}
						);
						break;
					default:
						//do nothing
						break;
				}
			}
		}
	}

	//filter with searchKeyword and add to res
	if(foundCandidates.length > 0){
		for(var index = 0, len = foundCandidates.length ; index < len ; index++){
			//check if candidates contains searchKeyword on head
			var candidate = foundCandidates[index].path;
			if(foundCandidates[index].path.toLocaleLowerCase().indexOf(searchKeyword.toLocaleLowerCase()) == 0){
				//add to res.
				var descriptionMessage =
					"Set this path : \"" + foundCandidates[index].path + "\"";
				//var redirect;
				var innerId, innerTitle, innerIcon, innerRedirect;
				switch(foundCandidates[index].state){
					case "drive":
						innerTitle = "";
						innerId =
							commandHeader + " " +
							currentDirectory + foundCandidates[index].path + "\\";
						innerIcon = "#fa fa-hdd-o";
						innerRedirect = innerId;
						break;
					case "file":
						innerTitle = ".\\";
						innerId =
							commandHeader + " " +
							currentDirectory + foundCandidates[index].path;
						innerIcon = "#fa fa-file";
						innerRedirect = innerId;
						break;
					case "folder":
						innerTitle = ".\\";
						innerId =
							commandHeader + " " +
							currentDirectory + foundCandidates[index].path + "\\";
						innerIcon = "#fa fa-folder";
						innerRedirect = innerId;
						break;
				}
				innerTitle = innerTitle +
					"" + foundCandidates[index].path.substring(0,searchKeyword.length) + "" +
					foundCandidates[index].path.substring(searchKeyword.length);
				res.add(
					{
						//id: checkingDrive,
						id: innerId,
						payload: 'complement',
						title: innerTitle,
						icon: innerIcon,
						desc: descriptionMessage,
						redirect:innerRedirect
					}
				);
			}
		}
	}
	//return foundCandidates;
}
<commit_msg>Add function to emphasize<commit_after>//to access filesystem
const fs = require('fs');
//to format filepath
const path = require('path');

var commonUtil = require("./commonUtil");
var commonSearchUtil = require("./commonSearchUtil");

const commandHeader = commonUtil.commandHeader;

function extractKeywords(formattedQuery){
	//search candidates to complement
	var splittedQuery =
		commonSearchUtil.separatePath(formattedQuery,false);

	var currentDirectory;
	var searchKeyword;
	var lengthOfSplittedQuery = splittedQuery.length;
	if(lengthOfSplittedQuery == 0){
		currentDirectory = "";
		searchKeyword = "";
	}else if(lengthOfSplittedQuery == 1){
		currentDirectory = "";
		searchKeyword = splittedQuery[0];
	}else{// length >= 2
		currentDirectory = "";
		for(index = 0 ; index < lengthOfSplittedQuery - 1 ; index++){
			currentDirectory = currentDirectory + splittedQuery[index] + "\\";
		}
		//currentDirectory.pop();
		searchKeyword = splittedQuery[lengthOfSplittedQuery - 1];
	}
	return [currentDirectory, searchKeyword];
}

//ex tags: ["",""]
function emphasize(target, keyword){
	var tag = ["",""];
	var empFlag = false;
	var eval = 0;
	var ret = "";
	var checkingPos = 0;
	var innerTarget = target;
	for(var indexOfKeyword = 0, lengthOfKeyword = keyword.length ; indexOfKeyword < lengthOfKeyword ; indexOfKeyword++){
		var foundPos = innerTarget.toLocaleLowerCase().indexOf(keyword[indexOfKeyword].toLocaleLowerCase());
		if(foundPos == -1){
			eval = eval - 1;
			if(indexOfKeyword == lengthOfKeyword - 1){
				if(empFlag == true){
					ret = ret + tag[1];
				}
				ret = ret + innerTarget;
			}
		}else{
			eval = eval + 1;
			if(empFlag == false){
				ret = ret + innerTarget.substring(0, foundPos);
				ret = ret + tag[0];
				ret = ret + innerTarget[foundPos];
				innerTarget = innerTarget.substring(foundPos + 1);
				empFlag = true;
			}else{
				if(foundPos > 0){ ret = ret + tag[1]; }
				ret = ret + innerTarget.substring(0, foundPos);
				if(foundPos > 0){ ret = ret + tag[0]; }
				ret = ret + innerTarget[foundPos];
				innerTarget = innerTarget.substring(foundPos + 1);
			}
			if(empFlag == true && indexOfKeyword == lengthOfKeyword - 1){
				ret = ret + tag[1];
				ret = ret + innerTarget;
			}
		}
	}
	if(eval > 0){
		return ret;
	}else{
		return "";
	}
}

exports.searchCandidates = function(formattedQuery, availableDrives, res){
	var keywords = extractKeywords(formattedQuery);
	var currentDirectory = keywords[0];
	var searchKeyword = keywords[1];
	//search available path to complement
	var foundCandidates = [];

	//check the existence of currentDirectory.
	if(currentDirectory == "" || commonSearchUtil.checkFileOrFolder(currentDirectory) == 2){
		//find candidates
		if(currentDirectory == ""){
			for (var index = 0, len = availableDrives.length ; index < len ; index++){
				if(availableDrives[index].isAvailable == true){
					foundCandidates.push(
						{
							path: availableDrives[index].driveName,
							state: "drive"
						}
					);
				}
			}
		}else{// when checkFileOrFolder(currentDirectory) == 2
			//(todo) make async (use fs.readdir)
			var foundList = fs.readdirSync(currentDirectory);
			//foundCandidates = fs.readdirSync(currentDirectory);
			for(var index = 0, len = foundList.length ; index < len ; index++){
				switch(commonSearchUtil.checkFileOrFolder(currentDirectory + foundList[index])){
					case 1://file
						foundCandidates.push(
							{
								path:foundList[index],
								state:"file"
							}
						);
						break;
					case 2://folder
						foundCandidates.push(
							{
								path:foundList[index],
								state:"folder"
							}
						);
						break;
					default:
						//do nothing
						break;
				}
			}
		}
	}

	//filter with searchKeyword and add to res
	if(foundCandidates.length > 0){
		for(var index = 0, len = foundCandidates.length ; index < len ; index++){
			//check if candidates contains searchKeyword on head
			var candidate = foundCandidates[index].path;
			var emphasizedCandidate = emphasize(foundCandidates[index].path, searchKeyword);
			//if(foundCandidates[index].path.toLocaleLowerCase().indexOf(searchKeyword.toLocaleLowerCase()) == 0){
			if(emphasizedCandidate.length > 0 || searchKeyword.length == 0){
				//add to res.
				var descriptionMessage =
					"Set this path : \"" + foundCandidates[index].path + "\"";
				//var redirect;
				var innerId, innerTitle, innerIcon, innerRedirect;
				switch(foundCandidates[index].state){
					case "drive":
						innerTitle = "";
						innerId =
							commandHeader + " " +
							currentDirectory + foundCandidates[index].path + "\\";
						innerIcon = "#fa fa-hdd-o";
						innerRedirect = innerId;
						break;
					case "file":
						innerTitle = ".\\";
						innerId =
							commandHeader + " " +
							currentDirectory + foundCandidates[index].path;
						innerIcon = "#fa fa-file";
						innerRedirect = innerId;
						break;
					case "folder":
						innerTitle = ".\\";
						innerId =
							commandHeader + " " +
							currentDirectory + foundCandidates[index].path + "\\";
						innerIcon = "#fa fa-folder";
						innerRedirect = innerId;
						break;
				}
				/*
				innerTitle = innerTitle +
					"" + foundCandidates[index].path.substring(0,searchKeyword.length) + "" +
					foundCandidates[index].path.substring(searchKeyword.length);
					*/
				if(searchKeyword.length == 0){
					innerTitle = innerTitle + foundCandidates[index].path;
				}else{
					innerTitle = innerTitle + emphasizedCandidate;
				}
				res.add(
					{
						//id: checkingDrive,
						id: innerId,
						payload: 'complement',
						title: innerTitle,
						icon: innerIcon,
						desc: descriptionMessage,
						redirect:innerRedirect
					}
				);
			}
		}
	}
	//return foundCandidates;
}
<|endoftext|>


<commit_before>var Comment = require('lib/models').Comment
var scopes = require('./scopes')

/**
 * Default find Method, to be used in favor of Model.find()
 * @method find
 * @param  {object} query - Mongoose query options
 * @return {Mongoose Query}
 */
function find (query) {
  return Comment.find(Object.assign({
    context: 'topic'
  }, query))
}

exports.find = find

/**
 * Get the public listing of comments from a topic
 * @method list
 * @param  {object} opts
 * @param  {objectId} opts.topicId
 * @param  {number} opts.limit - Amount of results per page
 * @param  {number} opts.page - Page number
 * @param  {document} opts.user - User data is beign fetched for
 * @param  {('score'|'-score'|'createdAt'|'-createdAt')} opts.sort
 * @return {promise}
 */
exports.list = function list (opts) {
  opts = opts || {}

  return find()
    .where({reference: opts.topicId})
    .populate(scopes.ordinary.populate)
    .select(scopes.ordinary.select)
    .limit(opts.limit)
    .skip((opts.page - 1) * opts.limit)
    .sort(opts.sort)
    .exec()
    .then((comments) => comments.map((comment) => {
      return scopes.ordinary.expose(comment, opts.user)
    }))
}

/**
 * Get the count of total comments of the public listing
 * @method listCount
 * @param  {object} opts
 * @param  {objectId} opts.topicId
 * @return {promise}
 */
exports.listCount = function listCount (opts) {
  opts = opts || {}

  return find()
    .where({reference: opts.topicId})
    .count()
    .exec()
}

/**
 * Create or Update a vote on a comment
 * @method vote
 * @param  {object} opts
 * @param {string} opts.id - Comment Id
 * @param {document} opts.user - Author of the vote
 * @param {('positive'|'negative')} opts.value - Vote value
 * @return {promise}
 */
exports.vote = function vote (opts) {
  const id = opts.id
  const user = opts.user
  const value = opts.value

  return find()
    .findOne()
    .where({_id: id})
    .populate(scopes.ordinary.populate)
    .select(scopes.ordinary.select)
    .exec()
    .then(verifyAutovote.bind(null, user))
    .then(doVote.bind(null, user, value))
    .then((comment) => scopes.ordinary.expose(comment, user))
}

function verifyAutovote (user, comment) {
  if (comment.author.equals(user._id)) {
    const err = new Error('A user can\'t vote his own comment.')
    err.code = 'NO_AUTOVOTE'
    throw err
  }
  return comment
}

function doVote (user, value, comment) {
  return new Promise((resolve, reject) => {
    comment.vote(user, value, function (err) {
      if (err) return reject(err)
      resolve(comment)
    })
  })
}

/**
 * Create or Update a vote on a comment
 * @method vote
 * @param  {object} opts
 * @param {string} opts.id - Comment Id
 * @param {document} opts.user - Author of the vote
 * @param {('positive'|'negative')} opts.value - Vote value
 * @return {promise}
 */
exports.unvote = function unvote (opts) {
  const id = opts.id
  const user = opts.user

  return find()
    .findOne()
    .where({_id: id})
    .populate(scopes.ordinary.populate)
    .select(scopes.ordinary.select)
    .exec()
    .then(doUnvote.bind(null, user))
    .then((comment) => scopes.ordinary.expose(comment, user))
}

function doUnvote (user, comment) {
  return new Promise((resolve, reject) => {
    comment.unvote(user, function (err) {
      if (err) return reject(err)
      resolve(comment)
    })
  })
}

/**
 * Create a comment
 * @method vote
 * @param  {object} opts
 * @param {string} opts.text - Comment text
 * @param {document} opts.user - Author of the comment
 * @param {document} opts.topicId - Topic where the comment is beign created
 * @return {promise}
 */
exports.create = function create (opts) {
  const text = opts.text
  const user = opts.user
  const topicId = opts.topicId

  return Comment
    .create({
      text: text,
      context: 'topic',
      reference: topicId,
      author: user
    })
    .then((comment) => scopes.ordinary.expose(comment, user))
}

/**
 * Create a reply
 * @method reply
 * @param  {object} opts
 * @param {string} opts.text - Reply text
 * @param {document} opts.user - Author of the comment
 * @param {document} opts.id - Comment id
 * @return {promise}
 */
exports.reply = function reply (opts) {
  const text = opts.text
  const user = opts.user
  const id = opts.id

  return find()
    .findOne()
    .where({_id: id})
    .populate(scopes.ordinary.populate)
    .select(scopes.ordinary.select)
    .exec()
    .then(doReply.bind(null, user, text))
    .then((comment) => scopes.ordinary.expose(comment, user))
}

function doReply (user, text, comment) {
  return new Promise((resolve, reject) => {
    const reply = comment.replies.create({
      text: text,
      author: user
    })
    comment.replies.push(reply)
    comment.save((err, commentSaved) => {
      if (err) reject(err)
      resolve(commentSaved)
    })
  })
}

/**
 * Delete comment
 * @method delete
 * @param  {object} opts
 * @param {document} opts.user - Author of the comment
 * @param {document} opts.id - Comment id
 * @return {promise}
 */
exports.removeComment = function remove (opts) {
  const id = opts.id

  return find()
    .findOne()
    .populate(scopes.ordinary.populate)
    .select(scopes.ordinary.select)
    .where({_id: id})
    .exec()
    .then((comment) => {
      if (comment.author.id !== opts.user.id && !opts.user.staff) {
        return Promise.reject({
          status: 403,
          code: 'NOT_YOURS'
        })
      }

      if (comment.replies.length > 0 && !opts.user.staff) {
        return Promise.reject({
          status: 403,
          code: 'HAS_REPLIES'
        })
      }

      return Promise.resolve(comment)
    })
    .then(doRemoveComment)
}

function doRemoveComment (comment) {
  return new Promise((resolve, reject) => {
    comment.remove((err) => {
      if (err) reject(err)
      resolve()
    })
  })
}

/**
 * Delete comment reply
 * @method delete
 * @param  {object} opts
 * @param {document} opts.user - Author of the comment
 * @param {document} opts.id - Comment id
 * @param {document} opts.replyId - Reply id
 * @return {promise}
 */
exports.removeReply = function remove (opts) {
  const id = opts.id

  return find()
    .findOne()
    .populate(scopes.ordinary.populate)
    .select(scopes.ordinary.select)
    .where({_id: id})
    .exec()
    .then((comment) => {
      var reply = comment.replies.id(opts.replyId)
      if (reply.author !== opts.user.id && !opts.user.staff) {
        return Promise.reject({
          status: 403,
          code: 'NOT_YOURS'
        })
      }
      return Promise.resolve(comment, opts.replyId)
    })
    .then(doRemoveReply)
}

function doRemoveReply (comment, replyId) {
  return new Promise((resolve, reject) => {
    comment.replies.id(replyId).remove()
    comment.save(function (err, _comment) {
      if (err) reject(err)
      resolve(_comment)
    })
  })
}
<commit_msg>update delete reply api method<commit_after>var Comment = require('lib/models').Comment
var scopes = require('./scopes')

/**
 * Default find Method, to be used in favor of Model.find()
 * @method find
 * @param  {object} query - Mongoose query options
 * @return {Mongoose Query}
 */
function find (query) {
  return Comment.find(Object.assign({
    context: 'topic'
  }, query))
}

exports.find = find

/**
 * Get the public listing of comments from a topic
 * @method list
 * @param  {object} opts
 * @param  {objectId} opts.topicId
 * @param  {number} opts.limit - Amount of results per page
 * @param  {number} opts.page - Page number
 * @param  {document} opts.user - User data is beign fetched for
 * @param  {('score'|'-score'|'createdAt'|'-createdAt')} opts.sort
 * @return {promise}
 */
exports.list = function list (opts) {
  opts = opts || {}

  return find()
    .where({reference: opts.topicId})
    .populate(scopes.ordinary.populate)
    .select(scopes.ordinary.select)
    .limit(opts.limit)
    .skip((opts.page - 1) * opts.limit)
    .sort(opts.sort)
    .exec()
    .then((comments) => comments.map((comment) => {
      return scopes.ordinary.expose(comment, opts.user)
    }))
}

/**
 * Get the count of total comments of the public listing
 * @method listCount
 * @param  {object} opts
 * @param  {objectId} opts.topicId
 * @return {promise}
 */
exports.listCount = function listCount (opts) {
  opts = opts || {}

  return find()
    .where({reference: opts.topicId})
    .count()
    .exec()
}

/**
 * Create or Update a vote on a comment
 * @method vote
 * @param  {object} opts
 * @param {string} opts.id - Comment Id
 * @param {document} opts.user - Author of the vote
 * @param {('positive'|'negative')} opts.value - Vote value
 * @return {promise}
 */
exports.vote = function vote (opts) {
  const id = opts.id
  const user = opts.user
  const value = opts.value

  return find()
    .findOne()
    .where({_id: id})
    .populate(scopes.ordinary.populate)
    .select(scopes.ordinary.select)
    .exec()
    .then(verifyAutovote.bind(null, user))
    .then(doVote.bind(null, user, value))
    .then((comment) => scopes.ordinary.expose(comment, user))
}

function verifyAutovote (user, comment) {
  if (comment.author.equals(user._id)) {
    const err = new Error('A user can\'t vote his own comment.')
    err.code = 'NO_AUTOVOTE'
    throw err
  }
  return comment
}

function doVote (user, value, comment) {
  return new Promise((resolve, reject) => {
    comment.vote(user, value, function (err) {
      if (err) return reject(err)
      resolve(comment)
    })
  })
}

/**
 * Create or Update a vote on a comment
 * @method vote
 * @param  {object} opts
 * @param {string} opts.id - Comment Id
 * @param {document} opts.user - Author of the vote
 * @param {('positive'|'negative')} opts.value - Vote value
 * @return {promise}
 */
exports.unvote = function unvote (opts) {
  const id = opts.id
  const user = opts.user

  return find()
    .findOne()
    .where({_id: id})
    .populate(scopes.ordinary.populate)
    .select(scopes.ordinary.select)
    .exec()
    .then(doUnvote.bind(null, user))
    .then((comment) => scopes.ordinary.expose(comment, user))
}

function doUnvote (user, comment) {
  return new Promise((resolve, reject) => {
    comment.unvote(user, function (err) {
      if (err) return reject(err)
      resolve(comment)
    })
  })
}

/**
 * Create a comment
 * @method vote
 * @param  {object} opts
 * @param {string} opts.text - Comment text
 * @param {document} opts.user - Author of the comment
 * @param {document} opts.topicId - Topic where the comment is beign created
 * @return {promise}
 */
exports.create = function create (opts) {
  const text = opts.text
  const user = opts.user
  const topicId = opts.topicId

  return Comment
    .create({
      text: text,
      context: 'topic',
      reference: topicId,
      author: user
    })
    .then((comment) => scopes.ordinary.expose(comment, user))
}

/**
 * Create a reply
 * @method reply
 * @param  {object} opts
 * @param {string} opts.text - Reply text
 * @param {document} opts.user - Author of the comment
 * @param {document} opts.id - Comment id
 * @return {promise}
 */
exports.reply = function reply (opts) {
  const text = opts.text
  const user = opts.user
  const id = opts.id

  return find()
    .findOne()
    .where({_id: id})
    .populate(scopes.ordinary.populate)
    .select(scopes.ordinary.select)
    .exec()
    .then(doReply.bind(null, user, text))
    .then((comment) => scopes.ordinary.expose(comment, user))
}

function doReply (user, text, comment) {
  return new Promise((resolve, reject) => {
    const reply = comment.replies.create({
      text: text,
      author: user
    })
    comment.replies.push(reply)
    comment.save((err, commentSaved) => {
      if (err) reject(err)
      resolve(commentSaved)
    })
  })
}

/**
 * Delete comment
 * @method delete
 * @param  {object} opts
 * @param {document} opts.user - Author of the comment
 * @param {document} opts.id - Comment id
 * @return {promise}
 */
exports.removeComment = function remove (opts) {
  const id = opts.id

  return find()
    .findOne()
    .populate(scopes.ordinary.populate)
    .select(scopes.ordinary.select)
    .where({_id: id})
    .exec()
    .then((comment) => {
      if (comment.author.id !== opts.user.id && !opts.user.staff) {
        return Promise.reject({
          status: 403,
          code: 'NOT_YOURS'
        })
      }

      if (comment.replies.length > 0 && !opts.user.staff) {
        return Promise.reject({
          status: 403,
          code: 'HAS_REPLIES'
        })
      }

      return Promise.resolve(comment)
    })
    .then(doRemoveComment)
}

function doRemoveComment (comment) {
  return new Promise((resolve, reject) => {
    comment.remove((err) => {
      if (err) reject(err)
      resolve()
    })
  })
}

/**
 * Delete comment reply
 * @method delete
 * @param  {object} opts
 * @param {document} opts.user - Author of the comment
 * @param {document} opts.id - Comment id
 * @param {document} opts.replyId - Reply id
 * @return {promise}
 */
exports.removeReply = function remove (opts) {
  const id = opts.id

  return find()
    .findOne()
    .populate(scopes.ordinary.populate)
    .select(scopes.ordinary.select)
    .where({_id: id})
    .exec()
    .then((comment) => {
      var reply = comment.replies.id(opts.replyId)
      if (reply.author.id !== opts.user.id && !opts.user.staff) {
        return Promise.reject({
          status: 403,
          code: 'NOT_YOURS'
        })
      }
      return new Promise((resolve, reject) => {
        reply.remove()
        comment.save(function (err, _comment) {
          if (err) reject(err)
          resolve(_comment)
        })
      })
    })
}
<|endoftext|>