<commit_before>import isInteger from "lodash/isInteger";
import repeat from "lodash/repeat";
import Buffer from "./buffer";
import * as n from "./node";
import * as t from "babel-types";

import * as generatorFunctions from "./generators";

const SCIENTIFIC_NOTATION = /e/i;
const ZERO_DECIMAL_INTEGER = /\.0+$/;
const NON_DECIMAL_LITERAL = /^0[box]/;

export type Format = {
  shouldPrintComment: (comment: string) => boolean,
  retainLines: boolean,
  retainFunctionParens: boolean,
  comments: boolean,
  auxiliaryCommentBefore: string,
  auxiliaryCommentAfter: string,
  compact: boolean | "auto",
  minified: boolean,
  quotes: "single" | "double",
  concise: boolean,
  indent: {
    adjustMultilineComment: boolean,
    style: string,
    base: number,
  },
};

export default class Printer {
  constructor(format, map) {
    this.format = format || {};
    this._buf = new Buffer(map);
  }

  format: Format;
  inForStatementInitCounter: number = 0;

  _buf: Buffer;
  _printStack: Array<Node> = [];
  _indent: number = 0;
  _insideAux: boolean = false;
  _printedCommentStarts: Object = {};
  _parenPushNewlineState: ?Object = null;
  _printAuxAfterOnNextUserNode: boolean = false;
  _printedComments: WeakSet = new WeakSet();
  _endsWithInteger = false;
  _endsWithWord = false;

  generate(ast) {
    this.print(ast);
    this._maybeAddAuxComment();

    return this._buf.get();
  }

  /**
   * Increment indent size.
   */

  indent(): void {
    if (this.format.compact || this.format.concise) return;

    this._indent++;
  }

  /**
   * Decrement indent size.
   */

  dedent(): void {
    if (this.format.compact || this.format.concise) return;

    this._indent--;
  }

  /**
   * Add a semicolon to the buffer.
   */

  semicolon(force: boolean = false): void {
    this._maybeAddAuxComment();
    this._append(";", !force /* queue */);
  }

  /**
   * Add a right brace to the buffer.
   */

  rightBrace(): void {
    if (this.format.minified) {
      this._buf.removeLastSemicolon();
    }
    this.token("}");
  }

  /**
   * Add a space to the buffer unless it is compact.
   */

  space(force: boolean = false): void {
    if (this.format.compact) return;

    if (
      (this._buf.hasContent() && !this.endsWith(" ") && !this.endsWith("\n")) ||
      force
    ) {
      this._space();
    }
  }

  /**
   * Writes a token that can't be safely parsed without taking whitespace into account.
   */

  word(str: string): void {
    if (this._endsWithWord) this._space();

    this._maybeAddAuxComment();
    this._append(str);

    this._endsWithWord = true;
  }

  /**
   * Writes a number token so that we can validate if it is an integer.
   */

  number(str: string): void {
    this.word(str);

    // Integer tokens need special handling because they cannot have '.'s inserted
    // immediately after them.
    this._endsWithInteger =
      isInteger(+str) &&
      !NON_DECIMAL_LITERAL.test(str) &&
      !SCIENTIFIC_NOTATION.test(str) &&
      !ZERO_DECIMAL_INTEGER.test(str) &&
      str[str.length - 1] !== ".";
  }

  /**
   * Writes a simple token.
   */

  token(str: string): void {
    // space is mandatory to avoid outputting <!--
    // http://javascript.spec.whatwg.org/#comment-syntax
    if (
      (str === "--" && this.endsWith("!")) ||
      // Need spaces for operators of the same kind to avoid: `a+++b`
      (str[0] === "+" && this.endsWith("+")) ||
      (str[0] === "-" && this.endsWith("-")) ||
      // Needs spaces to avoid changing '34' to '34.', which would still be a valid number.
      (str[0] === "." && this._endsWithInteger)
    ) {
      this._space();
    }

    this._maybeAddAuxComment();
    this._append(str);
  }

  /**
   * Add a newline (or many newlines), maintaining formatting.
   */

  newline(i?: number): void {
    if (this.format.retainLines || this.format.compact) return;

    if (this.format.concise) {
      this.space();
      return;
    }

    // never allow more than two lines
    if (this.endsWith("\n\n")) return;

    if (typeof i !== "number") i = 1;

    i = Math.min(2, i);
    if (this.endsWith("{\n") || this.endsWith(":\n")) i--;
    if (i <= 0) return;

    for (let j = 0; j < i; j++) {
      this._newline();
    }
  }

  endsWith(str: string): boolean {
    return this._buf.endsWith(str);
  }

  removeTrailingNewline(): void {
    this._buf.removeTrailingNewline();
  }

  source(prop: string, loc: Object): void {
    this._catchUp(prop, loc);

    this._buf.source(prop, loc);
  }

  withSource(prop: string, loc: Object, cb: () => void): void {
    this._catchUp(prop, loc);

    this._buf.withSource(prop, loc, cb);
  }

  _space(): void {
    this._append(" ", true /* queue */);
  }

  _newline(): void {
    this._append("\n", true /* queue */);
  }

  _append(str: string, queue: boolean = false) {
    this._maybeAddParen(str);
    this._maybeIndent(str);

    if (queue) this._buf.queue(str);
    else this._buf.append(str);

    this._endsWithWord = false;
    this._endsWithInteger = false;
  }

  _maybeIndent(str: string): void {
    // we've got a newline before us so prepend on the indentation
    if (this._indent && this.endsWith("\n") && str[0] !== "\n") {
      this._buf.queue(this._getIndent());
    }
  }

  _maybeAddParen(str: string): void {
    // see startTerminatorless() instance method
    const parenPushNewlineState = this._parenPushNewlineState;
    if (!parenPushNewlineState) return;
    this._parenPushNewlineState = null;

    let i;
    for (i = 0; i < str.length && str[i] === " "; i++) continue;
    if (i === str.length) return;

    const cha = str[i];
    if (cha === "\n" || cha === "/") {
      // we're going to break this terminator expression so we need to add a parentheses
      this.token("(");
      this.indent();
      parenPushNewlineState.printed = true;
    }
  }

  _catchUp(prop: string, loc: Object) {
    if (!this.format.retainLines) return;

    // catch up to this nodes newline if we're behind
    const pos = loc ? loc[prop] : null;
    if (pos && pos.line !== null) {
      const count = pos.line - this._buf.getCurrentLine();

      for (let i = 0; i < count; i++) {
        this._newline();
      }
    }
  }

  /**
   * Get the current indent.
   */

  _getIndent(): string {
    return repeat(this.format.indent.style, this._indent);
  }

  /**
   * Set some state that will be modified if a newline has been inserted before any
   * non-space characters.
   *
   * This is to prevent breaking semantics for terminatorless separator nodes. eg:
   *
   *    return foo;
   *
   * returns `foo`. But if we do:
   *
   *   return
   *   foo;
   *
   *  `undefined` will be returned and not `foo` due to the terminator.
   */

  startTerminatorless(): Object {
    return (this._parenPushNewlineState = {
      printed: false,
    });
  }

  /**
   * Print an ending parentheses if a starting one has been printed.
   */

  endTerminatorless(state: Object) {
    if (state.printed) {
      this.dedent();
      this.newline();
      this.token(")");
    }
  }

  print(node, parent) {
    if (!node) return;

    const oldConcise = this.format.concise;
    if (node._compact) {
      this.format.concise = true;
    }

    const printMethod = this[node.type];
    if (!printMethod) {
      // eslint-disable-next-line max-len
      throw new ReferenceError(
        `unknown node of type ${JSON.stringify(
          node.type,
        )} with constructor ${JSON.stringify(node && node.constructor.name)}`,
      );
    }

    this._printStack.push(node);

    const oldInAux = this._insideAux;
    this._insideAux = !node.loc;
    this._maybeAddAuxComment(this._insideAux && !oldInAux);

    let needsParens = n.needsParens(node, parent, this._printStack);
    if (
      this.format.retainFunctionParens &&
      node.type === "FunctionExpression" &&
      node.extra &&
      node.extra.parenthesized
    ) {
      needsParens = true;
    }
    if (needsParens) this.token("(");

    this._printLeadingComments(node, parent);

    const loc = t.isProgram(node) || t.isFile(node) ? null : node.loc;
    this.withSource("start", loc, () => {
      this[node.type](node, parent);
    });

    this._printTrailingComments(node, parent);

    if (needsParens) this.token(")");

    // end
    this._printStack.pop();

    this.format.concise = oldConcise;
    this._insideAux = oldInAux;
  }

  _maybeAddAuxComment(enteredPositionlessNode) {
    if (enteredPositionlessNode) this._printAuxBeforeComment();
    if (!this._insideAux) this._printAuxAfterComment();
  }

  _printAuxBeforeComment() {
    if (this._printAuxAfterOnNextUserNode) return;
    this._printAuxAfterOnNextUserNode = true;

    const comment = this.format.auxiliaryCommentBefore;
    if (comment) {
      this._printComment({
        type: "CommentBlock",
        value: comment,
      });
    }
  }

  _printAuxAfterComment() {
    if (!this._printAuxAfterOnNextUserNode) return;
    this._printAuxAfterOnNextUserNode = false;

    const comment = this.format.auxiliaryCommentAfter;
    if (comment) {
      this._printComment({
        type: "CommentBlock",
        value: comment,
      });
    }
  }

  getPossibleRaw(node) {
    const extra = node.extra;
    if (
      extra &&
      extra.raw != null &&
      extra.rawValue != null &&
      node.value === extra.rawValue
    ) {
      return extra.raw;
    }
  }

  printJoin(nodes: ?Array, parent: Object, opts = {}) {
    if (!nodes || !nodes.length) return;

    if (opts.indent) this.indent();

    const newlineOpts = {
      addNewlines: opts.addNewlines,
    };

    for (let i = 0; i < nodes.length; i++) {
      const node = nodes[i];
      if (!node) continue;

      if (opts.statement) this._printNewline(true, node, parent, newlineOpts);

      this.print(node, parent);

      if (opts.iterator) {
        opts.iterator(node, i);
      }

      if (opts.separator && i < nodes.length - 1) {
        opts.separator.call(this);
      }

      if (opts.statement) this._printNewline(false, node, parent, newlineOpts);
    }

    if (opts.indent) this.dedent();
  }

  printAndIndentOnComments(node, parent) {
    const indent = !!node.leadingComments;
    if (indent) this.indent();
    this.print(node, parent);
    if (indent) this.dedent();
  }

