<commit_before>// activity-test.js
//
// Test the AS2 conversion module
//
// Copyright 2017 AJ Jordan <alex@strugee.net>
//
// 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.

"use strict";

var assert = require("assert"),
    vows = require("vows"),
    databank = require("databank"),
    Step = require("step"),
    _ = require("lodash"),
    fs = require("fs"),
    path = require("path"),
    URLMaker = require("../lib/urlmaker").URLMaker,
    schema = require("../lib/schema").schema,
    modelBatch = require("./lib/model").modelBatch,
    Databank = databank.Databank,
    DatabankObject = databank.DatabankObject;

var suite = vows.describe("AS2 conversion module interface");

suite.addBatch({
    "When we get the AS2 conversion module": {
        topic: function() {
            return require("../lib/as2");
        },
        "it works": function(err, as2) {
            assert.ifError(err);
            assert.isFunction(as2);
        },
        "and we try to convert a post of a note to AS2": {
            topic: function(as2) {
                var act = {
                    id: "urn:uuid:77451568-ce6a-42eb-8a9f-60ece187725f",
                    actor: "acct:tim@w3.example",
                    verb: "post",
                    title: "I am a title!",
                    upstreamDuplicates: [],
                    downstreamDuplicates: [],
                    to: [{objectType: "collection",
                          id: "http://w3.example/socialwg"}],
                    object: {
                        id: "urn:uuid:33166eb9-2567-477c-ad90-9352dd904712",
                        objectType: "note"
                    },
                    displayName: "A note"
                };

                return as2(act);
            },
            "it has an @context": function(act) {
                assert.isTrue(act.hasOwnProperty("@context"));
                assert.equal(act["@context"], "https://www.w3.org/ns/activitystreams");
            },
            "id is renamed to @id": function(act) {
                assert.isFalse(act.hasOwnProperty("id"));
                assert.equal(act["@id"], "urn:uuid:77451568-ce6a-42eb-8a9f-60ece187725f");
            },
            "the `post` verb is converted specially to Create in @type": function(act) {
                assert.isFalse(act.hasOwnProperty("verb"));
                assert.equal(act["@type"], "Create");
            },
            "the object's objectType is renamed to @type": function(act) {
                assert.isFalse(act.object.hasOwnProperty("objectType"));
                assert.equal(act.object["@type"], "note");
            },
            "the `to` field's collection's objectType is renamed to @type": function(act) {
                assert.isFalse(act.to[0].hasOwnProperty("objectType"));
                assert.equal(act.to[0]["@type"], "collection");
            },
            // XXX should we test for all objectType renames? E.g. in the `to` field?
            "displayName is renamed to name": function(act) {
                assert.isFalse(act.hasOwnProperty("displayName"));
                assert.equal(act.name, "A note");
            },
            "title is dropped": function(act) {
                assert.isFalse(act.hasOwnProperty("title"));
            },
            "upstreamDuplicates is dropped": function(act) {
                assert.isFalse(act.hasOwnProperty("upstreamDuplicates"));
            },
            "downstreamDuplicates is dropped": function(act) {
                assert.isFalse(act.hasOwnProperty("downstreamDuplicates"));
            }
        },
        "and we try to convert a post of a note to a collection to AS2": {
            topic: function(as2) {
                var act = {
                    id: "urn:uuid:3738fceb-9705-4fa8-a0d3-59852770dc4d",
                    actor: "acct:chris@w3.example",
                    verb: "post",
                    title: "A rando note I want to categorize",
                    to: [{objectType: "collection",
                          id: "http://w3.example/socialwg"}],
                    object: {
                        id: "urn:uuid:33166eb9-2567-477c-ad90-9352dd904712",
                        objectType: "note"
                    },
                    target: {
                        id: "http://w3.example/chris/a-collection",
                        objectType: "collection"
                    }
                };

                return as2(act);
            },
            "the `post` verb is converted specially to Add in @type": function(act) {
                assert.isFalse(act.hasOwnProperty("verb"));
                assert.equal(act["@type"], "Add");
            }
        },
        "and we try to convert a like of a note to AS2": {
            topic: function(as2) {
                var act = {
                    id: "urn:uuid:db8b4174-a321-430f-bbbe-e11c65dd48ee",
                    actor: "acct:aj@w3.example",
                    verb: "like",
                    to: [{objectType: "collection",
                          id: "http://w3.example/socialwg"}],
                    // TODO check that this syntax is correct
                    target: {
                        id: "urn:uuid:77451568-ce6a-42eb-8a9f-60ece187725f",
                        objectType: "note"
                    }
                };

                return as2(act);
            },
            "verb is renamed to @type": function(act) {
                assert.isFalse(act.hasOwnProperty("verb"));
                assert.equal(act["@type"], "like");
            },
            "the target's objectType is renamed to @type": function(act) {
                assert.isFalse(act.target.hasOwnProperty("objectType"));
                assert.equal(act.target["@type"], "note");
            }
        }
    }
});

suite["export"](module);
<commit_msg>Nuke unnecessary require()s<commit_after>// activity-test.js
//
// Test the AS2 conversion module
//
// Copyright 2017 AJ Jordan <alex@strugee.net>
//
// 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.

"use strict";

var assert = require("assert"),
    vows = require("vows");

var suite = vows.describe("AS2 conversion module interface");

suite.addBatch({
    "When we get the AS2 conversion module": {
        topic: function() {
            return require("../lib/as2");
        },
        "it works": function(err, as2) {
            assert.ifError(err);
            assert.isFunction(as2);
        },
        "and we try to convert a post of a note to AS2": {
            topic: function(as2) {
                var act = {
                    id: "urn:uuid:77451568-ce6a-42eb-8a9f-60ece187725f",
                    actor: "acct:tim@w3.example",
                    verb: "post",
                    title: "I am a title!",
                    upstreamDuplicates: [],
                    downstreamDuplicates: [],
                    to: [{objectType: "collection",
                          id: "http://w3.example/socialwg"}],
                    object: {
                        id: "urn:uuid:33166eb9-2567-477c-ad90-9352dd904712",
                        objectType: "note"
                    },
                    displayName: "A note"
                };

                return as2(act);
            },
            "it has an @context": function(act) {
                assert.isTrue(act.hasOwnProperty("@context"));
                assert.equal(act["@context"], "https://www.w3.org/ns/activitystreams");
            },
            "id is renamed to @id": function(act) {
                assert.isFalse(act.hasOwnProperty("id"));
                assert.equal(act["@id"], "urn:uuid:77451568-ce6a-42eb-8a9f-60ece187725f");
            },
            "the `post` verb is converted specially to Create in @type": function(act) {
                assert.isFalse(act.hasOwnProperty("verb"));
                assert.equal(act["@type"], "Create");
            },
            "the object's objectType is renamed to @type": function(act) {
                assert.isFalse(act.object.hasOwnProperty("objectType"));
                assert.equal(act.object["@type"], "note");
            },
            "the `to` field's collection's objectType is renamed to @type": function(act) {
                assert.isFalse(act.to[0].hasOwnProperty("objectType"));
                assert.equal(act.to[0]["@type"], "collection");
            },
            // XXX should we test for all objectType renames? E.g. in the `to` field?
            "displayName is renamed to name": function(act) {
                assert.isFalse(act.hasOwnProperty("displayName"));
                assert.equal(act.name, "A note");
            },
            "title is dropped": function(act) {
                assert.isFalse(act.hasOwnProperty("title"));
            },
            "upstreamDuplicates is dropped": function(act) {
                assert.isFalse(act.hasOwnProperty("upstreamDuplicates"));
            },
            "downstreamDuplicates is dropped": function(act) {
                assert.isFalse(act.hasOwnProperty("downstreamDuplicates"));
            }
        },
        "and we try to convert a post of a note to a collection to AS2": {
            topic: function(as2) {
                var act = {
                    id: "urn:uuid:3738fceb-9705-4fa8-a0d3-59852770dc4d",
                    actor: "acct:chris@w3.example",
                    verb: "post",
                    title: "A rando note I want to categorize",
                    to: [{objectType: "collection",
                          id: "http://w3.example/socialwg"}],
                    object: {
                        id: "urn:uuid:33166eb9-2567-477c-ad90-9352dd904712",
                        objectType: "note"
                    },
                    target: {
                        id: "http://w3.example/chris/a-collection",
                        objectType: "collection"
                    }
                };

                return as2(act);
            },
            "the `post` verb is converted specially to Add in @type": function(act) {
                assert.isFalse(act.hasOwnProperty("verb"));
                assert.equal(act["@type"], "Add");
            }
        },
        "and we try to convert a like of a note to AS2": {
            topic: function(as2) {
                var act = {
                    id: "urn:uuid:db8b4174-a321-430f-bbbe-e11c65dd48ee",
                    actor: "acct:aj@w3.example",
                    verb: "like",
                    to: [{objectType: "collection",
                          id: "http://w3.example/socialwg"}],
                    // TODO check that this syntax is correct
                    target: {
                        id: "urn:uuid:77451568-ce6a-42eb-8a9f-60ece187725f",
                        objectType: "note"
                    }
                };

                return as2(act);
            },
            "verb is renamed to @type": function(act) {
                assert.isFalse(act.hasOwnProperty("verb"));
                assert.equal(act["@type"], "like");
            },
            "the target's objectType is renamed to @type": function(act) {
                assert.isFalse(act.target.hasOwnProperty("objectType"));
                assert.equal(act.target["@type"], "note");
            }
        }
    }
});

