<commit_before>"use babel"

const os = require("os");
const fs = require('fs');
const path = require('path');
const which = require('which');

const char1glslRegex = /^(.*(?:\.|_))(v|g|f)(\.glsl)$/
const char2glslRegex = /^(.*(?:\.|_))(vs|tc|te|gs|fs|cs)(\.glsl)$/
const char1shRegex = /^(.*\.)(v|g|f)sh$/
const char2Regex = /^(.*\.)(vs|tc|te|gs|fs|cs)$/
const defaultRegex = /^(.*\.)(vert|frag|geom|tesc|tese|comp)$/

const compileRegex = "^([\\w \\-]+): (\\d+):(\\d+): (.*)$";
const linkRegex = "^([\\w \\-]+): Linking {{typeName}} stage: (.*)$";

const shaderTypes = [{
  'char1': 'v',
  'char2': 'vs',
  'char4': 'vert',
  'name': 'vertex'
}, {
  'char1': 'f',
  'char2': 'fs',
  'char4': 'frag',
  'name': 'fragment'
}, {
  'char1': 'g',
  'char2': 'gs',
  'char4': 'geom',
  'name': 'geometry'
}, {
  'char2': 'te',
  'char4': 'tese',
  'name': 'tessellation evaluation'
}, {
  'char2': 'tc',
  'char4': 'tesc',
  'name': 'tessellation control'
}, {
  'char2': 'cs',
  'char4': 'comp',
  'name': 'compute'
}];

const shaderTypeLookup = (fieldName, fieldValue) =>
  shaderTypes.filter(shaderType => shaderType[fieldName] == fieldValue)[0];

const shaderByChar1 = char1 => shaderTypeLookup('char1', char1);
const shaderByChar2 = char2 => shaderTypeLookup('char2', char2);
const shaderByChar4 = char4 => shaderTypeLookup('char4', char4);
const shaderByName = name => shaderTypeLookup('name', name);

const parseGlslValidatorResponse = (inputs, output) => new Promise((resolve, reject) => {
  const toReturn = [];

  var currentShader;

  inputs.forEach(shader => {
    var compileStarted = false;
    var linkStarted = false;
    var typeName = shader.type.name;

    output.split(os.EOL).forEach(line => {
      if (line.endsWith(shader.name)) {
        compileStarted = true;
      } else if (compileStarted || inputs.length == 1) {
        var match = new RegExp(compileRegex).exec(line)
        if (match) {
          const lineStart = parseInt(match[3])
          const colStart = parseInt(match[2])
          const lineEnd = lineStart
          const colEnd = colStart

          toReturn.push({
            type: match[1],
            text: match[4].trim(),
            filePath: shader.fullFilename,
            range: [
              [lineStart > 0 ? lineStart - 1 : 0, colStart > 0 ? colStart - 1 : 0],
              [lineEnd > 0 ? lineEnd - 1 : 0, colEnd > 0 ? colEnd - 1 : 0]
            ]
          });
        } else {
          compileStarted = false;
        }
      }

      const linkMatch = new RegExp(linkRegex.replace("{{typeName}}", typeName)).exec(line)
      if (linkMatch) {
        toReturn.push({
          type: linkMatch[1],
          text: linkMatch[2].trim(),
          filePath: shader.fullFilename,
          range: [
            [0, 0],
            [0, 0]
          ]
        });
      }
    })
  })
  resolve(toReturn);
});

const extractShaderFilenameTokens = shaderFilename => {
  const fileName = path.basename(shaderFilename)
  const dirName = path.dirname(shaderFilename)

  var baseFilename;
  var baseShaderType;
  var baseExtension;

  var linkTargetPattern;
  var linkTargets;

  const extChar1GlslMatch = char1glslRegex.exec(fileName);
  const extChar2GlslMatch = char2glslRegex.exec(fileName);
  const extChar2Match = char2Regex.exec(fileName);
  const extChar1ShMatch = char1shRegex.exec(fileName);
  const extDefaultMatch = defaultRegex.exec(fileName);

  if (extChar1GlslMatch) {
    baseFilename = extChar1GlslMatch[1];
    baseShaderType = shaderByChar1(extChar1GlslMatch[2]);
    baseExtension = extChar1GlslMatch[3];

    linkTargets = shaderTypes.filter(shaderType => shaderType != baseShaderType);
    linkTargetPattern = `${baseFilename}{{char1}}${baseExtension}`;
  } else if (extChar2GlslMatch) {
    baseFilename = extChar2GlslMatch[1];
    baseShaderType = shaderByChar2(extChar2GlslMatch[2]);
    baseExtension = extChar2GlslMatch[3];

    linkTargets = shaderTypes.filter(shaderType => shaderType != baseShaderType);
    linkTargetPattern = `${baseFilename}{{char2}}${baseExtension}`;
  } else if (extChar1ShMatch) {
    baseFilename = extChar1ShMatch[1];
    baseShaderType = shaderByChar1(extChar1ShMatch[2]);
    baseExtension = extChar1ShMatch[3];

    linkTargets = shaderTypes.filter(shaderType => shaderType != baseShaderType);
    linkTargetPattern = `${baseFilename}{{char1}}${baseExtension}`;
  } else if (extChar2Match) {
    baseFilename = extChar2Match[1];
    baseShaderType = shaderByChar2(extChar2Match[2]);
    baseExtension = extChar2Match[3];

    linkTargets = shaderTypes.filter(shaderType => shaderType != baseShaderType);
    linkTargetPattern = `${baseFilename}{{char2}}${baseExtension}`;
  } else if (extDefaultMatch) {
    baseFilename = extDefaultMatch[1];
    baseShaderType = shaderByChar4(extDefaultMatch[2]);
    baseExtension = extDefaultMatch[2];

    linkTargets = shaderTypes.filter(shaderType => shaderType != baseShaderType);
    linkTargetPattern = `${baseFilename}{{char4}}`
  } else {
    throw "Unknown shader type"
  }

  var outFilename = baseFilename;
  if (!outFilename.endsWith(".")) outFilename += ".";

  outFilename += baseShaderType.char4;

  return {
    linkTargets: linkTargets,
    linkTargetPattern: linkTargetPattern,
    baseFilename: baseFilename,
    baseShaderType: baseShaderType,
    dirName: dirName,
    outFilename: outFilename,
    fullFilename: shaderFilename
  };
};

export default {
  config: {
    glslangValidatorPath: {
      type: "string",
      default: "glslangValidator",
      order: 1
    },
    linkSimilarShaders: {
      type: "boolean",
      default: false,
      order: 2
    }
  },

  activate: _ => {
    require("atom-package-deps").install("linter-glsl");

    CompositeDisposable = require('atom').CompositeDisposable;
    MessagePanelView = require('atom-message-panel').MessagePanelView;
    PlainMessageView = require('atom-message-panel').PlainMessageView;

    this.subscriptions = new CompositeDisposable();

    this.subscriptions.add(
      atom.config.observe('linter-glsl.linkSimilarShaders', (linkSimilarShaders) => {
        this.linkSimilarShaders = linkSimilarShaders;
      })
    );

    this.subscriptions.add(
      atom.config.observe('linter-glsl.glslangValidatorPath', (glslangValidatorPath) => {
        this.glslangValidatorPath = module.exports.config.glslangValidatorPath.default;
        if (fs.existsSync(glslangValidatorPath) && fs.statSync(glslangValidatorPath).isFile()) {
          try {
            fs.accessSync(glslangValidatorPath, fs.X_OK);
            this.glslangValidatorPath = glslangValidatorPath;
          } catch (error) {
            console.log(error);
          }
        } else {
          try {
            this.glslangValidatorPath = which.sync(glslangValidatorPath);
          } catch(error) {
            console.log(error);
          }
        }

        if (this.glslangValidatorPath) {
          if (this.messages) {
            this.messages.close();
            this.messages = undefined;
          }
        } else {
          if (!this.messages) {
            this.messages = new MessagePanelView({
              title: "linter-glsl"
            });
            this.messages.attach();
            this.messages.toggle();
          }
          this.messages.clear();
          this.messages.add(new PlainMessageView({
            message: `Unable to locate glslangValidator at '${glslangValidatorPath}'`,
            className: 'text-error'
          }));
        }
      }));
  },

  deactivate: _ => {
    this.subscriptions.dispose();
  },

  provideLinter: _ => {
    const helpers = require("atom-linter");

    return {
      name: "glsl",
      grammarScopes: ["source.glsl"],
      scope: "file",
      lintOnFly: true,
      lint: (activeEditor) => {
        const file = activeEditor.getPath();
        const content = activeEditor.getText();
        var command = this.glslangValidatorPath;

        if (this.glslangValidatorPath == undefined) {
          command = module.exports.config.glslangValidatorPath.default;
        }

        const shaderFileTokens = extractShaderFilenameTokens(file);

        var filesToValidate = [{
          'name': shaderFileTokens.outFilename,
          'fullFilename': file,
          'type': shaderFileTokens.baseShaderType,
          'contents': content
        }];
        var args = [];
        var results;

        if (this.linkSimilarShaders) {
          filesToValidate = filesToValidate.concat(shaderFileTokens.linkTargets
            .map(target => shaderFileTokens.linkTargetPattern
              .replace("{{char1}}", target.char1)
              .replace("{{char2}}", target.char2)
              .replace("{{char4}}", target.char4))
            .map(shader => path.join(shaderFileTokens.dirName, shader))
            .filter(fs.existsSync)
            .map(extractShaderFilenameTokens)
            .map(shader => {
              return {
                'name': shader.outFilename,
                'fullFilename': shader.fullFilename,
                'type': shader.baseShaderType,
                'contents': fs.readFileSync(shader.fullFilename, "UTF-8")
              }
            }));

          if (filesToValidate.length > 1) {
            args = ["-l"];
          }
        }
        return helpers.tempFiles(
            filesToValidate,
            files => helpers.exec(command, args.concat(files), {
              stream: "stdout",
              ignoreExitCode: true
            }))
          .then(output => parseGlslValidatorResponse(filesToValidate, output))
          .catch(console.log);
      }
    };
  }
};
<commit_msg>Fix initialization on Atom beta<commit_after>"use babel"

const os = require("os");
const fs = require('fs');
const path = require('path');
const which = require('which');

const char1glslRegex = /^(.*(?:\.|_))(v|g|f)(\.glsl)$/
const char2glslRegex = /^(.*(?:\.|_))(vs|tc|te|gs|fs|cs)(\.glsl)$/
const char1shRegex = /^(.*\.)(v|g|f)sh$/
const char2Regex = /^(.*\.)(vs|tc|te|gs|fs|cs)$/
const defaultRegex = /^(.*\.)(vert|frag|geom|tesc|tese|comp)$/

const compileRegex = "^([\\w \\-]+): (\\d+):(\\d+): (.*)$";
const linkRegex = "^([\\w \\-]+): Linking {{typeName}} stage: (.*)$";

