<commit_before>// @flow

import fs from 'fs';
import os from 'os';
import path from 'path';

import FileUtils from '../FileUtils';

import findPackageDependencies from '../findPackageDependencies';

const coreModules = [
  'meteor/accounts-base',
  'meteor/blaze',
  'meteor/check',
  'meteor/ddp-client',
  'meteor/ddp-rate-limiter',
  'meteor/ejson',
  'meteor/email',
  'meteor/http',
  'meteor/check',
  'meteor/meteor',
  'meteor/mongo',
  'meteor/random',
  'meteor/reactive-var',
  'meteor/session',
  'meteor/templating',
  'meteor/tracker',
];

const coreNamedExports = {
  'meteor/accounts-base': ['AccountsClient', 'Accounts', 'AccountsServer'],
  'meteor/blaze': ['Blaze'],
  'meteor/check': ['check', 'Match'],
  'meteor/ddp-client': ['DDP'],
  'meteor/ddp-rate-limiter': ['DDPRateLimiter'],
  'meteor/ejson': ['EJSON'],
  'meteor/email': ['Email'],
  'meteor/http': ['HTTP'],
  'meteor/meteor': ['Meteor'],
  'meteor/mongo': ['Mongo'],
  'meteor/random': ['Random'],
  'meteor/reactive-var': ['ReactiveVar'],
  'meteor/session': ['Session'],
  'meteor/templating': ['Template'],
  'meteor/tracker': ['Tracker'],
};

function meteorPackageDependencies(
  { config }: Object
): Array<string> {
  const meteorPackagesPath = path.join(config.workingDirectory, '.meteor/packages');

  if (!fs.existsSync(meteorPackagesPath)) {
    return [];
  }

  // Meteor is an "app" framework. As such, it has both apps and packages. When
  // working with a module that is part of an app, the list of Meteor packages
  // that a module may import is found in '.meteor/packages'. This file is
  // actually called a ProjectConstraintsFile in the meteor code.
  // The internal meteor routine that parses it may be found at
  // https://github.com/meteor/meteor/blob/f8b1bba6/tools/project-context.js#L841.
  //
  // After reverse engineering ProjectConstraintsFile.prototype._readfile at
  // that location, the following appears to be the pertinent facts for any
  // parser.
  //
  //  - the only true information within the file is a list of constraints
  //  - a constraint may not span a line
  //  - only one constraint may appear on a line
  //  - a constraint consists of a package name and an optional version
  //    constraint separated by the '@' symbol
  //  - white space may not appear within a constraint
  //  - the '#' symbol signifies that the rest of the line is a comment
  //  - all white space is ignored
  //  - package names
  //      - are allowed to contain [a-z0-9:.\-]
  //      - must have at least one lowercase letter
  //      - may not begin or end with a dot or colon
  //      - may not begin with a hyphen
  //      - may not contain two consecutive dots
  //
  // This routine is only interested in extracting the package names and has no
  // concern for precisely validating them. An assumption is made that the file
  // is basically valid. Thus, they may be extracted with a simple global,
  // multiline match of characters allowed to be in a package name that are at
  // the beginning of a line, possibly following white space.
  const packages: Array<string> = (fs.readFileSync(meteorPackagesPath, 'utf8')
    // extract an array of package names (possibly with preceding whitespace)
    // from the packages file
    .match(/^\s*[a-z0-9:.\-]+/gm) || [])
    // add 'meteor/' to the start of each name per Meteor convention
    .map((pkg: string): string => `meteor/${pkg.trimLeft()}`)
    // eliminate those packages that are considered to be core
    .filter((pkg: string): boolean => coreModules.indexOf(pkg) === -1);
  return packages;
}

function meteorPackageVersions(projectRootDir: string): ?Map<string, string> {
  // This function processes all of the package versions found in
  // .meteor/versions and returns a map containing them.

  const meteorVersionsPath = path.join(projectRootDir, '.meteor/versions');
  if (!fs.existsSync(meteorVersionsPath)) {
    // If we're even in an application directory, it must be broken. In any
    // case, we can't find the packages without their versions. Return null to
    // indicate that an issue occurred as opposed to processing an empty
    // .meteor/versions.
    return null;
  }

  const pkgVersions = new Map();
  const pkgVersionPairs: Array<string> = fs.readFileSync(meteorVersionsPath, 'utf8')
    .match(/^[^@\s]+@[^\s]+$/gm) || [];

  pkgVersionPairs.forEach((pkgVersionPair: string) => {
    const [pkg, version] = pkgVersionPair.split('@');
    pkgVersions.set(pkg, version);
  });

  return pkgVersions;
}

function extractExportsFromMeteorPackage(
  projectRootDir: string,
  pkg: string,
  pkgVersion: string
): ?Array<string> {
  // This function extracts the named exports from the package specified by pkg
  // and pkgVersion and returns them as an array of strings. If null is
  // returned, a problem was encountered in processing. If an empty array is
  // returned, no named exports were found.

  // Meteor packages are deployed as "isopacks". These can usually be found
  // within the meteor warehouse directory on the system at ~/.meteor/packages.
  // isopacks for local Meteor packages, i.e. those whose source is in the
  // <project-root>/packages directory, can usually be found in at
  // <project-root>/.meteor/local/isopacks.
  //
  // These isopacks do not exactly contain the original package source. In
  // particular, they are always missing a "package.js" file. This is important
  // to us because it is the one that specifies the interface. They do however
  // contain build products that resulted from that specification.
  //
  // Meteor is an isomorphic environment. An isopack contains multiple builds,
  // one for each targeted platform. Typically, this will include at least a
  // client and a server build.  An isopack contains an isopack.json file that
  // details which platforms are represented and leads us to other platform
  // specific isopack.json files that contain the interface details needed by
  // this routine.
  //
  // This routine will find all interfaces from all build platforms and create
  // namedExport entries for them.
  //
  // So, for example, if a project includes version 1.5.3 of package
  // "aldeed:simple-schema", the isopack for that package will usually be
  // located at
  //    ~/.meteor/packages/aldeed_simple-schema/1.5.3/
  // Within that directory, we will find the following important files
  //
  //    isopack.json
  //    os.json
  //    web.browser.json
  //    web.cordova.json
  //
  // isopack.json will point us to the others which all represent specific
  // platforms.
  //
  // There are two ways that meteor packages expose their exported interface,
  // pre-ES6 and post-ES6.
  //
  // The pre-ES6 modules method is to call api.exports within the package.js
  // file for each variable exported. These calls all result in declaredExports
  // entries in the individual platform's json files. Within
  // aldeed:simple-schema's web.browser.json for example, we find
  //
  //  "declaredExports": [
  //      {
  //        "name": "SimpleSchema",
  //        "testOnly": false
  //      },
  //      {
  //        "name": "MongoObject",
  //        "testOnly": false
  //      },
  //      {
  //        "name": "humanize",
  //        "testOnly": true
  //      }
  //    ],
  //
  // From this, we can determine that the namedExports entry should be
  //
  //   'aldeed:simple-schema': ['SimpleSchema', 'MongoObject']
  //
  // "humanize" was left off because it is testOnly. We may try to get smarter
  // and include that for some modules in the future.
  //
  // Post-ES6 modules, things become a bit more difficult. The package.js file
  // in a post-ES6 world specifies a mainModule. Each build will have no more
  // than one mainModule and they may be different. The mainModule's are denoted
  // in the same build json files that we process to find the pre-ES6
  // declaredExports. They are in the resources section which looks like
  //
  //   "resources": [
  //       {
  //         "type": "source",
  //         "extension": "js",
  //         "file": "web.browser/client_main.js",
  //         "length": 752,
  //         "offset": 0,
  //         "path": "client_main.js",
  //         "hash": "cf1eeaf24f21f7755a7cb3fe1e247d56b5e97acf",
  //         "fileOptions": {
  //           "mainModule": true
  //         }
  //       },
  //
  // We are interested only in the one resource that has fileOptions.mainModule
  // = true.
  //
  // The exports are defined by the export statements in the build's mainModule
  // file, "web.browser/client_main.js" in the above example. So, we'll have to
  // parse the file and identify the exports. If the file uses ES6 module export
  // syntax, we should be able to achieve this. If its using CommonJS, we may
  // have to punt and let the user define their own namedExports for that
  // package.

  // The isopack's for 3rd party packages are usually found at
  //   ~/.meteor/packages/<pkg>/<pkgVersion>
  // where pkg is the meteorPkg without the 'meteor/' prefix and colons are
  // replaced with underlines.
  let isopackRoot = path.join(os.homedir(), '.meteor/packages', pkg.replace(':', '_'), pkgVersion);
  let isopackPath = path.join(isopackRoot, 'isopack.json');
  let isopack = FileUtils.readJsonFile(isopackPath);

  if (!isopack) {
    // It is possible that this is a local package as opposed to a 3rd party
    // package. If so, it's isopack may be in
    // <project-root>/.meteor/isopacks/<pkg>.
    isopackRoot = path.join(
      projectRootDir, '.meteor', 'local', 'isopacks', pkg.replace(':', '_'));
    isopackPath = path.join(isopackRoot, 'isopack.json');
    isopack = FileUtils.readJsonFile(isopackPath);

    if (!isopack) {
      // Can't get anywhere without the main isopack.json.
      return null;
    }
  }

  // isopack.json often contains separate sections for every version of the
  // isopacks that has ever been made available. We're only interested in
  // isopack-2 or isopack-1 at this time. Prefer the newer isopack-2
  // specification if available
  const isopackVer = isopack['isopack-2'] || isopack['isopack-1'];
  if (!isopackVer || !isopackVer.builds) {
    // If we didn't find an isopack version we understand or documented builds
    // within it, we're done.
    return null;
  }

  // We can't guess which build the current module is being included in. So,
  // we'll find all declaredExports from all builds and combine them into one
  // namedExports specification.
  const declaredExports: Set<string> = new Set();

  isopackVer.builds.forEach((build: Object) => {
    const buildIsopackPath = path.join(isopackRoot, build.path);
    const buildIsopack = FileUtils.readJsonFile(buildIsopackPath);

    if (!buildIsopack || !buildIsopack.declaredExports) {
      // This build is missing, corrupted, or has no declaredExports. Try the
      // next one.
      return;
    }

    buildIsopack.declaredExports.forEach((declaredExport: Object) => {
      if (!declaredExport.testOnly) {
        declaredExports.add(declaredExport.name);
      }
    });

    // TODO: If the "resources" section of the buildIsopack specifies a
    // mainModule, we need to attempt to scan it to find exports.
  });

  return Array.from(declaredExports);
}