suite["export"](module);
<|endoftext|>


<commit_before>#!/usr/bin/env node
// @ts-check

const { stdin } = process;

// From https://github.com/sindresorhus/get-stdin/blob/main/index.js
async function getStdin() {
  let result = "";

  if (stdin.isTTY) {
    return result;
  }

  stdin.setEncoding("utf8");

  for await (const chunk of stdin) {
    result += chunk;
  }

  return result;
}

async function main() {
  const stdinData = await getStdin();
  console.log("stdin:", stdinData);

  /**
   * @type string[]
   **/
  const filesChanged = JSON.parse(stdinData);
  console.log("filesChanged:", filesChanged);

  const jobsToRun = [];

  // If changes are located only in one of the paths below
  if (
    filesChanged.every((fileChanged) =>
      fileChanged.startsWith("src/packages/cli")
    )
  ) {
    jobsToRun.push("-cli-");
  } else if (
    filesChanged.every((fileChanged) =>
      fileChanged.startsWith("src/packages/client")
    )
  ) {
    jobsToRun.push("-client-");
    jobsToRun.push("-integration-tests-");
    jobsToRun.push("-cli-");
  } else if (
    filesChanged.every((fileChanged) =>
      fileChanged.startsWith("src/packages/integration-tests")
    )
  ) {
    jobsToRun.push("-integration-tests-");
  } else if (
    filesChanged.every((fileChanged) =>
      fileChanged.startsWith("src/packages/migrate")
    )
  ) {
    jobsToRun.push("-migrate-");
    jobsToRun.push("-cli-");
  } else {
    jobsToRun.push("-all-");
  }

  console.log("jobsToRun:", jobsToRun);
  console.log("::set-output name=jobs::" + jobsToRun.join());
}