const shaderTypes = [{
  'char1': 'v',
  'char2': 'vs',
  'char4': 'vert',
  'name': 'vertex'
}, {
  'char1': 'f',
  'char2': 'fs',
  'char4': 'frag',
  'name': 'fragment'
}, {
  'char1': 'g',
  'char2': 'gs',
  'char4': 'geom',
  'name': 'geometry'
}, {
  'char2': 'te',
  'char4': 'tese',
  'name': 'tessellation evaluation'
}, {
  'char2': 'tc',
  'char4': 'tesc',
  'name': 'tessellation control'
}, {
  'char2': 'cs',
  'char4': 'comp',
  'name': 'compute'
}];

const shaderTypeLookup = (fieldName, fieldValue) =>
  shaderTypes.filter(shaderType => shaderType[fieldName] == fieldValue)[0];

const shaderByChar1 = char1 => shaderTypeLookup('char1', char1);
const shaderByChar2 = char2 => shaderTypeLookup('char2', char2);
const shaderByChar4 = char4 => shaderTypeLookup('char4', char4);
const shaderByName = name => shaderTypeLookup('name', name);

const parseGlslValidatorResponse = (inputs, output) => new Promise((resolve, reject) => {
  const toReturn = [];

  var currentShader;

  inputs.forEach(shader => {
    var compileStarted = false;
    var linkStarted = false;
    var typeName = shader.type.name;

    output.split(os.EOL).forEach(line => {
      if (line.endsWith(shader.name)) {
        compileStarted = true;
      } else if (compileStarted || inputs.length == 1) {
        var match = new RegExp(compileRegex).exec(line)
        if (match) {
          const lineStart = parseInt(match[3])
          const colStart = parseInt(match[2])
          const lineEnd = lineStart
          const colEnd = colStart

          toReturn.push({
            type: match[1],
            text: match[4].trim(),
            filePath: shader.fullFilename,
            range: [
              [lineStart > 0 ? lineStart - 1 : 0, colStart > 0 ? colStart - 1 : 0],
              [lineEnd > 0 ? lineEnd - 1 : 0, colEnd > 0 ? colEnd - 1 : 0]
            ]
          });
        } else {
          compileStarted = false;
        }
      }

      const linkMatch = new RegExp(linkRegex.replace("{{typeName}}", typeName)).exec(line)
      if (linkMatch) {
        toReturn.push({
          type: linkMatch[1],
          text: linkMatch[2].trim(),
          filePath: shader.fullFilename,
          range: [
            [0, 0],
            [0, 0]
          ]
        });
      }
    })
  })
  resolve(toReturn);
});

const extractShaderFilenameTokens = shaderFilename => {
  const fileName = path.basename(shaderFilename)
  const dirName = path.dirname(shaderFilename)

  var baseFilename;
  var baseShaderType;
  var baseExtension;

  var linkTargetPattern;
  var linkTargets;

  const extChar1GlslMatch = char1glslRegex.exec(fileName);
  const extChar2GlslMatch = char2glslRegex.exec(fileName);
  const extChar2Match = char2Regex.exec(fileName);
  const extChar1ShMatch = char1shRegex.exec(fileName);
  const extDefaultMatch = defaultRegex.exec(fileName);

  if (extChar1GlslMatch) {
    baseFilename = extChar1GlslMatch[1];
    baseShaderType = shaderByChar1(extChar1GlslMatch[2]);
    baseExtension = extChar1GlslMatch[3];

    linkTargets = shaderTypes.filter(shaderType => shaderType != baseShaderType);
    linkTargetPattern = `${baseFilename}{{char1}}${baseExtension}`;
  } else if (extChar2GlslMatch) {
    baseFilename = extChar2GlslMatch[1];
    baseShaderType = shaderByChar2(extChar2GlslMatch[2]);
    baseExtension = extChar2GlslMatch[3];

    linkTargets = shaderTypes.filter(shaderType => shaderType != baseShaderType);
    linkTargetPattern = `${baseFilename}{{char2}}${baseExtension}`;
  } else if (extChar1ShMatch) {
    baseFilename = extChar1ShMatch[1];
    baseShaderType = shaderByChar1(extChar1ShMatch[2]);
    baseExtension = extChar1ShMatch[3];

    linkTargets = shaderTypes.filter(shaderType => shaderType != baseShaderType);
    linkTargetPattern = `${baseFilename}{{char1}}${baseExtension}`;
  } else if (extChar2Match) {
    baseFilename = extChar2Match[1];
    baseShaderType = shaderByChar2(extChar2Match[2]);
    baseExtension = extChar2Match[3];

    linkTargets = shaderTypes.filter(shaderType => shaderType != baseShaderType);
    linkTargetPattern = `${baseFilename}{{char2}}${baseExtension}`;
  } else if (extDefaultMatch) {
    baseFilename = extDefaultMatch[1];
    baseShaderType = shaderByChar4(extDefaultMatch[2]);
    baseExtension = extDefaultMatch[2];

    linkTargets = shaderTypes.filter(shaderType => shaderType != baseShaderType);
    linkTargetPattern = `${baseFilename}{{char4}}`
  } else {
    throw "Unknown shader type"
  }

  var outFilename = baseFilename;
  if (!outFilename.endsWith(".")) outFilename += ".";

  outFilename += baseShaderType.char4;

  return {
    linkTargets: linkTargets,
    linkTargetPattern: linkTargetPattern,
    baseFilename: baseFilename,
    baseShaderType: baseShaderType,
    dirName: dirName,
    outFilename: outFilename,
    fullFilename: shaderFilename
  };
};

export default {
  config: {
    glslangValidatorPath: {
      type: "string",
      default: "glslangValidator",
      order: 1
    },
    linkSimilarShaders: {
      type: "boolean",
      default: false,
      order: 2
    }
  },

  activate() {
    require("atom-package-deps").install("linter-glsl");

    CompositeDisposable = require('atom').CompositeDisposable;
    MessagePanelView = require('atom-message-panel').MessagePanelView;
    PlainMessageView = require('atom-message-panel').PlainMessageView;

    this.subscriptions = new CompositeDisposable();

    this.subscriptions.add(
      atom.config.observe('linter-glsl.linkSimilarShaders', (linkSimilarShaders) => {
        this.linkSimilarShaders = linkSimilarShaders;
      })
    );

    this.subscriptions.add(
      atom.config.observe('linter-glsl.glslangValidatorPath', (glslangValidatorPath) => {
        this.glslangValidatorPath = module.exports.config.glslangValidatorPath.default;
        if (fs.existsSync(glslangValidatorPath) && fs.statSync(glslangValidatorPath).isFile()) {
          try {
            fs.accessSync(glslangValidatorPath, fs.X_OK);
            this.glslangValidatorPath = glslangValidatorPath;
          } catch (error) {
            console.log(error);
          }
        } else {
          try {
            this.glslangValidatorPath = which.sync(glslangValidatorPath);
          } catch(error) {
            console.log(error);
          }
        }

        if (this.glslangValidatorPath) {
          if (this.messages) {
            this.messages.close();
            this.messages = undefined;
          }
        } else {
          if (!this.messages) {
            this.messages = new MessagePanelView({
              title: "linter-glsl"
            });
            this.messages.attach();
            this.messages.toggle();
          }
          this.messages.clear();
          this.messages.add(new PlainMessageView({
            message: `Unable to locate glslangValidator at '${glslangValidatorPath}'`,
            className: 'text-error'
          }));
        }
      }));
  },

  deactivate() {
    this.subscriptions.dispose();
  },

  provideLinter() {
    const helpers = require("atom-linter");

    return {
      name: "glsl",
      grammarScopes: ["source.glsl"],
      scope: "file",
      lintOnFly: true,
      lint: (activeEditor) => {
        const file = activeEditor.getPath();
        const content = activeEditor.getText();
        var command = this.glslangValidatorPath;

        if (this.glslangValidatorPath == undefined) {
          command = module.exports.config.glslangValidatorPath.default;
        }

        const shaderFileTokens = extractShaderFilenameTokens(file);

        var filesToValidate = [{
          'name': shaderFileTokens.outFilename,
          'fullFilename': file,
          'type': shaderFileTokens.baseShaderType,
          'contents': content
        }];
        var args = [];
        var results;

        if (this.linkSimilarShaders) {
          filesToValidate = filesToValidate.concat(shaderFileTokens.linkTargets
            .map(target => shaderFileTokens.linkTargetPattern
              .replace("{{char1}}", target.char1)
              .replace("{{char2}}", target.char2)
              .replace("{{char4}}", target.char4))
            .map(shader => path.join(shaderFileTokens.dirName, shader))
            .filter(fs.existsSync)
            .map(extractShaderFilenameTokens)
            .map(shader => {
              return {
                'name': shader.outFilename,
                'fullFilename': shader.fullFilename,
                'type': shader.baseShaderType,
                'contents': fs.readFileSync(shader.fullFilename, "UTF-8")
              }
            }));

          if (filesToValidate.length > 1) {
            args = ["-l"];
          }
        }
        return helpers.tempFiles(
            filesToValidate,
            files => helpers.exec(command, args.concat(files), {
              stream: "stdout",
              ignoreExitCode: true
            }))
          .then(output => parseGlslValidatorResponse(filesToValidate, output))
          .catch(console.log);
      }
    };
  }
};
<|endoftext|>


<commit_before>
var
	Heket = require('heket');

var
	extend = req('/lib/utilities/extend'),
	add    = req('/lib/utilities/add');

var
	BodyValidator         = req('/lib/validators/body'),
	Delimiters            = req('/lib/constants/delimiters'),
	SourceTypes           = req('/lib/constants/source-types'),
	ErrorReasons          = req('/lib/constants/error-reasons'),
	UserDetails           = req('/lib/user-details'),
	ServerDetails         = req('/lib/server-details'),
	ChannelDetails        = req('/lib/channel-details'),
	ServiceDetails        = req('/lib/service-details'),
	Commands              = req('/lib/constants/commands'),
	MessageUnparser       = req('/lib/unparsers/message'),
	CommandParameterRules = req('/lib/rule-lists/command-parameters'),
	ReplyParameterRules   = req('/lib/rule-lists/reply-parameters');

var
	InvalidTargetError                   = req('/lib/errors/invalid-target'),
	InvalidSourceError                   = req('/lib/errors/invalid-source'),
	InvalidCommandError                  = req('/lib/errors/invalid-command'),
	InvalidCapParametersError            = req('/lib/errors/invalid-cap-parameters'),
	InvalidJoinParametersError           = req('/lib/errors/invalid-join-parameters'),
	InvalidModeParametersError           = req('/lib/errors/invalid-mode-parameters'),
	InvalidNickParametersError           = req('/lib/errors/invalid-nick-parameters'),
	InvalidNoticeParametersError         = req('/lib/errors/invalid-notice-parameters'),
	InvalidPartParametersError           = req('/lib/errors/invalid-part-parameters'),
	InvalidPassParametersError           = req('/lib/errors/invalid-pass-parameters'),
	InvalidPingParametersError           = req('/lib/errors/invalid-ping-parameters'),
	InvalidPongParametersError           = req('/lib/errors/invalid-pong-parameters'),
	InvalidPrivateMessageParametersError = req('/lib/errors/invalid-private-message-parameters'),
	InvalidQuitParametersError           = req('/lib/errors/invalid-quit-parameters'),
	InvalidTopicParametersError          = req('/lib/errors/invalid-topic-parameters'),
	InvalidUserParametersError           = req('/lib/errors/invalid-topic-parameters'),
	InvalidWhoParametersError            = req('/lib/errors/invalid-who-parameters'),
	InvalidWhoisParametersError          = req('/lib/errors/invalid-whois-parameters'),
	InvalidWhowasParametersError         = req('/lib/errors/invalid-whowas-parameters'),
	AbstractMethodNotImplementedError    = req('/lib/errors/abstract-method-not-implemented');