  printBlock(parent) {
    const node = parent.body;

    if (!t.isEmptyStatement(node)) {
      this.space();
    }

    this.print(node, parent);
  }

  _printTrailingComments(node, parent) {
    this._printComments(this._getComments(false, node, parent));
  }

  _printLeadingComments(node, parent) {
    this._printComments(this._getComments(true, node, parent));
  }

  printInnerComments(node, indent = true) {
    if (!node.innerComments) return;
    if (indent) this.indent();
    this._printComments(node.innerComments);
    if (indent) this.dedent();
  }

  printSequence(nodes, parent, opts = {}) {
    opts.statement = true;
    return this.printJoin(nodes, parent, opts);
  }

  printList(items, parent, opts = {}) {
    if (opts.separator == null) {
      opts.separator = commaSeparator;
    }

    return this.printJoin(items, parent, opts);
  }

  _printNewline(leading, node, parent, opts) {
    // Fast path since 'this.newline' does nothing when not tracking lines.
    if (this.format.retainLines || this.format.compact) return;

    // Fast path for concise since 'this.newline' just inserts a space when
    // concise formatting is in use.
    if (this.format.concise) {
      this.space();
      return;
    }

    let lines = 0;
    // don't add newlines at the beginning of the file
    if (this._buf.hasContent()) {
      if (!leading) lines++; // always include at least a single line after
      if (opts.addNewlines) lines += opts.addNewlines(leading, node) || 0;

      const needs = leading ? n.needsWhitespaceBefore : n.needsWhitespaceAfter;
      if (needs(node, parent)) lines++;
    }

    this.newline(lines);
  }

  _getComments(leading, node) {
    // Note, we use a boolean flag here instead of passing in the attribute name as it is faster
    // because this is called extremely frequently.
    return (
      (node && (leading ? node.leadingComments : node.trailingComments)) || []
    );
  }

  _printComment(comment) {
    if (!this.format.shouldPrintComment(comment.value)) return;

    // Some plugins use this to mark comments as removed using the AST-root 'comments' property,
    // where they can't manually mutate the AST node comment lists.
    if (comment.ignore) return;

    if (this._printedComments.has(comment)) return;
    this._printedComments.add(comment);

    if (comment.start != null) {
      if (this._printedCommentStarts[comment.start]) return;
      this._printedCommentStarts[comment.start] = true;
    }

    const isBlockComment = comment.type === "CommentBlock";

    // Always add a newline before a block comment
    this.newline(this._buf.hasContent() && isBlockComment ? 1 : 0);

    if (!this.endsWith("[") && !this.endsWith("{")) this.space();

    let val = !isBlockComment ? `//${comment.value}\n` : `/*${comment.value}*/`;

    //
    if (isBlockComment && this.format.indent.adjustMultilineComment) {
      const offset = comment.loc && comment.loc.start.column;
      if (offset) {
        const newlineRegex = new RegExp("\\n\\s{1," + offset + "}", "g");
        val = val.replace(newlineRegex, "\n");
      }

      const indentSize = Math.max(
        this._getIndent().length,
        this._buf.getCurrentColumn(),
      );
      val = val.replace(/\n(?!$)/g, `\n${repeat(" ", indentSize)}`);
    }

    // Avoid creating //* comments
    if (this.endsWith("/")) this._space();

    this.withSource("start", comment.loc, () => {
      this._append(val);
    });

    // Always add a newline after a block comment
    this.newline(isBlockComment ? 1 : 0);
  }

  _printComments(comments?: Array<Object>) {
    if (!comments || !comments.length) return;

    for (const comment of comments) {
      this._printComment(comment);
    }
  }
}

// Expose the node type functions and helpers on the prototype for easy usage.
Object.assign(Printer.prototype, generatorFunctions);

function commaSeparator() {
  this.token(",");
  this.space();
}
<commit_msg>chore: remove unused ESLint rule<commit_after>import isInteger from "lodash/isInteger";
import repeat from "lodash/repeat";
import Buffer from "./buffer";
import * as n from "./node";
import * as t from "babel-types";

import * as generatorFunctions from "./generators";

const SCIENTIFIC_NOTATION = /e/i;
const ZERO_DECIMAL_INTEGER = /\.0+$/;
const NON_DECIMAL_LITERAL = /^0[box]/;

export type Format = {
  shouldPrintComment: (comment: string) => boolean,
  retainLines: boolean,
  retainFunctionParens: boolean,
  comments: boolean,
  auxiliaryCommentBefore: string,
  auxiliaryCommentAfter: string,
  compact: boolean | "auto",
  minified: boolean,
  quotes: "single" | "double",
  concise: boolean,
  indent: {
    adjustMultilineComment: boolean,
    style: string,
    base: number,
  },
};

export default class Printer {
  constructor(format, map) {
    this.format = format || {};
    this._buf = new Buffer(map);
  }

  format: Format;
  inForStatementInitCounter: number = 0;

  _buf: Buffer;
  _printStack: Array<Node> = [];
  _indent: number = 0;
  _insideAux: boolean = false;
  _printedCommentStarts: Object = {};
  _parenPushNewlineState: ?Object = null;
  _printAuxAfterOnNextUserNode: boolean = false;
  _printedComments: WeakSet = new WeakSet();
  _endsWithInteger = false;
  _endsWithWord = false;

  generate(ast) {
    this.print(ast);
    this._maybeAddAuxComment();

    return this._buf.get();
  }

  /**
   * Increment indent size.
   */

  indent(): void {
    if (this.format.compact || this.format.concise) return;

    this._indent++;
  }

  /**
   * Decrement indent size.
   */

  dedent(): void {
    if (this.format.compact || this.format.concise) return;

    this._indent--;
  }

  /**
   * Add a semicolon to the buffer.
   */

  semicolon(force: boolean = false): void {
    this._maybeAddAuxComment();
    this._append(";", !force /* queue */);
  }

  /**
   * Add a right brace to the buffer.
   */

  rightBrace(): void {
    if (this.format.minified) {
      this._buf.removeLastSemicolon();
    }
    this.token("}");
  }

  /**
   * Add a space to the buffer unless it is compact.
   */

  space(force: boolean = false): void {
    if (this.format.compact) return;

    if (
      (this._buf.hasContent() && !this.endsWith(" ") && !this.endsWith("\n")) ||
      force
    ) {
      this._space();
    }
  }

  /**
   * Writes a token that can't be safely parsed without taking whitespace into account.
   */

  word(str: string): void {
    if (this._endsWithWord) this._space();

    this._maybeAddAuxComment();
    this._append(str);

    this._endsWithWord = true;
  }

  /**
   * Writes a number token so that we can validate if it is an integer.
   */

  number(str: string): void {
    this.word(str);

    // Integer tokens need special handling because they cannot have '.'s inserted
    // immediately after them.
    this._endsWithInteger =
      isInteger(+str) &&
      !NON_DECIMAL_LITERAL.test(str) &&
      !SCIENTIFIC_NOTATION.test(str) &&
      !ZERO_DECIMAL_INTEGER.test(str) &&
      str[str.length - 1] !== ".";
  }

  /**
   * Writes a simple token.
   */

  token(str: string): void {
    // space is mandatory to avoid outputting <!--
    // http://javascript.spec.whatwg.org/#comment-syntax
    if (
      (str === "--" && this.endsWith("!")) ||
      // Need spaces for operators of the same kind to avoid: `a+++b`
      (str[0] === "+" && this.endsWith("+")) ||
      (str[0] === "-" && this.endsWith("-")) ||
      // Needs spaces to avoid changing '34' to '34.', which would still be a valid number.
      (str[0] === "." && this._endsWithInteger)
    ) {
      this._space();
    }

    this._maybeAddAuxComment();
    this._append(str);
  }

  /**
   * Add a newline (or many newlines), maintaining formatting.
   */

  newline(i?: number): void {
    if (this.format.retainLines || this.format.compact) return;

    if (this.format.concise) {
      this.space();
      return;
    }

    // never allow more than two lines
    if (this.endsWith("\n\n")) return;

    if (typeof i !== "number") i = 1;

    i = Math.min(2, i);
    if (this.endsWith("{\n") || this.endsWith(":\n")) i--;
    if (i <= 0) return;

    for (let j = 0; j < i; j++) {
      this._newline();
    }
  }

  endsWith(str: string): boolean {
    return this._buf.endsWith(str);
  }

  removeTrailingNewline(): void {
    this._buf.removeTrailingNewline();
  }

  source(prop: string, loc: Object): void {
    this._catchUp(prop, loc);

    this._buf.source(prop, loc);
  }

  withSource(prop: string, loc: Object, cb: () => void): void {
    this._catchUp(prop, loc);

    this._buf.withSource(prop, loc, cb);
  }

  _space(): void {
    this._append(" ", true /* queue */);
  }

  _newline(): void {
    this._append("\n", true /* queue */);
  }

  _append(str: string, queue: boolean = false) {
    this._maybeAddParen(str);
    this._maybeIndent(str);

    if (queue) this._buf.queue(str);
    else this._buf.append(str);

    this._endsWithWord = false;
    this._endsWithInteger = false;
  }

  _maybeIndent(str: string): void {
    // we've got a newline before us so prepend on the indentation
    if (this._indent && this.endsWith("\n") && str[0] !== "\n") {
      this._buf.queue(this._getIndent());
    }
  }

  _maybeAddParen(str: string): void {
    // see startTerminatorless() instance method
    const parenPushNewlineState = this._parenPushNewlineState;
    if (!parenPushNewlineState) return;
    this._parenPushNewlineState = null;

    let i;
    for (i = 0; i < str.length && str[i] === " "; i++) continue;
    if (i === str.length) return;

    const cha = str[i];
    if (cha === "\n" || cha === "/") {
      // we're going to break this terminator expression so we need to add a parentheses
      this.token("(");
      this.indent();
      parenPushNewlineState.printed = true;
    }
  }

  _catchUp(prop: string, loc: Object) {
    if (!this.format.retainLines) return;

    // catch up to this nodes newline if we're behind
    const pos = loc ? loc[prop] : null;
    if (pos && pos.line !== null) {
      const count = pos.line - this._buf.getCurrentLine();

      for (let i = 0; i < count; i++) {
        this._newline();
      }
    }
  }

  /**
   * Get the current indent.
   */

  _getIndent(): string {
    return repeat(this.format.indent.style, this._indent);
  }

  /**
   * Set some state that will be modified if a newline has been inserted before any
   * non-space characters.
   *
   * This is to prevent breaking semantics for terminatorless separator nodes. eg:
   *
   *    return foo;
   *
   * returns `foo`. But if we do:
   *
   *   return
   *   foo;
   *
   *  `undefined` will be returned and not `foo` due to the terminator.
   */