function meteorPackageNamedExports({ config }: Object): Object {
  // This function seeks to extract the named exports from all non-core, 3rd
  // party or local meteor packages being utilized in the application. The
  // meteorPackageDependencies function identifies that list of packages.

  // Retrieve the versions of all Meteor packages. Note that local packages
  // aren't absolutely required to have versions.
  const pkgVersions = meteorPackageVersions(config.workingDirectory) || new Map();

  // Try to identify the exports of all packages identified by
  // meteorPackageDependencies
  const namedExports = {};

  meteorPackageDependencies({ config }).forEach((meteorPkg: string) => {
    const pkg = meteorPkg.slice(7);
    const pkgVersion = pkgVersions.get(pkg) || '';
    const extractedExports = extractExportsFromMeteorPackage(
      config.workingDirectory, pkg, pkgVersion);

    // If we found declared exports, create a namedExports entry for them.
    if (extractedExports && extractedExports.length) {
      namedExports[meteorPkg] = extractedExports;
    }
  });

  return namedExports;
}

export default {
  coreModules,

  moduleNameFormatter({ moduleName, pathToImportedModule }: Object): string {
    // If the module being imported is a Meteor package, it will begin with
    // 'meteor/' and should not be altered.
    if (moduleName.startsWith('meteor/')) {
      return moduleName;
    }
    // If the module being imported is an npm package, the path to the module
    // will start with 'node_modules/' and the moduleName should not be altered.
    // not be altered.
    if (pathToImportedModule.startsWith('node_modules/')) {
      return moduleName;
    }
    // If the moduleName does not start with a '.', then import-js is trying to
    // reference it via an absolute path. In this case, Meteor wants it to start
    // with a '/' and will interpret it as relative to the project directory
    // root.
    if (!moduleName.startsWith('.')) {
      return `/${moduleName}`;
    }
    // Otherwise, return the moduleName unchanged.
    return moduleName;
  },

  moduleSideEffectImports({ pathToCurrentFile, config }: Object): Array<string> {
    let basePath = '';

    if (pathToCurrentFile.endsWith('.js')) {
      basePath = pathToCurrentFile.slice(0, -3);
    } else if (pathToCurrentFile.endsWith('.jsx')) {
      basePath = pathToCurrentFile.slice(0, -4);
    } else {
      return [];
    }

    const moduleSpecifiers = [];

    ['.html', '.css'].forEach((ext: string) => {
      const moduleSpecifier = `${basePath}${ext}`;
      if (fs.existsSync(path.join(config.workingDirectory, moduleSpecifier))) {
        if (config.get('useRelativePaths')) {
          moduleSpecifiers.push('.'.concat(path.sep, path.basename(moduleSpecifier)));
        } else {
          // Strip the leading '.' off of the moduleSpecifier to turn it into a
          // Meteor compliant absolute path.
          moduleSpecifiers.push(moduleSpecifier.slice(1));
        }
      }
    });

    return moduleSpecifiers;
  },

  namedExports({ config }: Object): Object {
    const allNamedExports = coreNamedExports;
    // There are no worries about this overwriting the definitions of core
    // namedExports. meteorPackageNamedExports skips core packages. Even if it
    // did not, it would presumably find correct definitions.
    Object.assign(allNamedExports, meteorPackageNamedExports({ config }));
    return allNamedExports;
  },

  packageDependencies({ config }: Object): Array<string> {
    const npmPackages = findPackageDependencies(
      config.workingDirectory,
      config.get('importDevDependencies')
    );

    return [...meteorPackageDependencies({ config }), ...npmPackages];
  },
};
<commit_msg>Use Set for coreModules in meteorEnvironment<commit_after>// @flow

import fs from 'fs';
import os from 'os';
import path from 'path';

import FileUtils from '../FileUtils';

import findPackageDependencies from '../findPackageDependencies';

const coreModules = [
  'meteor/accounts-base',
  'meteor/blaze',
  'meteor/check',
  'meteor/ddp-client',
  'meteor/ddp-rate-limiter',
  'meteor/ejson',
  'meteor/email',
  'meteor/http',
  'meteor/check',
  'meteor/meteor',
  'meteor/mongo',
  'meteor/random',
  'meteor/reactive-var',
  'meteor/session',
  'meteor/templating',
  'meteor/tracker',
];

const coreNamedExports = {
  'meteor/accounts-base': ['AccountsClient', 'Accounts', 'AccountsServer'],
  'meteor/blaze': ['Blaze'],
  'meteor/check': ['check', 'Match'],
  'meteor/ddp-client': ['DDP'],
  'meteor/ddp-rate-limiter': ['DDPRateLimiter'],
  'meteor/ejson': ['EJSON'],
  'meteor/email': ['Email'],
  'meteor/http': ['HTTP'],
  'meteor/meteor': ['Meteor'],
  'meteor/mongo': ['Mongo'],
  'meteor/random': ['Random'],
  'meteor/reactive-var': ['ReactiveVar'],
  'meteor/session': ['Session'],
  'meteor/templating': ['Template'],
  'meteor/tracker': ['Tracker'],
};

function meteorPackageDependencies(
  { config }: Object
): Array<string> {
  const meteorPackagesPath = path.join(config.workingDirectory, '.meteor/packages');

  if (!fs.existsSync(meteorPackagesPath)) {
    return [];
  }

  // Meteor is an "app" framework. As such, it has both apps and packages. When
  // working with a module that is part of an app, the list of Meteor packages
  // that a module may import is found in '.meteor/packages'. This file is
  // actually called a ProjectConstraintsFile in the meteor code.
  // The internal meteor routine that parses it may be found at
  // https://github.com/meteor/meteor/blob/f8b1bba6/tools/project-context.js#L841.
  //
  // After reverse engineering ProjectConstraintsFile.prototype._readfile at
  // that location, the following appears to be the pertinent facts for any
  // parser.
  //
  //  - the only true information within the file is a list of constraints
  //  - a constraint may not span a line
  //  - only one constraint may appear on a line
  //  - a constraint consists of a package name and an optional version
  //    constraint separated by the '@' symbol
  //  - white space may not appear within a constraint
  //  - the '#' symbol signifies that the rest of the line is a comment
  //  - all white space is ignored
  //  - package names
  //      - are allowed to contain [a-z0-9:.\-]
  //      - must have at least one lowercase letter
  //      - may not begin or end with a dot or colon
  //      - may not begin with a hyphen
  //      - may not contain two consecutive dots
  //
  // This routine is only interested in extracting the package names and has no
  // concern for precisely validating them. An assumption is made that the file
  // is basically valid. Thus, they may be extracted with a simple global,
  // multiline match of characters allowed to be in a package name that are at
  // the beginning of a line, possibly following white space.
  const coreModulesSet = new Set(coreModules);
  const packages: Array<string> = (fs.readFileSync(meteorPackagesPath, 'utf8')
    // extract an array of package names (possibly with preceding whitespace)
    // from the packages file
    .match(/^\s*[a-z0-9:.\-]+/gm) || [])
    // add 'meteor/' to the start of each name per Meteor convention
    .map((pkg: string): string => `meteor/${pkg.trimLeft()}`)
    // eliminate those packages that are considered to be core
    .filter((pkg: string): boolean => !coreModulesSet.has(pkg));
  return packages;
}

function meteorPackageVersions(projectRootDir: string): ?Map<string, string> {
  // This function processes all of the package versions found in
  // .meteor/versions and returns a map containing them.

  const meteorVersionsPath = path.join(projectRootDir, '.meteor/versions');
  if (!fs.existsSync(meteorVersionsPath)) {
    // If we're even in an application directory, it must be broken. In any
    // case, we can't find the packages without their versions. Return null to
    // indicate that an issue occurred as opposed to processing an empty
    // .meteor/versions.
    return null;
  }

  const pkgVersions = new Map();
  const pkgVersionPairs: Array<string> = fs.readFileSync(meteorVersionsPath, 'utf8')
    .match(/^[^@\s]+@[^\s]+$/gm) || [];

  pkgVersionPairs.forEach((pkgVersionPair: string) => {
    const [pkg, version] = pkgVersionPair.split('@');
    pkgVersions.set(pkg, version);
  });

  return pkgVersions;
}