// IRC messages are restricted to a maximum of 512 characters, including CRLF:
const CHARACTER_LIMIT = 512;

class Message {

	constructor() {
		if (!this.constructor.has_done_sanity_checks) {
			this.doSanityChecks();
			this.constructor.has_done_sanity_checks = true;
		}

		this.buildParameterParser();
	}

	doSanityChecks() {
		if (this.hasReply()) {
			this.validateReply(this.reply);
		} else if (this.hasCommand()) {
			this.validateCommand(this.command);
		} else {
			throw new Error(`
				Message had neither reply numeric nor command
			`);
		}

		this.ensureAbstractMethodsAreImplemented();
	}

	ensureAbstractMethodsAreImplemented() {
		if (this.getValuesForParameters === Message.prototype.getValuesForParameters) {
			throw new AbstractMethodNotImplementedError(
				'getValuesForParameters()'
			);
		}

		if (this.getValuesForParameters === Message.prototype.getValuesForParameters) {
			throw new AbstractMethodNotImplementedError(
				'getValuesForParameters()'
			);
		}

		if (this.setValuesFromParameters === Message.prototype.setValuesFromParameters) {
			throw new AbstractMethodNotImplementedError(
				'setValuesFromParameters()'
			);
		}
	}

	validateReply() {
		throw new AbstractMethodNotImplementedError(
			'validateReply()'
		);
	}

	validateCommand() {
		throw new AbstractMethodNotImplementedError(
			'validateCommand()'
		);
	}

	validateBody(body) {
		BodyValidator.validate(body);
	}

	getParameterParser() {
		return this.constructor.parser;
	}

	buildParameterParser() {
		if (this.constructor.parser) {
			return;
		}

		var rule;

		if (this.hasCommand()) {
			rule = CommandParameterRules.getRule(this.getCommand());
		} else {
			let reply = this.getReply().replace(/_/g, '-');

			rule = ReplyParameterRules.getRule(reply);
		}

		this.constructor.parser = Heket.createParser(rule);
	}

	validateSource(source) {
		if (!source) {
			throw new InvalidSourceError(
				source,
				ErrorReasons.OMITTED
			);
		}

		var source_type = source.getSourceType();

		switch (source_type) {
			case SourceTypes.CLIENT:
			case SourceTypes.SERVER:
				return;

			default:
				throw new InvalidSourceError(
					source_type,
					ErrorReasons.UNKNOWN_TYPE
				);
		}
	}

	setRawMessage(raw_message) {
		this.raw_message = raw_message;
		return this;
	}

	getRawMessage() {
		return this.raw_message;
	}

	setPrefix(prefix) {
		this.prefix = prefix;
		return this;
	}

	setParameterString(parameter_string) {
		this.parameter_string = parameter_string;
		this.parseParameterString();

		return this;
	}

	getParameterString() {
		return this.parameter_string;
	}

	parseParameterString() {
		var
			parser            = this.getParameterParser(),
			parameter_string  = this.getParameterString(),
			parsed_parameters = parser.parse(parameter_string);

		if (parsed_parameters === null) {
			throw this.determineErrorForUnparseableParameters();
		}

		this.setParsedParameters(parsed_parameters);
	}

	determineErrorForUnparseableParameters() {
		throw new Error('implement!');
	}

	setParsedParameters(parsed_parameters) {
		this.parsed_parameters = parsed_parameters;
		return this;
	}

	setBody(body) {
		this.validateBody(body);
		this.body = body;
		return this;
	}

	getBody() {
		this.validateBody(this.body);
		return this.body;
	}

	getABNF() {
		return this.abnf;
	}

	hasABNF() {
		return this.getABNF() !== null;
	}

	getCommand() {
		return this.command;
	}

	hasCommand() {
		return this.getCommand() !== null;
	}

	hasReply() {
		return this.getReply() !== null;
	}

	setReply(reply) {
		this.validateReply(reply);
		this.reply = reply;
		return this;
	}

	getReply() {
		return this.reply;
	}

	getUserIdentifier() {
		return this.getUserDetails().getIdentifier();
	}

	setUserDetails(user_details) {
		this.user_details = user_details;
		return this;
	}

	getUserDetails() {
		if (this.user_details) {
			return this.user_details;
		}

		if (!this.hasUserTarget()) {
			return null;
		}

		this.user_details = this.getFirstUserTarget();

		return this.user_details;
	}

	setLocalServerDetails(server_details) {
		this.local_server_details = server_details;
		return this;
	}

	getLocalServerDetails() {
		return this.local_server_details;
	}

	getLocalServerIdentifier() {
		return this.getLocalServerDetails().getIdentifier();
	}

	setRemoteServerDetails(server_details) {
		this.remote_server_details = server_details;
		return this;
	}

	getRemoteServerDetails() {
		return this.remote_server_details;
	}

	createRemoteServerDetails() {
		this.remote_server_details = new ServerDetails();

		return this.getRemoteServerDetails();
	}

	getOrCreateRemoteServerDetails() {
		return this.getRemoteServerDetails() || this.createRemoteServerDetails();
	}

	getRemoteServerIdentifier() {
		return this.getRemoteServerDetails().getIdentifier();
	}

	setChannelDetails(channel_details) {
		this.channel_details = channel_details;
		return this;
	}

	getChannelDetails() {
		if (!this.channel_details) {
			if (this.hasChannelTarget()) {
				return this.getFirstChannelTarget();
			}

			this.channel_details = new ChannelDetails();
		}

		return this.channel_details;
	}

	getTargets() {
		if (!this.targets) {
			this.targets = [ ];
		}

		return this.targets;
	}

	getTargetStrings() {
		return this.getTargets().map(function map(target) {
			return target.getTargetString();
		});
	}

	setTargets(targets) {
		targets.forEach(function each(target) {
			this.addTargetFromString(target.getTargetString());
		}, this);
	}

	getChannelTargets() {
		return this.getTargets().filter(function filter(target) {
			return target instanceof ChannelDetails;
		});
	}

	getChannelTargetStrings() {
		return this.getChannelTargets().map(function map(target) {
			return target.getTargetString();
		});
	}

	getUserTargets() {
		return this.getTargets().filter(function filter(target) {
			return target instanceof UserDetails;
		});
	}

	getUserTargetStrings() {
		return this.getUserTargets().map(function map(target) {
			return target.getTargetString();
		});
	}

	getServerTargets() {
		return this.getTargets().filter(function filter(target) {
			return target instanceof ServerDetails;
		});
	}

	getServerTargetStrings() {
		return this.getServerTargets().map(function map(target) {
			return target.getTargetString();
		});
	}

	hasChannelTarget() {
		return this.getChannelTargets().length !== 0;
	}

	getFirstTarget() {
		return this.getTargets()[0];
	}

	getFirstChannelTarget() {
		return this.getChannelTargets()[0];
	}

	getFirstUserTarget() {
		return this.getUserTargets()[0];
	}

	hasUserTarget() {
		return this.getUserTargets().length !== 0;
	}

	hasServerTarget() {
		return this.getServerTargets().length !== 0;
	}

	hasAnyTarget() {
		return this.getTargets().length !== 0;
	}

	validateTarget(target) {
		if (
			   !(target instanceof UserDetails)
			&& !(target instanceof ChannelDetails)
			&& !(target instanceof ServerDetails)
			&& !(target instanceof ServiceDetails)
		) {
			throw new InvalidTargetError(target, ErrorReasons.WRONG_TYPE);
		}
	}

	addTarget(target) {
		if (this.hasTarget(target)) {
			return;
		}

		add(target).to(this.getTargets());
	}

	hasTarget(target) {
		this.validateTarget(target);

		if (!this.targets) {
			return false;
		}

		var index = 0;

		while (index < this.targets.length) {
			let target_to_check = this.targets[index];

			if (target_to_check.getTargetString() === target.getTargetString()) {
				return true;
			}

			index++;
		}

		return false;
	}

	/**
	 * @param   {string[]} target_strings
	 * @returns {self}
	 */
	setTargetStrings(target_strings) {
		target_strings.forEach(this.addTargetFromString, this);
		return this;
	}

	addTargetFromString(target_string) {
		throw new Error('die');
	}

	serialize() {
		return MessageUnparser.unparse(this.getValueForMessageRule, this);
	}

	getValueForMessageRule(rule_name) {
		switch (rule_name) {
			case 'prefix':
				return this.serializePrefix();

			case 'command':
				return this.serializeCommand();

			case 'params':
				return this.serializeParameters();

			default:
				return null;
		}
	}

	serializeParameters() {
		var values = this.getValuesForParameters();

		return this.getUnparser().unparse(values);
	}

	/**
	 * Determine whether the extended message prefix should be used.
	 * From RFC1459, section 2.3.1:
	 *
	 *    Use of the extended prefix (['!' <user> ] ['@' <host> ]) must
	 *    not be used in server to server communications and is only
	 *    intended for server to client messages in order to provide
	 *    clients with more useful information about who a message is
	 *    from without the need for additional queries.
	 *
	 * @returns {bool}
	 */
	shouldUseExtendedPrefix() {
		return false;
	}

	shouldOmitPrefix() {
		return this.omit_prefix;
	}

	setOmitPrefix(omit_prefix = true) {
		this.omit_prefix = omit_prefix;
		return this;
	}

	isFromServer() {
		return false;
	}

	serializePrefix() {
		if (this.shouldOmitPrefix()) {
			return '';
		}

		if (this.isFromServer()) {
			return Delimiters.COLON + this.getLocalServerIdentifier() + ' ';
		} else {
			return Delimiters.COLON + this.getUserIdentifier() + ' ';
		}
	}

	serializeCommand() {
		if (this.hasCommand()) {
			return this.getCommand();
		} else {
			return this.getReply();
		}
	}

	getValuesForParameters() {
		// TODO: Can we use AbstractMethodNotImplementedError here?
		throw new Error(
			'Must implement getValuesForParameters() in message subclass'
		);
	}

	setValuesFromParameters() {
		// TODO: Can we use AbstractMethodNotImplementedError here?
		throw new Error(
			'Must implement setValuesFromParameters() in message subclass'
		);
	}

	serializeTargets() {
		return this.getTargetStrings().join(', ');
	}

	validateMiddleParameterCount() {
	}

	throwInvalidParametersError(error_reason) {
		var invalid_parameters_error = this.getInvalidParametersError();

		console.log(invalid_parameters_error);

		throw new invalid_parameters_error(
			this.getRawMessage(),
			error_reason
		);
	}

	getMaximumMiddleParameterCount() {
		return this.max_middle_parameter_count;
	}

	getMinimumMiddleParameterCount() {
		return this.min_middle_parameter_count;
	}

	expectsTrailingParameter() {
		return this.expects_trailing_parameter;
	}

	allowsTrailingParameter() {
		return this.allows_trailing_parameter;
	}