  startTerminatorless(): Object {
    return (this._parenPushNewlineState = {
      printed: false,
    });
  }

  /**
   * Print an ending parentheses if a starting one has been printed.
   */

  endTerminatorless(state: Object) {
    if (state.printed) {
      this.dedent();
      this.newline();
      this.token(")");
    }
  }

  print(node, parent) {
    if (!node) return;

    const oldConcise = this.format.concise;
    if (node._compact) {
      this.format.concise = true;
    }

    const printMethod = this[node.type];
    if (!printMethod) {
      throw new ReferenceError(
        `unknown node of type ${JSON.stringify(
          node.type,
        )} with constructor ${JSON.stringify(node && node.constructor.name)}`,
      );
    }

    this._printStack.push(node);

    const oldInAux = this._insideAux;
    this._insideAux = !node.loc;
    this._maybeAddAuxComment(this._insideAux && !oldInAux);

    let needsParens = n.needsParens(node, parent, this._printStack);
    if (
      this.format.retainFunctionParens &&
      node.type === "FunctionExpression" &&
      node.extra &&
      node.extra.parenthesized
    ) {
      needsParens = true;
    }
    if (needsParens) this.token("(");

    this._printLeadingComments(node, parent);

    const loc = t.isProgram(node) || t.isFile(node) ? null : node.loc;
    this.withSource("start", loc, () => {
      this[node.type](node, parent);
    });

    this._printTrailingComments(node, parent);

    if (needsParens) this.token(")");

    // end
    this._printStack.pop();

    this.format.concise = oldConcise;
    this._insideAux = oldInAux;
  }

  _maybeAddAuxComment(enteredPositionlessNode) {
    if (enteredPositionlessNode) this._printAuxBeforeComment();
    if (!this._insideAux) this._printAuxAfterComment();
  }

  _printAuxBeforeComment() {
    if (this._printAuxAfterOnNextUserNode) return;
    this._printAuxAfterOnNextUserNode = true;

    const comment = this.format.auxiliaryCommentBefore;
    if (comment) {
      this._printComment({
        type: "CommentBlock",
        value: comment,
      });
    }
  }

  _printAuxAfterComment() {
    if (!this._printAuxAfterOnNextUserNode) return;
    this._printAuxAfterOnNextUserNode = false;

    const comment = this.format.auxiliaryCommentAfter;
    if (comment) {
      this._printComment({
        type: "CommentBlock",
        value: comment,
      });
    }
  }

  getPossibleRaw(node) {
    const extra = node.extra;
    if (
      extra &&
      extra.raw != null &&
      extra.rawValue != null &&
      node.value === extra.rawValue
    ) {
      return extra.raw;
    }
  }

  printJoin(nodes: ?Array, parent: Object, opts = {}) {
    if (!nodes || !nodes.length) return;

    if (opts.indent) this.indent();

    const newlineOpts = {
      addNewlines: opts.addNewlines,
    };

    for (let i = 0; i < nodes.length; i++) {
      const node = nodes[i];
      if (!node) continue;

      if (opts.statement) this._printNewline(true, node, parent, newlineOpts);

      this.print(node, parent);

      if (opts.iterator) {
        opts.iterator(node, i);
      }

      if (opts.separator && i < nodes.length - 1) {
        opts.separator.call(this);
      }

      if (opts.statement) this._printNewline(false, node, parent, newlineOpts);
    }

    if (opts.indent) this.dedent();
  }

  printAndIndentOnComments(node, parent) {
    const indent = !!node.leadingComments;
    if (indent) this.indent();
    this.print(node, parent);
    if (indent) this.dedent();
  }

  printBlock(parent) {
    const node = parent.body;

    if (!t.isEmptyStatement(node)) {
      this.space();
    }

    this.print(node, parent);
  }

  _printTrailingComments(node, parent) {
    this._printComments(this._getComments(false, node, parent));
  }

  _printLeadingComments(node, parent) {
    this._printComments(this._getComments(true, node, parent));
  }

  printInnerComments(node, indent = true) {
    if (!node.innerComments) return;
    if (indent) this.indent();
    this._printComments(node.innerComments);
    if (indent) this.dedent();
  }

  printSequence(nodes, parent, opts = {}) {
    opts.statement = true;
    return this.printJoin(nodes, parent, opts);
  }

  printList(items, parent, opts = {}) {
    if (opts.separator == null) {
      opts.separator = commaSeparator;
    }

    return this.printJoin(items, parent, opts);
  }

  _printNewline(leading, node, parent, opts) {
    // Fast path since 'this.newline' does nothing when not tracking lines.
    if (this.format.retainLines || this.format.compact) return;

    // Fast path for concise since 'this.newline' just inserts a space when
    // concise formatting is in use.
    if (this.format.concise) {
      this.space();
      return;
    }

    let lines = 0;
    // don't add newlines at the beginning of the file
    if (this._buf.hasContent()) {
      if (!leading) lines++; // always include at least a single line after
      if (opts.addNewlines) lines += opts.addNewlines(leading, node) || 0;

      const needs = leading ? n.needsWhitespaceBefore : n.needsWhitespaceAfter;
      if (needs(node, parent)) lines++;
    }

    this.newline(lines);
  }

  _getComments(leading, node) {
    // Note, we use a boolean flag here instead of passing in the attribute name as it is faster
    // because this is called extremely frequently.
    return (
      (node && (leading ? node.leadingComments : node.trailingComments)) || []
    );
  }

  _printComment(comment) {
    if (!this.format.shouldPrintComment(comment.value)) return;

    // Some plugins use this to mark comments as removed using the AST-root 'comments' property,
    // where they can't manually mutate the AST node comment lists.
    if (comment.ignore) return;

    if (this._printedComments.has(comment)) return;
    this._printedComments.add(comment);

    if (comment.start != null) {
      if (this._printedCommentStarts[comment.start]) return;
      this._printedCommentStarts[comment.start] = true;
    }

    const isBlockComment = comment.type === "CommentBlock";

    // Always add a newline before a block comment
    this.newline(this._buf.hasContent() && isBlockComment ? 1 : 0);

    if (!this.endsWith("[") && !this.endsWith("{")) this.space();

    let val = !isBlockComment ? `//${comment.value}\n` : `/*${comment.value}*/`;

    //
    if (isBlockComment && this.format.indent.adjustMultilineComment) {
      const offset = comment.loc && comment.loc.start.column;
      if (offset) {
        const newlineRegex = new RegExp("\\n\\s{1," + offset + "}", "g");
        val = val.replace(newlineRegex, "\n");
      }

      const indentSize = Math.max(
        this._getIndent().length,
        this._buf.getCurrentColumn(),
      );
      val = val.replace(/\n(?!$)/g, `\n${repeat(" ", indentSize)}`);
    }

    // Avoid creating //* comments
    if (this.endsWith("/")) this._space();

    this.withSource("start", comment.loc, () => {
      this._append(val);
    });

    // Always add a newline after a block comment
    this.newline(isBlockComment ? 1 : 0);
  }

  _printComments(comments?: Array<Object>) {
    if (!comments || !comments.length) return;

    for (const comment of comments) {
      this._printComment(comment);
    }
  }
}

// Expose the node type functions and helpers on the prototype for easy usage.
Object.assign(Printer.prototype, generatorFunctions);

function commaSeparator() {
  this.token(",");
  this.space();
}
<|endoftext|>


<commit_before>import { transaction } from 'objection'
import { HttpError, formatters } from 'express-err'
import express from 'express'
import multer from 'multer'
import S3 from 'aws-sdk/clients/s3'
import multerS3 from 'multer-s3'
import config from 'config'
import { formatUrlFromBuild } from 'modules/urls/buildUrl'
import Build from 'server/models/Build'
import Repository from 'server/models/Repository'
import ScreenshotBucket from 'server/models/ScreenshotBucket'
import buildJob from 'server/jobs/build'
import errorHandler from 'server/middlewares/errorHandler'

const router = new express.Router()
const s3 = new S3({
  signatureVersion: 'v4',
})
const upload = multer({
  storage: multerS3({
    s3,
    bucket: config.get('s3.screenshotsBucket'),
    contentType: multerS3.AUTO_CONTENT_TYPE,
  }),
})

/**
 * Takes a route handling function and returns
 * a function that wraps it in a `try/catch`. Caught
 * exceptions are forwarded to the `next` handler.
 */
export function errorChecking(routeHandler) {
  return async (req, res, next) => {
    try {
      await routeHandler(req, res, next)
    } catch (err) {
      // Handle objection errors
      const candidates = [err.status, err.statusCode, err.code, 500]
      err.status = candidates.find(Number.isInteger)
      next(err)
    }
  }
}

async function createBuild({ Build, ScreenshotBucket, data, repository }) {
  const bucket = await ScreenshotBucket.query().insertAndFetch({
    name: 'default',
    commit: data.commit,
    branch: data.branch,
    repositoryId: repository.id,
  })

  const build = await Build.query().insertAndFetch({
    baseScreenshotBucketId: null,
    compareScreenshotBucketId: bucket.id,
    repositoryId: repository.id,
    jobStatus: 'pending',
    externalId: data.externalBuildId || null,
    batchCount: data.batchCount ? 1 : null,
  })

  build.compareScreenshotBucket = bucket

  return build
}

async function useExistingBuild({ Build, ScreenshotBucket, data, repository }) {
  const build = await Build.query()
    .eager('compareScreenshotBucket')
    .findOne({
      'builds.repositoryId': repository.id,
      externalId: data.externalBuildId,
    })

  // @TODO Throw an error if batchCount is superior to expected

  if (build) {
    await build.$query().patch({ batchCount: build.batchCount + 1 })
    return build
  }

  return createBuild({ Build, ScreenshotBucket, data, repository })
}