function extractExportsFromMeteorPackage(
  projectRootDir: string,
  pkg: string,
  pkgVersion: string
): ?Array<string> {
  // This function extracts the named exports from the package specified by pkg
  // and pkgVersion and returns them as an array of strings. If null is
  // returned, a problem was encountered in processing. If an empty array is
  // returned, no named exports were found.

  // Meteor packages are deployed as "isopacks". These can usually be found
  // within the meteor warehouse directory on the system at ~/.meteor/packages.
  // isopacks for local Meteor packages, i.e. those whose source is in the
  // <project-root>/packages directory, can usually be found in at
  // <project-root>/.meteor/local/isopacks.
  //
  // These isopacks do not exactly contain the original package source. In
  // particular, they are always missing a "package.js" file. This is important
  // to us because it is the one that specifies the interface. They do however
  // contain build products that resulted from that specification.
  //
  // Meteor is an isomorphic environment. An isopack contains multiple builds,
  // one for each targeted platform. Typically, this will include at least a
  // client and a server build.  An isopack contains an isopack.json file that
  // details which platforms are represented and leads us to other platform
  // specific isopack.json files that contain the interface details needed by
  // this routine.
  //
  // This routine will find all interfaces from all build platforms and create
  // namedExport entries for them.
  //
  // So, for example, if a project includes version 1.5.3 of package
  // "aldeed:simple-schema", the isopack for that package will usually be
  // located at
  //    ~/.meteor/packages/aldeed_simple-schema/1.5.3/
  // Within that directory, we will find the following important files
  //
  //    isopack.json
  //    os.json
  //    web.browser.json
  //    web.cordova.json
  //
  // isopack.json will point us to the others which all represent specific
  // platforms.
  //
  // There are two ways that meteor packages expose their exported interface,
  // pre-ES6 and post-ES6.
  //
  // The pre-ES6 modules method is to call api.exports within the package.js
  // file for each variable exported. These calls all result in declaredExports
  // entries in the individual platform's json files. Within
  // aldeed:simple-schema's web.browser.json for example, we find
  //
  //  "declaredExports": [
  //      {
  //        "name": "SimpleSchema",
  //        "testOnly": false
  //      },
  //      {
  //        "name": "MongoObject",
  //        "testOnly": false
  //      },
  //      {
  //        "name": "humanize",
  //        "testOnly": true
  //      }
  //    ],
  //
  // From this, we can determine that the namedExports entry should be
  //
  //   'aldeed:simple-schema': ['SimpleSchema', 'MongoObject']
  //
  // "humanize" was left off because it is testOnly. We may try to get smarter
  // and include that for some modules in the future.
  //
  // Post-ES6 modules, things become a bit more difficult. The package.js file
  // in a post-ES6 world specifies a mainModule. Each build will have no more
  // than one mainModule and they may be different. The mainModule's are denoted
  // in the same build json files that we process to find the pre-ES6
  // declaredExports. They are in the resources section which looks like
  //
  //   "resources": [
  //       {
  //         "type": "source",
  //         "extension": "js",
  //         "file": "web.browser/client_main.js",
  //         "length": 752,
  //         "offset": 0,
  //         "path": "client_main.js",
  //         "hash": "cf1eeaf24f21f7755a7cb3fe1e247d56b5e97acf",
  //         "fileOptions": {
  //           "mainModule": true
  //         }
  //       },
  //
  // We are interested only in the one resource that has fileOptions.mainModule
  // = true.
  //
  // The exports are defined by the export statements in the build's mainModule
  // file, "web.browser/client_main.js" in the above example. So, we'll have to
  // parse the file and identify the exports. If the file uses ES6 module export
  // syntax, we should be able to achieve this. If its using CommonJS, we may
  // have to punt and let the user define their own namedExports for that
  // package.

  // The isopack's for 3rd party packages are usually found at
  //   ~/.meteor/packages/<pkg>/<pkgVersion>
  // where pkg is the meteorPkg without the 'meteor/' prefix and colons are
  // replaced with underlines.
  let isopackRoot = path.join(os.homedir(), '.meteor/packages', pkg.replace(':', '_'), pkgVersion);
  let isopackPath = path.join(isopackRoot, 'isopack.json');
  let isopack = FileUtils.readJsonFile(isopackPath);

  if (!isopack) {
    // It is possible that this is a local package as opposed to a 3rd party
    // package. If so, it's isopack may be in
    // <project-root>/.meteor/isopacks/<pkg>.
    isopackRoot = path.join(
      projectRootDir, '.meteor', 'local', 'isopacks', pkg.replace(':', '_'));
    isopackPath = path.join(isopackRoot, 'isopack.json');
    isopack = FileUtils.readJsonFile(isopackPath);

    if (!isopack) {
      // Can't get anywhere without the main isopack.json.
      return null;
    }
  }

  // isopack.json often contains separate sections for every version of the
  // isopacks that has ever been made available. We're only interested in
  // isopack-2 or isopack-1 at this time. Prefer the newer isopack-2
  // specification if available
  const isopackVer = isopack['isopack-2'] || isopack['isopack-1'];
  if (!isopackVer || !isopackVer.builds) {
    // If we didn't find an isopack version we understand or documented builds
    // within it, we're done.
    return null;
  }

  // We can't guess which build the current module is being included in. So,
  // we'll find all declaredExports from all builds and combine them into one
  // namedExports specification.
  const declaredExports: Set<string> = new Set();

  isopackVer.builds.forEach((build: Object) => {
    const buildIsopackPath = path.join(isopackRoot, build.path);
    const buildIsopack = FileUtils.readJsonFile(buildIsopackPath);

    if (!buildIsopack || !buildIsopack.declaredExports) {
      // This build is missing, corrupted, or has no declaredExports. Try the
      // next one.
      return;
    }

    buildIsopack.declaredExports.forEach((declaredExport: Object) => {
      if (!declaredExport.testOnly) {
        declaredExports.add(declaredExport.name);
      }
    });

    // TODO: If the "resources" section of the buildIsopack specifies a
    // mainModule, we need to attempt to scan it to find exports.
  });

  return Array.from(declaredExports);
}

function meteorPackageNamedExports({ config }: Object): Object {
  // This function seeks to extract the named exports from all non-core, 3rd
  // party or local meteor packages being utilized in the application. The
  // meteorPackageDependencies function identifies that list of packages.

  // Retrieve the versions of all Meteor packages. Note that local packages
  // aren't absolutely required to have versions.
  const pkgVersions = meteorPackageVersions(config.workingDirectory) || new Map();

  // Try to identify the exports of all packages identified by
  // meteorPackageDependencies
  const namedExports = {};

  meteorPackageDependencies({ config }).forEach((meteorPkg: string) => {
    const pkg = meteorPkg.slice(7);
    const pkgVersion = pkgVersions.get(pkg) || '';
    const extractedExports = extractExportsFromMeteorPackage(
      config.workingDirectory, pkg, pkgVersion);

    // If we found declared exports, create a namedExports entry for them.
    if (extractedExports && extractedExports.length) {
      namedExports[meteorPkg] = extractedExports;
    }
  });

  return namedExports;
}

export default {
  coreModules,

  moduleNameFormatter({ moduleName, pathToImportedModule }: Object): string {
    // If the module being imported is a Meteor package, it will begin with
    // 'meteor/' and should not be altered.
    if (moduleName.startsWith('meteor/')) {
      return moduleName;
    }
    // If the module being imported is an npm package, the path to the module
    // will start with 'node_modules/' and the moduleName should not be altered.
    // not be altered.
    if (pathToImportedModule.startsWith('node_modules/')) {
      return moduleName;
    }
    // If the moduleName does not start with a '.', then import-js is trying to
    // reference it via an absolute path. In this case, Meteor wants it to start
    // with a '/' and will interpret it as relative to the project directory
    // root.
    if (!moduleName.startsWith('.')) {
      return `/${moduleName}`;
    }
    // Otherwise, return the moduleName unchanged.
    return moduleName;
  },

  moduleSideEffectImports({ pathToCurrentFile, config }: Object): Array<string> {
    let basePath = '';

    if (pathToCurrentFile.endsWith('.js')) {
      basePath = pathToCurrentFile.slice(0, -3);
    } else if (pathToCurrentFile.endsWith('.jsx')) {
      basePath = pathToCurrentFile.slice(0, -4);
    } else {
      return [];
    }

    const moduleSpecifiers = [];

    ['.html', '.css'].forEach((ext: string) => {
      const moduleSpecifier = `${basePath}${ext}`;
      if (fs.existsSync(path.join(config.workingDirectory, moduleSpecifier))) {
        if (config.get('useRelativePaths')) {
          moduleSpecifiers.push('.'.concat(path.sep, path.basename(moduleSpecifier)));
        } else {
          // Strip the leading '.' off of the moduleSpecifier to turn it into a
          // Meteor compliant absolute path.
          moduleSpecifiers.push(moduleSpecifier.slice(1));
        }
      }
    });

    return moduleSpecifiers;
  },

  namedExports({ config }: Object): Object {
    const allNamedExports = coreNamedExports;
    // There are no worries about this overwriting the definitions of core
    // namedExports. meteorPackageNamedExports skips core packages. Even if it
    // did not, it would presumably find correct definitions.
    Object.assign(allNamedExports, meteorPackageNamedExports({ config }));
    return allNamedExports;
  },

  packageDependencies({ config }: Object): Array<string> {
    const npmPackages = findPackageDependencies(
      config.workingDirectory,
      config.get('importDevDependencies')
    );

    return [...meteorPackageDependencies({ config }), ...npmPackages];
  },
};
<|endoftext|>


<commit_before>/**
  Copyright 2010-2011 Ondrej Certik <ondrej@certik.cz>
  Copyright 2010-2011 Mateusz Paprocki <mattpap@gmail.com>
  Copyright 2011 Christian Iversen <ci@sikkerhed.org>

  Permission is hereby granted, free of charge, to any person
  obtaining a copy of this software and associated documentation
  files (the "Software"), to deal in the Software without
  restriction, including without limitation the rights to use,
  copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the
  Software is furnished to do so, subject to the following
  conditions:

  The above copyright notice and this permission notice shall be
  included in all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  OTHER DEALINGS IN THE SOFTWARE.
**/

var slice = __inherit(object, "slice");

slice.PY$__init__ = function(start, stop, step) {
    if (!defined(stop) && !defined(step))
    {
        stop = start;
        start = null;
    }
    if (!start && start != 0) start = null;
    if (!defined(stop)) stop = null;
    if (!defined(step)) step = null;
    this.start = js(start);
    this.stop = js(stop);
    this.step = js(step);
};

slice.PY$__str__ = function() {
    return str("slice(" + this.start + ", " + this.stop + ", " + this.step + ")");
};

slice.PY$indices = function(n) {
    n = js(n);
    var start = this.start;
    if (start === null)
        start = 0;
    if (start > n)
        start = n;
    if (start < 0)
        start = n+start;
    var stop = this.stop;
    if (stop > n)
        stop = n;
    if (stop === null)
        stop = n;
    if (stop < 0)
        stop = n+stop;
    var step = this.step;
    if (step === null)
        step = 1;
    return tuple([start, stop, step]);
};
<commit_msg> + Added $PY.slice<commit_after>/**
  Copyright 2010-2011 Ondrej Certik <ondrej@certik.cz>
  Copyright 2010-2011 Mateusz Paprocki <mattpap@gmail.com>
  Copyright 2011 Christian Iversen <ci@sikkerhed.org>

  Permission is hereby granted, free of charge, to any person
  obtaining a copy of this software and associated documentation
  files (the "Software"), to deal in the Software without
  restriction, including without limitation the rights to use,
  copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the
  Software is furnished to do so, subject to the following
  conditions:

  The above copyright notice and this permission notice shall be
  included in all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  OTHER DEALINGS IN THE SOFTWARE.
**/

var slice = __inherit(object, "slice");

$PY.slice = slice;

slice.PY$__init__ = function(start, stop, step) {
    if (!defined(stop) && !defined(step))
    {
        stop = start;
        start = null;
    }
    if (!start && start != 0) start = null;
    if (!defined(stop)) stop = null;
    if (!defined(step)) step = null;
    this.start = js(start);
    this.stop = js(stop);
    this.step = js(step);
};

slice.PY$__str__ = function() {
    return str("slice(" + this.start + ", " + this.stop + ", " + this.step + ")");
};

slice.PY$indices = function(n) {
    n = js(n);
    var start = this.start;
    if (start === null)
        start = 0;
    if (start > n)
        start = n;
    if (start < 0)
        start = n+start;
    var stop = this.stop;
    if (stop > n)
        stop = n;
    if (stop === null)
        stop = n;
    if (stop < 0)
        stop = n+stop;
    var step = this.step;
    if (step === null)
        step = 1;
    return tuple([start, stop, step]);
};
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

function DOMRequest() {
  this.onsuccess = null;
  this.onerror = null;
  this.error = null;
  this.result = undefined;
  this.readyState = "pending";
};

DOMRequest.prototype.fireSuccess = function (result) {
  this.result = result;
  this.readyState = "done";
  if (this.onsuccess) {
    this.onsuccess({type: "success", target: this, toString: function () {return '[object Event]';}});
  }
};

DOMRequest.prototype.fireError = function (errorName) {
  this.error = {name: errorName, type: "error"};
  this.readyState = "done";
  if (this.onerror) {
    this.onerror();
  }
};

DOMRequest.prototype.toString = function () {
  return '[object DOMRequest]';
}