	getInvalidParametersError() {
		if (!this.hasCommand()) {
			throw new Error('implement');
		}

		var command = this.getCommand();

		switch (command) {
			case Commands.CAP:
				return InvalidCapParametersError;

			case Commands.JOIN:
				return InvalidJoinParametersError;

			case Commands.MODE:
				return InvalidModeParametersError;

			case Commands.NICK:
				return InvalidNickParametersError;

			case Commands.NOTICE:
				return InvalidNoticeParametersError;

			case Commands.PART:
				return InvalidPartParametersError;

			case Commands.PASS:
				return InvalidPassParametersError;

			case Commands.PING:
				return InvalidPingParametersError;

			case Commands.PONG:
				return InvalidPongParametersError;

			case Commands.PRIVMSG:
				return InvalidPrivateMessageParametersError;

			case Commands.QUIT:
				return InvalidQuitParametersError;

			case Commands.TOPIC:
				return InvalidTopicParametersError;

			case Commands.USER:
				return InvalidUserParametersError;

			case Commands.WHO:
				return InvalidWhoParametersError;

			case Commands.WHOIS:
				return InvalidWhoisParametersError;

			case Commands.WHOWAS:
				return InvalidWhowasParametersError;

			default:
				throw new InvalidCommandError(
					command,
					ErrorReasons.UNSUPPORTED
				);
		}
	}

	getCharacterLimit() {
		return CHARACTER_LIMIT;
	}

	isAtCharacterLimit() {
		return this.getCharacterCount() >= this.getCharacterLimit();
	}

	getCharacterCount() {
		return this.serialize().length;
	}

	getRemainingCharacterCount() {
		return this.getCharacterLimit() - this.getCharacterCount();
	}

	hasRemainingCharacterCount() {
		return this.getRemainingCharacterCount() > 0;
	}

	canAccommodateAdditionalCharacters(count) {
		return this.getRemainingCharacterCount() >= count;
	}

	canAddTextToBody(text) {
		return this.canAccommodateAdditionalCharacters(text.length);
	}

	addTextToBody(text) {
		return this.setBody(this.getBody() + text);
	}

	hasBodyText() {
		return this.getBody().length > 0;
	}

}

extend(Message.prototype, {
	abnf:                  null,

	omit_prefix:           false,

	raw_message:           null,
	reply:                 null,
	command:               null,
	parameter_string:      null,
	parameter_parser:      null,

	targets:               null,
	user_details:          null,
	remote_server_details: null,
	channel_details:       null
});

module.exports = Message;
<commit_msg>Remove old message class<commit_after><|endoftext|>


<commit_before>'use strict'

const co     = require('co');
const qs     = require('qs');
const parse  = require('co-body');
const debug  = require('debug')('koa-restql:middlewares');

const common = require('./common');
const parseQuery         = common.parseQuery;
const getAssociationName = common.getAssociationName;

const capitalizeFirstLetter = (string) => {
  return string.charAt(0).toUpperCase() + string.slice(1);
}

const get = (method, model, association) => {
  return function * (next) {

    this.restql = this.restql || {};

    let params  = this.params
      , id      = params.id
      , query   = this.restql.query
      , data;

    let debugInfo = `get ${this.request.url}, using model: ${model.name}`;

    if (association) {
      debugInfo += `, with association:
        ${common.getAssociationName(association)}`;
    }

    debug(debugInfo);

    if (!query) {
      query = qs.parse(this.request.querystring, { allowDots: true });
    }

    if (!association) {
      query = parseQuery(query, model, method);

      if (id === undefined) {
        data = yield model.findAndCount({
          attributes : query._attributes,
          where      : query._where,
          order      : query._order,
          include    : query._include,
          limit      : query._limit,
          offset     : query._offset
        })

        this.response.set('X-Range',
          `objects ${query._offset}-${query._offset + data.rows.length}/${data.count}`);
        data = data.rows;
      } else {
        data = yield model.findOne({
          attributes : query._attributes,
          where      : { id },
          include    : query._include
        })

        if (!data) {
          this.throw(`${model.name} ${id} is not found`, 404);
        }
      }
    } else {
      let associationId    = params.associationId
        , associationModel = association.target
        , order;

      query = parseQuery(query, associationModel, method);

      if (!associationId) {
        let name     = getAssociationName(association)
          , isPlural = association.isMultiAssociation
          , getter   = `get${capitalizeFirstLetter(name)}`
          , counter  = `count${capitalizeFirstLetter(name)}`;

        data = yield model.findOne({
          where : { id }
        })

        if (!data) {
          this.throw(`${model.name} ${id} is not found`, 404);
        }

        let promises = [];

        promises.push(data[getter]({
          attributes : query._attributes,
          where      : query._where,
          order      : query._order,
          include    : query._include,
          through    : query._through,
          limit      : query._limit,
          offset     : query._offset
        }));

        if (isPlural) {
          promises.push(data[counter]({
            where: query._where
          }))
        }

        data = yield promises;

        if (isPlural) {
          this.response.set('X-Range',
            `objects ${query._offset}-${query._offset + data[0].length}/${data[1]}`);
        }

        data = data[0];
      } else {
        data = yield associationModel.findOne({
          attributes : query._attributes,
          where      : { id: associationId },
          include    : query._include
        })

        if (!data) {
          this.throw(`${model.name} ${id}
              association ${associationId} is not found`, 404);
        }
      }
    }

    this.body   = data;
    this.status = 200;

    yield next;
  }
}

const isSameDeletedAt = (a, b) => {
  if (a instanceof Date && b instanceof Date) {
    return a.getTime() === b.getTime();
  } else {
    return a === b;
  }
}

const getUniqueConstraintErrorFields = (model, e) => {

  let indexes     = model.options.indexes
    , primaryKeys = model.primaryKeys
    , attributes  = model.attributes
    , fields      = {}
    , uniqueIndex, fieldsIsValid;

  if (!e.fields)
    return;

  fieldsIsValid = Object.keys(e.fields).every(key => attributes[key]);

  if (fieldsIsValid)
    return e.fields;

  let index;
  if (e.fields.PRIMARY) {
    if (!primaryKeys)
      return;

    let keys = Object.keys(primaryKeys);
    if (!keys.length)
      return;

    index = {
      fields : keys,
      name   : 'PRIMARY'
    };

  } else {
    index =
      indexes.find(index => e.fields[index.name] && index.unique);
  }

  if (index && Array.isArray(index.fields)) {
    let value = e.fields[index.name];

    switch (typeof value) {
      case 'number':
        value = [ value ];
      case 'string':
        value = value.split('-');
        break;
    }

    index.fields.forEach((field, index) => {
      fields[field] = value[index]
    })
  }

  return fields;
}

const create = (model, row, options) => {
  return function * () {
    let id      = row.id
      , message = `${model.name} unique constraint error`
      , status  = 409
      , data;

    options = options || {}

    let {
      ignoreDuplicates = false
    } = options;

    try {

      if (!ignoreDuplicates && id) {
        delete row.id;
      }

      data =
        yield model.create(row, options);

    } catch (e) {
      if (e.name === 'SequelizeUniqueConstraintError') {

        let where        = getUniqueConstraintErrorFields(model, e)
          , attributes   = model.attributes
          , deletedAtCol = model.options.deletedAt;

        if (!deletedAtCol)
          this.throw(message, status);

        data = yield model.find({
          paranoid: false,
          where
        })

        if (!data) {
          this.throw('Sequelize goes wrong', 500);
        }

        let deletedAtVal = attributes[deletedAtCol].defaultValue;
        if (deletedAtVal === undefined) {
          deletedAtVal = null;
        }

        const upsert = (row) => {
          return function * () {
            yield model.upsert(row);
            let data = yield model.find({
              where
            });
            return data;
          }
        }

        if (!isSameDeletedAt(data[deletedAtCol], deletedAtVal)) {
          yield data.restore();
          data = yield upsert(row);
        } else {
          if (!ignoreDuplicates) {
            this.throw(message, status);
          }
          data = yield upsert(row);
        }
      } else {
        throw new Error(e);
      }
    }

    return data;
  }
}

const post = (method, model, association) => {
  return function * (next) {

    this.restql = this.restql || {};

    let body   = this.request.body || (yield parse(this))
      , params = this.params
      , query  = this.restql.query
      , data;

    let debugInfo = `post ${this.request.url}, using model: ${model.name}`;

    if (association) {
      debugInfo += `, with association:
        ${common.getAssociationName(association)}`;
    }

    debug(debugInfo);

    if (!query) {
      query = qs.parse(this.request.querystring, { allowDots: true });
    }

    const _create = (model, ignoreDuplicates) => {
      return function * () {
        query = parseQuery(query, model, method);
        if (Array.isArray(body)) {
          let promises = body.map(row => {
            let gen = create(model, row, {
              include          : query._include,
              ignoreDuplicates : ignoreDuplicates || query._ignoreDuplicates
            })
            return co(gen);
          })

          let data = yield promises;
          return data;
        } else {
          return yield create(model, body, {
            include          : query._include,
            ignoreDuplicates : ignoreDuplicates || query._ignoreDuplicates
          });
        }
      }
    }

    if (!association) {
      data = yield _create(model);
    } else {
      let id = params.id;

      let isBelongsToMany =
        association.associationType === 'BelongsToMany';

      data = yield model.findOne({
        where: { id }
      });

      if (!data) {
        this.throw(`${model.name} ${id} is not found`, 404);
      }

      let associationData =
        yield _create(association.target, isBelongsToMany);

      let options = association.options
        , name;

      if (Array.isArray(associationData)) {
        name = options.name.plural;
      } else {
        name = options.name.singular;
      }

      if (!isBelongsToMany) {
        let foreignKey = association.foreignKey;
        if (Array.isArray(body)) {
          body.forEach(row => row[foreignKey] = id)
        } else {
          body[foreignKey] = id
        }
      }

      let add = `add${capitalizeFirstLetter(name)}`;
      yield data[add](associationData);

      if (isBelongsToMany) {
        data = associationData;
      } else {
        if (Array.isArray(associationData)) {
          let ids = associationData.map(value => value.id);
          data =
            yield association.target.findAll({
              where: { id: ids }
            })
        } else {
          data =
            yield association.target.find({
              where: { id: associationData.id }
            })
        }
      }
    }

    this.body   = data;
    this.status = 201;

    yield next;
  }
}

const put = (method, model, association) => {
  return function * (next) {
    let body    = this.request.body || (yield parse(this))
      , params  = this.params
      , id      = params.id
      , include = association ? [association] : []
      , data, status;

    let debugInfo = `put ${this.request.url}, using model: ${model.name}`;

    if (association) {
      debugInfo += `, with association:
        ${common.getAssociationName(association)}`;
    }

    debug(debugInfo);

    data = yield model.findOne({
      where: { id },
      include
    })

    if (!data) {
      this.throw(`${model.name} ${id} is not found`, 404);
    }

    if (!association) {
      yield data.update(body);
    } else {
      let associationId = params.associationId;

      if (associationId) {
        /*
         * plural assocation
         */
        data = yield association.target.findOne({
          where: {
            id: associationId
          }
        });

        if (!data) {
          this.throw(`${model.name} ${id}
              association ${associationId} is not found`, 404);
        }

        delete body.id;
        yield data.update(body);
      } else {
        /*
         * singular association
         */
        let name   = association.options.name.singular
          , setter = `set${capitalizeFirstLetter(name)}`;

        let associationData = data[name];

        delete body.id;
        if (!associationData) {
          associationData = yield create(association.target, body);
          status = 201;
          yield data[setter](associationData);
        } else {
          yield associationData.update(body);
        }
        data = associationData;
      }
    }

    this.body   = data;
    this.status = status || 200;

    yield next;
  }
}