router.post(
  '/builds',
  upload.array('screenshots[]', 500),
  errorChecking(async (req, res) => {
    const data = JSON.parse(req.body.data)

    if (!data.token) {
      throw new HttpError(401, 'Missing token')
    }

    if (!data.commit) {
      throw new HttpError(401, 'Missing commit')
    }

    const repository = await Repository.query()
      .where({ token: data.token })
      .limit(1)
      .first()

    if (!repository) {
      throw new HttpError(400, `Repository not found (token: "${data.token}")`)
    }

    if (!repository.enabled) {
      throw new HttpError(
        400,
        `Repository not enabled (name: "${repository.name}")`,
      )
    }

    const strategy = data.externalBuildId ? useExistingBuild : createBuild

    let build = await transaction(
      Build,
      ScreenshotBucket,
      async (Build, ScreenshotBucket) => {
        const build = await strategy({
          Build,
          ScreenshotBucket,
          data,
          repository,
        })

        await build.compareScreenshotBucket.$relatedQuery('screenshots').insert(
          req.files.map((file, index) => ({
            screenshotBucketId: build.compareScreenshotBucket.id,
            name: data.names[index],
            s3Id: file.key,
          })),
        )

        return build
      },
    )

    // So we don't reuse the previous transaction
    build = await Build.query().findById(build.id)

    const buildUrl = await formatUrlFromBuild(build)

    if (!data.batchCount || data.batchCount === build.batchCount) {
      await buildJob.push(build.id)
    }

    res.send({
      build: {
        ...build,
        repository: undefined,
        buildUrl,
      },
    })
  }),
)

router.get(
  '/buckets',
  errorChecking(async (req, res) => {
    let query = ScreenshotBucket.query()

    if (req.query.branch) {
      query = query.where({ branch: req.query.branch })
    }

    res.send(await query)
  }),
)

router.use(
  errorHandler({
    formatters: {
      json: formatters.json,
      default: formatters.json,
    },
  }),
)