main().then(function () {
  console.log("Done");
});
<commit_msg>ci: fix detect-jobs-to-run (#7616)<commit_after>#!/usr/bin/env node
// @ts-check

const { stdin } = process;

// From https://github.com/sindresorhus/get-stdin/blob/main/index.js
async function getStdin() {
  let result = "";

  if (stdin.isTTY) {
    return result;
  }

  stdin.setEncoding("utf8");

  for await (const chunk of stdin) {
    result += chunk;
  }

  return result;
}

async function main() {
  const stdinData = await getStdin();
  console.log("stdin:", stdinData);

  /**
   * @type string[]
   **/
  const filesChanged = JSON.parse(stdinData);
  console.log("filesChanged:", filesChanged);

  const jobsToRun = [];

  // If changes are located only in one of the paths below
  if (
    filesChanged.every((fileChanged) =>
      fileChanged.startsWith("src/packages/cli/")
    )
  ) {
    jobsToRun.push("-cli-");
  } else if (
    filesChanged.every((fileChanged) =>
      fileChanged.startsWith("src/packages/client/")
    )
  ) {
    jobsToRun.push("-client-");
    jobsToRun.push("-integration-tests-");
    jobsToRun.push("-cli-");
  } else if (
    filesChanged.every((fileChanged) =>
      fileChanged.startsWith("src/packages/integration-tests/")
    )
  ) {
    jobsToRun.push("-integration-tests-");
  } else if (
    filesChanged.every((fileChanged) =>
      fileChanged.startsWith("src/packages/migrate/")
    )
  ) {
    jobsToRun.push("-migrate-");
    jobsToRun.push("-cli-");
  } else {
    jobsToRun.push("-all-");
  }

  console.log("jobsToRun:", jobsToRun);
  console.log("::set-output name=jobs::" + jobsToRun.join());
}

main().then(function () {
  console.log("Done");
});
<|endoftext|>


<commit_before>import Hunk from './hunk';
import {toGitPathSep} from '../helpers';

class File {
  constructor({path, mode, symlink}) {
    this.path = path;
    this.mode = mode;
    this.symlink = symlink;
  }

  getPath() {
    return this.path;
  }

  getMode() {
    return this.mode;
  }

  getSymlink() {
    return this.symlink;
  }

  clone(opts = {}) {
    return new File({
      path: opts.path !== undefined ? opts.path : this.path,
      mode: opts.mode !== undefined ? opts.mode : this.mode,
      symlink: opts.symlink !== undefined ? opts.symlink : this.symlink,
    });
  }
}

class Patch {
  constructor({status, hunks}) {
    this.status = status;
    this.hunks = hunks;
  }

  getStatus() {
    return this.status;
  }

  getHunks() {
    return this.hunks;
  }

  clone(opts = {}) {
    return new Patch({
      status: opts.status !== undefined ? opts.status : this.status,
      hunks: opts.hunks !== undefined ? opts.hunks : this.hunks,
    });
  }
}

export default class FilePatch {
  static File = File;
  static Patch = Patch;

  constructor(oldFile, newFile, patch) {
    this.oldFile = oldFile;
    this.newFile = newFile;
    this.patch = patch;

    this.changedLineCount = this.getHunks().reduce((acc, hunk) => {
      return acc + hunk.getLines().filter(line => line.isChanged()).length;
    }, 0);
  }

  clone(opts = {}) {
    const oldFile = opts.oldFile !== undefined ? opts.oldFile : this.getOldFile();
    const newFile = opts.newFile !== undefined ? opts.newFile : this.getNewFile();
    const patch = opts.patch !== undefined ? opts.patch : this.patch;
    return new FilePatch(oldFile, newFile, patch);
  }

  getOldFile() {
    return this.oldFile;
  }

  getNewFile() {
    return this.newFile;
  }

  getPatch() {
    return this.patch;
  }

  getOldPath() {
    return this.getOldFile().getPath();
  }

  getNewPath() {
    return this.getNewFile().getPath();
  }

  getOldMode() {
    return this.getOldFile().getMode();
  }

  getNewMode() {
    return this.getNewFile().getMode();
  }

  didModeChange() {
    // Not a mode change if it's a creation or deletion
    const oldMode = this.getOldMode();
    const newMode = this.getNewMode();
    return oldMode && newMode && oldMode !== newMode;
  }

  getSymlink() {
    return this.getOldFile().getSymlink() || this.getNewFile().getSymlink();
  }

  getPath() {
    return this.getOldPath() || this.getNewPath();
  }

  getStatus() {
    return this.getPatch().getStatus();
  }

  getHunks() {
    return this.getPatch().getHunks();
  }

  getStagePatchForHunk(selectedHunk) {
    return this.getStagePatchForLines(new Set(selectedHunk.getLines()));
  }

  getStagePatchForLines(selectedLines) {
    if (this.changedLineCount === [...selectedLines].filter(line => line.isChanged()).length) {
      return this;
    }

    let delta = 0;
    const hunks = [];
    for (const hunk of this.getHunks()) {
      const newStartRow = (hunk.getNewStartRow() || 1) + delta;
      let newLineNumber = newStartRow;
      const lines = [];
      let hunkContainsSelectedLines = false;
      for (const line of hunk.getLines()) {
        if (line.getStatus() === 'nonewline') {
          lines.push(line.copy({oldLineNumber: -1, newLineNumber: -1}));
        } else if (selectedLines.has(line)) {
          hunkContainsSelectedLines = true;
          if (line.getStatus() === 'deleted') {
            lines.push(line.copy());
          } else {
            lines.push(line.copy({newLineNumber: newLineNumber++}));
          }
        } else if (line.getStatus() === 'deleted') {
          lines.push(line.copy({newLineNumber: newLineNumber++, status: 'unchanged'}));
        } else if (line.getStatus() === 'unchanged') {
          lines.push(line.copy({newLineNumber: newLineNumber++}));
        }
      }
      const newRowCount = newLineNumber - newStartRow;
      if (hunkContainsSelectedLines) {
        // eslint-disable-next-line max-len
        hunks.push(new Hunk(hunk.getOldStartRow(), newStartRow, hunk.getOldRowCount(), newRowCount, hunk.getSectionHeading(), lines));
      }
      delta += newRowCount - hunk.getNewRowCount();
    }

    return this.clone({
      newFile: this.getNewPath() ? this.getNewFile() : this.getOldFile(),
      patch: this.getPatch().clone({hunks}),
    });
  }

  getUnstagePatch() {
    let invertedStatus;
    switch (this.getStatus()) {
      case 'modified':
        invertedStatus = 'modified';
        break;
      case 'added':
        invertedStatus = 'deleted';
        break;
      case 'deleted':
        invertedStatus = 'added';
        break;
      default:
        throw new Error(`Unknown Status: ${this.getStatus()}`);
    }
    const invertedHunks = this.getHunks().map(h => h.invert());
    return this.clone({
      oldFile: this.getNewFile(),
      newFile: this.getOldFile(),
      patch: this.getPatch().clone({
        status: invertedStatus,
        hunks: invertedHunks,
      }),
    });
  }

  getUnstagePatchForHunk(hunk) {
    return this.getUnstagePatchForLines(new Set(hunk.getLines()));
  }

  getUnstagePatchForLines(selectedLines) {
    if (this.changedLineCount === [...selectedLines].filter(line => line.isChanged()).length) {
      return this.getUnstagePatch();
    }

    let delta = 0;
    const hunks = [];
    for (const hunk of this.getHunks()) {
      const oldStartRow = (hunk.getOldStartRow() || 1) + delta;
      let oldLineNumber = oldStartRow;
      const lines = [];
      let hunkContainsSelectedLines = false;
      for (const line of hunk.getLines()) {
        if (line.getStatus() === 'nonewline') {
          lines.push(line.copy({oldLineNumber: -1, newLineNumber: -1}));
        } else if (selectedLines.has(line)) {
          hunkContainsSelectedLines = true;
          if (line.getStatus() === 'added') {
            lines.push(line.copy());
          } else {
            lines.push(line.copy({oldLineNumber: oldLineNumber++}));
          }
        } else if (line.getStatus() === 'added') {
          lines.push(line.copy({oldLineNumber: oldLineNumber++, status: 'unchanged'}));
        } else if (line.getStatus() === 'unchanged') {
          lines.push(line.copy({oldLineNumber: oldLineNumber++}));
        }
      }
      const oldRowCount = oldLineNumber - oldStartRow;
      if (hunkContainsSelectedLines) {
        // eslint-disable-next-line max-len
        hunks.push(new Hunk(oldStartRow, hunk.getNewStartRow(), oldRowCount, hunk.getNewRowCount(), hunk.getSectionHeading(), lines));
      }
      delta += oldRowCount - hunk.getOldRowCount();
    }

    return this.clone({
      oldFile: this.getOldPath() ? this.getOldFile() : this.getNewFile(),
      patch: this.getPatch().clone({hunks}),
    }).getUnstagePatch();
  }

  toString() {
    return this.getHunks().map(h => h.toString()).join('');
  }

  getHeaderString() {
    let header = this.getOldPath() ? `--- a/${toGitPathSep(this.getOldPath())}` : '--- /dev/null';
    header += '\n';
    header += this.getNewPath() ? `+++ b/${toGitPathSep(this.getNewPath())}` : '+++ /dev/null';
    header += '\n';
    return header;
  }
}
<commit_msg>Add FilePatch#didChangeExecutableMode and FilePatch#didChangeSymlinkMode<commit_after>import Hunk from './hunk';
import {toGitPathSep} from '../helpers';

class File {
  constructor({path, mode, symlink}) {
    this.path = path;
    this.mode = mode;
    this.symlink = symlink;
  }

  getPath() {
    return this.path;
  }

  getMode() {
    return this.mode;
  }

  getSymlink() {
    return this.symlink;
  }

  clone(opts = {}) {
    return new File({
      path: opts.path !== undefined ? opts.path : this.path,
      mode: opts.mode !== undefined ? opts.mode : this.mode,
      symlink: opts.symlink !== undefined ? opts.symlink : this.symlink,
    });
  }
}

class Patch {
  constructor({status, hunks}) {
    this.status = status;
    this.hunks = hunks;
  }

  getStatus() {
    return this.status;
  }

  getHunks() {
    return this.hunks;
  }

  clone(opts = {}) {
    return new Patch({
      status: opts.status !== undefined ? opts.status : this.status,
      hunks: opts.hunks !== undefined ? opts.hunks : this.hunks,
    });
  }
}

export default class FilePatch {
  static File = File;
  static Patch = Patch;

  constructor(oldFile, newFile, patch) {
    this.oldFile = oldFile;
    this.newFile = newFile;
    this.patch = patch;

    this.changedLineCount = this.getHunks().reduce((acc, hunk) => {
      return acc + hunk.getLines().filter(line => line.isChanged()).length;
    }, 0);
  }

  clone(opts = {}) {
    const oldFile = opts.oldFile !== undefined ? opts.oldFile : this.getOldFile();
    const newFile = opts.newFile !== undefined ? opts.newFile : this.getNewFile();
    const patch = opts.patch !== undefined ? opts.patch : this.patch;
    return new FilePatch(oldFile, newFile, patch);
  }

  getOldFile() {
    return this.oldFile;
  }

  getNewFile() {
    return this.newFile;
  }

  getPatch() {
    return this.patch;
  }

  getOldPath() {
    return this.getOldFile().getPath();
  }

  getNewPath() {
    return this.getNewFile().getPath();
  }

  getOldMode() {
    return this.getOldFile().getMode();
  }

  getNewMode() {
    return this.getNewFile().getMode();
  }

  didChangeExecutableMode() {
    const oldMode = this.getOldMode();
    const newMode = this.getNewMode();
    return oldMode === '100755' && newMode !== '100755' ||
      oldMode !== '100755' && newMode === '100755';
  }

  didChangeSymlinkMode() {
    const oldMode = this.getOldMode();
    const newMode = this.getNewMode();
    return oldMode === '120000' && newMode !== '120000' ||
      oldMode !== '120000' && newMode === '120000';
  }

  hasSymlink() {
    return this.getOldFile().getSymlink() || this.getNewFile().getSymlink();
  }

  getPath() {
    return this.getOldPath() || this.getNewPath();
  }

  getStatus() {
    return this.getPatch().getStatus();
  }

  getHunks() {
    return this.getPatch().getHunks();
  }

  getStagePatchForHunk(selectedHunk) {
    return this.getStagePatchForLines(new Set(selectedHunk.getLines()));
  }

  getStagePatchForLines(selectedLines) {
    if (this.changedLineCount === [...selectedLines].filter(line => line.isChanged()).length) {
      return this;
    }

    let delta = 0;
    const hunks = [];
    for (const hunk of this.getHunks()) {
      const newStartRow = (hunk.getNewStartRow() || 1) + delta;
      let newLineNumber = newStartRow;
      const lines = [];
      let hunkContainsSelectedLines = false;
      for (const line of hunk.getLines()) {
        if (line.getStatus() === 'nonewline') {
          lines.push(line.copy({oldLineNumber: -1, newLineNumber: -1}));
        } else if (selectedLines.has(line)) {
          hunkContainsSelectedLines = true;
          if (line.getStatus() === 'deleted') {
            lines.push(line.copy());
          } else {
            lines.push(line.copy({newLineNumber: newLineNumber++}));
          }
        } else if (line.getStatus() === 'deleted') {
          lines.push(line.copy({newLineNumber: newLineNumber++, status: 'unchanged'}));
        } else if (line.getStatus() === 'unchanged') {
          lines.push(line.copy({newLineNumber: newLineNumber++}));
        }
      }
      const newRowCount = newLineNumber - newStartRow;
      if (hunkContainsSelectedLines) {
        // eslint-disable-next-line max-len
        hunks.push(new Hunk(hunk.getOldStartRow(), newStartRow, hunk.getOldRowCount(), newRowCount, hunk.getSectionHeading(), lines));
      }
      delta += newRowCount - hunk.getNewRowCount();
    }

    return this.clone({
      newFile: this.getNewPath() ? this.getNewFile() : this.getOldFile(),
      patch: this.getPatch().clone({hunks}),
    });
  }

  getUnstagePatch() {
    let invertedStatus;
    switch (this.getStatus()) {
      case 'modified':
        invertedStatus = 'modified';
        break;
      case 'added':
        invertedStatus = 'deleted';
        break;
      case 'deleted':
        invertedStatus = 'added';
        break;
      default:
        throw new Error(`Unknown Status: ${this.getStatus()}`);
    }
    const invertedHunks = this.getHunks().map(h => h.invert());
    return this.clone({
      oldFile: this.getNewFile(),
      newFile: this.getOldFile(),
      patch: this.getPatch().clone({
        status: invertedStatus,
        hunks: invertedHunks,
      }),
    });
  }

  getUnstagePatchForHunk(hunk) {
    return this.getUnstagePatchForLines(new Set(hunk.getLines()));
  }

  getUnstagePatchForLines(selectedLines) {
    if (this.changedLineCount === [...selectedLines].filter(line => line.isChanged()).length) {
      return this.getUnstagePatch();
    }

    let delta = 0;
    const hunks = [];
    for (const hunk of this.getHunks()) {
      const oldStartRow = (hunk.getOldStartRow() || 1) + delta;
      let oldLineNumber = oldStartRow;
      const lines = [];
      let hunkContainsSelectedLines = false;
      for (const line of hunk.getLines()) {
        if (line.getStatus() === 'nonewline') {
          lines.push(line.copy({oldLineNumber: -1, newLineNumber: -1}));
        } else if (selectedLines.has(line)) {
          hunkContainsSelectedLines = true;
          if (line.getStatus() === 'added') {
            lines.push(line.copy());
          } else {
            lines.push(line.copy({oldLineNumber: oldLineNumber++}));
          }
        } else if (line.getStatus() === 'added') {
          lines.push(line.copy({oldLineNumber: oldLineNumber++, status: 'unchanged'}));
        } else if (line.getStatus() === 'unchanged') {
          lines.push(line.copy({oldLineNumber: oldLineNumber++}));
        }
      }
      const oldRowCount = oldLineNumber - oldStartRow;
      if (hunkContainsSelectedLines) {
        // eslint-disable-next-line max-len
        hunks.push(new Hunk(oldStartRow, hunk.getNewStartRow(), oldRowCount, hunk.getNewRowCount(), hunk.getSectionHeading(), lines));
      }
      delta += oldRowCount - hunk.getOldRowCount();
    }

    return this.clone({
      oldFile: this.getOldPath() ? this.getOldFile() : this.getNewFile(),
      patch: this.getPatch().clone({hunks}),
    }).getUnstagePatch();
  }

  toString() {
    return this.getHunks().map(h => h.toString()).join('');
  }

  getHeaderString() {
    let header = this.getOldPath() ? `--- a/${toGitPathSep(this.getOldPath())}` : '--- /dev/null';
    header += '\n';
    header += this.getNewPath() ? `+++ b/${toGitPathSep(this.getNewPath())}` : '+++ /dev/null';
    header += '\n';
    return header;
  }
}
<|endoftext|>


<commit_before>/*
Copyright 2015, 2016 OpenMarket Ltd

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.
*/
"use strict";
/**
 * @module models/room-state
 */
var EventEmitter = require("events").EventEmitter;

var utils = require("../utils");
var RoomMember = require("./room-member");

/**
 * Construct room state.
 * @constructor
 * @param {string} roomId Required. The ID of the room which has this state.
 * @prop {Object.<string, RoomMember>} members The room member dictionary, keyed
 * on the user's ID.
 * @prop {Object.<string, Object.<string, MatrixEvent>>} events The state
 * events dictionary, keyed on the event type and then the state_key value.
 * @prop {string} paginationToken The pagination token for this state.
 */
function RoomState(roomId) {
    this.roomId = roomId;
    this.members = {
        // userId: RoomMember
    };
    this.events = {
        // eventType: { stateKey: MatrixEvent }
    };
    this.paginationToken = null;

    this._sentinels = {
        // userId: RoomMember
    };
    this._updateModifiedTime();
    this._displayNameToUserIds = {};
    this._userIdsToDisplayNames = {};
    this._tokenToInvite = {}; // 3pid invite state_key to m.room.member invite
}
utils.inherits(RoomState, EventEmitter);

/**
 * Get all RoomMembers in this room.
 * @return {Array<RoomMember>} A list of RoomMembers.
 */
RoomState.prototype.getMembers = function() {
    return utils.values(this.members);
};

/**
 * Get a room member by their user ID.
 * @param {string} userId The room member's user ID.
 * @return {RoomMember} The member or null if they do not exist.
 */
RoomState.prototype.getMember = function(userId) {
    return this.members[userId] || null;
};

/**
 * Get a room member whose properties will not change with this room state. You
 * typically want this if you want to attach a RoomMember to a MatrixEvent which
 * may no longer be represented correctly by Room.currentState or Room.oldState.
 * The term 'sentinel' refers to the fact that this RoomMember is an unchanging
 * guardian for state at this particular point in time.
 * @param {string} userId The room member's user ID.
 * @return {RoomMember} The member or null if they do not exist.
 */
RoomState.prototype.getSentinelMember = function(userId) {
    return this._sentinels[userId] || null;
};

/**
 * Get state events from the state of the room.
 * @param {string} eventType The event type of the state event.
 * @param {string} stateKey Optional. The state_key of the state event. If
 * this is undefined then all matching state events will be
 * returned.
 * @return {MatrixEvent[]|MatrixEvent} A list of events if state_key was
 * undefined, else a single event (or null if no match found).
 */
RoomState.prototype.getStateEvents = function(eventType, stateKey) {
    if (!this.events[eventType]) {
        // no match
        return stateKey === undefined ? [] : null;
    }
    if (stateKey === undefined) { // return all values
        return utils.values(this.events[eventType]);
    }
    var event = this.events[eventType][stateKey];
    return event ? event : null;
};

/**
 * Add an array of one or more state MatrixEvents, overwriting
 * any existing state with the same {type, stateKey} tuple. Will fire
 * "RoomState.events" for every event added. May fire "RoomState.members"
 * if there are m.room.member events.
 * @param {MatrixEvent[]} stateEvents a list of state events for this room.
 * @fires module:client~MatrixClient#event:"RoomState.members"
 * @fires module:client~MatrixClient#event:"RoomState.newMember"
 * @fires module:client~MatrixClient#event:"RoomState.events"
 */
RoomState.prototype.setStateEvents = function(stateEvents) {
    var self = this;
    this._updateModifiedTime();

    // update the core event dict
    utils.forEach(stateEvents, function(event) {
        if (event.getRoomId() !== self.roomId) { return; }
        if (!event.isState()) { return; }

        if (self.events[event.getType()] === undefined) {
            self.events[event.getType()] = {};
        }
        self.events[event.getType()][event.getStateKey()] = event;
        if (event.getType() === "m.room.member") {
            _updateDisplayNameCache(
                self, event.getStateKey(), event.getContent().displayname
            );
            _updateThirdPartyTokenCache(self, event);
        }
        self.emit("RoomState.events", event, self);
    });

    // update higher level data structures. This needs to be done AFTER the
    // core event dict as these structures may depend on other state events in
    // the given array (e.g. disambiguating display names in one go to do both
    // clashing names rather than progressively which only catches 1 of them).
    utils.forEach(stateEvents, function(event) {
        if (event.getRoomId() !== self.roomId) { return; }
        if (!event.isState()) { return; }

        if (event.getType() === "m.room.member") {
            var userId = event.getStateKey();
            var member = self.members[userId];
            if (!member) {
                member = new RoomMember(event.getRoomId(), userId);
                self.emit("RoomState.newMember", event, self, member);
            }
            // Add a new sentinel for this change. We apply the same
            // operations to both sentinel and member rather than deep copying
            // so we don't make assumptions about the properties of RoomMember
            // (e.g. and manage to break it because deep copying doesn't do
            // everything).
            var sentinel = new RoomMember(event.getRoomId(), userId);
            utils.forEach([member, sentinel], function(roomMember) {
                roomMember.setMembershipEvent(event, self);
                // this member may have a power level already, so set it.
                var pwrLvlEvent = self.getStateEvents("m.room.power_levels", "");
                if (pwrLvlEvent) {
                    roomMember.setPowerLevelEvent(pwrLvlEvent);
                }
            });

            self._sentinels[userId] = sentinel;
            self.members[userId] = member;
            self.emit("RoomState.members", event, self, member);
        }
        else if (event.getType() === "m.room.power_levels") {
            var members = utils.values(self.members);
            utils.forEach(members, function(member) {
                member.setPowerLevelEvent(event);
                self.emit("RoomState.members", event, self, member);
            });
        }
    });
};

/**
 * Set the current typing event for this room.
 * @param {MatrixEvent} event The typing event
 */
RoomState.prototype.setTypingEvent = function(event) {
    utils.forEach(utils.values(this.members), function(member) {
        member.setTypingEvent(event);
    });
};

/**
 * Get the m.room.member event which has the given third party invite token.
 *
 * @param {string} token The token
 * @return {?MatrixEvent} The m.room.member event or null
 */
RoomState.prototype.getInviteForThreePidToken = function(token) {
    return this._tokenToInvite[token] || null;
};

/**
 * Update the last modified time to the current time.
 */
RoomState.prototype._updateModifiedTime = function() {
    this._modified = Date.now();
};

/**
 * Get the timestamp when this room state was last updated. This timestamp is
 * updated when this object has received new state events.
 * @return {number} The timestamp
 */
RoomState.prototype.getLastModifiedTime = function() {
    return this._modified;
};

/**
 * Get user IDs with the specified display name.
 * @param {string} displayName The display name to get user IDs from.
 * @return {string[]} An array of user IDs or an empty array.
 */
RoomState.prototype.getUserIdsWithDisplayName = function(displayName) {
    return this._displayNameToUserIds[displayName] || [];
};

/**
 * Returns true if the given MatrixClient has permission to send a state
 * event of type `stateEventType` into this room.
 * @param {string} type The type of state events to test
 * @param {MatrixClient}  The client to test permission for
 * @return {boolean} true if the given client should be permitted to send
 *                        the given type of state event into this room,
 *                        according to the room's state.
 */
RoomState.prototype.mayClientSendStateEvent = function(stateEventType, cli) {
    if (cli.isGuest()) return false;
    return this.maySendStateEvent(stateEventType, cli.credentials.userId);
};

/**
 * Returns true if the given user ID has permission to send a state
 * event of type `stateEventType` into this room.
 * @param {string} type The type of state events to test
 * @param {string} userId The user ID of the user to test permission for
 * @return {boolean} true if the given user ID should be permitted to send
 *                        the given type of state event into this room,
 *                        according to the room's state.
 */
RoomState.prototype.maySendStateEvent = function(stateEventType, userId) {
    var member = this.getMember(userId);
    if (!member || member.membership == 'leave') { return false; }

    var power_levels_event = this.getStateEvents('m.room.power_levels', '');

    var power_levels;
    var events_levels = {};

    var default_user_level = 0;
    var user_levels = [];

    var state_default = 0;
    if (power_levels_event) {
        power_levels = power_levels_event.getContent();
        events_levels = power_levels.events || {};

        default_user_level = parseInt(power_levels.users_default || 0);
        user_levels = power_levels.users || {};

        if (power_levels.state_default !== undefined) {
            state_default = power_levels.state_default;
        } else {
            state_default = 50;
        }
    }

    var state_event_level = state_default;
    if (events_levels[stateEventType] !== undefined) {
        state_event_level = events_levels[stateEventType];
    }
    return member.powerLevel >= state_event_level;
};

/**
 * The RoomState class.
 */
module.exports = RoomState;

function _updateThirdPartyTokenCache(roomState, memberEvent) {
    if (!memberEvent.getContent().third_party_invite) {
        return;
    }
    var token = (memberEvent.getContent().third_party_invite.signed || {}).token;
    if (!token) {
        return;
    }
    var threePidInvite = roomState.getStateEvents(
        "m.room.third_party_invite", token
    );
    if (!threePidInvite) {
        return;
    }
    roomState._tokenToInvite[token] = memberEvent;
}

function _updateDisplayNameCache(roomState, userId, displayName) {
    var oldName = roomState._userIdsToDisplayNames[userId];
    delete roomState._userIdsToDisplayNames[userId];
    if (oldName) {
        // Remove the old name from the cache.
        // We clobber the user_id > name lookup but the name -> [user_id] lookup
        // means we need to remove that user ID from that array rather than nuking
        // the lot.
        var existingUserIds = roomState._displayNameToUserIds[oldName] || [];
        for (var i = 0; i < existingUserIds.length; i++) {
            if (existingUserIds[i] === userId) {
                // remove this user ID from this array
                existingUserIds.splice(i, 1);
                i--;
            }
        }
        roomState._displayNameToUserIds[oldName] = existingUserIds;
    }

    roomState._userIdsToDisplayNames[userId] = displayName;
    if (!roomState._displayNameToUserIds[displayName]) {
        roomState._displayNameToUserIds[displayName] = [];
    }
    roomState._displayNameToUserIds[displayName].push(userId);
}

/**
 * Fires whenever the event dictionary in room state is updated.
 * @event module:client~MatrixClient#"RoomState.events"
 * @param {MatrixEvent} event The matrix event which caused this event to fire.
 * @param {RoomState} state The room state whose RoomState.events dictionary
 * was updated.
 * @example
 * matrixClient.on("RoomState.events", function(event, state){
 *   var newStateEvent = event;
 * });
 */

/**
 * Fires whenever a member in the members dictionary is updated in any way.
 * @event module:client~MatrixClient#"RoomState.members"
 * @param {MatrixEvent} event The matrix event which caused this event to fire.
 * @param {RoomState} state The room state whose RoomState.members dictionary
 * was updated.
 * @param {RoomMember} member The room member that was updated.
 * @example
 * matrixClient.on("RoomState.members", function(event, state, member){
 *   var newMembershipState = member.membership;
 * });
 */

 /**
 * Fires whenever a member is added to the members dictionary. The RoomMember
 * will not be fully populated yet (e.g. no membership state).
 * @event module:client~MatrixClient#"RoomState.newMember"
 * @param {MatrixEvent} event The matrix event which caused this event to fire.
 * @param {RoomState} state The room state whose RoomState.members dictionary
 * was updated with a new entry.
 * @param {RoomMember} member The room member that was added.
 * @example
 * matrixClient.on("RoomState.newMember", function(event, state, member){
 *   // add event listeners on 'member'
 * });
 */
<commit_msg>fix lint<commit_after>/*
Copyright 2015, 2016 OpenMarket Ltd

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.
*/
"use strict";
/**
 * @module models/room-state
 */
var EventEmitter = require("events").EventEmitter;

var utils = require("../utils");
var RoomMember = require("./room-member");

/**
 * Construct room state.
 * @constructor
 * @param {string} roomId Required. The ID of the room which has this state.
 * @prop {Object.<string, RoomMember>} members The room member dictionary, keyed
 * on the user's ID.
 * @prop {Object.<string, Object.<string, MatrixEvent>>} events The state
 * events dictionary, keyed on the event type and then the state_key value.
 * @prop {string} paginationToken The pagination token for this state.
 */
function RoomState(roomId) {
    this.roomId = roomId;
    this.members = {
        // userId: RoomMember
    };
    this.events = {
        // eventType: { stateKey: MatrixEvent }
    };
    this.paginationToken = null;

    this._sentinels = {
        // userId: RoomMember
    };
    this._updateModifiedTime();
    this._displayNameToUserIds = {};
    this._userIdsToDisplayNames = {};
    this._tokenToInvite = {}; // 3pid invite state_key to m.room.member invite
}
utils.inherits(RoomState, EventEmitter);

/**
 * Get all RoomMembers in this room.
 * @return {Array<RoomMember>} A list of RoomMembers.
 */
RoomState.prototype.getMembers = function() {
    return utils.values(this.members);
};

/**
 * Get a room member by their user ID.
 * @param {string} userId The room member's user ID.
 * @return {RoomMember} The member or null if they do not exist.
 */
RoomState.prototype.getMember = function(userId) {
    return this.members[userId] || null;
};

/**
 * Get a room member whose properties will not change with this room state. You
 * typically want this if you want to attach a RoomMember to a MatrixEvent which
 * may no longer be represented correctly by Room.currentState or Room.oldState.
 * The term 'sentinel' refers to the fact that this RoomMember is an unchanging
 * guardian for state at this particular point in time.
 * @param {string} userId The room member's user ID.
 * @return {RoomMember} The member or null if they do not exist.
 */
RoomState.prototype.getSentinelMember = function(userId) {
    return this._sentinels[userId] || null;
};

/**
 * Get state events from the state of the room.
 * @param {string} eventType The event type of the state event.
 * @param {string} stateKey Optional. The state_key of the state event. If
 * this is undefined then all matching state events will be
 * returned.
 * @return {MatrixEvent[]|MatrixEvent} A list of events if state_key was
 * undefined, else a single event (or null if no match found).
 */
RoomState.prototype.getStateEvents = function(eventType, stateKey) {
    if (!this.events[eventType]) {
        // no match
        return stateKey === undefined ? [] : null;
    }
    if (stateKey === undefined) { // return all values
        return utils.values(this.events[eventType]);
    }
    var event = this.events[eventType][stateKey];
    return event ? event : null;
};

/**
 * Add an array of one or more state MatrixEvents, overwriting
 * any existing state with the same {type, stateKey} tuple. Will fire
 * "RoomState.events" for every event added. May fire "RoomState.members"
 * if there are m.room.member events.
 * @param {MatrixEvent[]} stateEvents a list of state events for this room.
 * @fires module:client~MatrixClient#event:"RoomState.members"
 * @fires module:client~MatrixClient#event:"RoomState.newMember"
 * @fires module:client~MatrixClient#event:"RoomState.events"
 */
RoomState.prototype.setStateEvents = function(stateEvents) {
    var self = this;
    this._updateModifiedTime();

    // update the core event dict
    utils.forEach(stateEvents, function(event) {
        if (event.getRoomId() !== self.roomId) { return; }
        if (!event.isState()) { return; }

        if (self.events[event.getType()] === undefined) {
            self.events[event.getType()] = {};
        }
        self.events[event.getType()][event.getStateKey()] = event;
        if (event.getType() === "m.room.member") {
            _updateDisplayNameCache(
                self, event.getStateKey(), event.getContent().displayname
            );
            _updateThirdPartyTokenCache(self, event);
        }
        self.emit("RoomState.events", event, self);
    });

    // update higher level data structures. This needs to be done AFTER the
    // core event dict as these structures may depend on other state events in
    // the given array (e.g. disambiguating display names in one go to do both
    // clashing names rather than progressively which only catches 1 of them).
    utils.forEach(stateEvents, function(event) {
        if (event.getRoomId() !== self.roomId) { return; }
        if (!event.isState()) { return; }

        if (event.getType() === "m.room.member") {
            var userId = event.getStateKey();
            var member = self.members[userId];
            if (!member) {
                member = new RoomMember(event.getRoomId(), userId);
                self.emit("RoomState.newMember", event, self, member);
            }
            // Add a new sentinel for this change. We apply the same
            // operations to both sentinel and member rather than deep copying
            // so we don't make assumptions about the properties of RoomMember
            // (e.g. and manage to break it because deep copying doesn't do
            // everything).
            var sentinel = new RoomMember(event.getRoomId(), userId);
            utils.forEach([member, sentinel], function(roomMember) {
                roomMember.setMembershipEvent(event, self);
                // this member may have a power level already, so set it.
                var pwrLvlEvent = self.getStateEvents("m.room.power_levels", "");
                if (pwrLvlEvent) {
                    roomMember.setPowerLevelEvent(pwrLvlEvent);
                }
            });

            self._sentinels[userId] = sentinel;
            self.members[userId] = member;
            self.emit("RoomState.members", event, self, member);
        }
        else if (event.getType() === "m.room.power_levels") {
            var members = utils.values(self.members);
            utils.forEach(members, function(member) {
                member.setPowerLevelEvent(event);
                self.emit("RoomState.members", event, self, member);
            });
        }
    });
};

/**
 * Set the current typing event for this room.
 * @param {MatrixEvent} event The typing event
 */
RoomState.prototype.setTypingEvent = function(event) {
    utils.forEach(utils.values(this.members), function(member) {
        member.setTypingEvent(event);
    });
};

/**
 * Get the m.room.member event which has the given third party invite token.
 *
 * @param {string} token The token
 * @return {?MatrixEvent} The m.room.member event or null
 */
RoomState.prototype.getInviteForThreePidToken = function(token) {
    return this._tokenToInvite[token] || null;
};

/**
 * Update the last modified time to the current time.
 */
RoomState.prototype._updateModifiedTime = function() {
    this._modified = Date.now();
};

/**
 * Get the timestamp when this room state was last updated. This timestamp is
 * updated when this object has received new state events.
 * @return {number} The timestamp
 */
RoomState.prototype.getLastModifiedTime = function() {
    return this._modified;
};

/**
 * Get user IDs with the specified display name.
 * @param {string} displayName The display name to get user IDs from.
 * @return {string[]} An array of user IDs or an empty array.
 */
RoomState.prototype.getUserIdsWithDisplayName = function(displayName) {
    return this._displayNameToUserIds[displayName] || [];
};

/**
 * Returns true if the given MatrixClient has permission to send a state
 * event of type `stateEventType` into this room.
 * @param {string} type The type of state events to test
 * @param {MatrixClient}  The client to test permission for
 * @return {boolean} true if the given client should be permitted to send
 *                        the given type of state event into this room,
 *                        according to the room's state.
 */
RoomState.prototype.mayClientSendStateEvent = function(stateEventType, cli) {
    if (cli.isGuest()) {
        return false;
    }
    return this.maySendStateEvent(stateEventType, cli.credentials.userId);
};

/**
 * Returns true if the given user ID has permission to send a state
 * event of type `stateEventType` into this room.
 * @param {string} type The type of state events to test
 * @param {string} userId The user ID of the user to test permission for
 * @return {boolean} true if the given user ID should be permitted to send
 *                        the given type of state event into this room,
 *                        according to the room's state.
 */
RoomState.prototype.maySendStateEvent = function(stateEventType, userId) {
    var member = this.getMember(userId);
    if (!member || member.membership == 'leave') { return false; }

    var power_levels_event = this.getStateEvents('m.room.power_levels', '');

    var power_levels;
    var events_levels = {};

    var default_user_level = 0;
    var user_levels = [];

    var state_default = 0;
    if (power_levels_event) {
        power_levels = power_levels_event.getContent();
        events_levels = power_levels.events || {};

        default_user_level = parseInt(power_levels.users_default || 0);
        user_levels = power_levels.users || {};

        if (power_levels.state_default !== undefined) {
            state_default = power_levels.state_default;
        } else {
            state_default = 50;
        }
    }

    var state_event_level = state_default;
    if (events_levels[stateEventType] !== undefined) {
        state_event_level = events_levels[stateEventType];
    }
    return member.powerLevel >= state_event_level;
};

/**
 * The RoomState class.
 */
module.exports = RoomState;

function _updateThirdPartyTokenCache(roomState, memberEvent) {
    if (!memberEvent.getContent().third_party_invite) {
        return;
    }
    var token = (memberEvent.getContent().third_party_invite.signed || {}).token;
    if (!token) {
        return;
    }
    var threePidInvite = roomState.getStateEvents(
        "m.room.third_party_invite", token
    );
    if (!threePidInvite) {
        return;
    }
    roomState._tokenToInvite[token] = memberEvent;
}

function _updateDisplayNameCache(roomState, userId, displayName) {
    var oldName = roomState._userIdsToDisplayNames[userId];
    delete roomState._userIdsToDisplayNames[userId];
    if (oldName) {
        // Remove the old name from the cache.
        // We clobber the user_id > name lookup but the name -> [user_id] lookup
        // means we need to remove that user ID from that array rather than nuking
        // the lot.
        var existingUserIds = roomState._displayNameToUserIds[oldName] || [];
        for (var i = 0; i < existingUserIds.length; i++) {
            if (existingUserIds[i] === userId) {
                // remove this user ID from this array
                existingUserIds.splice(i, 1);
                i--;
            }
        }
        roomState._displayNameToUserIds[oldName] = existingUserIds;
    }

    roomState._userIdsToDisplayNames[userId] = displayName;
    if (!roomState._displayNameToUserIds[displayName]) {
        roomState._displayNameToUserIds[displayName] = [];
    }
    roomState._displayNameToUserIds[displayName].push(userId);
}

/**
 * Fires whenever the event dictionary in room state is updated.
 * @event module:client~MatrixClient#"RoomState.events"
 * @param {MatrixEvent} event The matrix event which caused this event to fire.
 * @param {RoomState} state The room state whose RoomState.events dictionary
 * was updated.
 * @example
 * matrixClient.on("RoomState.events", function(event, state){
 *   var newStateEvent = event;
 * });
 */

/**
 * Fires whenever a member in the members dictionary is updated in any way.
 * @event module:client~MatrixClient#"RoomState.members"
 * @param {MatrixEvent} event The matrix event which caused this event to fire.
 * @param {RoomState} state The room state whose RoomState.members dictionary
 * was updated.
 * @param {RoomMember} member The room member that was updated.
 * @example
 * matrixClient.on("RoomState.members", function(event, state, member){
 *   var newMembershipState = member.membership;
 * });
 */

 /**
 * Fires whenever a member is added to the members dictionary. The RoomMember
 * will not be fully populated yet (e.g. no membership state).
 * @event module:client~MatrixClient#"RoomState.newMember"
 * @param {MatrixEvent} event The matrix event which caused this event to fire.
 * @param {RoomState} state The room state whose RoomState.members dictionary
 * was updated with a new entry.
 * @param {RoomMember} member The room member that was added.
 * @example
 * matrixClient.on("RoomState.newMember", function(event, state, member){
 *   // add event listeners on 'member'
 * });
 */
<|endoftext|>


<commit_before>'use strict';

var vows = require('vows'),
	assert = require('assert'),
	bot = require('../lib/bot');

vows.describe('bot class').addBatch({
	'supports config object': {
		topic: function() {
			return new bot({
				server: 'pl.wikipedia.org',
				path: '/w'
			});
		},
		'server is properly passed': function(client) {
			assert.equal(client.api.server, 'pl.wikipedia.org');
		},
		'path is properly passed': function(client) {
			assert.equal(client.api.path, '/w');
		}
	},
	'supports config file': {
		topic: function() {
			return new bot(__dirname + '/config.json');
		},
		'server is properly passed': function(client) {
			assert.equal(client.api.server, 'pl.wikipedia.org');
		},
		'path is properly passed': function(client) {
			assert.equal(client.api.path, '/w');
		}
	},
	'getConfig()': {
		topic: function() {
			return new bot(__dirname + '/config.json');
		},
		'gets a correct value': function(client) {
			assert.equal(client.getConfig('server'), 'pl.wikipedia.org');
			assert.equal(client.getConfig('goo'), 123);
		},
		'gets a default value': function(client) {
			assert.isUndefined(client.getConfig('foo'));
			assert.equal(client.getConfig('foo', 'bar'), 'bar');
		}
	},
	'setConfig()': {
		topic: function() {
			return new bot(__dirname + '/config.json');
		},
		'sets a value': function(client) {
			assert.isUndefined(client.getConfig('foo'));

			client.setConfig('foo', 'bar');

			assert.equal(client.getConfig('foo'), 'bar');
			assert.equal(client.getConfig('foo', 123), 'bar');
		}
	},
	'user agent': {
		topic: function() {
			return new bot({
				userAgent: 'Custom UA'
			});
		},
		'can be customized': function(client) {
			assert.equal(client.api.userAgent, 'Custom UA');
		}
	},
	'dry run mode': {
		topic: function() {
			var client = new bot({
				server: 'pl.wikipedia.org',
				path: '/w',
				dryRun: true
			});

			client.edit('Page', 'Content', 'Summary', function(e) {
				this.callback(null, e);
			}.bind(this));
		},
		'is correctly handled by edit()': function(fake, err) {
			assert.isTrue(err instanceof Error);
			assert.equal('In dry-run mode', err.message);
		}
	}

}).export(module);
<commit_msg>Test client.diff<commit_after>'use strict';

var vows = require('vows'),
	assert = require('assert'),
	bot = require('../lib/bot');

vows.describe('bot class').addBatch({
	'supports config object': {
		topic: function() {
			return new bot({
				server: 'pl.wikipedia.org',
				path: '/w'
			});
		},
		'server is properly passed': function(client) {
			assert.equal(client.api.server, 'pl.wikipedia.org');
		},
		'path is properly passed': function(client) {
			assert.equal(client.api.path, '/w');
		}
	},
	'supports config file': {
		topic: function() {
			return new bot(__dirname + '/config.json');
		},
		'server is properly passed': function(client) {
			assert.equal(client.api.server, 'pl.wikipedia.org');
		},
		'path is properly passed': function(client) {
			assert.equal(client.api.path, '/w');
		}
	},
	'getConfig()': {
		topic: function() {
			return new bot(__dirname + '/config.json');
		},
		'gets a correct value': function(client) {
			assert.equal(client.getConfig('server'), 'pl.wikipedia.org');
			assert.equal(client.getConfig('goo'), 123);
		},
		'gets a default value': function(client) {
			assert.isUndefined(client.getConfig('foo'));
			assert.equal(client.getConfig('foo', 'bar'), 'bar');
		}
	},
	'setConfig()': {
		topic: function() {
			return new bot(__dirname + '/config.json');
		},
		'sets a value': function(client) {
			assert.isUndefined(client.getConfig('foo'));

			client.setConfig('foo', 'bar');

			assert.equal(client.getConfig('foo'), 'bar');
			assert.equal(client.getConfig('foo', 123), 'bar');
		}
	},
	'user agent': {
		topic: function() {
			return new bot({
				userAgent: 'Custom UA'
			});
		},
		'can be customized': function(client) {
			assert.equal(client.api.userAgent, 'Custom UA');
		}
	},
	'dry run mode': {
		topic: function() {
			var client = new bot({
				server: 'pl.wikipedia.org',
				path: '/w',
				dryRun: true
			});

			client.edit('Page', 'Content', 'Summary', function(e) {
				this.callback(null, e);
			}.bind(this));
		},
		'is correctly handled by edit()': function(fake, err) {
			assert.isTrue(err instanceof Error);
			assert.equal('In dry-run mode', err.message);
		}
	},
	'client.diff': {
		topic: function() {
			var client = new bot(__dirname + '/config.json'),
				prev = 'foo 123 bar',
				current = '[[foo]] bar';

			return client.diff(prev, current);
		},
		'is correctly generated': function(diff) {
			assert.equal(true, diff.indexOf('foo') > -1);
			assert.equal(true, diff.indexOf('bar') > -1);
		}
	}
}).export(module);
<|endoftext|>


<commit_before>var fs = require('fs');
var chalk = require('chalk');
var socket_io = require('socket.io');
var plugin = require('./plugin');
var files = require('./files');
var logMsg = require('./utils').logMsg;
var commands = {};
var socketEvents = [
  'error',
  'connect',
  'disconnect',
  'newListener',
  'removeListener'
];

function assemblyClientApiScript(clientApiScripts, result, fromCache, callback){
  if (!clientApiScripts.length)
    return callback(null, result, fromCache);

  var script = clientApiScripts[0];
  files.readFileIfNeeded(script.filename, function(err, filename, content){
    if (err)
      return callback('(' + filename + '): ' + err);

    result.push({
      filename: files.relativePath(filename),
      content: script.preprocess ? script.preprocess(content) : content
    });

    if (files.get(filename).content !== content)
      fromCache = false;

    assemblyClientApiScript(clientApiScripts.slice(1), result, fromCache, callback);
  });
}

function serveClientApi(httpApi, clientApiScripts){
  var mtime = new Date(Math.max.apply(null, clientApiScripts.map(function(script){
    return fs.statSync(script.filename).mtime;
  })));

  if (httpApi.isContentModified(mtime))
    assemblyClientApiScript(clientApiScripts, [], true, function(err, files, fromCache){
      if (err)
        return httpApi.responseError(500, 'Can\'t read file ' + err);

      // first file use as is - it's socket.io-client
      // second file is client source wrapper
      // others inject in special place of second one
      var content = files[0].content + files.slice(2).reduce(function(result, file){
        return result.replace(
          '\n  // <!--inject-->',
          function(m){ // use function to avoid special replacement patterns substitution
            return (
              '\n(function(){\n' +
                file.content +
                '\n//# sourceURL=/' + file.filename +
              '\n})();' +
              '\n' + m
            );
          }
        );
      }, files[1].content);

      httpApi.responseToClient(content, {
        contentType: 'application/javascript',
        mtime: mtime
      }, fromCache ? chalk.green('(from cache)') : chalk.yellow('(read)'));
    });
}

function addCommand(name, fn){
  if (commands.hasOwnProperty(name))
    throw new Error('WS command `' + name + '` is already in use');

  commands[name] = fn;
}

function getFeatures(socket){
  // EventEmmiter#eventNames implemented since node.js 6.0
  var events = socket.eventNames ? socket.eventNames() : Object.keys(socket._events);
  return events.filter(function(name){
    return socketEvents.indexOf(name) === -1;
  });
}

// extend plugin API
plugin.extendApi(function(api){
  api.addSocketCommand = addCommand;
});

module.exports = function createWsServer(httpServer, options){
  var wsServer = socket_io(httpServer, { serveClient: false });
  var clientCount = 0;
  var clientApiScripts = [
    { filename: require.resolve('socket.io-client/dist/socket.io.js') },
    { filename: require.resolve('./ws/client.js') }
  ];

  wsServer.hasClients = function(){
    return clientCount > 0;
  };

  wsServer.addCommand = addCommand;
  wsServer.addClientApi = function(filename, preprocess){
    clientApiScripts.push({
      filename: filename,
      preprocess: preprocess
    });
  };

  wsServer.on('connection', function(socket){
    logMsg('socket', 'client ' + chalk.yellow('connected') + ' ' + chalk.gray(socket.id));
    clientCount++;

    for (var name in commands)
      if (commands.hasOwnProperty(name))
        socket.on(name, commands[name]);

    socket
      .on('getAppProfile', require('./ws/getAppProfile')(options))
      .on('getBundle', require('./ws/getBundle')(options))
      .on('disconnect', function(){
        logMsg('socket', 'client ' + chalk.yellow('disconnected') + ' ' + chalk.gray(socket.id));
        clientCount--;
      });

    // socket.on('newListener', ...);
    socket.emit('features', getFeatures(socket));
  });

  // client-side API
  httpServer.addVirtualPath('/basisjs-tools/ws.js', function(httpApi){
    serveClientApi(httpApi, clientApiScripts);
  });

  return wsServer;
};
<commit_msg>use slim version of socket.io-client and fix script concat<commit_after>var fs = require('fs');
var chalk = require('chalk');
var socket_io = require('socket.io');
var plugin = require('./plugin');
var files = require('./files');
var logMsg = require('./utils').logMsg;
var commands = {};
var socketEvents = [
  'error',
  'connect',
  'disconnect',
  'newListener',
  'removeListener'
];

function assemblyClientApiScript(clientApiScripts, result, fromCache, callback){
  if (!clientApiScripts.length)
    return callback(null, result, fromCache);

  var script = clientApiScripts[0];
  files.readFileIfNeeded(script.filename, function(err, filename, content){
    if (err)
      return callback('(' + filename + '): ' + err);

    result.push({
      filename: files.relativePath(filename),
      content: script.preprocess ? script.preprocess(content) : content
    });

    if (files.get(filename).content !== content)
      fromCache = false;

    assemblyClientApiScript(clientApiScripts.slice(1), result, fromCache, callback);
  });
}

function serveClientApi(httpApi, clientApiScripts){
  var mtime = new Date(Math.max.apply(null, clientApiScripts.map(function(script){
    return fs.statSync(script.filename).mtime;
  })));

  if (httpApi.isContentModified(mtime))
    assemblyClientApiScript(clientApiScripts, [], true, function(err, files, fromCache){
      if (err)
        return httpApi.responseError(500, 'Can\'t read file ' + err);

      // first file use as is - it's socket.io-client
      // second file is client source wrapper
      // others inject in special place of second one
      var content = files[0].content + '\n' + files.slice(2).reduce(function(result, file){
        return result.replace(
          '\n  // <!--inject-->',
          function(m){ // use function to avoid special replacement patterns substitution
            return (
              '\n(function(){\n' +
                file.content +
                '\n//# sourceURL=/' + file.filename +
              '\n})();' +
              '\n' + m
            );
          }
        );
      }, files[1].content);

      httpApi.responseToClient(content, {
        contentType: 'application/javascript',
        mtime: mtime
      }, fromCache ? chalk.green('(from cache)') : chalk.yellow('(read)'));
    });
}

function addCommand(name, fn){
  if (commands.hasOwnProperty(name))
    throw new Error('WS command `' + name + '` is already in use');

  commands[name] = fn;
}

function getFeatures(socket){
  // EventEmmiter#eventNames implemented since node.js 6.0
  var events = socket.eventNames ? socket.eventNames() : Object.keys(socket._events);
  return events.filter(function(name){
    return socketEvents.indexOf(name) === -1;
  });
}

// extend plugin API
plugin.extendApi(function(api){
  api.addSocketCommand = addCommand;
});

module.exports = function createWsServer(httpServer, options){
  var wsServer = socket_io(httpServer, { serveClient: false });
  var clientCount = 0;
  var clientApiScripts = [
    { filename: require.resolve('socket.io-client/dist/socket.io.slim.min.js') },
    { filename: require.resolve('./ws/client.js') }
  ];

  wsServer.hasClients = function(){
    return clientCount > 0;
  };

  wsServer.addCommand = addCommand;
  wsServer.addClientApi = function(filename, preprocess){
    clientApiScripts.push({
      filename: filename,
      preprocess: preprocess
    });
  };

  wsServer.on('connection', function(socket){
    logMsg('socket', 'client ' + chalk.yellow('connected') + ' ' + chalk.gray(socket.id));
    clientCount++;

    for (var name in commands)
      if (commands.hasOwnProperty(name))
        socket.on(name, commands[name]);

    socket
      .on('getAppProfile', require('./ws/getAppProfile')(options))
      .on('getBundle', require('./ws/getBundle')(options))
      .on('disconnect', function(){
        logMsg('socket', 'client ' + chalk.yellow('disconnected') + ' ' + chalk.gray(socket.id));
        clientCount--;
      });

    // socket.on('newListener', ...);
    socket.emit('features', getFeatures(socket));
  });

  // client-side API
  httpServer.addVirtualPath('/basisjs-tools/ws.js', function(httpApi){
    serveClientApi(httpApi, clientApiScripts);
  });

  return wsServer;
};
<|endoftext|>


<commit_before>var mongoose = require('mongoose');

var Schema = mongoose.Schema;

var UserExtAccountSchema = new Schema({
  // ref User._id
  uid: { type: String, index: { unique: true } }

  , google: {
    id: { type: String }
    , accessToken: { type: String }
    , refreshToken: { type: String }
  }
  , amazon: {
    id: { type: String }
    , accessToken: { type: String }
    , refreshToken: { type: String }
  }
});

UserExtAccountSchema.index( { 'google.id': 1 }, { unique: true, sparse: true } );
UserExtAccountSchema.index( { 'amazon.id': 1 }, { unique: true, sparse: true } );

/*
 static function

 e.g) UserExtAccountSchema.load

*/

UserExtAccountSchema.statics = {

  load: function(options, callback) {
    options.select = options.select || 'uid';
    this.findOne(options.criteria)
      .select(options.select)
      .exec(callback);
  }
};

mongoose.model('UserExtAccount', UserExtAccountSchema);
<commit_msg>add static method UserExtAccount.linkExtAccount<commit_after>var mongoose = require('mongoose');

var Schema = mongoose.Schema;

var UserExtAccountSchema = new Schema({
  // ref User._id
  uid: { type: String, index: { unique: true } }

  , google: {
    id: { type: String }
    , accessToken: { type: String }
    , refreshToken: { type: String }
  }
  , amazon: {
    id: { type: String }
    , accessToken: { type: String }
    , refreshToken: { type: String }
  }
});

UserExtAccountSchema.index( { 'google.id': 1 }, { unique: true, sparse: true } );
UserExtAccountSchema.index( { 'amazon.id': 1 }, { unique: true, sparse: true } );

/*
 static function

 e.g) UserExtAccountSchema.load

*/

UserExtAccountSchema.statics = {

  load: function(options, callback) {
    options.select = options.select || 'uid';
    this.findOne(options.criteria)
      .select(options.select)
      .exec(callback);
  }

  ,linkExtAccount: function( uid, name, data, callback ) {

    var condition = {};
    condition['uid'] = uid;

    var update = {};
    update['$set'] = {};
    update['$set'][name] = data;

    //console.log('linkExtAccount');
    //console.log(update);

    this.update( condition, update, function(err,numberAffected,raw) {
      if (err) { console.log(err); }
      console.log('update numberAffected='+numberAffected);
      console.log(raw);
      return callback(err,numberAffected);
    });
  }

};

mongoose.model('UserExtAccount', UserExtAccountSchema);
<|endoftext|>