const del = (method, model, association) => {
  return function * (next) {
    let params  = this.params
      , id      = params.id
      , include = association ? [association] : []
      , data    = null;

    let debugInfo = `delete ${this.request.url}, using model: ${model.name}`;

    if (association) {
      debugInfo += `, with association:
        ${common.getAssociationName(association)}`;
    }

    debug(debugInfo);

    data = yield model.findOne({
      where: { id },
      include
    })

    if (!data) {
      this.throw(`${model.name} ${id} is not found`, 404);
    }

    if (!association) {
      yield model.destroy({
        where: {
          id: data.id
        }
      });
    } else {
      let associationId    = params.associationId
        , name             = association.options.name.singular
        , remove           = `remove${capitalizeFirstLetter(name)}`
        , associationModel = association.target
        , associationData  = null;

      if (associationId) {
        /*
         * plural assocation
         */
        associationData = yield associationModel.findOne({
          where: {
            id: associationId
          }
        });

        if (!data) {
          this.throw(`${model.name} ${id}
              association ${associationId} is not found`, 404);
        }

        let associationType = association.associationType;
        if (associationType === 'HasMany') {
          yield associationModel.destroy({
            where: {
              id: associationId
            }
          })
        } else {
          yield data[remove](associationData);
        }
      } else {
        /*
         * singular association
         */

        associationData = data[name];

        if (associationData) {
          yield associationModel.destroy({
            where: {
              id: associationData.id
            }
          });
        }
      }
    }

    this.data   = {};
    this.status = 204;

    yield next;
  }
}

module.exports.handlers = {
  get, post, put, del
};
<commit_msg>Updated<commit_after>'use strict'

const co     = require('co');
const qs     = require('qs');
const parse  = require('co-body');
const debug  = require('debug')('koa-restql:middlewares');

const common = require('./common');
const parseQuery         = common.parseQuery;
const getAssociationName = common.getAssociationName;

const capitalizeFirstLetter = (string) => {
  return string.charAt(0).toUpperCase() + string.slice(1);
}

const get = (method, model, association) => {
  return function * (next) {

    this.restql = this.restql || {};

    let params  = this.params
      , id      = params.id
      , query   = this.restql.query
      , data;

    let debugInfo = `get ${this.request.url}, using model: ${model.name}`;

    if (association) {
      debugInfo += `, with association:
        ${common.getAssociationName(association)}`;
    }

    debug(debugInfo);

    if (!query) {
      query = qs.parse(this.request.querystring, { allowDots: true });
    }

    if (!association) {
      query = parseQuery(query, model, method);

      if (id === undefined) {
        data = yield model.findAndCount({
          attributes : query._attributes,
          where      : query._where,
          order      : query._order,
          include    : query._include,
          limit      : query._limit,
          offset     : query._offset
        })

        this.response.set('X-Range',
          `objects ${query._offset}-${query._offset + data.rows.length}/${data.count}`);
        data = data.rows;
      } else {
        data = yield model.findOne({
          attributes : query._attributes,
          where      : { id },
          include    : query._include
        })

        if (!data) {
          this.throw(`${model.name} ${id} is not found`, 404);
        }
      }
    } else {
      let associationId    = params.associationId
        , associationModel = association.target
        , order;

      query = parseQuery(query, associationModel, method);

      if (!associationId) {
        let name     = getAssociationName(association)
          , isPlural = association.isMultiAssociation
          , getter   = `get${capitalizeFirstLetter(name)}`
          , counter  = `count${capitalizeFirstLetter(name)}`;

        data = yield model.findOne({
          where : { id }
        })

        if (!data) {
          this.throw(`${model.name} ${id} is not found`, 404);
        }

        let promises = [];

        promises.push(data[getter]({
          attributes : query._attributes,
          where      : query._where,
          order      : query._order,
          include    : query._include,
          through    : query._through,
          limit      : query._limit,
          offset     : query._offset
        }));

        if (isPlural) {
          promises.push(data[counter]({
            where: query._where
          }))
        }

        data = yield promises;

        if (isPlural) {
          this.response.set('X-Range',
            `objects ${query._offset}-${query._offset + data[0].length}/${data[1]}`);
        }

        data = data[0];
      } else {
        data = yield associationModel.findOne({
          attributes : query._attributes,
          where      : { id: associationId },
          include    : query._include
        })

        if (!data) {
          this.throw(`${model.name} ${id}
              association ${associationId} is not found`, 404);
        }
      }
    }

    this.body   = data;
    this.status = 200;

    yield next;
  }
}

const isSameDeletedAt = (a, b) => {
  if (a instanceof Date && b instanceof Date) {
    return a.getTime() === b.getTime();
  } else {
    return a === b;
  }
}

const getUniqueConstraintErrorFields = (model, e) => {

  let indexes     = model.options.indexes
    , primaryKeys = model.primaryKeys
    , attributes  = model.attributes
    , fields      = {}
    , uniqueIndex, fieldsIsValid;

  if (!e.fields)
    return;

  fieldsIsValid = Object.keys(e.fields).every(key => attributes[key]);

  if (fieldsIsValid)
    return e.fields;

  let index;
  if (e.fields.PRIMARY) {
    if (!primaryKeys)
      return;

    let keys = Object.keys(primaryKeys);
    if (!keys.length)
      return;

    index = {
      fields : keys,
      name   : 'PRIMARY'
    };

  } else {
    index =
      indexes.find(index => e.fields[index.name] && index.unique);
  }

  if (index && Array.isArray(index.fields)) {
    let value = e.fields[index.name];

    switch (typeof value) {
      case 'number':
        value = [ value ];
      case 'string':
        value = value.split('-');
        break;
    }

    index.fields.forEach((field, index) => {
      fields[field] = value[index]
    })
  }

  return fields;
}

const create = (model, row, options) => {
  return function * () {
    let id      = row.id
      , message = `${model.name} unique constraint error`
      , status  = 409
      , data;

    options = options || {}

    let {
      ignoreDuplicates = false
    } = options;

    try {

      if (!ignoreDuplicates && id) {
        delete row.id;
      }

      data =
        yield model.create(row, options);

    } catch (e) {
      if (e.name === 'SequelizeUniqueConstraintError') {

        let where        = getUniqueConstraintErrorFields(model, e)
          , attributes   = model.attributes
          , deletedAtCol = model.options.deletedAt;

        if (!deletedAtCol)
          this.throw(message, status);

        data = yield model.find({
          paranoid: false,
          where
        })

        if (!data) {
          this.throw('Sequelize goes wrong', 500);
        }

        let deletedAtVal = attributes[deletedAtCol].defaultValue;
        if (deletedAtVal === undefined) {
          deletedAtVal = null;
        }

        const upsert = (row) => {
          return function * () {
            yield model.upsert(row);
            let data = yield model.find({
              where
            });
            return data;
          }
        }

        if (!isSameDeletedAt(data[deletedAtCol], deletedAtVal)) {
          yield data.restore();
          data = yield upsert(row);
        } else {
          if (!ignoreDuplicates) {
            this.throw(message, status);
          }
          data = yield upsert(row);
        }
      } else {
        throw new Error(e);
      }
    }

    return data;
  }
}

const post = (method, model, association) => {
  return function * (next) {

    this.restql = this.restql || {};

    let body   = this.request.body || (yield parse(this))
      , params = this.params
      , query  = this.restql.query
      , data;

    let debugInfo = `post ${this.request.url}, using model: ${model.name}`;

    if (association) {
      debugInfo += `, with association:
        ${common.getAssociationName(association)}`;
    }

    debug(debugInfo);

    if (!query) {
      query = qs.parse(this.request.querystring, { allowDots: true });
    }

    const _create = (model, ignoreDuplicates) => {
      return function * () {
        query = parseQuery(query, model, method);
        if (Array.isArray(body)) {
          let promises = body.map(row => {
            let gen = create(model, row, {
              include          : query._include,
              ignoreDuplicates : ignoreDuplicates || query._ignoreDuplicates
            })
            return co(gen);
          })

          let data = yield promises;
          return data;
        } else {
          return yield create(model, body, {
            include          : query._include,
            ignoreDuplicates : ignoreDuplicates || query._ignoreDuplicates
          });
        }
      }
    }

    if (!association) {
      data = yield _create(model);
    } else {
      let id = params.id;

      let isBelongsToMany =
        association.associationType === 'BelongsToMany';

      data = yield model.findOne({
        where: { id }
      });

      if (!data) {
        this.throw(`${model.name} ${id} is not found`, 404);
      }

      if (!isBelongsToMany) {
        let foreignKey = association.foreignKey;
        if (Array.isArray(body)) {
          body.forEach(row => row[foreignKey] = id)
        } else {
          body[foreignKey] = id
        }
      }

      let associationData =
        yield _create(association.target, isBelongsToMany);

      if (isBelongsToMany) {

        let options = association.options
          , name;

        if (Array.isArray(associationData)) {
          name = options.name.plural;
        } else {
          name = options.name.singular;
        }

        let add = `add${capitalizeFirstLetter(name)}`;
        yield data[add](associationData);

        data = associationData;

      } else {

        if (Array.isArray(associationData)) {
          let ids = associationData.map(value => value.id);
          data =
            yield association.target.findAll({
              where: { id: ids }
            })
        } else {
          data =
            yield association.target.find({
              where: { id: associationData.id }
            })
        }

      }
    }

    this.body   = data;
    this.status = 201;

    yield next;
  }
}

const put = (method, model, association) => {
  return function * (next) {
    let body    = this.request.body || (yield parse(this))
      , params  = this.params
      , id      = params.id
      , include = association ? [association] : []
      , data, status;

    let debugInfo = `put ${this.request.url}, using model: ${model.name}`;

    if (association) {
      debugInfo += `, with association:
        ${common.getAssociationName(association)}`;
    }

    debug(debugInfo);

    data = yield model.findOne({
      where: { id },
      include
    })

    if (!data) {
      this.throw(`${model.name} ${id} is not found`, 404);
    }

    if (!association) {
      yield data.update(body);
    } else {
      let associationId = params.associationId;

      if (associationId) {
        /*
         * plural assocation
         */
        data = yield association.target.findOne({
          where: {
            id: associationId
          }
        });

        if (!data) {
          this.throw(`${model.name} ${id}
              association ${associationId} is not found`, 404);
        }

        delete body.id;
        yield data.update(body);
      } else {
        /*
         * singular association
         */
        let name   = association.options.name.singular
          , setter = `set${capitalizeFirstLetter(name)}`;

        let associationData = data[name];

        delete body.id;
        if (!associationData) {
          associationData = yield create(association.target, body);
          status = 201;
          yield data[setter](associationData);
        } else {
          yield associationData.update(body);
        }
        data = associationData;
      }
    }

    this.body   = data;
    this.status = status || 200;

    yield next;
  }
}