export default router
<commit_msg>fix(batch): cast batchCount String to Int before comparison (#358)<commit_after>import { transaction } from 'objection'
import { HttpError, formatters } from 'express-err'
import express from 'express'
import multer from 'multer'
import S3 from 'aws-sdk/clients/s3'
import multerS3 from 'multer-s3'
import config from 'config'
import { formatUrlFromBuild } from 'modules/urls/buildUrl'
import Build from 'server/models/Build'
import Repository from 'server/models/Repository'
import ScreenshotBucket from 'server/models/ScreenshotBucket'
import buildJob from 'server/jobs/build'
import errorHandler from 'server/middlewares/errorHandler'

const router = new express.Router()
const s3 = new S3({
  signatureVersion: 'v4',
})
const upload = multer({
  storage: multerS3({
    s3,
    bucket: config.get('s3.screenshotsBucket'),
    contentType: multerS3.AUTO_CONTENT_TYPE,
  }),
})

/**
 * Takes a route handling function and returns
 * a function that wraps it in a `try/catch`. Caught
 * exceptions are forwarded to the `next` handler.
 */
export function errorChecking(routeHandler) {
  return async (req, res, next) => {
    try {
      await routeHandler(req, res, next)
    } catch (err) {
      // Handle objection errors
      const candidates = [err.status, err.statusCode, err.code, 500]
      err.status = candidates.find(Number.isInteger)
      next(err)
    }
  }
}

async function createBuild({ Build, ScreenshotBucket, data, repository }) {
  const bucket = await ScreenshotBucket.query().insertAndFetch({
    name: 'default',
    commit: data.commit,
    branch: data.branch,
    repositoryId: repository.id,
  })

  const build = await Build.query().insertAndFetch({
    baseScreenshotBucketId: null,
    compareScreenshotBucketId: bucket.id,
    repositoryId: repository.id,
    jobStatus: 'pending',
    externalId: data.externalBuildId || null,
    batchCount: data.batchCount ? 1 : null,
  })

  build.compareScreenshotBucket = bucket

  return build
}

async function useExistingBuild({ Build, ScreenshotBucket, data, repository }) {
  const build = await Build.query()
    .eager('compareScreenshotBucket')
    .findOne({
      'builds.repositoryId': repository.id,
      externalId: data.externalBuildId,
    })

  // @TODO Throw an error if batchCount is superior to expected

  if (build) {
    await build.$query().patch({ batchCount: build.batchCount + 1 })
    return build
  }

  return createBuild({ Build, ScreenshotBucket, data, repository })
}

router.post(
  '/builds',
  upload.array('screenshots[]', 500),
  errorChecking(async (req, res) => {
    const data = JSON.parse(req.body.data)

    if (!data.token) {
      throw new HttpError(401, 'Missing token')
    }

    if (!data.commit) {
      throw new HttpError(401, 'Missing commit')
    }

    const repository = await Repository.query()
      .where({ token: data.token })
      .limit(1)
      .first()

    if (!repository) {
      throw new HttpError(400, `Repository not found (token: "${data.token}")`)
    }

    if (!repository.enabled) {
      throw new HttpError(
        400,
        `Repository not enabled (name: "${repository.name}")`,
      )
    }

    const strategy = data.externalBuildId ? useExistingBuild : createBuild

    let build = await transaction(
      Build,
      ScreenshotBucket,
      async (Build, ScreenshotBucket) => {
        const build = await strategy({
          Build,
          ScreenshotBucket,
          data,
          repository,
        })

        await build.compareScreenshotBucket.$relatedQuery('screenshots').insert(
          req.files.map((file, index) => ({
            screenshotBucketId: build.compareScreenshotBucket.id,
            name: data.names[index],
            s3Id: file.key,
          })),
        )

        return build
      },
    )

    // So we don't reuse the previous transaction
    build = await Build.query().findById(build.id)

    const buildUrl = await formatUrlFromBuild(build)

    if (!data.batchCount || Number(data.batchCount) === build.batchCount) {
      await buildJob.push(build.id)
    }

    res.send({
      build: {
        ...build,
        repository: undefined,
        buildUrl,
      },
    })
  }),
)

router.get(
  '/buckets',
  errorChecking(async (req, res) => {
    let query = ScreenshotBucket.query()

    if (req.query.branch) {
      query = query.where({ branch: req.query.branch })
    }

    res.send(await query)
  }),
)

router.use(
  errorHandler({
    formatters: {
      json: formatters.json,
      default: formatters.json,
    },
  }),
)

export default router
<|endoftext|>


<commit_before>const protocols = ['http', 'https', 'mailto', 'tel']

module.exports = function uriTransformer(uri) {
  const url = (uri || '').trim()
  const first = url.charAt(0)

  if (first === '#' || first === '/') {
    return url
  }

  const colon = url.indexOf(':')
  if (colon === -1) {
    return url
  }

  const length = protocols.length
  let index = -1

  while (++index < length) {
    const protocol = protocols[index]

    if (colon === protocol.length && url.slice(0, protocol.length) === protocol) {
      return url
    }
  }

  index = url.indexOf('?')
  if (index !== -1 && colon > index) {
    return url
  }

  index = url.indexOf('#')
  if (index !== -1 && colon > index) {
    return url
  }

  // eslint-disable-next-line no-script-url
  return 'javascript:void(0)'
}
<commit_msg>Check URI protocol in case-insensitive manner (#222)<commit_after>const protocols = ['http', 'https', 'mailto', 'tel']

module.exports = function uriTransformer(uri) {
  const url = (uri || '').trim()
  const first = url.charAt(0)

  if (first === '#' || first === '/') {
    return url
  }

  const colon = url.indexOf(':')
  if (colon === -1) {
    return url
  }

  const length = protocols.length
  let index = -1

  while (++index < length) {
    const protocol = protocols[index]

    if (colon === protocol.length && url.slice(0, protocol.length).toLowerCase() === protocol) {
      return url
    }
  }

  index = url.indexOf('?')
  if (index !== -1 && colon > index) {
    return url
  }

  index = url.indexOf('#')
  if (index !== -1 && colon > index) {
    return url
  }

  // eslint-disable-next-line no-script-url
  return 'javascript:void(0)'
}
<|endoftext|>


<commit_before>/**
 *
 * @license
 * Copyright (C) 2016 Joseph Roque
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @author Joseph Roque
 * @created 2016-10-30
 * @file TextUtils.js
 * @providesModule TextUtils
 * @description Defines a set of methods to manipulate strings.
 *
 * @flow
 */
'use strict';

/* eslint-disable no-magic-numbers */

import type {
  TimeFormat,
} from 'types';

/**
 * Formats certain link formats to display.
 *
 * @param {string} link link to format. Accepted formats are 'tel:\d+', 'mailto:.*'.
 * @returns {string} a link which is better for display, or the original.
 */
export function formatLink(link: ?string): string {
  if (link == null) {
    return '';
  }

  if (link.indexOf('tel:') === 0) {
    if (link.length === 14) {
      return '(' + link.substr(4, 3) + ') ' + link.substr(7, 3) + '-' + link.substr(10, 4);
    } else {
      return link.substring(4);
    }
  } else if (link.indexOf('mailto:') === 0) {
    return link.substring(7);
  } else {
    return link;
  }
}

/**
 * If a time has an hour greater than 23, it is adjusted to be within 24 hours.
 *
 * @param {string} time time to convert, in '00:00' format.
 * @returns {string} a time between 00:00 and 23:59.
 */
export function get24HourAdjustedTime(time: string): string {
  const minutes = time.substring(3, 5);
  let hours = parseInt(time.substring(0, 2));
  if (hours > 23) {
    hours -= 24;
  }

  return leftPad(hours.toString(), 2, '0') + ':' + minutes;
}

/**
 * If text length is greater than {maxLength}, the text is shortened to maxLength - 2 and 2 periods are added to the
 * end of the text.
 *
 * @param {string} text      string to shorten if too long.
 * @param {number} maxLength maximum length string to return.
 * @returns {string} a string with a max length of {maxLength}.
 */
export function getTextWithEllipses(text: string, maxLength: number): string {
  if (text.length > maxLength) {
    return text.substr(0, Math.max(maxLength - 2, 0)) + '..';
  } else {
    return text;
  }
}

/**
 * Pads the beginning of a string with a character until it is of {desiredLength}. If no character is provided,
 * pads with spaces.
 *
 * @param {string} text          string to pad.
 * @param {number} desiredLength length of string to return.
 * @param {?string} char         optional argument. Character to pad with. Uses ' ' by default.
 * @returns {string} a string with at least a length of {desiredLength}.
 */
export function leftPad(text: string, desiredLength: number, char: ?string): string {
  const replacementChar = char || ' ';
  let replacedString = text;
  let replacedStringLength = replacedString.length;

  if (replacementChar.length !== 1) {
    return text;
  }

  while (replacedStringLength < desiredLength) {
    replacedString = replacementChar + replacedString;
    replacedStringLength++;
  }

  return replacedString;
}

/**
 * Converts a time to either a 12h or 24h format. If the time is already in the format specified,
 * then it is returned. If the time is invalid, an error is thrown.
 *
 * @param {TimeFormat} format either '12' or '24',
 * @param {string} time       the time to convert
 * @returns {string} the converted time, with 'am' or 'pm' suffix for 12h time format
 */
export function convertTimeFormat(format: TimeFormat, time: string): string {
  if (format !== '24h' && format !== '12h') {
    throw new Error('Invalid time format: ' + format);
  }

  if (/^([0-1][0-9]|2[0-4]):[0-5][0-9]$/.test(time)) {
    // 24 hour time
    if (format === '24h') {
      return time;
    } else {
      // Convert to 12h time
      let suffix;
      let hours = parseInt(time.substr(0, 2));
      if (hours >= 12) {
        hours -= 12;
        suffix = 'pm';
      } else {
        suffix = 'am';
      }

      if (hours === 0) {
        hours = 12;
      }

      return `${hours}:${time.substr(3, 2)} ${suffix}`;
    }
  } else if (/^([1-9]|1[0-2]):[0-5][0-9] ?[ap]\.?m\.?$/i.test(time)) {
    // 12 hour time
    if (format === '12h') {
      return time.replace(/[.]/g, '').toLowerCase();
    } else {
      const colonIndex = time.indexOf(':');

      // Convert to 24h time
      let hours = parseInt(time.substring(0, colonIndex));
      if (/a/i.test(time)) {
        // Time is AM
        if (hours === 12) {
          hours = 0;
        }
      } else {
        // Time is PM
        hours += 12;
      }

      const strHours = (hours < 10)
          ? `0${hours}`
          : hours.toString();

      return `${strHours}:${time.substr(colonIndex + 1, 2)}`;
    }
  } else {
    // invalid time format
    throw new Error('Invalid time format (not 12h or 24h): ' + time);
  }
}
<commit_msg>Strip additional args from mailto<commit_after>/**
 *
 * @license
 * Copyright (C) 2016 Joseph Roque
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @author Joseph Roque
 * @created 2016-10-30
 * @file TextUtils.js
 * @providesModule TextUtils
 * @description Defines a set of methods to manipulate strings.
 *
 * @flow
 */
'use strict';

/* eslint-disable no-magic-numbers */

import type {
  TimeFormat,
} from 'types';

/**
 * Formats certain link formats to display.
 *
 * @param {string} link link to format. Accepted formats are 'tel:\d+', 'mailto:.*'.
 * @returns {string} a link which is better for display, or the original.
 */
export function formatLink(link: ?string): string {
  if (link == null) {
    return '';
  }

  if (link.indexOf('tel:') === 0) {
    if (link.length === 14) {
      return '(' + link.substr(4, 3) + ') ' + link.substr(7, 3) + '-' + link.substr(10, 4);
    } else {
      return link.substring(4);
    }
  } else if (link.indexOf('mailto:') === 0) {
    const questionMark = link.indexOf('?');
    if (questionMark >= 0) {
      return link.substring(7, questionMark);
    } else {
      return link.substring(7);
    }
  } else {
    return link;
  }
}

/**
 * If a time has an hour greater than 23, it is adjusted to be within 24 hours.
 *
 * @param {string} time time to convert, in '00:00' format.
 * @returns {string} a time between 00:00 and 23:59.
 */
export function get24HourAdjustedTime(time: string): string {
  const minutes = time.substring(3, 5);
  let hours = parseInt(time.substring(0, 2));
  if (hours > 23) {
    hours -= 24;
  }

  return leftPad(hours.toString(), 2, '0') + ':' + minutes;
}

/**
 * If text length is greater than {maxLength}, the text is shortened to maxLength - 2 and 2 periods are added to the
 * end of the text.
 *
 * @param {string} text      string to shorten if too long.
 * @param {number} maxLength maximum length string to return.
 * @returns {string} a string with a max length of {maxLength}.
 */
export function getTextWithEllipses(text: string, maxLength: number): string {
  if (text.length > maxLength) {
    return text.substr(0, Math.max(maxLength - 2, 0)) + '..';
  } else {
    return text;
  }
}

/**
 * Pads the beginning of a string with a character until it is of {desiredLength}. If no character is provided,
 * pads with spaces.
 *
 * @param {string} text          string to pad.
 * @param {number} desiredLength length of string to return.
 * @param {?string} char         optional argument. Character to pad with. Uses ' ' by default.
 * @returns {string} a string with at least a length of {desiredLength}.
 */
export function leftPad(text: string, desiredLength: number, char: ?string): string {
  const replacementChar = char || ' ';
  let replacedString = text;
  let replacedStringLength = replacedString.length;

  if (replacementChar.length !== 1) {
    return text;
  }

  while (replacedStringLength < desiredLength) {
    replacedString = replacementChar + replacedString;
    replacedStringLength++;
  }

  return replacedString;
}

/**
 * Converts a time to either a 12h or 24h format. If the time is already in the format specified,
 * then it is returned. If the time is invalid, an error is thrown.
 *
 * @param {TimeFormat} format either '12' or '24',
 * @param {string} time       the time to convert
 * @returns {string} the converted time, with 'am' or 'pm' suffix for 12h time format
 */
export function convertTimeFormat(format: TimeFormat, time: string): string {
  if (format !== '24h' && format !== '12h') {
    throw new Error('Invalid time format: ' + format);
  }

  if (/^([0-1][0-9]|2[0-4]):[0-5][0-9]$/.test(time)) {
    // 24 hour time
    if (format === '24h') {
      return time;
    } else {
      // Convert to 12h time
      let suffix;
      let hours = parseInt(time.substr(0, 2));
      if (hours >= 12) {
        hours -= 12;
        suffix = 'pm';
      } else {
        suffix = 'am';
      }

      if (hours === 0) {
        hours = 12;
      }

      return `${hours}:${time.substr(3, 2)} ${suffix}`;
    }
  } else if (/^([1-9]|1[0-2]):[0-5][0-9] ?[ap]\.?m\.?$/i.test(time)) {
    // 12 hour time
    if (format === '12h') {
      return time.replace(/[.]/g, '').toLowerCase();
    } else {
      const colonIndex = time.indexOf(':');

      // Convert to 24h time
      let hours = parseInt(time.substring(0, colonIndex));
      if (/a/i.test(time)) {
        // Time is AM
        if (hours === 12) {
          hours = 0;
        }
      } else {
        // Time is PM
        hours += 12;
      }

      const strHours = (hours < 10)
          ? `0${hours}`
          : hours.toString();

      return `${strHours}:${time.substr(colonIndex + 1, 2)}`;
    }
  } else {
    // invalid time format
    throw new Error('Invalid time format (not 12h or 24h): ' + time);
  }
}
<|endoftext|>


<commit_before>var login = require("facebook-chat-api");
var $ = require("jquery");
var request = require('request');
var fs = require("fs")
var strftime = require('strftime');
var unirest = require('unirest');
var log = require('npmlog');

var command = {
	meta :{
		api: null,
		admins:[]
	},
	cachedConvos:null,
	chatUsers:{}
}

String.prototype.replaceAt=function(index, character) {
    return this.substr(0, index) + character + this.substr(index+character.length);
}
String.prototype.capitalizeFirstLetter = function() {
    return this.charAt(0).toUpperCase() + this.slice(1);
}

function isInArray(haystack,needle){
	return haystack.indexOf(needle) > -1;
}

function prettifyMessage(type,time,sender,mesg){
	var message="";
	var nameLength = 20;
	var nameWidth = sender.length;
	var spacesToAdd = nameLength-sender.length;
	var doDot = spacesToAdd < 0;
	if(type){
		message+=type;
		message+=" ";
	}
	message+="["+time+"]";
	if (doDot) {
		sender = sender.slice(0,nameLength);
		sender = sender.replaceAt(sender.length-3,".");
		sender = sender.replaceAt(sender.length-2,".");
		sender = sender.replaceAt(sender.length-1,".");
	}
	message+="["+sender+"]";
	for (var i = 0; i < spacesToAdd; i++) {
		message+=" ";
	}
	message+=": ";
	message+=mesg;

	return message;
}

function getRandomInt(min, max) {
	return Math.floor(Math.random() * (max - min)) + min;
}

function getSpecialValue(arg, args){
	var specialValue = args['special_value'][arg];
	return specialValue;
}

function sendPhotoFromUrl(threadID, msg, imgurl, fileName, error,keep){
	var api = command.meta.api;
	var fileName = "./images/"+fileName;
	request(imgurl).on('response',function(response){
		if(response.statusCode!=200){
			var message = prettifyMessage(null,strftime('%F %T'),"DEBUG","Got status "+response.statusCode);
			log.error(message);
			if(typeof(error) == "function"){
				error();
			}
			return false;
		}
	}).pipe(fs.createWriteStream(fileName)).on('close',function(){
		var message = {
 			attachment: fs.createReadStream(fileName),
 			body: ""
		};
		if(msg){
			message['body']+=msg;
		}
    	sendMessage(message, threadID);
    	if(keep == undefined || keep!=true)
    		fs.unlink(fileName);
    	return true;
	});
}

function sendMessage(message, threadID, error){
	var api = command.meta.api;
	api.sendMessage(message, threadID,function(err, info){
		if(err){
			var message = prettifyMessage(null,strftime('%F %T'),"DEBUG","Error when sending message: "+err.errorDescription);
			log.error(message);
			if(typeof(error) == "function"){
				error();
			}
		}
	});
}

function getFilesize(filename) {
	try{
		var stats = fs.statSync(filename);
		var fileSizeInBytes = stats["size"]
		return fileSizeInBytes
	}catch(e){
		return 0;
	}
}

function getEditDistance(a, b){
  if(a.length == 0) return b.length;
  if(b.length == 0) return a.length;

  var matrix = [];

  // increment along the first column of each row
  var i;
  for(i = 0; i <= b.length; i++){
    matrix[i] = [i];
  }

  // increment each column in the first row
  var j;
  for(j = 0; j <= a.length; j++){
    matrix[0][j] = j;
  }

  // Fill in the rest of the matrix
  for(i = 1; i <= b.length; i++){
    for(j = 1; j <= a.length; j++){
      if(b.charAt(i-1) == a.charAt(j-1)){
        matrix[i][j] = matrix[i-1][j-1];
      } else {
        matrix[i][j] = Math.min(matrix[i-1][j-1] + 1, // substitution
                                Math.min(matrix[i][j-1] + 1, // insertion
                                         matrix[i-1][j] + 1)); // deletion
      }
    }
  }
  return matrix[b.length][a.length];
};

function findMostLikelyName(threadID,name){
	var users = command.chatUsers[threadID];
	if(users == undefined){
		return name
	}
	var justNames = [];
	var likelyUsers = [];
	var name = name.toLowerCase();

	for(id in users){
		var cur = users[id];
		var name_user = cur.name.toLowerCase();
		justNames.push(name_user);
	}
	if(justNames.indexOf(name.toLowerCase()) > -1){
		return name;
	}
	for(i in justNames){
		var name_user = justNames[i].toLowerCase();
		var name_user_split = name_user.split(" ");
		var name_split = name.split(" ");
		var allbreak = false;
		for(i in name_user_split){
			for(j in name_split){
				if(name_user_split[i]==name_split[j]){
					likelyUsers.push(name_user);
					allbreak = true;
					break;
				}
			}
			if(allbreak){
				break;
			}
		}
	}
	var unique_users = likelyUsers.filter(function onlyUnique(value, index, self) {
    	return self.indexOf(value) == index;
	});

	if(unique_users.length == 1){
		return unique_users[0];
	}else if(unique_users.length == 0){
		var lowest_edit_dist = 1024;
		var likelyUser = false;
		for(i in justNames){
			var cur_edit_dist = getEditDistance(name,justNames[i]);
			console.log(cur_edit_dist,lowest_edit_dist);
			console.log(name,justNames[i]);
			if(cur_edit_dist<lowest_edit_dist){
				lowest_edit_dist = cur_edit_dist;
				likelyUsers=justNames[i];
			}
		}
		return likelyUser;
	}

	return unique_users;
}

module.exports = {
	prettifyMessage   : prettifyMessage,
	isInArray         : isInArray,
	getRandomInt      : getRandomInt,
	getSpecialValue   : getSpecialValue,
	sendPhotoFromUrl  : sendPhotoFromUrl,
	sendMessage       : sendMessage,
	metaData          : command,
	getFilesize       : getFilesize,
	findMostLikelyName: findMostLikelyName
}<commit_msg>Tweaked name finder<commit_after>var login = require("facebook-chat-api");
var $ = require("jquery");
var request = require('request');
var fs = require("fs")
var strftime = require('strftime');
var unirest = require('unirest');
var log = require('npmlog');

var command = {
	meta :{
		api: null,
		admins:[]
	},
	cachedConvos:null,
	chatUsers:{}
}

String.prototype.replaceAt=function(index, character) {
    return this.substr(0, index) + character + this.substr(index+character.length);
}
String.prototype.capitalizeFirstLetter = function() {
    return this.charAt(0).toUpperCase() + this.slice(1);
}

function isInArray(haystack,needle){
	return haystack.indexOf(needle) > -1;
}

function prettifyMessage(type,time,sender,mesg){
	var message="";
	var nameLength = 20;
	var nameWidth = sender.length;
	var spacesToAdd = nameLength-sender.length;
	var doDot = spacesToAdd < 0;
	if(type){
		message+=type;
		message+=" ";
	}
	message+="["+time+"]";
	if (doDot) {
		sender = sender.slice(0,nameLength);
		sender = sender.replaceAt(sender.length-3,".");
		sender = sender.replaceAt(sender.length-2,".");
		sender = sender.replaceAt(sender.length-1,".");
	}
	message+="["+sender+"]";
	for (var i = 0; i < spacesToAdd; i++) {
		message+=" ";
	}
	message+=": ";
	message+=mesg;

	return message;
}

function getRandomInt(min, max) {
	return Math.floor(Math.random() * (max - min)) + min;
}

function getSpecialValue(arg, args){
	var specialValue = args['special_value'][arg];
	return specialValue;
}

function sendPhotoFromUrl(threadID, msg, imgurl, fileName, error,keep){
	var api = command.meta.api;
	var fileName = "./images/"+fileName;
	request(imgurl).on('response',function(response){
		if(response.statusCode!=200){
			var message = prettifyMessage(null,strftime('%F %T'),"DEBUG","Got status "+response.statusCode);
			log.error(message);
			if(typeof(error) == "function"){
				error();
			}
			return false;
		}
	}).pipe(fs.createWriteStream(fileName)).on('close',function(){
		var message = {
 			attachment: fs.createReadStream(fileName),
 			body: ""
		};
		if(msg){
			message['body']+=msg;
		}
    	sendMessage(message, threadID);
    	if(keep == undefined || keep!=true)
    		fs.unlink(fileName);
    	return true;
	});
}

function sendMessage(message, threadID, error){
	var api = command.meta.api;
	api.sendMessage(message, threadID,function(err, info){
		if(err){
			var message = prettifyMessage(null,strftime('%F %T'),"DEBUG","Error when sending message: "+err.errorDescription);
			log.error(message);
			if(typeof(error) == "function"){
				error();
			}
		}
	});
}

function getFilesize(filename) {
	try{
		var stats = fs.statSync(filename);
		var fileSizeInBytes = stats["size"]
		return fileSizeInBytes
	}catch(e){
		return 0;
	}
}

function getEditDistance(a, b){
  if(a.length == 0) return b.length;
  if(b.length == 0) return a.length;

  var matrix = [];

  // increment along the first column of each row
  var i;
  for(i = 0; i <= b.length; i++){
    matrix[i] = [i];
  }

  // increment each column in the first row
  var j;
  for(j = 0; j <= a.length; j++){
    matrix[0][j] = j;
  }

  // Fill in the rest of the matrix
  for(i = 1; i <= b.length; i++){
    for(j = 1; j <= a.length; j++){
      if(b.charAt(i-1) == a.charAt(j-1)){
        matrix[i][j] = matrix[i-1][j-1];
      } else {
        matrix[i][j] = Math.min(matrix[i-1][j-1] + 1, // substitution
                                Math.min(matrix[i][j-1] + 1, // insertion
                                         matrix[i-1][j] + 1)); // deletion
      }
    }
  }
  return matrix[b.length][a.length];
};

function findMostLikelyName(threadID,name){
	if(name == undefined){
		return 2;
	}
	var users = command.chatUsers[threadID];
	if(users == undefined){
		return name
	}
	var justNames = [];
	var likelyUsers = [];
	var name = name.toLowerCase();

	for(id in users){
		var cur = users[id];
		var name_user = cur.name.toLowerCase();
		justNames.push(name_user);
	}
	if(justNames.indexOf(name.toLowerCase()) > -1){
		return name;
	}
	for(i in justNames){
		var name_user = justNames[i].toLowerCase();
		var name_user_split = name_user.split(" ");
		var name_split = name.split(" ");
		var allbreak = false;
		for(i in name_user_split){
			for(j in name_split){
				if(name_user_split[i]==name_split[j]){
					likelyUsers.push(name_user);
					allbreak = true;
					break;
				}
			}
			if(allbreak){
				break;
			}
		}
	}
	var unique_users = likelyUsers.filter(function onlyUnique(value, index, self) {
    	return self.indexOf(value) == index;
	});

	if(unique_users.length == 1){
		return unique_users[0];
	}else if(unique_users.length == 0){
		var lowest_edit_dist = 1024;
		var likelyUser = false;
		for(i in justNames){
			var cur_edit_dist = getEditDistance(name,justNames[i]);
			console.log(cur_edit_dist,lowest_edit_dist);
			console.log(name,justNames[i]);
			if(cur_edit_dist<lowest_edit_dist){
				lowest_edit_dist = cur_edit_dist;
				likelyUsers=justNames[i];
			}
		}
		return likelyUser;
	}

	return unique_users;
}

module.exports = {
	prettifyMessage   : prettifyMessage,
	isInArray         : isInArray,
	getRandomInt      : getRandomInt,
	getSpecialValue   : getSpecialValue,
	sendPhotoFromUrl  : sendPhotoFromUrl,
	sendMessage       : sendMessage,
	metaData          : command,
	getFilesize       : getFilesize,
	findMostLikelyName: findMostLikelyName
}<|endoftext|>


<commit_before>import isNative from './isNative';

const longerTimeoutBrowsers = ['Edge', 'Trident', 'Firefox'];
let timeoutDuration = 0;
for (let i = 0; i < longerTimeoutBrowsers.length; i += 1) {
  if (navigator.userAgent.indexOf(longerTimeoutBrowsers[i]) >= 0) {
    timeoutDuration = 1;
    break;
  }
}

function microtaskDebounce(fn) {
  let scheduled = false;
  let i = 0;
  const elem = document.createElement('span');

  // MutationObserver provides a mechanism for scheduling microtasks, which
  // are scheduled *before* the next task. This gives us a way to debounce
  // a function but ensure it's called *before* the next paint.
  const observer = new MutationObserver(() => {
    fn();
    scheduled = false;
  });

  observer.observe(elem, { childList: true });

  return () => {
    if (!scheduled) {
      scheduled = true;
      elem.textContent = `${i}`;
      i += 1;
    }
  };
}

function taskDebounce(fn) {
  let scheduled = false;
  return () => {
    if (!scheduled) {
      scheduled = true;
      setTimeout(() => {
        scheduled = false;
        fn();
      }, timeoutDuration);
    }
  };
}

// It's common for MutationObserver polyfills to be seen in the wild, however
// these rely on Mutation Events which only occur when an element is connected
// to the DOM. The algorithm used in this module does not use a connected element,
// and so we must ensure that a *native* MutationObserver is available.
const supportsNativeMutationObserver = isNative(window.MutationObserver);

/**
 * Create a debounced version of a method, that's asynchronously deferred
 * but called in the minimum time possible.
 *
 * @method
 * @memberof Popper.Utils
 * @argument {Function} fn
 * @returns {Function}
 */
export default supportsNativeMutationObserver ? microtaskDebounce : taskDebounce;
<commit_msg>Fix JSDoc for debounce.<commit_after>import isNative from './isNative';

const longerTimeoutBrowsers = ['Edge', 'Trident', 'Firefox'];
let timeoutDuration = 0;
for (let i = 0; i < longerTimeoutBrowsers.length; i += 1) {
  if (navigator.userAgent.indexOf(longerTimeoutBrowsers[i]) >= 0) {
    timeoutDuration = 1;
    break;
  }
}

function microtaskDebounce(fn) {
  let scheduled = false;
  let i = 0;
  const elem = document.createElement('span');

  // MutationObserver provides a mechanism for scheduling microtasks, which
  // are scheduled *before* the next task. This gives us a way to debounce
  // a function but ensure it's called *before* the next paint.
  const observer = new MutationObserver(() => {
    fn();
    scheduled = false;
  });

  observer.observe(elem, { childList: true });

  return () => {
    if (!scheduled) {
      scheduled = true;
      elem.textContent = `${i}`;
      i += 1;
    }
  };
}

function taskDebounce(fn) {
  let scheduled = false;
  return () => {
    if (!scheduled) {
      scheduled = true;
      setTimeout(() => {
        scheduled = false;
        fn();
      }, timeoutDuration);
    }
  };
}

// It's common for MutationObserver polyfills to be seen in the wild, however
// these rely on Mutation Events which only occur when an element is connected
// to the DOM. The algorithm used in this module does not use a connected element,
// and so we must ensure that a *native* MutationObserver is available.
const supportsNativeMutationObserver = isNative(window.MutationObserver);

/**
 * Create a debounced version of a method, that's asynchronously deferred
 * but called in the minimum time possible.
 *
 * @method
 * @argument {Function} fn
 * @returns {Function}
 */
export default supportsNativeMutationObserver ? microtaskDebounce : taskDebounce;
<|endoftext|>


<commit_before>/*
 * BSD 3-Clause License
 *
 * Copyright (c) 2017, MapCreator
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 *
 *  Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 *  Neither the name of the copyright holder nor the names of its
 *   contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import fetchPonyfill from 'fetch-ponyfill';
export const {fetch, Request, Response, Headers} = fetchPonyfill({Promise});

/**
 * Makes a HTTP request and returns a promise. Promise will fail/reject if the
 * status code isn't 2XX.
 * @param {string} url - Target url
 * @param {string} method - HTTP method
 * @param {string|object<string, string>} body - raw body content or object to be json encoded
 * @param {object<string, string>} headers - headers
 * @param {string} responseType - XMLHttpRequest response type
 *
 * @returns {Promise} - resolves/rejects with {@link XMLHttpRequest} object. Rejects if status code != 2xx
 * @protected
 * @deprecated
 * @todo Better nodejs compatibility, maybe a requests library
 */
export function makeRequest(url, method = 'GET', body = '', headers = {}, responseType = '') {
  return new Promise((resolve, reject) => {
    method = method.toUpperCase();

    const request = new XMLHttpRequest();

    request.responseType = responseType;

    function hasHeader(h) {
      return Object.keys(headers)
        .filter(x => x.toLowerCase() === h.toLowerCase())
        .length > 0;
    }

    request.open(method, url, true);

    // Apply headers
    for (const key of Object.keys(headers)) {
      request.setRequestHeader(key, headers[key]);
    }

    request.onreadystatechange = () => {
      // State 4 === Done
      if (request.readyState === XMLHttpRequest.DONE) {
        if (request.status >= 200 && request.status < 300) {
          resolve(request);
        } else {
          reject(request);
        }
      }
    };

    if (body && method !== 'GET') {
      request.send(body);
    } else {
      request.send();
    }
  });
}

/**
 * Encodes an object to a http query string with support for recursion
 * @param {object<string, *>} paramsObject - data to be encoded
 * @returns {string} - encoded http query string
 *
 * @protected
 */
export function encodeQueryString(paramsObject) {
  return _encodeQueryString(paramsObject).replace('&&', '&');
}

/**
 * Encodes an object to a http query string with support for recursion
 * @param {Object<string, *>} paramsObject - data to be encoded
 * @param {Array<string>} _basePrefix - Used internally for tracking recursion
 * @returns {string} - encoded http query string
 *
 * @see http://stackoverflow.com/a/39828481
 * @private
 */
function _encodeQueryString(paramsObject, _basePrefix = []) {
  return Object
    .keys(paramsObject)
    .sort()
    .map(key => {
      const prefix = _basePrefix.slice(0);

      if (typeof paramsObject[key] === 'object') {
        prefix.push(key);

        return _encodeQueryString(paramsObject[key], prefix);
      }

      prefix.push(key);

      let out = '';

      out += encodeURIComponent(prefix.shift()); // main key
      out += prefix.map(item => `[${encodeURIComponent(item)}]`).join(''); // optional array keys
      out += '=' + encodeURIComponent(paramsObject[key]); // value

      return out;
    }).join('&');
}
<commit_msg>Remove makeRequest method<commit_after>/*
 * BSD 3-Clause License
 *
 * Copyright (c) 2017, MapCreator
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 *
 *  Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 *  Neither the name of the copyright holder nor the names of its
 *   contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import fetchPonyfill from 'fetch-ponyfill';
export const {fetch, Request, Response, Headers} = fetchPonyfill({Promise});

/**
 * Encodes an object to a http query string with support for recursion
 * @param {object<string, *>} paramsObject - data to be encoded
 * @returns {string} - encoded http query string
 *
 * @protected
 */
export function encodeQueryString(paramsObject) {
  return _encodeQueryString(paramsObject).replace('&&', '&');
}

/**
 * Encodes an object to a http query string with support for recursion
 * @param {Object<string, *>} paramsObject - data to be encoded
 * @param {Array<string>} _basePrefix - Used internally for tracking recursion
 * @returns {string} - encoded http query string
 *
 * @see http://stackoverflow.com/a/39828481
 * @private
 */
function _encodeQueryString(paramsObject, _basePrefix = []) {
  return Object
    .keys(paramsObject)
    .sort()
    .map(key => {
      const prefix = _basePrefix.slice(0);

      if (typeof paramsObject[key] === 'object') {
        prefix.push(key);

        return _encodeQueryString(paramsObject[key], prefix);
      }

      prefix.push(key);

      let out = '';

      out += encodeURIComponent(prefix.shift()); // main key
      out += prefix.map(item => `[${encodeURIComponent(item)}]`).join(''); // optional array keys
      out += '=' + encodeURIComponent(paramsObject[key]); // value

      return out;
    }).join('&');
}
<|endoftext|>


<commit_before>import { promises as fs } from 'fs'

import { render } from 'mustache'

export const renderTemplate = async function ({ template, data }) {
  const htmlFile = await fs.readFile(template, 'utf8')
  const htmlString = render(htmlFile, data)
  return htmlString
}
<commit_msg>Fix imports of mustache<commit_after>import { promises as fs } from 'fs'

import mustache from 'mustache'

export const renderTemplate = async function ({ template, data }) {
  const htmlFile = await fs.readFile(template, 'utf8')
  const htmlString = mustache.render(htmlFile, data)
  return htmlString
}
<|endoftext|>


<commit_before>/**
 * Population module
 *
 * @module
 * @copyright Bill Robitske, Jr. 2017
 * @author    Bill Robitske, Jr. <bill.robitske.jr@gmail.com>
 * @license   MIT
 */

/**
 * WeakMaps for private data
 */
const populations = new WeakMap();
const birthRates = new WeakMap();
const deathRates = new WeakMap();
const immigrationRates = new WeakMap();
const emmigrationRates = new WeakMap();

/**
 * Population encapsulation
 *
 * @class
 */
export default class Population {
  constructor() {
    populations.set(this, 0);
    birthRates.set(this, 0);
    deathRates.set(this, 0);
    immigrationRates.set(this, 0);
    emmigrationRates.set(this, 0);
  }

  /**
   * Retrieve the current population
   *
   * @return  {Number}  Current population
   */
  get currentPopulation() {
    return Math.round(populations.get(this));
  }

  /**
   * Retrive the current birth rate
   *
   * @return  {Number}  Current birth rate
   */
  get birthRate() {
    return birthRates.get(this);
  }

  /**
   * Retrieve the current death rate
   *
   * @return  {Number}  Current death rate
   */
  get deathRate() {
    return deathRates.get(this);
  }

  /**
   * Retrieve current immigration rate
   *
   * @return  {Number}  Current immigration rate
   */
  get immigrationRate() {
    return immigrationRates.get(this);
  }

  /**
   * Retrieve current emmigration rate
   *
   * @return  {Number}  Current emmigration rate
   */
  get emmigrationRate() {
    return emmigrationRates.get(this);
  }

  /**
   * Updates the current population based on the current birth rate, death rate,
   * immigration rate, and emmigration rate.
   */
  tickPopulationGrowth() {
    const increase = this.currentPopulation * (this.birthRate + this.immigrationRate);
    const decrease = this.currentPopulation * (this.deathRate + this.emmigrationRate);
    populations.set(this, populations.get(this) + (increase - decrease));
  }
}
<commit_msg>Switched to Symbols & calculate birth and death rates<commit_after>/**
 * Population module
 *
 * @module
 * @copyright Bill Robitske, Jr. 2017
 * @author    Bill Robitske, Jr. <bill.robitske.jr@gmail.com>
 * @license   MIT
 */

/**
 * Symbols for private data
 */
const population = Symbol('population');
const birthRate = Symbol('birthRate');
const deathRate = Symbol('deathRate');
const immigrationRate = Symbol('immigrationRate');
const emmigrationRate = Symbol('emmigrationRate');

/**
 * Population encapsulation
 *
 * @class
 */
export default class Population {
  constructor() {
    this[population] = 0;
    this[birthRate] = 0;
    this[deathRate] = 0;
    this[immigrationRate] = 0;
    this[emmigrationRate] = 0;
  }

  /**
   * Retrieve the current population
   *
   * @return  {Number}  Current population
   */
  get population() {
    return Math.round(this[population]);
  }

  /**
   * Retrive the current birth rate
   *
   * @return  {Number}  Current birth rate
   */
  get birthRate() {
    return this[birthRate];
  }

  /**
   * Retrieve the current death rate
   *
   * @return  {Number}  Current death rate
   */
  get deathRate() {
    return this[deathRate];
  }

  /**
   * Retrieve current immigration rate
   *
   * @return  {Number}  Current immigration rate
   */
  get immigrationRate() {
    return this[immigrationRate];
  }

  /**
   * Retrieve current emmigration rate
   *
   * @return  {Number}  Current emmigration rate
   */
  get emmigrationRate() {
    return this[emmigrationRate];
  }

  /**
   * Updates the current population based on the current birth rate, death rate,
   * immigration rate, and emmigration rate.
   */
  tickPopulationGrowth() {
    const increase = this.population * (this.birthRate + this.immigrationRate);
    const decrease = this.population * (this.deathRate + this.emmigrationRate);
    this[population] = this[population] + (increase - decrease);
  }

  /**
   * Updates the current birth rate based on the current population, food, and
   * territory.
   *
   * @param   {Number}  food        Current food
   * @param   {Number}  territory   Current territory
   */
  updateBirthRate(food = 0, territory = 0) {
    const undercrowding = Math.log((1 + (this.population / (1 + territory))) / 50);
    const baseRate = (1 / 1040) * undercrowding;
    this[birthRate] = baseRate > 2 ? 1 - ((baseRate - 2) / baseRate) : baseRate;
  }

  /**
   * Updates the current death rate based on the current population, food, and
   * territory.
   *
   * @param   {Number}  food        Current food
   * @param   {Number}  territory   Current territory
   */
  updateDeathRate(food = 0, territory = 0) {
    const hunger = 1 / Math.min((this.population - food) / this.population, 1);
    const suicide = Math.min(1 / Math.log((this.population / (1 + territory)) / 777), 1);
    this[deathRate] = (1 / 3640) * hunger * suicide;
  }
}
<|endoftext|>


<commit_before>import React from 'react';
import { connect } from 'react-redux';

import NodesChart from '../charts/nodes-chart';
import NodesError from '../charts/nodes-error';
import { findTopologyById, isTopologyEmpty } from '../utils/topology-utils';

const navbarHeight = 160;
const marginTop = 0;
const detailsWidth = 450;

/**
 * dynamic coords precision based on topology size
 */
function getLayoutPrecision(nodesCount) {
  let precision;
  if (nodesCount >= 50) {
    precision = 0;
  } else if (nodesCount > 20) {
    precision = 1;
  } else if (nodesCount > 10) {
    precision = 2;
  } else {
    precision = 3;
  }

  return precision;
}

function getNodeType(topology, topologies) {
  if (!topology || topologies.size === 0) {
    return '';
  }
  let name = topology.get('name');
  if (topology.get('parentId')) {
    const parentTopology = findTopologyById(topologies, topology.get('parentId'));
    name = parentTopology.get('name');
  }
  return name.toLowerCase();
}

class Nodes extends React.Component {
  constructor(props, context) {
    super(props, context);
    this.handleResize = this.handleResize.bind(this);

    this.state = {
      width: window.innerWidth,
      height: window.innerHeight - navbarHeight - marginTop
    };
  }

  componentDidMount() {
    window.addEventListener('resize', this.handleResize);
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.handleResize);
  }

  renderEmptyTopologyError(show) {
    return (
      <NodesError faIconClass="fa-circle-thin" hidden={!show}>

Nothing to show. This can have any of these reasons:

We haven't received any reports from probes recently.
           Are the probes properly configured?

There are nodes, but they're currently hidden. Check the view options
           in the bottom-left if they allow for showing hidden nodes.

Containers view only: you're not running Docker,
           or you don't have any containers.

      </NodesError>
    );
  }

  renderLoading(message, show) {
    return (
      <NodesError mainClassName="nodes-chart-loading" faIconClass="fa-circle-thin" hidden={!show}>

{message}

      </NodesError>
    );
  }

  render() {
    const { nodes, selectedNodeId, topologyEmpty, topologiesLoaded, nodesLoaded, topologies,
      topology } = this.props;
    const layoutPrecision = getLayoutPrecision(nodes.size);
    const hasSelectedNode = selectedNodeId && nodes.has(selectedNodeId);

    return (

        {this.renderLoading('Loading topologies...', !topologiesLoaded)}
        {this.renderLoading(`Loading ${getNodeType(topology, topologies)}...`,
          topologiesLoaded && !nodesLoaded)}
        {this.renderEmptyTopologyError(topologiesLoaded && nodesLoaded && topologyEmpty)}
        <NodesChart {...this.state}
          detailsWidth={detailsWidth}
          layoutPrecision={layoutPrecision}
          hasSelectedNode={hasSelectedNode}
        />

    );
  }

  handleResize() {
    this.setDimensions();
  }

  setDimensions() {
    const width = window.innerWidth;
    const height = window.innerHeight - navbarHeight - marginTop;

    this.setState({height, width});
  }
}

function mapStateToProps(state) {
  return {
    nodes: state.get('nodes'),
    nodesLoaded: state.get('nodesLoaded'),
    selectedNodeId: state.get('selectedNodeId'),
    topologies: state.get('topologies'),
    topologiesLoaded: state.get('topologiesLoaded'),
    topologyEmpty: isTopologyEmpty(state),
    topology: state.get('currentTopology'),
  };
}

export default connect(
  mapStateToProps
)(Nodes);
<commit_msg>More personal loading messages<commit_after>import React from 'react';
import _ from 'lodash';
import { connect } from 'react-redux';

import NodesChart from '../charts/nodes-chart';
import NodesError from '../charts/nodes-error';
import { findTopologyById, isTopologyEmpty } from '../utils/topology-utils';

const navbarHeight = 160;
const marginTop = 0;
const detailsWidth = 450;

const APOLOGIES = [
  "We're terribly sorry, the THINGS will be ready in a second!",
  "Forgive us! We're just loading the THINGS...",
  'A thousand apologies! Here come the THINGS!... any second now...'
];

function apologizeFor(nodeType, template) {
  return template.replace('THINGS', nodeType);
}

/**
 * dynamic coords precision based on topology size
 */
function getLayoutPrecision(nodesCount) {
  let precision;
  if (nodesCount >= 50) {
    precision = 0;
  } else if (nodesCount > 20) {
    precision = 1;
  } else if (nodesCount > 10) {
    precision = 2;
  } else {
    precision = 3;
  }

  return precision;
}

function getNodeType(topology, topologies) {
  if (!topology || topologies.size === 0) {
    return '';
  }
  let name = topology.get('name');
  if (topology.get('parentId')) {
    const parentTopology = findTopologyById(topologies, topology.get('parentId'));
    name = parentTopology.get('name');
  }
  return name.toLowerCase();
}

class Nodes extends React.Component {
  constructor(props, context) {
    super(props, context);
    this.handleResize = this.handleResize.bind(this);

    const [topologiesLoadingTemplate, nodeLoadingTemplate] = _.sampleSize(APOLOGIES, 2);
    this.state = {
      width: window.innerWidth,
      height: window.innerHeight - navbarHeight - marginTop,
      topologiesLoadingTemplate,
      nodeLoadingTemplate,
    };
  }

  componentDidMount() {
    window.addEventListener('resize', this.handleResize);
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.handleResize);
  }

  renderEmptyTopologyError(show) {
    return (
      <NodesError faIconClass="fa-circle-thin" hidden={!show}>

Nothing to show. This can have any of these reasons:

We haven't received any reports from probes recently.
           Are the probes properly configured?

There are nodes, but they're currently hidden. Check the view options
           in the bottom-left if they allow for showing hidden nodes.

Containers view only: you're not running Docker,
           or you don't have any containers.

      </NodesError>
    );
  }

  renderLoading(message, show) {
    return (
      <NodesError mainClassName="nodes-chart-loading" faIconClass="fa-circle-thin" hidden={!show}>

{message}

      </NodesError>
    );
  }

  render() {
    const { nodes, selectedNodeId, topologyEmpty, topologiesLoaded, nodesLoaded, topologies,
      topology } = this.props;
    const layoutPrecision = getLayoutPrecision(nodes.size);
    const hasSelectedNode = selectedNodeId && nodes.has(selectedNodeId);
    const topologyLoadingMessage = apologizeFor('toplogies', this.state.topologiesLoadingTemplate);
    const nodeLoadingMessage = apologizeFor(getNodeType(topology, topologies),
      this.state.nodeLoadingTemplate);

    return (

        {this.renderLoading(topologyLoadingMessage, !topologiesLoaded)}
        {this.renderLoading(nodeLoadingMessage, topologiesLoaded && !nodesLoaded)}
        {this.renderEmptyTopologyError(topologiesLoaded && nodesLoaded && topologyEmpty)}
        <NodesChart {...this.state}
          detailsWidth={detailsWidth}
          layoutPrecision={layoutPrecision}
          hasSelectedNode={hasSelectedNode}
        />

    );
  }

  handleResize() {
    this.setDimensions();
  }

  setDimensions() {
    const width = window.innerWidth;
    const height = window.innerHeight - navbarHeight - marginTop;

    this.setState({height, width});
  }
}