var mockMozApps = {
  _repo: {},
  monkeypatch: function () {
    navigator.mozApps = this;
  },
  install: function (manifestURL, installData) {
    var pending = new DOMRequest();
    var req = new XMLHttpRequest();
    var self = this;
    req.open("GET", manifestURL);
    req.onreadystatechange = function () {
      if (req.readyState != 4) {
        return;
      }
      if (req.status != 200) {
        pending.fireError("NETWORK_ERROR");
        return;
      }
      try {
        var manifest = JSON.parse(req.responseText);
      } catch (e) {
        pending.fireError("MANIFEST_PARSE_ERROR");
        return;
      }
      self._installManifest(manifestURL, manifest, this._getOrigin(location.href), installData, pending);
    };
    req.send();
    return pending;
  },

  _installManifest: function (manifestURL, manifest, installOrigin, installData, pending) {
    var origin = this._getOrigin(manifestURL);
    var appData = this._repo[origin] = {
      manifestURL: manifestURL,
      manifest: manifest,
      installTime: Date.now(),
      installData: installData,
      origin: origin,
      installOrigin: installOrigin
    };
    var app = new Application(this, appData);
    pending.fireSuccess(app);
  },

  _getOrigin: function (url) {
    return URLParse(url).originOnly().toString();
  },

  getSelf: function () {
    var pending = new DOMRequest();
    var self = this;
    setTimeout(function () {
      var thisOrigin = URLParse(location.href).originOnly().toString();
      var appData = self._repo[thisOrigin];
      if (! appData) {
        pending.fireSuccess(null);
      } else {
        pending.fireSuccess(new Application(self, appData));
      }
    });
    return pending;
  }

};

function Application(repo, data) {
  this._repo = repo;
  this._rawData = data;
  this.manifestURL = data.manifestURL;
  // Note: doesn't do a deep copy
  this.manifest = data.manifest;
  this.origin = data.origin;
  this.installTime = data.installTime;
  this.receipts = data.installData && data.installData.receipts;
};

Application.prototype = {
  launch: function () {
    throw 'app.launch() not implemented';
  },
  uninstall: function () {
    var pending = new DOMRequest();
    var self = this;
    setTimeout(function () {
      delete self._repo[self.origin];
      pending.fireSuccess(this);
    });
    return pending;
  }
};
<commit_msg>Added mockMozApps._clear() and fixed some jshint stuff<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

function DOMRequest() {
  this.onsuccess = null;
  this.onerror = null;
  this.error = null;
  this.result = undefined;
  this.readyState = "pending";
}

DOMRequest.prototype.fireSuccess = function (result) {
  this.result = result;
  this.readyState = "done";
  if (this.onsuccess) {
    this.onsuccess({type: "success", target: this, toString: function () {return '[object Event]';}});
  }
};

DOMRequest.prototype.fireError = function (errorName) {
  this.error = {name: errorName, type: "error"};
  this.readyState = "done";
  if (this.onerror) {
    this.onerror();
  }
};

DOMRequest.prototype.toString = function () {
  return '[object DOMRequest]';
};

var mockMozApps = {
  _repo: {},
  monkeypatch: function () {
    navigator.mozApps = this;
  },
  install: function (manifestURL, installData) {
    var pending = new DOMRequest();
    var req = new XMLHttpRequest();
    var self = this;
    req.open("GET", manifestURL);
    req.onreadystatechange = function () {
      if (req.readyState != 4) {
        return;
      }
      if (req.status != 200) {
        pending.fireError("NETWORK_ERROR");
        return;
      }
      var manifest;
      try {
        manifest = JSON.parse(req.responseText);
      } catch (e) {
        pending.fireError("MANIFEST_PARSE_ERROR");
        return;
      }
      self._installManifest(manifestURL, manifest, this._getOrigin(location.href), installData, pending);
    };
    req.send();
    return pending;
  },

  _installManifest: function (manifestURL, manifest, installOrigin, installData, pending) {
    var origin = this._getOrigin(manifestURL);
    var appData = this._repo[origin] = {
      manifestURL: manifestURL,
      manifest: manifest,
      installTime: Date.now(),
      installData: installData,
      origin: origin,
      installOrigin: installOrigin
    };
    var app = new Application(this, appData);
    pending.fireSuccess(app);
  },

  _clear: function () {
    this._repo = {};
  },

  _getOrigin: function (url) {
    return URLParse(url).originOnly().toString();
  },

  getSelf: function () {
    var pending = new DOMRequest();
    var self = this;
    setTimeout(function () {
      var thisOrigin = URLParse(location.href).originOnly().toString();
      var appData = self._repo[thisOrigin];
      if (! appData) {
        pending.fireSuccess(null);
      } else {
        pending.fireSuccess(new Application(self, appData));
      }
    });
    return pending;
  }

};

function Application(repo, data) {
  this._repo = repo;
  this._rawData = data;
  this.manifestURL = data.manifestURL;
  // Note: doesn't do a deep copy
  this.manifest = data.manifest;
  this.origin = data.origin;
  this.installTime = data.installTime;
  this.receipts = data.installData && data.installData.receipts;
}

Application.prototype = {
  launch: function () {
    throw 'app.launch() not implemented';
  },
  uninstall: function () {
    var pending = new DOMRequest();
    var self = this;
    setTimeout(function () {
      delete self._repo[self.origin];
      pending.fireSuccess(this);
    });
    return pending;
  }
};
<|endoftext|>


<commit_before>import { useEffect } from 'react'
import { connect } from 'react-redux'
import api from '../../utils/api'
import { endpoints, getTransform } from './endpoints'
import * as actions from './actions'

const insertRouteParams = (route, params) => {
  Object.keys(params).forEach(param => {
    route = route.replace(`:${param}`, params[param])
  })
  return route
}
const ProjectInitHandler = connect(null, actions)(({ match: {params}, ...props}) => {
  const fetchSection = (sectionIndex) => new Promise(async (resolve, reject) => {
    if(sectionIndex === 4 || sectionIndex === 6 || sectionIndex === 7 || sectionIndex === 8 || sectionIndex === 10){
      props.fetchSectionRoot(sectionIndex)
    }
    if(sectionIndex === 4){
      resolve()
      return
    }
    const _endpoints = endpoints[`section${sectionIndex}`]
    // fetch root
    if(_endpoints.hasOwnProperty('root')){
      await api.get(insertRouteParams(_endpoints.root, { projectId: params.id }))
        .then(({data}) => props.fetchFields(sectionIndex, data))
    }
    // fetch sets
    const setEndpoints = Object
      .keys(_endpoints)
      .filter(it => it !== 'root' && it.indexOf('.') === -1)
      .map(it => ({ setName: it, endpoint: _endpoints[it]}))
    if(setEndpoints.length > 0) {
      const fetchSet = (index) => {
        const { endpoint, setName } = setEndpoints[index]
        const _params = endpoint === '/project_location/' ? {location_target: params.id} : {project: params.id}
        api.get(endpoint, _params, getTransform(sectionIndex, setName, 'response'))
          .then(({ data: { results } }) => {
            props.fetchSetItems(sectionIndex, setName, results)
            if(index < setEndpoints.length - 1) fetchSet(index + 1)
            else resolve()
          })
      }
      fetchSet(0)
    }
    else resolve()
  })
  let nextSectionIndex = 0
  const fetchNextSection = () => {
    nextSectionIndex += 1
    fetchSection(nextSectionIndex)
    .then(() => {
      props.setSectionFetched(nextSectionIndex)
      if(nextSectionIndex < 11){
        fetchNextSection()
      }
    })
  }
  useEffect(() => {
    if(params.id !== 'new'){
      props.setProjectId(params.id)
      fetchNextSection()
    } else {
      props.resetProject()
    }
  }, [])
  return null
})