const del = (method, model, association) => {
  return function * (next) {
    let params  = this.params
      , id      = params.id
      , include = association ? [association] : []
      , data    = null;

    let debugInfo = `delete ${this.request.url}, using model: ${model.name}`;

    if (association) {
      debugInfo += `, with association:
        ${common.getAssociationName(association)}`;
    }

    debug(debugInfo);

    data = yield model.findOne({
      where: { id },
      include
    })

    if (!data) {
      this.throw(`${model.name} ${id} is not found`, 404);
    }

    if (!association) {
      yield model.destroy({
        where: {
          id: data.id
        }
      });
    } else {
      let associationId    = params.associationId
        , name             = association.options.name.singular
        , remove           = `remove${capitalizeFirstLetter(name)}`
        , associationModel = association.target
        , associationData  = null;

      if (associationId) {
        /*
         * plural assocation
         */
        associationData = yield associationModel.findOne({
          where: {
            id: associationId
          }
        });

        if (!data) {
          this.throw(`${model.name} ${id}
              association ${associationId} is not found`, 404);
        }

        let associationType = association.associationType;
        if (associationType === 'HasMany') {
          yield associationModel.destroy({
            where: {
              id: associationId
            }
          })
        } else {
          yield data[remove](associationData);
        }
      } else {
        /*
         * singular association
         */

        associationData = data[name];

        if (associationData) {
          yield associationModel.destroy({
            where: {
              id: associationData.id
            }
          });
        }
      }
    }

    this.data   = {};
    this.status = 204;

    yield next;
  }
}

module.exports.handlers = {
  get, post, put, del
};
<|endoftext|>


<commit_before>/**
    *   @name Inbetween-Time
    *   @version 0.01
    *   @description Inbetween Time is Contructor with an iterator method that is called a certain number of times and stops.
    *   @param spec {Object} timer, count, method.
    *   @param timer{number} Time in ms between each Iteration
    *   @param count{number} Max number of iterations
    *   @param method{count} Method to be called durring each iteration
    *   @example
    *
    *    let repeater = () => {
    *        console.log('this needs to run three Times!');
    *    };
    *
    *    let ropes = t_t({
    *        timer: 1000,
    *        count: 4,
    *        method: repeater
    *    });
    *
    *    console.log(ropes.iterator());
*/
"use strict";

var t_t = function t_t(spec) {
    var current = 0;
    var timer = spec.timer,
        count = spec.count,
        method = spec.method;

    var _iterator = function iterator() {
        var _wrapper = function wrapper() {
            if (current < count) {
                method();
                current++;
            } else {
                _iterator = null;
                timeout = null;
                _wrapper = null;
            }

            timeout = setTimeout(_wrapper, timer);
        };
        var timeout = setTimeout(_wrapper, timer);
    };

    return Object.freeze({
        iterator: _iterator
    });
};
<commit_msg>Mine change<commit_after>/**
    *   @name Inbetween-Time
    *   @version 0.01
    *   @description Inbetween Time is Contructor with an iterator method that is called a certain number of times and stops.
    *   @param spec {Object} timer, count, method.
    *   @param timer{number} Time in ms between each Iteration
    *   @param count{number} Max number of iterations
    *   @param method{count} Method to be called durring each iteration
    *   @example
    *
    *    let repeater = () => {
    *        console.log('this needs to run three Times!');
    *    };
    *
    *    let ropes = t_t({
    *        timer: 1000,
    *        count: 4,
    *        method: repeater
    *    });
    *
    *    console.log(ropes.iterator());
*/
"use strict";

var t_t = function t_t(spec) {
    var current = 0;
    var timer = spec.timer,
        count = spec.count,
        method = spec.method;

    var _iterator = function iterator() {
        var _wrapper = function wrapper() {
            if (current < count) {
                method();
                current++;
            } else {
                _iterator = undefined;
                timeout = undefined;
                _wrapper = undefined;
            }

            timeout = setTimeout(_wrapper, timer);
        };
        var timeout = setTimeout(_wrapper, timer);
    };

    return Object.freeze({
        iterator: _iterator
    });
};
<|endoftext|>


<commit_before>printShardingStatus = function( configDB , verbose ){
    if (configDB === undefined)
        configDB = db.getSisterDB('config')

    var version = configDB.getCollection( "version" ).findOne();
    if ( version == null ){
        print( "printShardingStatus: this db does not have sharding enabled. be sure you are connecting to a mongos from the shell and not to a mongod." );
        return;
    }

    var raw = "";
    var output = function(s){
        raw += s + "\n";
    }
    output( "--- Sharding Status --- " );
    output( "  sharding version: " + tojson( configDB.getCollection( "version" ).findOne(), "  " ) );

    output( "  shards:" );
    configDB.shards.find().sort( { _id : 1 } ).forEach(
        function(z){
            output( "    " + tojsononeline( z ) );
        }
    );

    output( "  databases:" );
    configDB.databases.find().sort( { name : 1 } ).forEach(
        function(db){
            output( "    " + tojsononeline(db,"",true) );

            if (db.partitioned){
                configDB.collections.find( { _id : new RegExp( "^" +
                    RegExp.escape(db._id) + "\\." ) } ).
                    sort( { _id : 1 } ).forEach( function( coll ){
                        if ( coll.dropped == false ){
                            output( "    " + coll._id );
                            output( "      shard key: " + tojson(coll.key, 0, true) );
                            output( "      chunks:" );

                            res = configDB.chunks.aggregate(
                                { "$match": { ns: coll._id } },
                                { "$group": { _id: "$shard", nChunks: { "$sum": 1 } } }
                            ).result

                            var totalChunks = 0;
                            res.forEach( function(z){
                                totalChunks += z.nChunks;
                                output( "        " + z._id + ": " + z.nChunks );
                            } )

                            if ( totalChunks < 20 || verbose ){
                                configDB.chunks.find( { "ns" : coll._id } ).sort( { min : 1 } ).forEach(
                                    function(chunk){
                                        output( "        " +
                                            tojson( chunk.min, 0, true) + " -> " +
                                            tojson( chunk.max, 0, true ) +
                                            " on: " + colorize(chunk.shard, {color: 'cyan'}) + " " +
                                            ( chunk.jumbo ? "jumbo " : "" )
                                        );
                                    }
                                );
                            }
                            else {
                                output( "\t\t\ttoo many chunks to print, use verbose if you want to force print" );
                            }

                            configDB.tags.find( { ns : coll._id } ).sort( { min : 1 } ).forEach(
                                function( tag ) {
                                    output( "        tag: " + tag.tag + "  " + tojson( tag.min ) + " -> " + tojson( tag.max ) );
                                }
                            )
                        }
                    }
                )
            }
        }
    );

    print( raw );
}
<commit_msg>include balancer state in sh.status() as per 3.0 shell<commit_after>sh.getRecentMigrations = function () {
    var configDB = db.getSiblingDB("config");
    var yesterday = new Date( new Date() - 24 * 60 * 60 * 1000 );
    var result = [];
    result = result.concat(configDB.changelog.aggregate( [
        { $match : { time : { $gt : yesterday }, what : "moveChunk.from", "details.errmsg" : {
            "$exists" : false } } },
        { $group : { _id: { msg: "$details.errmsg" }, count : { "$sum":1 } } },
        { $project : { _id : { $ifNull: [ "$_id.msg", "Success" ] }, count : "$count" } }
    ] ).result);
    result = result.concat(configDB.changelog.aggregate( [
        { $match : { time : { $gt : yesterday }, what : "moveChunk.from", "details.errmsg" : {
            "$exists" : true } } },
        { $group : { _id: { msg: "$details.errmsg", from : "$details.from", to: "$details.to" },
            count : { "$sum":1 } } },
        { $project : { _id : "$_id.msg" , from : "$_id.from", to : "$_id.to" , count : "$count" } }
    ] ).result);
    return result;
};

printShardingStatus = function( configDB , verbose ){
    if (configDB === undefined)
        configDB = db.getSisterDB('config')

    var version = configDB.getCollection( "version" ).findOne();
    if ( version == null ){
        print( "printShardingStatus: this db does not have sharding enabled. be sure you are connecting to a mongos from the shell and not to a mongod." );
        return;
    }

    var raw = "";
    var output = function(s){
        raw += s + "\n";
    }
    output( "--- Sharding Status --- " );
    output( "  sharding version: " + tojson( configDB.getCollection( "version" ).findOne(), "  " ) );

    output( "  shards:" );
    configDB.shards.find().sort( { _id : 1 } ).forEach(
        function(z){
            output( "    " + tojsononeline( z ) );
        }
    );

    // All of the balancer information functions below depend on a connection to a liveDB
    // This isn't normally a problem, but can cause issues in testing and running with --nodb
    if ( typeof db !== "undefined" ) {
        output( "  balancer:" );

        //Is the balancer currently enabled
        output( "\tCurrently enabled:  " + ( sh.getBalancerState() ? colorize("yes", {color: "cyan"}) : colorize("no", {color: "red"}) ) );

        //Is the balancer currently active
        output( "\tCurrently running:  " + colorize(( sh.isBalancerRunning() ? "yes" : "no" ), {color: "gray"}) );

        //Output details of the current balancer round
        var balLock = sh.getBalancerLockDetails();
        if ( balLock ) {
            output( "\t\tBalancer lock taken at " + colorize(balLock.when, {color: "gray"}) + " by " + colorize(balLock.who, {color: "cyan"}) );
        }

        //Output the balancer window
        var balSettings = sh.getBalancerWindow();
        if ( balSettings ) {
            output( "\t\tBalancer active window is set between " +
                colorize(balSettings.start, {color: "gray"}) + " and " + colorize(balSettings.stop, {color: "gray"}) + " server local time");
        }

        //Output the list of active migrations
        var activeMigrations = sh.getActiveMigrations();
        if (activeMigrations.length > 0 ){
            output("\tCollections with active migrations: ");
            activeMigrations.forEach( function(migration){
                output("\t\t"+colorize(migration._id, {color: "cyan"})+ " started at " + colorize(migration.when, {color: "gray"}) );
            });
        }

        // Actionlog and version checking only works on 2.7 and greater
        var versionHasActionlog = false;
        var metaDataVersion = configDB.getCollection("version").findOne().currentVersion;
        if ( metaDataVersion > 5 ) {
            versionHasActionlog = true;
        }
        if ( metaDataVersion == 5 ) {
            var verArray = db.serverBuildInfo().versionArray;
            if (verArray[0] == 2 && verArray[1] > 6){
                versionHasActionlog = true;
            }
        }

        if ( versionHasActionlog ) {
            //Review config.actionlog for errors
            var actionReport = sh.getRecentFailedRounds();
            //Always print the number of failed rounds
            output( "\tFailed balancer rounds in last 5 attempts:  " + colorize(actionReport.count, {color: "red"}) );

            //Only print the errors if there are any
            if ( actionReport.count > 0 ){
                output( "\tLast reported error:  " + actionReport.lastErr );
                output( "\tTime of Reported error:  " + actionReport.lastTime );
            }

            output("\tMigration Results for the last 24 hours: ");
            var migrations = sh.getRecentMigrations();
            if(migrations.length > 0) {
                migrations.forEach( function(x) {
                    if (x._id === "Success"){
                        output( "\t\t" + colorize(x.count, {color: "gray"}) + " : " + colorize(x._id, {color: "cyan"}));
                    } else {
                        output( "\t\t" + colorize(x.count, {color: "gray"}) + " : Failed with error '" + colorize(x._id, {color: "red"}) +
                        "', from " + x.from + " to " + x.to );
                    }
                });
            } else {
                    output( "\t\tNo recent migrations");
            }
        }
    }

    output( "  databases:" );
    configDB.databases.find().sort( { name : 1 } ).forEach(
        function(db){
            output( "    " + tojsononeline(db,"",true) );

            if (db.partitioned){
                configDB.collections.find( { _id : new RegExp( "^" +
                    RegExp.escape(db._id) + "\\." ) } ).
                    sort( { _id : 1 } ).forEach( function( coll ){
                        if ( coll.dropped == false ){
                            output( "    " + coll._id );
                            output( "      shard key: " + tojson(coll.key, 0, true) );
                            output( "      chunks:" );

                            res = configDB.chunks.aggregate(
                                { "$match": { ns: coll._id } },
                                { "$group": { _id: "$shard", nChunks: { "$sum": 1 } } },
                                { "$project" : { _id : 0 , shard : "$_id" , nChunks : "$nChunks" } },
                                { "$sort" : { shard : 1 } }
                            ).result

                            var totalChunks = 0;
                            res.forEach( function(z){
                                totalChunks += z.nChunks;
                                output( "        " + z.shard + ": " + z.nChunks );
                            } )

                            if ( totalChunks < 20 || verbose ){
                                configDB.chunks.find( { "ns" : coll._id } ).sort( { min : 1 } ).forEach(
                                    function(chunk){
                                        output( "        " +
                                            tojson( chunk.min, 0, true) + " -> " +
                                            tojson( chunk.max, 0, true ) +
                                            " on: " + colorize(chunk.shard, {color: 'cyan'}) + " " + tojson( chunk.lastmod ) + " " +
                                            ( chunk.jumbo ? "jumbo " : "" )
                                        );
                                    }
                                );
                            }
                            else {
                                output( "\t\t\ttoo many chunks to print, use verbose if you want to force print" );
                            }

                            configDB.tags.find( { ns : coll._id } ).sort( { min : 1 } ).forEach(
                                function( tag ) {
                                    output( "        tag: " + tag.tag + "  " + tojson( tag.min ) + " -> " + tojson( tag.max ) );
                                }
                            )
                        }
                    }
                )
            }
        }
    );

    print( raw );
}
<|endoftext|>