function mapStateToProps(state) {
  return {
    nodes: state.get('nodes'),
    nodesLoaded: state.get('nodesLoaded'),
    selectedNodeId: state.get('selectedNodeId'),
    topologies: state.get('topologies'),
    topologiesLoaded: state.get('topologiesLoaded'),
    topologyEmpty: isTopologyEmpty(state),
    topology: state.get('currentTopology'),
  };
}

export default connect(
  mapStateToProps
)(Nodes);
<|endoftext|>


<commit_before>angular.module('dashboard.services.Cache', [
  'dashboard.Config',
  'dashboard.Utils',
  'ngCookies'
])

.service('CacheService', function() {

  this.KEY_DELIMITER = '-';

  this.get = function(key) {
    if(!localStorage.getItem(key)) return null;
    try {
        var cached = JSON.parse(localStorage.getItem(key));
        return cached;
    }
    catch (e) {
        return null;
    }
  };

  this.set = function(key,value) {
    try{
        localStorage.setItem(key,JSON.stringify(value));
    } catch(e) {
        this.remove(key);
    }
  };

  this.remove = function(key) {
    localStorage.removeItem(key);
  };

  this.getKeyForAction = function(action,params) {
    var key = action.options.model + this.KEY_DELIMITER + action.route;
    if(params) key += this.KEY_DELIMITER + JSON.stringify(params);
    return key;
  }

  this.clear = function(model) {
    var key = model;
    var regex = new RegExp('^'+key)
    for(var k in localStorage)
    {
        if(regex.test(k))
        {
            this.remove(k);
        }
    }
  };

  this.reset = function()
  {
    localStorage.clear();
    localStorage['lastActive'] = new Date(); //for session tracking
  }
})