export default ProjectInitHandler
<commit_msg>[#3749] Fetch partnership info after a project is created<commit_after>import { useEffect } from 'react'
import { connect } from 'react-redux'
import api from '../../utils/api'
import { endpoints, getTransform } from './endpoints'
import * as actions from './actions'

const insertRouteParams = (route, params) => {
  Object.keys(params).forEach(param => {
    route = route.replace(`:${param}`, params[param])
  })
  return route
}
let prevParams

const ProjectInitHandler = connect(null, actions)(({ match: {params}, ...props}) => {
  const fetchSection = (sectionIndex) => new Promise(async (resolve, reject) => {
    if(sectionIndex === 4 || sectionIndex === 6 || sectionIndex === 7 || sectionIndex === 8 || sectionIndex === 10){
      props.fetchSectionRoot(sectionIndex)
    }
    if(sectionIndex === 4){
      resolve()
      return
    }
    const _endpoints = endpoints[`section${sectionIndex}`]
    // fetch root
    if(_endpoints.hasOwnProperty('root')){
      await api.get(insertRouteParams(_endpoints.root, { projectId: params.id }))
        .then(({data}) => props.fetchFields(sectionIndex, data))
    }
    // fetch sets
    const setEndpoints = Object
      .keys(_endpoints)
      .filter(it => it !== 'root' && it.indexOf('.') === -1)
      .map(it => ({ setName: it, endpoint: _endpoints[it]}))
    if(setEndpoints.length > 0) {
      const fetchSet = (index) => {
        const { endpoint, setName } = setEndpoints[index]
        const _params = endpoint === '/project_location/' ? {location_target: params.id} : {project: params.id}
        api.get(endpoint, _params, getTransform(sectionIndex, setName, 'response'))
          .then(({ data: { results } }) => {
            props.fetchSetItems(sectionIndex, setName, results)
            if(index < setEndpoints.length - 1) fetchSet(index + 1)
            else resolve()
          })
      }
      fetchSet(0)
    }
    else resolve()
  })
  let nextSectionIndex = 0
  const fetchNextSection = () => {
    nextSectionIndex += 1
    fetchSection(nextSectionIndex)
    .then(() => {
      props.setSectionFetched(nextSectionIndex)
      if(nextSectionIndex < 11){
        fetchNextSection()
      }
    })
  }
  useEffect(() => {
    if (prevParams && prevParams.id !== params.id && params.id !== 'new'){
      fetchSection(3)
    }
  }, [params.id])
  useEffect(() => {
    prevParams = params
  })
  useEffect(() => {
    if (params.id !== 'new') {
      props.setProjectId(params.id)
      fetchNextSection()
    } else {
      props.resetProject()
    }
  }, [])
  return null
})

export default ProjectInitHandler
<|endoftext|>


<commit_before>/* @flow weak */
/*global StringView */

"use strict";

var Buffer, Buffers,
  flux = require("flukes"),
  utils = require("../utils"),
  prefs = require("./userPref_model"),
  _ = require("lodash"),
  path = require("path"),
  floop = require("./floop"),
  editorAction = require("./editor_action"),
  bufferAction = require("./buffer_action"),
  // messageAction = require("./message_action"),
  FlooDMP = require("../floodmp").FlooDMP,
  dmp = new FlooDMP(),
  fields = flux.FieldTypes;

/**
 * @param {number} id
 * @param {string} owner
 * @param {string} workspace
 */
function guid(id, owner, workspace) {
  return owner + "/" + workspace + "/" + id;
}

/**
 * @param {Object} data
 * @param {number} id
 * @param {string} owner
 * @param {string} workspace
 * @constructor
 */

Buffer = flux.createModel({
  modelName: "Buffer",
  fieldTypes: {
    buf: fields.string,
    highlights: fields.array,
    id: fields.number,
    md5: fields.number,
    open: fields.bool,
    path: fields.string,
    visible: fields.bool,
    encoding: fields.string,
    populated: fields.bool,
  },
  getDefaultFields: function () {
    return {
      buf: null,
      encoding: "utf8",
    };
  },
});

Buffer.prototype.abs_path = function() {
  return utils.to_abs_path(fl.base_path, this.path);
};

Buffer.prototype.setGetBufTimeout = function () {
  var id = this.id;
  this.strike_timeout = setTimeout(function () {
    console.log("strikeout triggered because before md5s were different");
    floop.send_get_buf(id);
  }, 1100);
};

Buffer.prototype.cleanUp = function() {
  clearTimeout(this.strike_timeout);
  clearTimeout(this.saveTimeout);
};

Buffer.prototype.setSaveBufTimeout = function () {
  var id = this.id;
  clearTimeout(this.saveTimeout);
  this.saveTimeout = setTimeout(function () {
    floop.send_save_buf({id: id});
  }, 2500);
};

Buffer.prototype.send_patch = function (newTextPointer) {
  var patches, patchInfo, newTxt;

  if (!this.populated) {
    return;
  }

  newTxt = newTextPointer[0];

  patches = dmp.patch_make(this.buf, newTxt);

  if (patches.length === 0) {
    return;
  }

  patchInfo = {
    md5_before: this.md5,
    md5_after: utils.md5(newTxt),
    id: this.id,
    patch: dmp.patch_toText(patches)
  };

  this.set({buf: newTxt, md5: patchInfo.md5}, {silent: true});
  prefs.pauseFollowMode(2000);
  floop.send_patch(patchInfo);
};

Buffer.prototype.patch = function (patch_obj) {
  let patches;
  try {
    patches = dmp.patch_fromText(patch_obj.patch);
  } catch (e) {
    throw new Error("Received Invalid bytes from workspace." + e.toString() + "Patch was: " + patch_obj.patch);
  }

  console.debug("applying", patch_obj.patch, "to", patch_obj.path);

  const md5_before = utils.md5(this.buf);
  if (patch_obj.md5_before !== md5_before) {
    console.log("starting md5s don't match! ours:", md5_before, "patch's:", patch_obj.md5_before);
  }
  if (_.isEmpty(patches)) {
    console.log("Got an empty set of patches.");
    return;
  }
  let result;
  try {
    result = dmp.patch_apply(patches, this.buf);
  } catch (e) {
    console.sError("Patch apply error on web.",
      {
        patch: patches,
        buf: this.buf,
        result: result,
        subject: "Patch Apply Failed ",
        error: e,
        message: { description: "Patch apply failed"},
      }
    );
    throw e; //Continue to throw since it's already broken anyway.
  }
  const text = result[0];
  let clean_patch = true;
  for (let i = 0; i < result[1].length; i++) {
    if (result[1][i] !== true) {
      clean_patch = false;
      break;
    }
  }
  if (clean_patch === false) {
    // XXX: don't reset this. ask the server to merge/rebase/whatever
    console.error("Couldn't apply patch. Getting buffer from server...", result);
    // messageAction.log("Couldn't apply patch to buffer " + this.path + " Re-fetching buffer from server...");
    this.populated = false;
    clearTimeout(this.strike_timeout);
    floop.send_get_buf(this.id);
    return;
  }

  const md5_after = utils.md5(text);

  if (patch_obj.md5_before === md5_before && patch_obj.md5_after !== md5_after) {
    // XXX: again, don't reset this. ask the server to merge/rebase/whatever
    console.log("md5's don't match! buffer is", md5_after, "but we expected", patch_obj.md5_after);
    this.populated = false;
    clearTimeout(this.strike_timeout);
    floop.send_get_buf(this.id);
    return;
  }

  if (this.buf === text) {
    clearTimeout(this.strike_timeout);
    if (patch_obj.md5_after !== md5_after) {
      this.setGetBufTimeout();
    }
  } else if (patch_obj.md5_before !== md5_before) {
    clearTimeout(this.strike_timeout);
    this.setGetBufTimeout();
  } else {
    console.log("replacing didn't match :( should be", text, "but was", this.buf);
    try {
      console.log(dmp.patch_toText(dmp.patch_make(this.buf, text)));
    } catch (e) {
      console.log("Error making patch of text --> this.buf:", e);
    }
  }
  return {text: text, patches: result};
};

Buffers = flux.createCollection({
  modelName: "Buffers",
  model: Buffer,
  paths_to_ids: {},
  getBufferByPath: function (p) {
    var id = this.paths_to_ids[p];
    if (!id) {
      return;
    }
    return this.get(id);
  },
  findFluffer: function(obj) {
    var p = obj.getRealPathSync ? obj.getRealPathSync() : obj.getPath();
    if (!p) {
      return;
    }

    p = path.relative(fl.base_path, p);

    if (p.indexOf("..") !== -1) {
      return;
    }

    return this.getBufferByPath(p);
  },
  init: function () {
    this.ids_ = [];
  },
  on_patch: function (data) {
    var buf = this.get(data.id);
    if (!buf) {
      return;
    }
    // this.setup_buf(buf);
    if (!buf.populated) {
      console.log("buf isn't populated. fetching");
      floop.send_get_buf(buf.id);
      return;
    }
    if (buf.encoding === "base64") {
      console.log("Floobits can't handle binary patches at the moment. Re-fetching.");
      floop.send_get_buf(buf.id);
      return;
    }
    if (!data.patch.length) {
      console.log("wtf? no patches!");
      return;
    }
    const patches = buf.patch(data);
    if (!patches) {
      return;
    }
    const charPointer = [patches.text];
    bufferAction.changed(buf, charPointer, patches.patches);
    buf.set({buf: charPointer[0], silent: true});
  },
  on_saved: function (data) {
    var b = this.get(data.id);
    if (!b.populated) {
      return;
    }
    bufferAction.saved(b);
  },
  on_get_buf: function (data) {
    var b = this.get(data.id);
    data.populated = true;
    b.set(data, {silent: true});
    bufferAction.changed(b);
    clearTimeout(b.strike_timeout);
  },
  on_create_buf: function (buf) {
    this.add(buf);
  },
  on_rename_buf: function (d) {
    var buf = this.get(d.id);
    var old_path = buf.abs_path();
    delete this.paths_to_ids[old_path];
    buf.path = d.path;
    this.paths_to_ids[buf.path] = buf.id;
    bufferAction.rename(buf, old_path, buf.abs_path());
  },
  on_delete_buf: function (d) {
    var b = this.get(d.id);
    this.remove(b.id);
    delete this.paths_to_ids[d.path];
    bufferAction.deleted(b, d.force);
  },
  add: function (d) {
    this.super_.prototype.add.call(this, d);
    this.paths_to_ids[d.path] = d.id;
    bufferAction.created(this.get(d.id));
  },
  start: function () {
    this.ids_.push(floop.onPATCH(this.on_patch, this));
    this.ids_.push(floop.onGET_BUF(this.on_get_buf, this));
    this.ids_.push(floop.onSAVED(this.on_saved, this));
    this.ids_.push(floop.onDELETE_BUF(this.on_delete_buf, this));
    this.ids_.push(floop.onCREATE_BUF(this.on_create_buf, this));
    this.ids_.push(floop.onRENAME_BUF(this.on_rename_buf, this));
  },
  stop: function () {
    _.each(this.ids_, function (id) {
      floop.off(id);
    });
    this.forEach(function (b) {
      b.cleanUp();
    })
  }
});

module.exports = {
  Buffer: Buffer,
  Buffers: Buffers,
};
<commit_msg>correctly set md5s after patches<commit_after>/* @flow weak */
/*global StringView */

"use strict";

var Buffer, Buffers,
  flux = require("flukes"),
  utils = require("../utils"),
  prefs = require("./userPref_model"),
  _ = require("lodash"),
  path = require("path"),
  floop = require("./floop"),
  editorAction = require("./editor_action"),
  bufferAction = require("./buffer_action"),
  // messageAction = require("./message_action"),
  FlooDMP = require("../floodmp").FlooDMP,
  dmp = new FlooDMP(),
  fields = flux.FieldTypes;

/**
 * @param {number} id
 * @param {string} owner
 * @param {string} workspace
 */
function guid(id, owner, workspace) {
  return owner + "/" + workspace + "/" + id;
}

/**
 * @param {Object} data
 * @param {number} id
 * @param {string} owner
 * @param {string} workspace
 * @constructor
 */

Buffer = flux.createModel({
  modelName: "Buffer",
  fieldTypes: {
    buf: fields.string,
    highlights: fields.array,
    id: fields.number,
    md5: fields.number,
    open: fields.bool,
    path: fields.string,
    visible: fields.bool,
    encoding: fields.string,
    populated: fields.bool,
  },
  getDefaultFields: function () {
    return {
      buf: null,
      encoding: "utf8",
    };
  },
});

Buffer.prototype.abs_path = function() {
  return utils.to_abs_path(fl.base_path, this.path);
};

Buffer.prototype.setGetBufTimeout = function () {
  var id = this.id;
  this.strike_timeout = setTimeout(function () {
    console.log("strikeout triggered because before md5s were different");
    floop.send_get_buf(id);
  }, 1100);
};

Buffer.prototype.cleanUp = function() {
  clearTimeout(this.strike_timeout);
  clearTimeout(this.saveTimeout);
};

Buffer.prototype.setSaveBufTimeout = function () {
  var id = this.id;
  clearTimeout(this.saveTimeout);
  this.saveTimeout = setTimeout(function () {
    floop.send_save_buf({id: id});
  }, 2500);
};

Buffer.prototype.send_patch = function (newTextPointer) {
  var patches, patchInfo, newTxt;

  if (!this.populated) {
    return;
  }

  newTxt = newTextPointer[0];

  patches = dmp.patch_make(this.buf, newTxt);

  if (patches.length === 0) {
    return;
  }

  patchInfo = {
    md5_before: this.md5,
    md5_after: utils.md5(newTxt),
    id: this.id,
    patch: dmp.patch_toText(patches)
  };

  this.set({buf: newTxt, md5: patchInfo.md5_after}, {silent: true});
  prefs.pauseFollowMode(2000);
  floop.send_patch(patchInfo);
};

Buffer.prototype.patch = function (patch_obj) {
  let patches;
  try {
    patches = dmp.patch_fromText(patch_obj.patch);
  } catch (e) {
    throw new Error("Received Invalid bytes from workspace." + e.toString() + "Patch was: " + patch_obj.patch);
  }

  console.debug("applying", patch_obj.patch, "to", patch_obj.path);

  const md5_before = utils.md5(this.buf);
  if (patch_obj.md5_before !== md5_before) {
    console.log("starting md5s don't match! ours:", md5_before, "patch's:", patch_obj.md5_before);
  }
  if (_.isEmpty(patches)) {
    console.log("Got an empty set of patches.");
    return;
  }
  let result;
  try {
    result = dmp.patch_apply(patches, this.buf);
  } catch (e) {
    console.sError("Patch apply error on web.",
      {
        patch: patches,
        buf: this.buf,
        result: result,
        subject: "Patch Apply Failed ",
        error: e,
        message: { description: "Patch apply failed"},
      }
    );
    throw e; //Continue to throw since it's already broken anyway.
  }
  const text = result[0];
  let clean_patch = true;
  for (let i = 0; i < result[1].length; i++) {
    if (result[1][i] !== true) {
      clean_patch = false;
      break;
    }
  }
  if (clean_patch === false) {
    // XXX: don't reset this. ask the server to merge/rebase/whatever
    console.error("Couldn't apply patch. Getting buffer from server...", result);
    // messageAction.log("Couldn't apply patch to buffer " + this.path + " Re-fetching buffer from server...");
    this.populated = false;
    clearTimeout(this.strike_timeout);
    floop.send_get_buf(this.id);
    return;
  }

  const md5_after = utils.md5(text);

  if (patch_obj.md5_before === md5_before && patch_obj.md5_after !== md5_after) {
    // XXX: again, don't reset this. ask the server to merge/rebase/whatever
    console.log("md5's don't match! buffer is", md5_after, "but we expected", patch_obj.md5_after);
    this.populated = false;
    clearTimeout(this.strike_timeout);
    floop.send_get_buf(this.id);
    return;
  }

  if (this.buf === text) {
    clearTimeout(this.strike_timeout);
    if (patch_obj.md5_after !== md5_after) {
      this.setGetBufTimeout();
    }
  } else if (patch_obj.md5_before !== md5_before) {
    clearTimeout(this.strike_timeout);
    this.setGetBufTimeout();
  } else {
    console.log("replacing didn't match :( should be", text, "but was", this.buf);
    try {
      console.log(dmp.patch_toText(dmp.patch_make(this.buf, text)));
    } catch (e) {
      console.log("Error making patch of text --> this.buf:", e);
    }
  }
  return {text: text, patches: result, md5: md5_after};
};

Buffers = flux.createCollection({
  modelName: "Buffers",
  model: Buffer,
  paths_to_ids: {},
  getBufferByPath: function (p) {
    var id = this.paths_to_ids[p];
    if (!id) {
      return;
    }
    return this.get(id);
  },
  findFluffer: function(obj) {
    var p = obj.getRealPathSync ? obj.getRealPathSync() : obj.getPath();
    if (!p) {
      return;
    }

    p = path.relative(fl.base_path, p);

    if (p.indexOf("..") !== -1) {
      return;
    }

    return this.getBufferByPath(p);
  },
  init: function () {
    this.ids_ = [];
  },
  on_patch: function (data) {
    var buf = this.get(data.id);
    if (!buf) {
      return;
    }
    // this.setup_buf(buf);
    if (!buf.populated) {
      console.log("buf isn't populated. fetching");
      floop.send_get_buf(buf.id);
      return;
    }
    if (buf.encoding === "base64") {
      console.log("Floobits can't handle binary patches at the moment. Re-fetching.");
      floop.send_get_buf(buf.id);
      return;
    }
    if (!data.patch.length) {
      console.log("wtf? no patches!");
      return;
    }
    const patches = buf.patch(data);
    if (!patches) {
      return;
    }
    const charPointer = [patches.text];
    bufferAction.changed(buf, charPointer, patches.patches);
    buf.set({buf: charPointer[0], md5: patches.md5}, {silent: true});
  },
  on_saved: function (data) {
    var b = this.get(data.id);
    if (!b.populated) {
      return;
    }
    bufferAction.saved(b);
  },
  on_get_buf: function (data) {
    var b = this.get(data.id);
    data.populated = true;
    b.set(data, {silent: true});
    bufferAction.changed(b);
    clearTimeout(b.strike_timeout);
  },
  on_create_buf: function (buf) {
    this.add(buf);
  },
  on_rename_buf: function (d) {
    var buf = this.get(d.id);
    var old_path = buf.abs_path();
    delete this.paths_to_ids[old_path];
    buf.path = d.path;
    this.paths_to_ids[buf.path] = buf.id;
    bufferAction.rename(buf, old_path, buf.abs_path());
  },
  on_delete_buf: function (d) {
    var b = this.get(d.id);
    this.remove(b.id);
    delete this.paths_to_ids[d.path];
    bufferAction.deleted(b, d.force);
  },
  add: function (d) {
    this.super_.prototype.add.call(this, d);
    this.paths_to_ids[d.path] = d.id;
    bufferAction.created(this.get(d.id));
  },
  start: function () {
    this.ids_.push(floop.onPATCH(this.on_patch, this));
    this.ids_.push(floop.onGET_BUF(this.on_get_buf, this));
    this.ids_.push(floop.onSAVED(this.on_saved, this));
    this.ids_.push(floop.onDELETE_BUF(this.on_delete_buf, this));
    this.ids_.push(floop.onCREATE_BUF(this.on_create_buf, this));
    this.ids_.push(floop.onRENAME_BUF(this.on_rename_buf, this));
  },
  stop: function () {
    _.each(this.ids_, function (id) {
      floop.off(id);
    });
    this.forEach(function (b) {
      b.cleanUp();
    })
  }
});

module.exports = {
  Buffer: Buffer,
  Buffers: Buffers,
};
<|endoftext|>


<commit_before>
var gj_CONSTANTS = {
    DELTA_TIME: 35,
    TILE_SIZE: 16,
    keys: {
        JUMP: 87,
        MOVE_LEFT: 65,
        MOVE_RIGHT: 68
    },
    eras: {
        PAST: 1,
        PRESENT: 2,
        FUTURE: 3
    }
};

if (typeof(module) !== 'undefined') module.exports = gj_CONSTANTS;
<commit_msg>Changed jump back to space<commit_after>
var gj_CONSTANTS = {
    DELTA_TIME: 35,
    TILE_SIZE: 16,
    keys: {
        JUMP: 32,
        MOVE_LEFT: 65,
        MOVE_RIGHT: 68
    },
    eras: {
        PAST: 1,
        PRESENT: 2,
        FUTURE: 3
    }
};

if (typeof(module) !== 'undefined') module.exports = gj_CONSTANTS;
<|endoftext|>


<commit_before>var React = require('react');
var warning = require('react/lib/warning');
var invariant = require('react/lib/invariant');
var emptyFunction = require('react/lib/emptyFunction');
var ExecutionEnvironment = require('react/lib/ExecutionEnvironment');
var mergeProperties = require('../helpers/mergeProperties');
var goBack = require('../helpers/goBack');
var replaceWith = require('../helpers/replaceWith');
var transitionTo = require('../helpers/transitionTo');
var withoutProperties = require('../helpers/withoutProperties');
var Path = require('../helpers/Path');
var ActiveStore = require('../stores/ActiveStore');
var RouteStore = require('../stores/RouteStore');
var URLStore = require('../stores/URLStore');
var Promise = require('es6-promise').Promise;

/**
 * A map of <Route> component props that are reserved for use by the
 * router and/or React. All other props are considered "static" and
 * are passed through to the route handler.
 */
var RESERVED_PROPS = {
  location: true,
  handler: true,
  name: true,
  path: true,
  children: true // ReactChildren
};

/**
 * The ref name that can be used to reference the active route component.
 */
var REF_NAME = '__activeRoute__';

/**
 * <Route> components specify components that are rendered to the page when the
 * URL matches a given pattern.
 *
 * Routes are arranged in a nested tree structure. When a new URL is requested,
 * the tree is searched depth-first to find a route whose path matches the URL.
 * When one is found, all routes in the tree that lead to it are considered
 * "active" and their components are rendered into the DOM, nested in the same
 * order as they are in the tree.
 *
 * Unlike Ember, a nested route's path does not build upon that of its parents.
 * This may seem like it creates more work up front in specifying URLs, but it
 * has the nice benefit of decoupling nested UI from "nested" URLs.
 *
 * The preferred way to configure a router is using JSX. The XML-like syntax is
 * a great way to visualize how routes are laid out in an application.
 *
 *   React.renderComponent((
 *     <Route handler={App}>
 *       <Route name="login" handler={Login}/>
 *       <Route name="logout" handler={Logout}/>
 *       <Route name="about" handler={About}/>
 *     </Route>
 *   ), document.body);
 *
 * If you don't use JSX, you can also assemble a Router programmatically using
 * the standard React component JavaScript API.
 *
 *   React.renderComponent((
 *     Route({ handler: App },
 *       Route({ name: 'login', handler: Login }),
 *       Route({ name: 'logout', handler: Logout }),
 *       Route({ name: 'about', handler: About })
 *     )
 *   ), document.body);
 *
 * Handlers for Route components that contain children can render their active
 * child route using the activeRouteHandler prop.
 *
 *   var App = React.createClass({
 *     render: function () {
 *       return (
 *

 *           {this.props.activeRouteHandler()}
 *

 *       );
 *     }
 *   });
 */
var Route = React.createClass({
  displayName: 'Route',

  statics: {

    getUnreservedProps: function (props) {
      return withoutProperties(props, RESERVED_PROPS);
    },

    /**
     * Handles errors that were thrown asynchronously. By default, the
     * error is re-thrown so we don't swallow them silently.
     */
    handleAsyncError: function (error, route) {
      throw error; // This error probably originated in a transition hook.
    },

    /**
     * Handles cancelled transitions. By default, redirects replace the
     * current URL and aborts roll it back.
     */
    handleCancelledTransition: function (transition, route) {
      var reason = transition.cancelReason;

      if (reason instanceof Redirect) {
        replaceWith(reason.to, reason.params, reason.query);
      } else if (reason instanceof Abort) {
        goBack();
      }
    }

  },

  propTypes: {
    location: React.PropTypes.oneOf([ 'hash', 'history' ]).isRequired,
    handler: React.PropTypes.any.isRequired,
    path: React.PropTypes.string,
    name: React.PropTypes.string,
  },

  getDefaultProps: function () {
    return {
      location: 'hash'
    };
  },

  getInitialState: function () {
    return {};
  },

  componentWillMount: function () {
    RouteStore.registerRoute(this);

    if (!URLStore.isSetup() && ExecutionEnvironment.canUseDOM)
      URLStore.setup(this.props.location);

    URLStore.addChangeListener(this.handleRouteChange);
  },

  componentDidMount: function () {
    this.dispatch(URLStore.getCurrentPath());
  },

  componentWillUnmount: function () {
    URLStore.removeChangeListener(this.handleRouteChange);
  },

  handleRouteChange: function () {
    this.dispatch(URLStore.getCurrentPath());
  },

  /**
   * Performs a depth-first search for the first route in the tree that matches
   * on the given path. Returns an array of all routes in the tree leading to
   * the one that matched in the format { route, params } where params is an
   * object that contains the URL parameters relevant to that route. Returns
   * null if no route in the tree matches the path.
   *
   *   ( <Route handler={App}>
   *       <Route name="posts" handler={Posts}>
   *         <Route name="newPost" path="/posts/new" handler={NewPost}/>
   *         <Route name="showPost" path="/posts/:id" handler={Post}/>
   *       </Route>
   *     </Route>
   *   ).match('/posts/123'); => [ { route: <AppRoute>, params: {} },
   *                               { route: <PostsRoute>, params: {} },
   *                               { route: <PostRoute>, params: { id: '123' } } ]
   */
  match: function (path) {
    return findMatches(Path.withoutQuery(path), this);
  },

  /**
   * Performs a transition to the given path and returns a promise for the
   * Transition object that was used.
   *
   * In order to do this, the router first determines which routes are involved
   * in the transition beginning with the current route, up the route tree to
   * the first parent route that is shared with the destination route, and back
   * down the tree to the destination route. The willTransitionFrom static
   * method is invoked on all route handlers we're transitioning away from, in
   * reverse nesting order. Likewise, the willTransitionTo static method
   * is invoked on all route handlers we're transitioning to.
   *
   * Both willTransitionFrom and willTransitionTo hooks may either abort or
   * redirect the transition. If they need to resolve asynchronously, they may
   * return a promise.
   *
   * Any error that occurs asynchronously during the transition is re-thrown in
   * the top-level scope unless returnRejectedPromise is true, in which case a
   * rejected promise is returned so the caller may handle the error.
   *
   * Note: This function does not update the URL in a browser's location bar.
   * If you want to keep the URL in sync with transitions, use Router.transitionTo,
   * Router.replaceWith, or Router.goBack instead.
   */
  dispatch: function (path, returnRejectedPromise) {
    var transition = new Transition(path);
    var route = this;

    var promise = syncWithTransition(route, transition).then(function (newState) {
      if (transition.isCancelled) {
        Route.handleCancelledTransition(transition, route);
      } else if (newState) {
        ActiveStore.updateState(newState);
      }

      return transition;
    });

    if (!returnRejectedPromise) {
      promise = promise.then(undefined, function (error) {
        // Use setTimeout to break the promise chain.
        setTimeout(function () {
          Route.handleAsyncError(error, route);
        });
      });
    }

    return promise;
  },

  render: function () {
    if (!this.state.path)
      return null;

    return this.props.handler(computeHandlerProps(this.state.matches || [], this.state.activeQuery));
  }

});

function Transition(path) {
  this.path = path;
  this.cancelReason = null;
  this.isCancelled = false;
}

mergeProperties(Transition.prototype, {

  abort: function () {
    this.cancelReason = new Abort();
    this.isCancelled = true;
  },

  redirect: function (to, params, query) {
    this.cancelReason = new Redirect(to, params, query);
    this.isCancelled = true;
  },

  retry: function () {
    transitionTo(this.path);
  }

});

function Abort() {}

function Redirect(to, params, query) {
  this.to = to;
  this.params = params;
  this.query = query;
}

function findMatches(path, route) {
  var children = route.props.children, matches;

  // Check the subtree first to find the most deeply-nested match.
  if (Array.isArray(children)) {
    for (var i = 0, len = children.length; matches == null && i < len; ++i) {
      matches = findMatches(path, children[i]);
    }
  } else if (children) {
    matches = findMatches(path, children);
  }

  if (matches) {
    var rootParams = getRootMatch(matches).params;
    var params = {};

    Path.extractParamNames(route.props.path).forEach(function (paramName) {
      params[paramName] = rootParams[paramName];
    });

    matches.unshift(makeMatch(route, params));

    return matches;
  }

  // No routes in the subtree matched, so check this route.
  var params = Path.extractParams(route.props.path, path);

  if (params)
    return [ makeMatch(route, params) ];

  return null;
}

function makeMatch(route, params) {
  return { route: route, params: params };
}

function hasMatch(matches, match) {
  return matches.some(function (m) {
    if (m.route !== match.route)
      return false;

    for (var property in m.params) {
      if (m.params[property] !== match.params[property])
        return false;
    }

    return true;
  });
}

function getRootMatch(matches) {
  return matches[matches.length - 1];
}

function updateMatchComponents(matches, refs) {
  var i = 0, component;
  while (component = refs[REF_NAME]) {
    matches[i++].component = component;
    refs = component.refs;
  }
}

/**
 * Runs all transition hooks that are required to get from the current state
 * to the state specified by the given transition and updates the current state
 * if they all pass successfully. Returns a promise that resolves to the new
 * state if it needs to be updated, or undefined if not.
 */
function syncWithTransition(route, transition) {
  if (route.state.path === transition.path)
    return Promise.resolve(); // Nothing to do!

  var currentMatches = route.state.matches;
  var nextMatches = route.match(transition.path);

  warning(
    nextMatches,
    'No route matches path "' + transition.path + '". Make sure you have ' +
    '<Route path="' + transition.path + '"> somewhere in your routes'
  );

  if (!nextMatches)
    nextMatches = [];

  var fromMatches, toMatches;
  if (currentMatches) {
    updateMatchComponents(currentMatches, route.refs);

    fromMatches = currentMatches.filter(function (match) {
      return !hasMatch(nextMatches, match);
    });

    toMatches = nextMatches.filter(function (match) {
      return !hasMatch(currentMatches, match);
    });
  } else {
    fromMatches = [];
    toMatches = nextMatches;
  }

  return checkTransitionFromHooks(fromMatches, transition).then(function () {
    if (transition.isCancelled)
      return; // No need to continue.

    return checkTransitionToHooks(toMatches, transition).then(function () {
      if (transition.isCancelled)
        return; // No need to continue.

      var rootMatch = getRootMatch(nextMatches);
      var params = (rootMatch && rootMatch.params) || {};
      var query = Path.extractQuery(transition.path) || {};
      var state = {
        path: transition.path,
        matches: nextMatches,
        activeParams: params,
        activeQuery: query,
        activeRoutes: nextMatches.map(function (match) {
          return match.route;
        })
      };

      route.setState(state);

      return state;
    });
  });
}

/**
 * Calls the willTransitionFrom hook of all handlers in the given matches
 * serially in reverse with the transition object and the current instance of
 * the route's handler, so that the deepest nested handlers are called first.
 * Returns a promise that resolves after the last handler.
 */
function checkTransitionFromHooks(matches, transition) {
  var promise = Promise.resolve();

  reversedArray(matches).forEach(function (match) {
    promise = promise.then(function () {
      var handler = match.route.props.handler;

      if (!transition.isCancelled && handler.willTransitionFrom)
        return handler.willTransitionFrom(transition, match.component);
    });
  });

  return promise;
}

/**
 * Calls the willTransitionTo hook of all handlers in the given matches serially
 * with the transition object and any params that apply to that handler. Returns
 * a promise that resolves after the last handler.
 */
function checkTransitionToHooks(matches, transition) {
  var promise = Promise.resolve();

  matches.forEach(function (match, index) {
    promise = promise.then(function () {
      var handler = match.route.props.handler;

      if (!transition.isCancelled && handler.willTransitionTo)
        return handler.willTransitionTo(transition, match.params);
    });
  });

  return promise;
}

/**
 * Returns a props object for a component that renders the routes in the
 * given matches.
 */
function computeHandlerProps(matches, query) {
  var props = {
    ref: null,
    key: null,
    params: null,
    query: null,
    activeRouteHandler: emptyFunction.thatReturnsNull
  };

  var childHandler;
  reversedArray(matches).forEach(function (match) {
    var route = match.route;

    props = Route.getUnreservedProps(route.props);

    props.ref = REF_NAME;
    props.key = Path.injectParams(route.props.path, match.params);
    props.params = match.params;
    props.query = query;

    if (childHandler) {
      props.activeRouteHandler = childHandler;
    } else {
      props.activeRouteHandler = emptyFunction.thatReturnsNull;
    }

    childHandler = function (props, addedProps) {
      if (arguments.length > 2 && typeof arguments[2] !== 'undefined')
        throw new Error('Passing children to a route handler is not supported');

      return route.props.handler(mergeProperties(props, addedProps));
    }.bind(this, props);
  });

  return props;
}

function reversedArray(array) {
  return array.slice(0).reverse();
}

module.exports = Route;
<commit_msg>[removed] Dependency on react/lib/emptyFunction<commit_after>var React = require('react');
var warning = require('react/lib/warning');
var invariant = require('react/lib/invariant');
var ExecutionEnvironment = require('react/lib/ExecutionEnvironment');
var mergeProperties = require('../helpers/mergeProperties');
var goBack = require('../helpers/goBack');
var replaceWith = require('../helpers/replaceWith');
var transitionTo = require('../helpers/transitionTo');
var withoutProperties = require('../helpers/withoutProperties');
var Path = require('../helpers/Path');
var ActiveStore = require('../stores/ActiveStore');
var RouteStore = require('../stores/RouteStore');
var URLStore = require('../stores/URLStore');
var Promise = require('es6-promise').Promise;

/**
 * A map of <Route> component props that are reserved for use by the
 * router and/or React. All other props are considered "static" and
 * are passed through to the route handler.
 */
var RESERVED_PROPS = {
  location: true,
  handler: true,
  name: true,
  path: true,
  children: true // ReactChildren
};

/**
 * The ref name that can be used to reference the active route component.
 */
var REF_NAME = '__activeRoute__';

/**
 * <Route> components specify components that are rendered to the page when the
 * URL matches a given pattern.
 *
 * Routes are arranged in a nested tree structure. When a new URL is requested,
 * the tree is searched depth-first to find a route whose path matches the URL.
 * When one is found, all routes in the tree that lead to it are considered
 * "active" and their components are rendered into the DOM, nested in the same
 * order as they are in the tree.
 *
 * Unlike Ember, a nested route's path does not build upon that of its parents.
 * This may seem like it creates more work up front in specifying URLs, but it
 * has the nice benefit of decoupling nested UI from "nested" URLs.
 *
 * The preferred way to configure a router is using JSX. The XML-like syntax is
 * a great way to visualize how routes are laid out in an application.
 *
 *   React.renderComponent((
 *     <Route handler={App}>
 *       <Route name="login" handler={Login}/>
 *       <Route name="logout" handler={Logout}/>
 *       <Route name="about" handler={About}/>
 *     </Route>
 *   ), document.body);
 *
 * If you don't use JSX, you can also assemble a Router programmatically using
 * the standard React component JavaScript API.
 *
 *   React.renderComponent((
 *     Route({ handler: App },
 *       Route({ name: 'login', handler: Login }),
 *       Route({ name: 'logout', handler: Logout }),
 *       Route({ name: 'about', handler: About })
 *     )
 *   ), document.body);
 *
 * Handlers for Route components that contain children can render their active
 * child route using the activeRouteHandler prop.
 *
 *   var App = React.createClass({
 *     render: function () {
 *       return (
 *

 *           {this.props.activeRouteHandler()}
 *

 *       );
 *     }
 *   });
 */
var Route = React.createClass({
  displayName: 'Route',

  statics: {

    getUnreservedProps: function (props) {
      return withoutProperties(props, RESERVED_PROPS);
    },

    /**
     * Handles errors that were thrown asynchronously. By default, the
     * error is re-thrown so we don't swallow them silently.
     */
    handleAsyncError: function (error, route) {
      throw error; // This error probably originated in a transition hook.
    },

    /**
     * Handles cancelled transitions. By default, redirects replace the
     * current URL and aborts roll it back.
     */
    handleCancelledTransition: function (transition, route) {
      var reason = transition.cancelReason;

      if (reason instanceof Redirect) {
        replaceWith(reason.to, reason.params, reason.query);
      } else if (reason instanceof Abort) {
        goBack();
      }
    }

  },

  propTypes: {
    location: React.PropTypes.oneOf([ 'hash', 'history' ]).isRequired,
    handler: React.PropTypes.any.isRequired,
    path: React.PropTypes.string,
    name: React.PropTypes.string,
  },

  getDefaultProps: function () {
    return {
      location: 'hash'
    };
  },

  getInitialState: function () {
    return {};
  },

  componentWillMount: function () {
    RouteStore.registerRoute(this);

    if (!URLStore.isSetup() && ExecutionEnvironment.canUseDOM)
      URLStore.setup(this.props.location);

    URLStore.addChangeListener(this.handleRouteChange);
  },

  componentDidMount: function () {
    this.dispatch(URLStore.getCurrentPath());
  },

  componentWillUnmount: function () {
    URLStore.removeChangeListener(this.handleRouteChange);
  },

  handleRouteChange: function () {
    this.dispatch(URLStore.getCurrentPath());
  },

  /**
   * Performs a depth-first search for the first route in the tree that matches
   * on the given path. Returns an array of all routes in the tree leading to
   * the one that matched in the format { route, params } where params is an
   * object that contains the URL parameters relevant to that route. Returns
   * null if no route in the tree matches the path.
   *
   *   ( <Route handler={App}>
   *       <Route name="posts" handler={Posts}>
   *         <Route name="newPost" path="/posts/new" handler={NewPost}/>
   *         <Route name="showPost" path="/posts/:id" handler={Post}/>
   *       </Route>
   *     </Route>
   *   ).match('/posts/123'); => [ { route: <AppRoute>, params: {} },
   *                               { route: <PostsRoute>, params: {} },
   *                               { route: <PostRoute>, params: { id: '123' } } ]
   */
  match: function (path) {
    return findMatches(Path.withoutQuery(path), this);
  },

  /**
   * Performs a transition to the given path and returns a promise for the
   * Transition object that was used.
   *
   * In order to do this, the router first determines which routes are involved
   * in the transition beginning with the current route, up the route tree to
   * the first parent route that is shared with the destination route, and back
   * down the tree to the destination route. The willTransitionFrom static
   * method is invoked on all route handlers we're transitioning away from, in
   * reverse nesting order. Likewise, the willTransitionTo static method
   * is invoked on all route handlers we're transitioning to.
   *
   * Both willTransitionFrom and willTransitionTo hooks may either abort or
   * redirect the transition. If they need to resolve asynchronously, they may
   * return a promise.
   *
   * Any error that occurs asynchronously during the transition is re-thrown in
   * the top-level scope unless returnRejectedPromise is true, in which case a
   * rejected promise is returned so the caller may handle the error.
   *
   * Note: This function does not update the URL in a browser's location bar.
   * If you want to keep the URL in sync with transitions, use Router.transitionTo,
   * Router.replaceWith, or Router.goBack instead.
   */
  dispatch: function (path, returnRejectedPromise) {
    var transition = new Transition(path);
    var route = this;

    var promise = syncWithTransition(route, transition).then(function (newState) {
      if (transition.isCancelled) {
        Route.handleCancelledTransition(transition, route);
      } else if (newState) {
        ActiveStore.updateState(newState);
      }

      return transition;
    });

    if (!returnRejectedPromise) {
      promise = promise.then(undefined, function (error) {
        // Use setTimeout to break the promise chain.
        setTimeout(function () {
          Route.handleAsyncError(error, route);
        });
      });
    }

    return promise;
  },

  render: function () {
    if (!this.state.path)
      return null;

    return this.props.handler(computeHandlerProps(this.state.matches || [], this.state.activeQuery));
  }

});

function Transition(path) {
  this.path = path;
  this.cancelReason = null;
  this.isCancelled = false;
}

mergeProperties(Transition.prototype, {

  abort: function () {
    this.cancelReason = new Abort();
    this.isCancelled = true;
  },

  redirect: function (to, params, query) {
    this.cancelReason = new Redirect(to, params, query);
    this.isCancelled = true;
  },

  retry: function () {
    transitionTo(this.path);
  }

});

function Abort() {}

function Redirect(to, params, query) {
  this.to = to;
  this.params = params;
  this.query = query;
}

function findMatches(path, route) {
  var children = route.props.children, matches;

  // Check the subtree first to find the most deeply-nested match.
  if (Array.isArray(children)) {
    for (var i = 0, len = children.length; matches == null && i < len; ++i) {
      matches = findMatches(path, children[i]);
    }
  } else if (children) {
    matches = findMatches(path, children);
  }

  if (matches) {
    var rootParams = getRootMatch(matches).params;
    var params = {};

    Path.extractParamNames(route.props.path).forEach(function (paramName) {
      params[paramName] = rootParams[paramName];
    });

    matches.unshift(makeMatch(route, params));

    return matches;
  }

  // No routes in the subtree matched, so check this route.
  var params = Path.extractParams(route.props.path, path);

  if (params)
    return [ makeMatch(route, params) ];

  return null;
}

function makeMatch(route, params) {
  return { route: route, params: params };
}

function hasMatch(matches, match) {
  return matches.some(function (m) {
    if (m.route !== match.route)
      return false;

    for (var property in m.params) {
      if (m.params[property] !== match.params[property])
        return false;
    }

    return true;
  });
}

function getRootMatch(matches) {
  return matches[matches.length - 1];
}

function updateMatchComponents(matches, refs) {
  var i = 0, component;
  while (component = refs[REF_NAME]) {
    matches[i++].component = component;
    refs = component.refs;
  }
}

/**
 * Runs all transition hooks that are required to get from the current state
 * to the state specified by the given transition and updates the current state
 * if they all pass successfully. Returns a promise that resolves to the new
 * state if it needs to be updated, or undefined if not.
 */
function syncWithTransition(route, transition) {
  if (route.state.path === transition.path)
    return Promise.resolve(); // Nothing to do!

  var currentMatches = route.state.matches;
  var nextMatches = route.match(transition.path);

  warning(
    nextMatches,
    'No route matches path "' + transition.path + '". Make sure you have ' +
    '<Route path="' + transition.path + '"> somewhere in your routes'
  );

  if (!nextMatches)
    nextMatches = [];

  var fromMatches, toMatches;
  if (currentMatches) {
    updateMatchComponents(currentMatches, route.refs);

    fromMatches = currentMatches.filter(function (match) {
      return !hasMatch(nextMatches, match);
    });

    toMatches = nextMatches.filter(function (match) {
      return !hasMatch(currentMatches, match);
    });
  } else {
    fromMatches = [];
    toMatches = nextMatches;
  }

  return checkTransitionFromHooks(fromMatches, transition).then(function () {
    if (transition.isCancelled)
      return; // No need to continue.

    return checkTransitionToHooks(toMatches, transition).then(function () {
      if (transition.isCancelled)
        return; // No need to continue.

      var rootMatch = getRootMatch(nextMatches);
      var params = (rootMatch && rootMatch.params) || {};
      var query = Path.extractQuery(transition.path) || {};
      var state = {
        path: transition.path,
        matches: nextMatches,
        activeParams: params,
        activeQuery: query,
        activeRoutes: nextMatches.map(function (match) {
          return match.route;
        })
      };

      route.setState(state);

      return state;
    });
  });
}

/**
 * Calls the willTransitionFrom hook of all handlers in the given matches
 * serially in reverse with the transition object and the current instance of
 * the route's handler, so that the deepest nested handlers are called first.
 * Returns a promise that resolves after the last handler.
 */
function checkTransitionFromHooks(matches, transition) {
  var promise = Promise.resolve();

  reversedArray(matches).forEach(function (match) {
    promise = promise.then(function () {
      var handler = match.route.props.handler;

      if (!transition.isCancelled && handler.willTransitionFrom)
        return handler.willTransitionFrom(transition, match.component);
    });
  });

  return promise;
}

/**
 * Calls the willTransitionTo hook of all handlers in the given matches serially
 * with the transition object and any params that apply to that handler. Returns
 * a promise that resolves after the last handler.
 */
function checkTransitionToHooks(matches, transition) {
  var promise = Promise.resolve();

  matches.forEach(function (match, index) {
    promise = promise.then(function () {
      var handler = match.route.props.handler;

      if (!transition.isCancelled && handler.willTransitionTo)
        return handler.willTransitionTo(transition, match.params);
    });
  });

  return promise;
}

/**
 * Returns a props object for a component that renders the routes in the
 * given matches.
 */
function computeHandlerProps(matches, query) {
  var props = {
    ref: null,
    key: null,
    params: null,
    query: null,
    activeRouteHandler: returnNull
  };

  var childHandler;
  reversedArray(matches).forEach(function (match) {
    var route = match.route;

    props = Route.getUnreservedProps(route.props);

    props.ref = REF_NAME;
    props.key = Path.injectParams(route.props.path, match.params);
    props.params = match.params;
    props.query = query;

    if (childHandler) {
      props.activeRouteHandler = childHandler;
    } else {
      props.activeRouteHandler = returnNull;
    }

    childHandler = function (props, addedProps) {
      if (arguments.length > 2 && typeof arguments[2] !== 'undefined')
        throw new Error('Passing children to a route handler is not supported');

      return route.props.handler(mergeProperties(props, addedProps));
    }.bind(this, props);
  });

  return props;
}

function returnNull() {
  return null;
}

function reversedArray(array) {
  return array.slice(0).reverse();
}

module.exports = Route;
<|endoftext|>