<commit_before>const Trello = require("node-trello")
const moment = require("moment")
const trelloApi = new Trello(process.env.TRELLO_APP_ID, process.env.TRELLO_APP_TOKEN)

const shorten = value => {
  const maxLength = 50
  const trimmedText = value.length > maxLength ? value.substring(0, maxLength) + "..." : value
  return trimmedText.replace('\\n', '').replace('\r', '')
}

const sendCard = (card, send) => {
  const board = trelloApi.get("/1/boards/" + card.idBoard, (err, data) => {

    data = data ||  {'name':'  board saknas (?)'}
    if(!data.closed){
      const formattedMessage = `
          ---------------------------------------
          *${shorten(card.name)}* på _ ${data.name}_ (${moment(card.dateLastActivity).format('LL')})
          ${shorten(card.desc) || "~ingen beskrivning~"}
          ${card.shortUrl}`
          console.log('not closed');
          send(formattedMessage)
    }

  })
}

module.exports = tiny => {
  tiny.listen(/trello (.+)/i, (send, match) => {
    const cards = trelloApi.get("/1/search", {
      query: match[1].trim(),
      cards_limit: 100,
      card_fields: 'idBoard,shortUrl,name,desc,dateLastActivity,closed'
    }, (err, data) => {
      console.log(data.cards);
      const uniqueCards = data.cards.filter(c => !c.closed).reduce((acc, val) => {
        return acc.indexOf(val) === -1
          ? acc.concat([val])
          : acc
      }, [])

       send(`Hittade *${uniqueCards.length}* kort som innehåller *${match[1]}*`)
      uniqueCards.map(card => sendCard(card, send))
    })
  })
}<commit_msg>#3 filtering out closed boards and cards<commit_after>const Trello = require("node-trello")
const moment = require("moment")
const trelloApi = new Trello(process.env.TRELLO_APP_ID, process.env.TRELLO_APP_TOKEN)

const shorten = value => {
  const maxLength = 50
  const trimmedText = value.length > maxLength ? value.substring(0, maxLength) + "..." : value
  return trimmedText.replace('\\n', '').replace('\r', '')
}

const sendCard = (card, send) => {
  const board = trelloApi.get("/1/boards/" + card.idBoard, (err, data) => {

    data = data ||  {'name':'  board saknas (?)'}
    if(!data.closed){
      const formattedMessage = `
          ---------------------------------------
          *${shorten(card.name)}* på _ ${data.name}_ (${moment(card.dateLastActivity).format('LL')})
          ${shorten(card.desc) || "~ingen beskrivning~"}
          ${card.shortUrl}`
          console.log('not closed');
          send(formattedMessage)
    }

  })
}

module.exports = tiny => {
  tiny.listen(/trello (.+)/i, (send, match) => {
    const cards = trelloApi.get("/1/search", {
      query: match[1].trim(),
      cards_limit: 100,
      card_fields: 'idBoard,shortUrl,name,desc,dateLastActivity,closed'
    }, (err, data) => {
      console.log(data.cards);
      const uniqueCards = data.cards.filter(c => !c.closed).reduce((acc, val) => {
        return acc.indexOf(val) === -1
          ? acc.concat([val])
          : acc
      }, [])

       send(`Hittade *${uniqueCards.length}* kort som innehåller *${match[1]}* \n Visar öppna kort på öppna tavlor `)
      uniqueCards.map(card => sendCard(card, send))
    })
  })
}<|endoftext|>


<commit_before>/*
---

name: Event.Rotate

description: Provides an event that indicates the window rotated.

license: MIT-style license.

author:
	- Jean-Philippe Dery (jeanphilippe.dery@gmail.com)

requires:
	- Core/Event
	- Core/Element.Event
	- Custom-Event/Element.defineCustomEvent

provides:
	- Event.Rotate

...
*/

(function() {

	Object.append(window, {

		getOrientationName: function() {
			return 	Math.abs(window.orientation) == 0 ? 'portrait' : 'landscape';
		}

	});

	var condition = function(e) {
		e.orientationName = window.getOrientationName();
		return true;
	};

	Element.defineCustomEvent('rotate', {
		base: 'orientationchange',
		condition: condition
	});

})();<commit_msg>The default orientation should be portrait<commit_after>/*
---

name: Event.Rotate

description: Provides an event that indicates the window rotated.

license: MIT-style license.

author:
	- Jean-Philippe Dery (jeanphilippe.dery@gmail.com)

requires:
	- Core/Event
	- Core/Element.Event
	- Custom-Event/Element.defineCustomEvent

provides:
	- Event.Rotate

...
*/

(function() {

	Object.append(window, {

		getOrientationName: function() {
			return 	Math.abs(window.orientation) == 90 ? 'landscape' : 'portrait';
		}

	});

	var condition = function(e) {
		e.orientationName = window.getOrientationName();
		return true;
	};

	Element.defineCustomEvent('rotate', {
		base: 'orientationchange',
		condition: condition
	});

})();<|endoftext|>


<commit_before>const {it, fit, ffit, beforeEach} = require('./async-spec-helpers') // eslint-disable-line no-unused-vars
const etch = require('etch')