;
<commit_msg>Updated CacheService to use api path for key if provided<commit_after>angular.module('dashboard.services.Cache', [
  'dashboard.Config',
  'dashboard.Utils',
  'ngCookies'
])

.service('CacheService', function() {

  this.KEY_DELIMITER = '-';

  this.get = function(key) {
    if(!localStorage.getItem(key)) return null;
    try {
        var cached = JSON.parse(localStorage.getItem(key));
        return cached;
    }
    catch (e) {
        return null;
    }
  };

  this.set = function(key,value) {
    try{
        localStorage.setItem(key,JSON.stringify(value));
    } catch(e) {
        this.remove(key);
    }
  };

  this.remove = function(key) {
    localStorage.removeItem(key);
  };

  this.getKeyForAction = function(action,params) {
    var key = action.options.model + this.KEY_DELIMITER + action.route;
    if (action.options.api) key = action.options.api;
    if(params) key += this.KEY_DELIMITER + JSON.stringify(params);
    return key;
  }

  this.clear = function(model) {
    var key = model;
    var regex = new RegExp('^'+key)
    for(var k in localStorage)
    {
        if(regex.test(k))
        {
            this.remove(k);
        }
    }
  };

  this.reset = function()
  {
    localStorage.clear();
    localStorage['lastActive'] = new Date(); //for session tracking
  }
})

;
<|endoftext|>