describe('KeyBindingResolverView', () => {
  let workspaceElement, bottomDockElement

  beforeEach(async () => {
    workspaceElement = atom.views.getView(atom.workspace)
    bottomDockElement = atom.views.getView(atom.workspace.getBottomDock())
    await atom.packages.activatePackage('keybinding-resolver')
  })

  describe('when the key-binding-resolver:toggle event is triggered', () => {
    it('toggles the view', async () => {
      expect(atom.workspace.getBottomDock().isVisible()).toBe(false)
      expect(bottomDockElement.querySelector('.key-binding-resolver')).not.toExist()

      await atom.commands.dispatch(workspaceElement, 'key-binding-resolver:toggle')
      expect(atom.workspace.getBottomDock().isVisible()).toBe(true)
      expect(bottomDockElement.querySelector('.key-binding-resolver')).toExist()

      await atom.commands.dispatch(workspaceElement, 'key-binding-resolver:toggle')
      expect(atom.workspace.getBottomDock().isVisible()).toBe(false)
      expect(bottomDockElement.querySelector('.key-binding-resolver')).toExist()

      await atom.commands.dispatch(workspaceElement, 'key-binding-resolver:toggle')
      expect(atom.workspace.getBottomDock().isVisible()).toBe(true)
      expect(bottomDockElement.querySelector('.key-binding-resolver')).toExist()
    })

    it('focuses the view if it is not visible instead of destroying it', async () => {
      expect(atom.workspace.getBottomDock().isVisible()).toBe(false)
      expect(bottomDockElement.querySelector('.key-binding-resolver')).not.toExist()

      await atom.commands.dispatch(workspaceElement, 'key-binding-resolver:toggle')
      expect(atom.workspace.getBottomDock().isVisible()).toBe(true)
      expect(bottomDockElement.querySelector('.key-binding-resolver')).toExist()

      atom.workspace.getBottomDock().hide()
      await atom.commands.dispatch(workspaceElement, 'key-binding-resolver:toggle')

      expect(atom.workspace.getBottomDock().isVisible()).toBe(true)
      expect(bottomDockElement.querySelector('.key-binding-resolver')).toExist()
    })
  })

  describe('capturing keybinding events', () => {
    it('captures events when the keybinding resolver is visible', async () => {
      await atom.commands.dispatch(workspaceElement, 'key-binding-resolver:toggle')
      const keybindingResolverView = atom.workspace.getBottomDock().getActivePaneItem()
      expect(keybindingResolverView.keybindingDisposables).not.toBe(null)

      document.dispatchEvent(atom.keymaps.constructor.buildKeydownEvent('x', {target: bottomDockElement}))
      await etch.getScheduler().getNextUpdatePromise()
      expect(bottomDockElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe('x')
    })

    it('does not capture events when the keybinding resolver is not the active pane item', async () => {
      await atom.commands.dispatch(workspaceElement, 'key-binding-resolver:toggle')
      const keybindingResolverView = atom.workspace.getBottomDock().getActivePaneItem()
      expect(keybindingResolverView.keybindingDisposables).not.toBe(null)

      atom.workspace.getBottomDock().getActivePane().splitRight()
      expect(keybindingResolverView.keybindingDisposables).toBe(null)

      atom.workspace.getBottomDock().getActivePane().destroy()
      document.dispatchEvent(atom.keymaps.constructor.buildKeydownEvent('x', {target: bottomDockElement}))
      await etch.getScheduler().getNextUpdatePromise()
      expect(bottomDockElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe('x')
    })

    it('does not capture events when the dock the keybinding resolver is in is not visible', async () => {
      await atom.commands.dispatch(workspaceElement, 'key-binding-resolver:toggle')
      const keybindingResolverView = atom.workspace.getBottomDock().getActivePaneItem()
      expect(keybindingResolverView.keybindingDisposables).not.toBe(null)

      atom.workspace.getBottomDock().hide()
      expect(keybindingResolverView.keybindingDisposables).toBe(null)

      atom.workspace.getBottomDock().show()
      document.dispatchEvent(atom.keymaps.constructor.buildKeydownEvent('x', {target: bottomDockElement}))
      await etch.getScheduler().getNextUpdatePromise()
      expect(bottomDockElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe('x')
    })
  })

  describe('when a keydown event occurs', () => {
    it('displays all commands for the keydown event but does not clear for the keyup when there is no keyup binding', async () => {
      atom.keymaps.add('name', {
        '.workspace': {
          'x': 'match-1'
        }
      })
      atom.keymaps.add('name', {
        '.workspace': {
          'x': 'match-2'
        }
      })
      atom.keymaps.add('name', {
        '.never-again': {
          'x': 'unmatch-2'
        }
      })

      await atom.commands.dispatch(workspaceElement, 'key-binding-resolver:toggle')

      document.dispatchEvent(atom.keymaps.constructor.buildKeydownEvent('x', {target: bottomDockElement}))
      await etch.getScheduler().getNextUpdatePromise()
      expect(bottomDockElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe('x')
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength(1)
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength(1)
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength(1)

      // It should not render the keyup event data because there is no match
      spyOn(etch.getScheduler(), 'updateDocument').andCallThrough()
      document.dispatchEvent(atom.keymaps.constructor.buildKeyupEvent('x', {target: bottomDockElement}))
      expect(etch.getScheduler().updateDocument).not.toHaveBeenCalled()
      expect(bottomDockElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe('x')
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength(1)
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength(1)
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength(1)
    })

    it('displays all commands for the keydown event but does not clear for the keyup when there is no keyup binding', async () => {
      atom.keymaps.add('name', {
        '.workspace': {
          'x': 'match-1'
        }
      })
      atom.keymaps.add('name', {
        '.workspace': {
          'x ^x': 'match-2'
        }
      })
      atom.keymaps.add('name', {
        '.workspace': {
          'a ^a': 'match-3'
        }
      })
      atom.keymaps.add('name', {
        '.never-again': {
          'x': 'unmatch-2'
        }
      })

      await atom.commands.dispatch(workspaceElement, 'key-binding-resolver:toggle')

      // Not partial because it dispatches the command for `x` immediately due to only having keyup events in remainder of partial match
      document.dispatchEvent(atom.keymaps.constructor.buildKeydownEvent('x', {target: bottomDockElement}))
      await etch.getScheduler().getNextUpdatePromise()
      expect(bottomDockElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe('x')
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength(1)
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength(0)
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength(1)

      // It should not render the keyup event data because there is no match
      document.dispatchEvent(atom.keymaps.constructor.buildKeyupEvent('x', {target: bottomDockElement}))
      await etch.getScheduler().getNextUpdatePromise()
      expect(bottomDockElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe('x ^x')
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength(1)
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength(0)
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength(0)

      document.dispatchEvent(atom.keymaps.constructor.buildKeydownEvent('a', {target: bottomDockElement}))
      await etch.getScheduler().getNextUpdatePromise()
      expect(bottomDockElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe('a (partial)')
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength(0)
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength(1)
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength(0)

      document.dispatchEvent(atom.keymaps.constructor.buildKeyupEvent('a', {target: bottomDockElement}))
      await etch.getScheduler().getNextUpdatePromise()
      expect(bottomDockElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe('a ^a')
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength(1)
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength(0)
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength(0)
    })
  })
})
<commit_msg>Fix failing specs<commit_after>const {it, fit, ffit, beforeEach} = require('./async-spec-helpers') // eslint-disable-line no-unused-vars
const etch = require('etch')

describe('KeyBindingResolverView', () => {
  let workspaceElement, bottomDockElement

  beforeEach(async () => {
    workspaceElement = atom.views.getView(atom.workspace)
    bottomDockElement = atom.views.getView(atom.workspace.getBottomDock())
    await atom.packages.activatePackage('keybinding-resolver')
    jasmine.attachToDOM(workspaceElement);
  })

  describe('when the key-binding-resolver:toggle event is triggered', () => {
    it('toggles the view', async () => {
      expect(atom.workspace.getBottomDock().isVisible()).toBe(false)
      expect(bottomDockElement.querySelector('.key-binding-resolver')).not.toExist()

      await atom.commands.dispatch(workspaceElement, 'key-binding-resolver:toggle')
      expect(atom.workspace.getBottomDock().isVisible()).toBe(true)
      expect(bottomDockElement.querySelector('.key-binding-resolver')).toExist()

      await atom.commands.dispatch(workspaceElement, 'key-binding-resolver:toggle')
      expect(atom.workspace.getBottomDock().isVisible()).toBe(false)
      expect(bottomDockElement.querySelector('.key-binding-resolver')).toExist()

      await atom.commands.dispatch(workspaceElement, 'key-binding-resolver:toggle')
      expect(atom.workspace.getBottomDock().isVisible()).toBe(true)
      expect(bottomDockElement.querySelector('.key-binding-resolver')).toExist()
    })

    it('focuses the view if it is not visible instead of destroying it', async () => {
      expect(atom.workspace.getBottomDock().isVisible()).toBe(false)
      expect(bottomDockElement.querySelector('.key-binding-resolver')).not.toExist()

      await atom.commands.dispatch(workspaceElement, 'key-binding-resolver:toggle')
      expect(atom.workspace.getBottomDock().isVisible()).toBe(true)
      expect(bottomDockElement.querySelector('.key-binding-resolver')).toExist()

      atom.workspace.getBottomDock().hide()
      await atom.commands.dispatch(workspaceElement, 'key-binding-resolver:toggle')

      expect(atom.workspace.getBottomDock().isVisible()).toBe(true)
      expect(bottomDockElement.querySelector('.key-binding-resolver')).toExist()
    })
  })

  describe('capturing keybinding events', () => {
    it('captures events when the keybinding resolver is visible', async () => {
      await atom.commands.dispatch(workspaceElement, 'key-binding-resolver:toggle')
      const keybindingResolverView = atom.workspace.getBottomDock().getActivePaneItem()
      expect(keybindingResolverView.keybindingDisposables).not.toBe(null)

      document.dispatchEvent(atom.keymaps.constructor.buildKeydownEvent('x', {target: bottomDockElement}))
      await etch.getScheduler().getNextUpdatePromise()
      expect(bottomDockElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe('x')
    })

    it('does not capture events when the keybinding resolver is not the active pane item', async () => {
      await atom.commands.dispatch(workspaceElement, 'key-binding-resolver:toggle')
      const keybindingResolverView = atom.workspace.getBottomDock().getActivePaneItem()
      expect(keybindingResolverView.keybindingDisposables).not.toBe(null)

      atom.workspace.getBottomDock().getActivePane().splitRight()
      expect(keybindingResolverView.keybindingDisposables).toBe(null)

      atom.workspace.getBottomDock().getActivePane().destroy()
      document.dispatchEvent(atom.keymaps.constructor.buildKeydownEvent('x', {target: bottomDockElement}))
      await etch.getScheduler().getNextUpdatePromise()
      expect(bottomDockElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe('x')
    })

    it('does not capture events when the dock the keybinding resolver is in is not visible', async () => {
      await atom.commands.dispatch(workspaceElement, 'key-binding-resolver:toggle')
      const keybindingResolverView = atom.workspace.getBottomDock().getActivePaneItem()
      expect(keybindingResolverView.keybindingDisposables).not.toBe(null)

      atom.workspace.getBottomDock().hide()
      expect(keybindingResolverView.keybindingDisposables).toBe(null)

      atom.workspace.getBottomDock().show()
      document.dispatchEvent(atom.keymaps.constructor.buildKeydownEvent('x', {target: bottomDockElement}))
      await etch.getScheduler().getNextUpdatePromise()
      expect(bottomDockElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe('x')
    })
  })

  describe('when a keydown event occurs', () => {
    it('displays all commands for the keydown event but does not clear for the keyup when there is no keyup binding', async () => {
      atom.keymaps.add('name', {
        '.workspace': {
          'x': 'match-1'
        }
      })
      atom.keymaps.add('name', {
        '.workspace': {
          'x': 'match-2'
        }
      })
      atom.keymaps.add('name', {
        '.never-again': {
          'x': 'unmatch-2'
        }
      })

      await atom.commands.dispatch(workspaceElement, 'key-binding-resolver:toggle')

      document.dispatchEvent(atom.keymaps.constructor.buildKeydownEvent('x', {target: bottomDockElement}))
      await etch.getScheduler().getNextUpdatePromise()
      expect(bottomDockElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe('x')
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength(1)
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength(1)
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength(1)

      // It should not render the keyup event data because there is no match
      spyOn(etch.getScheduler(), 'updateDocument').andCallThrough()
      document.dispatchEvent(atom.keymaps.constructor.buildKeyupEvent('x', {target: bottomDockElement}))
      expect(etch.getScheduler().updateDocument).not.toHaveBeenCalled()
      expect(bottomDockElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe('x')
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength(1)
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength(1)
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength(1)
    })

    it('displays all commands for the keydown event but does not clear for the keyup when there is no keyup binding', async () => {
      atom.keymaps.add('name', {
        '.workspace': {
          'x': 'match-1'
        }
      })
      atom.keymaps.add('name', {
        '.workspace': {
          'x ^x': 'match-2'
        }
      })
      atom.keymaps.add('name', {
        '.workspace': {
          'a ^a': 'match-3'
        }
      })
      atom.keymaps.add('name', {
        '.never-again': {
          'x': 'unmatch-2'
        }
      })

      await atom.commands.dispatch(workspaceElement, 'key-binding-resolver:toggle')

      // Not partial because it dispatches the command for `x` immediately due to only having keyup events in remainder of partial match
      document.dispatchEvent(atom.keymaps.constructor.buildKeydownEvent('x', {target: bottomDockElement}))
      await etch.getScheduler().getNextUpdatePromise()
      expect(bottomDockElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe('x')
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength(1)
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength(0)
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength(1)

      // It should not render the keyup event data because there is no match
      document.dispatchEvent(atom.keymaps.constructor.buildKeyupEvent('x', {target: bottomDockElement}))
      await etch.getScheduler().getNextUpdatePromise()
      expect(bottomDockElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe('x ^x')
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength(1)
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength(0)
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength(0)

      document.dispatchEvent(atom.keymaps.constructor.buildKeydownEvent('a', {target: bottomDockElement}))
      await etch.getScheduler().getNextUpdatePromise()
      expect(bottomDockElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe('a (partial)')
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength(0)
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength(1)
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength(0)

      document.dispatchEvent(atom.keymaps.constructor.buildKeyupEvent('a', {target: bottomDockElement}))
      await etch.getScheduler().getNextUpdatePromise()
      expect(bottomDockElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe('a ^a')
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength(1)
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength(0)
      expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength(0)
    })
  })
})
<|endoftext|>