<commit_before>// For a detailed explanation regarding each configuration property, visit:
// https://jestjs.io/docs/en/configuration.html

module.exports = {
  // All imported modules in your tests should be mocked automatically
  // automock: false,

  // Stop running tests after `n` failures
  // bail: 0,

  // Respect "browser" field in package.json when resolving modules
  // browser: false,

  // The directory where Jest should store its cached dependency information

  // Automatically clear mock calls and instances between every test
  // clearMocks: true,

  // Indicates whether the coverage information should be collected while executing the test
  collectCoverage: true,

  // An array of glob patterns indicating a set of files for which coverage information should be collected
  collectCoverageFrom: ["../lib/main.node.js"],

  // The directory where Jest should output its coverage files
  coverageDirectory: "coverage",

  // An array of regexp pattern strings used to skip coverage collection
  // coveragePathIgnorePatterns: [
  //   "\\\\node_modules\\\\"
  // ],

  // A list of reporter names that Jest uses when writing coverage reports
  coverageReporters: [
     "json",
     "text",
     "lcov",
     "clover"
  ],

  // An object that configures minimum threshold enforcement for coverage results
  // coverageThreshold: null,

  // A path to a custom dependency extractor
  // dependencyExtractor: null,

  // Make calling deprecated APIs throw helpful error messages
  // errorOnDeprecated: false,

  // Force coverage collection from ignored files usin a array of glob patterns
  // forceCoverageMatch: [],

  // A path to a module which exports an async function that is triggered once before all test suites
  // globalSetup: null,

  // A path to a module which exports an async function that is triggered once after all test suites
  // globalTeardown: null,

  // A set of global variables that need to be available in all test environments
  // globals: {},

  // An array of directory names to be searched recursively up from the requiring module's location
  // moduleDirectories: [
  //   "node_modules"
  // ],

  // An array of file extensions your modules use
  // moduleFileExtensions: [
  //   "js",
  //   "json",
  //   "jsx",
  //   "ts",
  //   "tsx",
  //   "node"
  // ],

  // A map from regular expressions to module names that allow to stub out resources with a single module
  // moduleNameMapper: {},

  // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
  // modulePathIgnorePatterns: [],

  // Activates notifications for test results
  // notify: false,

  // An enum that specifies notification mode. Requires { notify: true }
  // notifyMode: "failure-change",

  // A preset that is used as a base for Jest's configuration
  // preset: null,

  // Run tests from one or more projects
  // projects: null,

  // Use this configuration option to add custom reporters to Jest
  // reporters: undefined,

  // Automatically reset mock state between every test
  // resetMocks: false,

  // Reset the module registry before running each individual test
  // resetModules: false,

  // A path to a custom resolver
  // resolver: null,

  // Automatically restore mock state between every test
  // restoreMocks: false,

  // The root directory that Jest should scan for tests and modules within
  rootDir: "./spec/",

  // A list of paths to directories that Jest should use to search for files in
  // roots: [
  //   "<rootDir>"
  // ],

  // Allows you to use a custom runner instead of Jest's default test runner
  // runner: "jest-runner",

  // The paths to modules that run some code to configure or set up the testing environment before each test
  setupFiles : ["./helpers/mock.js", "./helpers/customMatchers.js", "./helpers/skeleton.js"],

  // A list of paths to modules that run some code to configure or set up the testing framework before each test
  // setupFilesAfterEnv: [],

  // A list of paths to snapshot serializer modules Jest should use for snapshot testing
  // snapshotSerializers: [],

  // The test environment that will be used for testing
  testEnvironment: "node",

  // Options that will be passed to the testEnvironment
  // testEnvironmentOptions: {},

  // Adds a location field to test results
  // testLocationInResults: false,

  // The glob patterns Jest uses to detect test files
  // testMatch: [
  //   "**/__tests__/**/*.[jt]s?(x)",
  //   "**/?(*.)+(spec|test).[tj]s?(x)"
  // ],
  // testMatch: ["./**/*[sS]pec.js"],
  testMatch: ["./**/SceneSpec.js"],

  // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
  // testPathIgnorePatterns: [
  //   "\\\\node_modules\\\\"
  // ],

  // The regexp pattern or array of patterns that Jest uses to detect test files
  // testRegex: [],

  // This option allows the use of a custom results processor
  // testResultsProcessor: null,

  // This option allows use of a custom test runner
  // testRunner: "jasmine2",

  // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
  // testURL: "http://localhost",

  // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
  // timers: "real",

  // A map from regular expressions to paths to transformers
  // transform: null,

  // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
  // transformIgnorePatterns: [
  //   "\\\\node_modules\\\\"
  // ],

  // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
  // unmockedModulePathPatterns: undefined,

  // Indicates whether each individual test should be reported during the run
  // verbose: null,

  // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
  // watchPathIgnorePatterns: [],

  // Whether to use watchman for file crawling
  // watchman: true,
};
<commit_msg>export coverage<commit_after>// For a detailed explanation regarding each configuration property, visit:
// https://jestjs.io/docs/en/configuration.html

module.exports = {
  // All imported modules in your tests should be mocked automatically
  // automock: false,

  // Stop running tests after `n` failures
  // bail: 0,

  // Respect "browser" field in package.json when resolving modules
  // browser: false,

  // The directory where Jest should store its cached dependency information

  // Automatically clear mock calls and instances between every test
  // clearMocks: true,

  // Indicates whether the coverage information should be collected while executing the test
  collectCoverage: true,

  // An array of glob patterns indicating a set of files for which coverage information should be collected
  collectCoverageFrom: ["lib/main.node.js"],

  // The directory where Jest should output its coverage files
  coverageDirectory: "coverage",

  // An array of regexp pattern strings used to skip coverage collection
  // coveragePathIgnorePatterns: [
  //   "\\\\node_modules\\\\"
  // ],

  // A list of reporter names that Jest uses when writing coverage reports
  coverageReporters: [
  // "json",
  // "text",
   "lcov"
  // "clover"
  ],

  // An object that configures minimum threshold enforcement for coverage results
  // coverageThreshold: null,

  // A path to a custom dependency extractor
  // dependencyExtractor: null,

  // Make calling deprecated APIs throw helpful error messages
  // errorOnDeprecated: false,

  // Force coverage collection from ignored files usin a array of glob patterns
  // forceCoverageMatch: [],

  // A path to a module which exports an async function that is triggered once before all test suites
  // globalSetup: null,

  // A path to a module which exports an async function that is triggered once after all test suites
  // globalTeardown: null,

  // A set of global variables that need to be available in all test environments
  // globals: {},

  // An array of directory names to be searched recursively up from the requiring module's location
  // moduleDirectories: [
  //   "node_modules"
  // ],

  // An array of file extensions your modules use
  // moduleFileExtensions: [
  //   "js",
  //   "json",
  //   "jsx",
  //   "ts",
  //   "tsx",
  //   "node"
  // ],

  // A map from regular expressions to module names that allow to stub out resources with a single module
  // moduleNameMapper: {},

  // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
  // modulePathIgnorePatterns: [],

  // Activates notifications for test results
  // notify: false,

  // An enum that specifies notification mode. Requires { notify: true }
  // notifyMode: "failure-change",

  // A preset that is used as a base for Jest's configuration
  // preset: null,

  // Run tests from one or more projects
  // projects: null,

  // Use this configuration option to add custom reporters to Jest
  // reporters: undefined,

  // Automatically reset mock state between every test
  // resetMocks: false,

  // Reset the module registry before running each individual test
  // resetModules: false,

  // A path to a custom resolver
  // resolver: null,

  // Automatically restore mock state between every test
  // restoreMocks: false,

  // The root directory that Jest should scan for tests and modules within
  rootDir: "./",

  // A list of paths to directories that Jest should use to search for files in
  // roots: [
  //   "<rootDir>"
  // ],

  // Allows you to use a custom runner instead of Jest's default test runner
  // runner: "jest-runner",

  // The paths to modules that run some code to configure or set up the testing environment before each test
  setupFiles : ["./spec/helpers/mock.js", "./spec/helpers/customMatchers.js", "./spec/helpers/skeleton.js"],

  // A list of paths to modules that run some code to configure or set up the testing framework before each test
  // setupFilesAfterEnv: [],

  // A list of paths to snapshot serializer modules Jest should use for snapshot testing
  // snapshotSerializers: [],

  // The test environment that will be used for testing
  testEnvironment: "node",

  // Options that will be passed to the testEnvironment
  // testEnvironmentOptions: {},

  // Adds a location field to test results
  // testLocationInResults: false,

  // The glob patterns Jest uses to detect test files
  // testMatch: [
  //   "**/__tests__/**/*.[jt]s?(x)",
  //   "**/?(*.)+(spec|test).[tj]s?(x)"
  // ],
  // testMatch: ["./**/*[sS]pec.js"],
  testMatch: ["./**/SceneSpec.js"],

  // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
  // testPathIgnorePatterns: [
  //   "\\\\node_modules\\\\"
  // ],

  // The regexp pattern or array of patterns that Jest uses to detect test files
  // testRegex: [],

  // This option allows the use of a custom results processor
  // testResultsProcessor: null,

  // This option allows use of a custom test runner
  // testRunner: "jasmine2",

  // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
  // testURL: "http://localhost",

  // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
  // timers: "real",

  // A map from regular expressions to paths to transformers
  // transform: null,

  // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
  // transformIgnorePatterns: [
  //   "\\\\node_modules\\\\"
  // ],

  // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
  // unmockedModulePathPatterns: undefined,

  // Indicates whether each individual test should be reported during the run
  // verbose: null,

  // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
  // watchPathIgnorePatterns: [],

  // Whether to use watchman for file crawling
  // watchman: true,
};
<|endoftext|>


<commit_before>module.exports = {
  collectCoverage: true,
  coverageDirectory: "./coverage/",
  coveragePathIgnorePatterns: ["/.cache/", "/node_modules/"],
  globals: {
    __PATH_PREFIX__: ``,
  },
  moduleNameMapper: {
    ".+\\.(css|styl|less|sass|scss)$": `identity-obj-proxy`,
    ".+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": `<rootDir>/__mocks__/file-mock.js`,
  },
  setupFiles: [`<rootDir>/loadershim.js`],
  testPathIgnorePatterns: [`node_modules`, `.cache`],
  testURL: `http://localhost:8000`,
  transform: {
    "^.+\\.jsx?$": `<rootDir>/jest-preprocess.js`,
  },
  transformIgnorePatterns: [`node_modules/(?!(gatsby)/)`],
}
<commit_msg>tests(jest.config.js) ignore cypress on unit tests<commit_after>module.exports = {
  collectCoverage: true,
  coverageDirectory: "./coverage/",
  coveragePathIgnorePatterns: ["/.cache/", "/cypress/", "/node_modules/"],
  globals: {
    __PATH_PREFIX__: ``,
  },
  moduleNameMapper: {
    ".+\\.(css|styl|less|sass|scss)$": `identity-obj-proxy`,
    ".+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": `<rootDir>/__mocks__/file-mock.js`,
  },
  setupFiles: [`<rootDir>/loadershim.js`],
  testPathIgnorePatterns: [".cache", "cypress", "node_modules"],
  testURL: `http://localhost:8000`,
  transform: {
    "^.+\\.jsx?$": `<rootDir>/jest-preprocess.js`,
  },
  transformIgnorePatterns: [`node_modules/(?!(gatsby)/)`],
}
<|endoftext|>


<commit_before>// test dependencies that require transformation
const pluginsToTransform = [
  'tdp_comments',
  'tdp_ui',
  'tdp_core',
  'bootstrap-sass', // required to transform for phovea_ui
  'phovea_ui',
  'phovea_clue',
  'phovea_core',
  'phovea_security_flask',
  'sandbox',
  'tdp_marvinjs'
].join('|');

/**
 * TODO check if we can process inline webpack loaders (e.g. as found in https://github.com/phovea/phovea_ui/blob/master/src/_bootstrap.ts)
 * see also https://jestjs.io/docs/en/webpack#mocking-css-modules
 */
module.exports = {
  transform: {
    "^.+\\.tsx?$": "ts-jest",
    "\\.xml$": "jest-raw-loader"
  },
  testRegex: "(.*(test|spec))\\.(tsx?)$",
  testURL: "http://localhost/",
  moduleFileExtensions: [
    "ts",
    "tsx",
    "js",
    "jsx",
    "json",
    "node"
  ],
  modulePaths: [
    "src",
    "../node_modules",
    "../"
  ],
  transformIgnorePatterns: [`../node_modules/(?!${pluginsToTransform})`],
  globals: {
    "__VERSION__": "TEST_VERSION",
    "__APP_CONTEXT__": "TEST_CONTEXT"
  },
  moduleNameMapper: {
    "^.+\\.(css|less|scss|sass)$": "identity-obj-proxy",
    "imports-loader?.*": "imports-loader",
    "raw-loader?.*": "raw-loader",
    "file-loader?.*": "file-loader",
    "script-loader?.*": "script-loader"
  }
}
<commit_msg>fix jest config<commit_after>// test dependencies that require transformation
const pluginsToTransform = [
  'tdp_core'
].join('|');

/**
 * TODO check if we can process inline webpack loaders (e.g. as found in https://github.com/phovea/phovea_ui/blob/master/src/_bootstrap.ts)
 * see also https://jestjs.io/docs/en/webpack#mocking-css-modules
 */
module.exports = {
  transform: {
    "^.+\\.tsx?$": "ts-jest",
    "\\.xml$": "jest-raw-loader"
  },
  testRegex: "(.*(test|spec))\\.(tsx?)$",
  testURL: "http://localhost/",
  moduleFileExtensions: [
    "ts",
    "tsx",
    "js",
    "jsx",
    "json",
    "node"
  ],
  modulePaths: [
    "src",
    "../"
  ],
  transformIgnorePatterns: [`../node_modules/(?!${pluginsToTransform})`],
  globals: {
    "__VERSION__": "TEST_VERSION",
    "__APP_CONTEXT__": "TEST_CONTEXT"
  },
  moduleNameMapper: {
    "^.+\\.(css|less|scss|sass)$": "identity-obj-proxy",
    "imports-loader?.*": "imports-loader",
    "raw-loader?.*": "raw-loader",
    "file-loader?.*": "file-loader",
    "script-loader?.*": "script-loader"
  }
}
<|endoftext|>


<commit_before>module.exports = {
    rootDir: "./src",
    setupFilesAfterEnv: ["<rootDir>/setupTests.js"],
    watchPlugins: ["jest-watch-typeahead/filename", "jest-watch-typeahead/testname"],
    transform: {
        "^.+\\.[t|j]sx?$": "babel-jest",
        "\\.(html|xml)$": "jest-raw-loader",
    },
    moduleNameMapper: {
        "\\.(css|less|sass|scss)$": "identity-obj-proxy",
    },
    testEnvironment: "jsdom",
    transformIgnorePatterns: ["/node_modules/(?!.*patternslib/*).+\\.[t|j]sx?$"],
};
<commit_msg>Jest svg load error: Load svg as raw also in tests.<commit_after>module.exports = {
    rootDir: "./src",
    setupFilesAfterEnv: ["<rootDir>/setupTests.js"],
    watchPlugins: ["jest-watch-typeahead/filename", "jest-watch-typeahead/testname"],
    transform: {
        "^.+\\.[t|j]sx?$": "babel-jest",
        "\\.(html|xml|svg)$": "jest-raw-loader",
    },
    moduleNameMapper: {
        "\\.(css|less|sass|scss)$": "identity-obj-proxy",
    },
    testEnvironment: "jsdom",
    transformIgnorePatterns: ["/node_modules/(?!.*patternslib/*).+\\.[t|j]sx?$"],
};
<|endoftext|>


<commit_before>const { argv } = process;

const basicConfig = {
  coveragePathIgnorePatterns: [".story.js", "lib/*"],
  setupFiles: [
    "./.storybook/jest/require.context.js",
    "<rootDir>/node_modules/regenerator-runtime/runtime",
    // ⬆ for async/await tests to work in jest 22
    // Ref.: https://github.com/facebook/jest/issues/5698
  ],
  setupFilesAfterEnv: ["<rootDir>/jest.testFramework.js"],
  moduleNameMapper: {
    "\\.(css|jpg|png|svg)$": "<rootDir>/empty-module.js",
  },
};

const completeConfig = {
  ...basicConfig,
  verbose: true,
};

const fastConfig = {
  ...basicConfig,
  verbose: false,
  onlyChanged: true,
  testPathIgnorePatterns: ["Storyshots.test.js"],
};

const useComplete = argv.includes("-u") || argv.includes("--coverage");

module.exports = useComplete ? completeConfig : fastConfig;
<commit_msg>test(storyshots): add support for MDX stories<commit_after>const { argv } = process;

const basicConfig = {
  coveragePathIgnorePatterns: [".story.js", "lib/*"],
  setupFiles: [
    "./.storybook/jest/require.context.js",
    "<rootDir>/node_modules/regenerator-runtime/runtime",
    // ⬆ for async/await tests to work in jest 22
    // Ref.: https://github.com/facebook/jest/issues/5698
  ],
  setupFilesAfterEnv: ["<rootDir>/jest.testFramework.js"],
  moduleNameMapper: {
    "\\.(css|jpg|png|svg)$": "<rootDir>/empty-module.js",
  },
};

const completeConfig = {
  ...basicConfig,
  verbose: true,
  transform: {
    "^.+\\.[tj]sx?$": "babel-jest",
    "^.+\\.mdx$": "@storybook/addon-docs/jest-transform-mdx",
  },
};

const fastConfig = {
  ...basicConfig,
  verbose: false,
  onlyChanged: true,
  testPathIgnorePatterns: ["Storyshots.test.js"],
};

const useComplete = argv.includes("-u") || argv.includes("--coverage");

module.exports = useComplete ? completeConfig : fastConfig;
<|endoftext|>


<commit_before>import 'instapromise';

import AwaitLock from 'await-lock';

import crypto from 'crypto';
import fs from 'fs';
import fstreamNpm from 'fstream-npm';
import get from 'lodash/get';
import isEmpty from 'lodash/isEmpty';
import isEqual from 'lodash/isEqual';
import map from 'lodash/map';
import sortBy from 'lodash/sortBy';
import toPairsIn from 'lodash/toPairsIn';
import npmPackageArg from 'npm-package-arg';
import path from 'path';
import rimraf from 'rimraf';
import promiseProps from '@exponent/promise-props';

import { execNpmInstallAsync } from './npm';
import { execYarnInstallAsync } from './yarn';

const STATE_FILE = '.hyperinstall-state.json';
const CONFIG_FILE = 'hyperinstall.json';

// Global cache breaker to force updating all packages
const CACHE_BREAKER = 0;

export default class Hyperinstall {
  constructor(root) {
    this.root = root;
    this.forceInstallation = false;
    this.state = {};
    this.updatedPackages = {};
    this.installLock = new AwaitLock();
  }

  createPackageListAsync() {
    let filename = path.join(this.root, CONFIG_FILE);
    return fs.promise.writeFile(filename, '{\n}\n');
  }

  async installAsync() {
    let [state, packages] = await Promise.all([
      this.readInstallationStateAsync(),
      this.readPackageListAsync(),
    ]);
    this.state = state;

    if (state.cacheBreaker !== CACHE_BREAKER) {
      console.log('Global cache breaker has been updated; installing all packages');
      await Promise.all(map(packages, async (cacheBreaker, name) => {
        let targetPackageState = this.readTargetPackageStateAsync(name);
        await this.updatePackageAsync(name, cacheBreaker, targetPackageState);
      }));
    } else {
      await Promise.all(map(packages, async (cacheBreaker, name) => {
        await this.updatePackageIfNeededAsync(name, cacheBreaker);
      }));
    }

    if (!isEmpty(this.updatedPackages)) {
      let packageNames = Object.keys(this.updatedPackages);
      let count = packageNames.length;
      let packageWord = (count === 1) ? 'package' : 'packages';
      console.log('Updated %d %s:', count, packageWord);
      for (let name of packageNames) {
        console.log('  %s', name);
      }
    }

    // Update the installation state
    state.cacheBreaker = CACHE_BREAKER;
    state.packages = Object.assign({}, state.packages, this.updatedPackages);
    for (let name of Object.keys(state.packages)) {
      if (!packages.hasOwnProperty(name)) {
        delete state.packages[name];
      }
    }
    await this.writeInstallationStateAsync(state);
  }

  async readInstallationStateAsync() {
    let filename = path.join(this.root, STATE_FILE);
    let contents;
    try {
      contents = await fs.promise.readFile(filename, 'utf8');
    } catch (e) {
      if (e.code === 'ENOENT') {
        return {};
      }
      throw e;
    }
    return JSON.parse(contents);
  }

  async writeInstallationStateAsync(state) {
    let contents = JSON.stringify(state, null, 2);
    let filename = path.join(this.root, STATE_FILE);
    await fs.promise.writeFile(filename, contents, 'utf8');
  }

  async readPackageListAsync() {
    let filename = path.join(this.root, CONFIG_FILE);
    let contents;
    try {
      contents = await fs.promise.readFile(filename, 'utf8');
    } catch (e) {
      if (e.code === 'ENOENT') {
        console.warn(`Specify the packages to install in ${CONFIG_FILE}.`);
        return {};
      }
      throw e;
    }
    return JSON.parse(contents);
  }

  async readTargetPackageStateAsync(name) {
    let [deps, yarnLockfile, shrinkwrap] = await Promise.all([
      this.readPackageDepsAsync(name),
      this.readYarnLockfileAsync(name),
      this.readShrinkwrapAsync(name),
    ]);

    if (yarnLockfile) {
      return { yarnLockfile };
    }

    let unversionedDepChecksums = await this.readUnversionedDepChecksumsAsync(name, deps);
    return {
      dependencies: deps,
      unversionedDependencyChecksums: unversionedDepChecksums,
      shrinkwrap,
    };
  }

  async updatePackageIfNeededAsync(name, cacheBreaker) {
    let targetPackageState = await this.readTargetPackageStateAsync(name);
    if (this.forceInstallation) {
      await this.removeNodeModulesDirAsync(name);
      await this.updatePackageAsync(name, cacheBreaker, targetPackageState);
    } else {
      let needsUpdate = await this.packageNeedsUpdateAsync(
        name,
        cacheBreaker,
        targetPackageState,
      );
      if (needsUpdate) {
        await this.updatePackageAsync(name, cacheBreaker, targetPackageState);
      }
    }
  }

  async updatePackageAsync(name, cacheBreaker, targetPackageState) {
    let packagePath = path.resolve(this.root, name);
    await this.installLock.acquireAsync();
    try {
      console.log('Package "%s" has been updated; installing...', name);
      if (targetPackageState.yarnLockfile) {
        await execYarnInstallAsync(packagePath);
      } else {
        await execNpmInstallAsync(packagePath);
      }
      console.log('Finished installing "%s"\n', name);
    } finally {
      this.installLock.release();
    }

    this.updatedPackages[name] = {
      ...targetPackageState,
      cacheBreaker,
    };
  }

  async removeNodeModulesDirAsync(name) {
    let nodeModulesPath = path.resolve(this.root, name, 'node_modules');
    await rimraf.promise(nodeModulesPath);
    console.log('Removed node_modules for "%s"\n', name);
  }

  async readYarnLockfileAsync(name) {
    let lockfilePath = path.resolve(this.root, name, 'yarn.lock');
    let lockfile;
    try {
      lockfile = await fs.promise.readFile(lockfilePath, 'utf8');
    } catch (e) {
      if (e.code === 'ENOENT') {
        return undefined;
      }
      throw e;
    }
    return lockfile;
  }

  async readShrinkwrapAsync(name) {
    let shrinkwrapJSONPath = path.resolve(this.root, name, 'npm-shrinkwrap.json');
    let shrinkwrapJSON;
    try {
      shrinkwrapJSON = await fs.promise.readFile(shrinkwrapJSONPath, 'utf8');
    } catch (e) {
      if (e.code === 'ENOENT') {
        return undefined;
      }
      throw e;
    }
    return JSON.parse(shrinkwrapJSON);
  }

  async readPackageDepsAsync(name) {
    let packageJSONPath = path.resolve(this.root, name, 'package.json');
    let packageJSON = await fs.promise.readFile(packageJSONPath, 'utf8');
    packageJSON = JSON.parse(packageJSON);

    let packageDeps = {};
    Object.assign(packageDeps, packageJSON.dependencies);
    Object.assign(packageDeps, packageJSON.devDependencies);
    return packageDeps;
  }

  async readUnversionedDepChecksumsAsync(name, deps) {
    let packagePath = path.resolve(this.root, name);
    let unversionedDeps = this.filterLocalDeps(name, deps);
    let promises = {};
    for (let [dep, depPath] of toPairsIn(unversionedDeps)) {
      let absoluteDepPath = path.resolve(packagePath, depPath);
      promises[dep] = this.readPackageChecksumAsync(absoluteDepPath);
    }
    return await promiseProps(promises);
  }

  filterLocalDeps(name, deps) {
    // Change the working directory since npm-package-arg uses it when calling
    // path.resolve
    let originalCwd = process.cwd();
    let packagePath = path.resolve(this.root, name);
    process.chdir(packagePath);

    let localDeps = {};
    try {
      for (let [dep, version] of toPairsIn(deps)) {
        let descriptor = npmPackageArg(`${dep}@${version}`);
        if (descriptor.type === 'local') {
          localDeps[dep] = descriptor.spec;
        }
      }
    } finally {
      process.chdir(originalCwd);
    }
    return localDeps;
  }

  async readPackageChecksumAsync(packagePath) {
    return new Promise((resolve, reject) => {
      let fileChecksumPromises = {};
      let fileListStream = fstreamNpm({ path: packagePath });

      fileListStream.on('child', (entry) => {
        let absoluteFilePath = entry.props.path;
        let relativeFilePath = path.relative(packagePath, absoluteFilePath);
        fileChecksumPromises[relativeFilePath] = this.readFileChecksumAsync(absoluteFilePath, 'sha1');
      });

      fileListStream.on('error', (error) => {
        fileListStream.removeAllListeners();
        reject(error);
      });

      fileListStream.on('end', async () => {
        fileListStream.removeAllListeners();
        let fileChecksums = await promiseProps(fileChecksumPromises);
        // Compute a stable hash of the hashes
        let hashStream = crypto.createHash('sha1');
        for (let checksum of sortBy(fileChecksums)) {
          hashStream.update(checksum, 'utf8');
        }
        resolve(hashStream.digest('hex'));
      });
    });
  }

  async readFileChecksumAsync(filePath, algorithm) {
    let contents = await fs.promise.readFile(filePath);
    let hashStream = crypto.createHash(algorithm);
    hashStream.update(contents);
    return hashStream.digest('hex');
  }

  async packageNeedsUpdateAsync(name, cacheBreaker, targetPackageState) {
    let packageState = get(this.state.packages, name);
    if (!packageState || packageState.cacheBreaker !== cacheBreaker) {
      return true;
    }

    let targetYarnLockfile = targetPackageState.yarnLockfile;
    let installedYarnLockfile = packageState.yarnLockfile;
    if (targetYarnLockfile) {
      return !isEqual(targetYarnLockfile, installedYarnLockfile);
    }

    let targetShrinkwrap = targetPackageState.shrinkwrap;
    let installedShrinkwrap = packageState.shrinkwrap;
    if (targetShrinkwrap && !isEqual(targetShrinkwrap, installedShrinkwrap)) {
      return true;
    }

    let targetDeps = targetPackageState.dependencies;
    let installedDeps = packageState.dependencies;
    if (!isEqual(targetDeps, installedDeps)) {
      return true;
    }

    let targetUnversionedDepChecksums = targetPackageState.unversionedDependencyChecksums;
    let installedUnversionedDepChecksums = packageState.unversionedDependencyChecksums;
    if (!isEqual(targetUnversionedDepChecksums, installedUnversionedDepChecksums)) {
      return true;
    }

    // If there are dependencies but node_modules is missing, we definitely need
    // to update the package
    if (!isEmpty(installedDeps)) {
      let nodeModulesPath = path.resolve(this.root, name, 'node_modules');
      let isNodeModulesPresent = await this.isDirectoryAsync(nodeModulesPath);
      return !isNodeModulesPresent;
    }

    return false;
  }

  async cleanAsync() {
    let stateFilename = path.join(this.root, STATE_FILE);
    await fs.promise.unlink(stateFilename);
  }

  async isDirectoryAsync(directoryPath) {
    let stat;
    try {
      stat = await fs.promise.stat(directoryPath);
    } catch (e) {
      if (e.code === 'ENOENT') {
        return false;
      }
      throw e;
    }
    return stat.isDirectory();
  }
}
<commit_msg>Make sure we await readTargetPackageStateAsync<commit_after>import 'instapromise';

import AwaitLock from 'await-lock';

import crypto from 'crypto';
import fs from 'fs';
import fstreamNpm from 'fstream-npm';
import get from 'lodash/get';
import isEmpty from 'lodash/isEmpty';
import isEqual from 'lodash/isEqual';
import map from 'lodash/map';
import sortBy from 'lodash/sortBy';
import toPairsIn from 'lodash/toPairsIn';
import npmPackageArg from 'npm-package-arg';
import path from 'path';
import rimraf from 'rimraf';
import promiseProps from '@exponent/promise-props';

import { execNpmInstallAsync } from './npm';
import { execYarnInstallAsync } from './yarn';

const STATE_FILE = '.hyperinstall-state.json';
const CONFIG_FILE = 'hyperinstall.json';

// Global cache breaker to force updating all packages
const CACHE_BREAKER = 0;

export default class Hyperinstall {
  constructor(root) {
    this.root = root;
    this.forceInstallation = false;
    this.state = {};
    this.updatedPackages = {};
    this.installLock = new AwaitLock();
  }

  createPackageListAsync() {
    let filename = path.join(this.root, CONFIG_FILE);
    return fs.promise.writeFile(filename, '{\n}\n');
  }

  async installAsync() {
    let [state, packages] = await Promise.all([
      this.readInstallationStateAsync(),
      this.readPackageListAsync(),
    ]);
    this.state = state;

    if (state.cacheBreaker !== CACHE_BREAKER) {
      console.log('Global cache breaker has been updated; installing all packages');
      await Promise.all(map(packages, async (cacheBreaker, name) => {
        let targetPackageState = await this.readTargetPackageStateAsync(name);
        await this.updatePackageAsync(name, cacheBreaker, targetPackageState);
      }));
    } else {
      await Promise.all(map(packages, async (cacheBreaker, name) => {
        await this.updatePackageIfNeededAsync(name, cacheBreaker);
      }));
    }

    if (!isEmpty(this.updatedPackages)) {
      let packageNames = Object.keys(this.updatedPackages);
      let count = packageNames.length;
      let packageWord = (count === 1) ? 'package' : 'packages';
      console.log('Updated %d %s:', count, packageWord);
      for (let name of packageNames) {
        console.log('  %s', name);
      }
    }

    // Update the installation state
    state.cacheBreaker = CACHE_BREAKER;
    state.packages = Object.assign({}, state.packages, this.updatedPackages);
    for (let name of Object.keys(state.packages)) {
      if (!packages.hasOwnProperty(name)) {
        delete state.packages[name];
      }
    }
    await this.writeInstallationStateAsync(state);
  }

  async readInstallationStateAsync() {
    let filename = path.join(this.root, STATE_FILE);
    let contents;
    try {
      contents = await fs.promise.readFile(filename, 'utf8');
    } catch (e) {
      if (e.code === 'ENOENT') {
        return {};
      }
      throw e;
    }
    return JSON.parse(contents);
  }

  async writeInstallationStateAsync(state) {
    let contents = JSON.stringify(state, null, 2);
    let filename = path.join(this.root, STATE_FILE);
    await fs.promise.writeFile(filename, contents, 'utf8');
  }

  async readPackageListAsync() {
    let filename = path.join(this.root, CONFIG_FILE);
    let contents;
    try {
      contents = await fs.promise.readFile(filename, 'utf8');
    } catch (e) {
      if (e.code === 'ENOENT') {
        console.warn(`Specify the packages to install in ${CONFIG_FILE}.`);
        return {};
      }
      throw e;
    }
    return JSON.parse(contents);
  }

  async readTargetPackageStateAsync(name) {
    let [deps, yarnLockfile, shrinkwrap] = await Promise.all([
      this.readPackageDepsAsync(name),
      this.readYarnLockfileAsync(name),
      this.readShrinkwrapAsync(name),
    ]);

    if (yarnLockfile) {
      return { yarnLockfile };
    }

    let unversionedDepChecksums = await this.readUnversionedDepChecksumsAsync(name, deps);
    return {
      dependencies: deps,
      unversionedDependencyChecksums: unversionedDepChecksums,
      shrinkwrap,
    };
  }

  async updatePackageIfNeededAsync(name, cacheBreaker) {
    let targetPackageState = await this.readTargetPackageStateAsync(name);
    if (this.forceInstallation) {
      await this.removeNodeModulesDirAsync(name);
      await this.updatePackageAsync(name, cacheBreaker, targetPackageState);
    } else {
      let needsUpdate = await this.packageNeedsUpdateAsync(
        name,
        cacheBreaker,
        targetPackageState,
      );
      if (needsUpdate) {
        await this.updatePackageAsync(name, cacheBreaker, targetPackageState);
      }
    }
  }

  async updatePackageAsync(name, cacheBreaker, targetPackageState) {
    let packagePath = path.resolve(this.root, name);
    await this.installLock.acquireAsync();
    try {
      console.log('Package "%s" has been updated; installing...', name);
      if (targetPackageState.yarnLockfile) {
        await execYarnInstallAsync(packagePath);
      } else {
        await execNpmInstallAsync(packagePath);
      }
      console.log('Finished installing "%s"\n', name);
    } finally {
      this.installLock.release();
    }

    this.updatedPackages[name] = {
      ...targetPackageState,
      cacheBreaker,
    };
  }

  async removeNodeModulesDirAsync(name) {
    let nodeModulesPath = path.resolve(this.root, name, 'node_modules');
    await rimraf.promise(nodeModulesPath);
    console.log('Removed node_modules for "%s"\n', name);
  }

  async readYarnLockfileAsync(name) {
    let lockfilePath = path.resolve(this.root, name, 'yarn.lock');
    let lockfile;
    try {
      lockfile = await fs.promise.readFile(lockfilePath, 'utf8');
    } catch (e) {
      if (e.code === 'ENOENT') {
        return undefined;
      }
      throw e;
    }
    return lockfile;
  }

  async readShrinkwrapAsync(name) {
    let shrinkwrapJSONPath = path.resolve(this.root, name, 'npm-shrinkwrap.json');
    let shrinkwrapJSON;
    try {
      shrinkwrapJSON = await fs.promise.readFile(shrinkwrapJSONPath, 'utf8');
    } catch (e) {
      if (e.code === 'ENOENT') {
        return undefined;
      }
      throw e;
    }
    return JSON.parse(shrinkwrapJSON);
  }

  async readPackageDepsAsync(name) {
    let packageJSONPath = path.resolve(this.root, name, 'package.json');
    let packageJSON = await fs.promise.readFile(packageJSONPath, 'utf8');
    packageJSON = JSON.parse(packageJSON);

    let packageDeps = {};
    Object.assign(packageDeps, packageJSON.dependencies);
    Object.assign(packageDeps, packageJSON.devDependencies);
    return packageDeps;
  }

  async readUnversionedDepChecksumsAsync(name, deps) {
    let packagePath = path.resolve(this.root, name);
    let unversionedDeps = this.filterLocalDeps(name, deps);
    let promises = {};
    for (let [dep, depPath] of toPairsIn(unversionedDeps)) {
      let absoluteDepPath = path.resolve(packagePath, depPath);
      promises[dep] = this.readPackageChecksumAsync(absoluteDepPath);
    }
    return await promiseProps(promises);
  }

  filterLocalDeps(name, deps) {
    // Change the working directory since npm-package-arg uses it when calling
    // path.resolve
    let originalCwd = process.cwd();
    let packagePath = path.resolve(this.root, name);
    process.chdir(packagePath);

    let localDeps = {};
    try {
      for (let [dep, version] of toPairsIn(deps)) {
        let descriptor = npmPackageArg(`${dep}@${version}`);
        if (descriptor.type === 'local') {
          localDeps[dep] = descriptor.spec;
        }
      }
    } finally {
      process.chdir(originalCwd);
    }
    return localDeps;
  }

  async readPackageChecksumAsync(packagePath) {
    return new Promise((resolve, reject) => {
      let fileChecksumPromises = {};
      let fileListStream = fstreamNpm({ path: packagePath });

      fileListStream.on('child', (entry) => {
        let absoluteFilePath = entry.props.path;
        let relativeFilePath = path.relative(packagePath, absoluteFilePath);
        fileChecksumPromises[relativeFilePath] = this.readFileChecksumAsync(absoluteFilePath, 'sha1');
      });

      fileListStream.on('error', (error) => {
        fileListStream.removeAllListeners();
        reject(error);
      });

      fileListStream.on('end', async () => {
        fileListStream.removeAllListeners();
        let fileChecksums = await promiseProps(fileChecksumPromises);
        // Compute a stable hash of the hashes
        let hashStream = crypto.createHash('sha1');
        for (let checksum of sortBy(fileChecksums)) {
          hashStream.update(checksum, 'utf8');
        }
        resolve(hashStream.digest('hex'));
      });
    });
  }

  async readFileChecksumAsync(filePath, algorithm) {
    let contents = await fs.promise.readFile(filePath);
    let hashStream = crypto.createHash(algorithm);
    hashStream.update(contents);
    return hashStream.digest('hex');
  }

  async packageNeedsUpdateAsync(name, cacheBreaker, targetPackageState) {
    let packageState = get(this.state.packages, name);
    if (!packageState || packageState.cacheBreaker !== cacheBreaker) {
      return true;
    }

    let targetYarnLockfile = targetPackageState.yarnLockfile;
    let installedYarnLockfile = packageState.yarnLockfile;
    if (targetYarnLockfile) {
      return !isEqual(targetYarnLockfile, installedYarnLockfile);
    }

    let targetShrinkwrap = targetPackageState.shrinkwrap;
    let installedShrinkwrap = packageState.shrinkwrap;
    if (targetShrinkwrap && !isEqual(targetShrinkwrap, installedShrinkwrap)) {
      return true;
    }

    let targetDeps = targetPackageState.dependencies;
    let installedDeps = packageState.dependencies;
    if (!isEqual(targetDeps, installedDeps)) {
      return true;
    }

    let targetUnversionedDepChecksums = targetPackageState.unversionedDependencyChecksums;
    let installedUnversionedDepChecksums = packageState.unversionedDependencyChecksums;
    if (!isEqual(targetUnversionedDepChecksums, installedUnversionedDepChecksums)) {
      return true;
    }

    // If there are dependencies but node_modules is missing, we definitely need
    // to update the package
    if (!isEmpty(installedDeps)) {
      let nodeModulesPath = path.resolve(this.root, name, 'node_modules');
      let isNodeModulesPresent = await this.isDirectoryAsync(nodeModulesPath);
      return !isNodeModulesPresent;
    }

    return false;
  }

  async cleanAsync() {
    let stateFilename = path.join(this.root, STATE_FILE);
    await fs.promise.unlink(stateFilename);
  }

  async isDirectoryAsync(directoryPath) {
    let stat;
    try {
      stat = await fs.promise.stat(directoryPath);
    } catch (e) {
      if (e.code === 'ENOENT') {
        return false;
      }
      throw e;
    }
    return stat.isDirectory();
  }
}
<|endoftext|>


<commit_before>import * as utils from './utils.js';
import baseLogger from './logger.js';

class Interpolator {
  constructor(options = {}) {
    this.logger = baseLogger.create('interpolator');

    this.options = options;
    this.format = (options.interpolation && options.interpolation.format) || (value => value);
    this.init(options);
  }

  /* eslint no-param-reassign: 0 */
  init(options = {}) {
    if (!options.interpolation) options.interpolation = { escapeValue: true };

    const iOpts = options.interpolation;

    this.escape = iOpts.escape !== undefined ? iOpts.escape : utils.escape;
    this.escapeValue = iOpts.escapeValue !== undefined ? iOpts.escapeValue : true;
    this.useRawValueToEscape =
      iOpts.useRawValueToEscape !== undefined ? iOpts.useRawValueToEscape : false;

    this.prefix = iOpts.prefix ? utils.regexEscape(iOpts.prefix) : iOpts.prefixEscaped || '{{';
    this.suffix = iOpts.suffix ? utils.regexEscape(iOpts.suffix) : iOpts.suffixEscaped || '}}';

    this.formatSeparator = iOpts.formatSeparator
      ? iOpts.formatSeparator
      : iOpts.formatSeparator || ',';

    this.unescapePrefix = iOpts.unescapeSuffix ? '' : iOpts.unescapePrefix || '-';
    this.unescapeSuffix = this.unescapePrefix ? '' : iOpts.unescapeSuffix || '';

    this.nestingPrefix = iOpts.nestingPrefix
      ? utils.regexEscape(iOpts.nestingPrefix)
      : iOpts.nestingPrefixEscaped || utils.regexEscape('$t(');
    this.nestingSuffix = iOpts.nestingSuffix
      ? utils.regexEscape(iOpts.nestingSuffix)
      : iOpts.nestingSuffixEscaped || utils.regexEscape(')');

    this.nestingOptionsSeparator = iOpts.nestingOptionsSeparator
      ? iOpts.nestingOptionsSeparator
      : iOpts.nestingOptionsSeparator || ',';

    this.maxReplaces = iOpts.maxReplaces ? iOpts.maxReplaces : 1000;

    this.alwaysFormat = iOpts.alwaysFormat !== undefined ? iOpts.alwaysFormat : false;

    // the regexp
    this.resetRegExp();
  }

  reset() {
    if (this.options) this.init(this.options);
  }

  resetRegExp() {
    // the regexp
    const regexpStr = `${this.prefix}(.+?)${this.suffix}`;
    this.regexp = new RegExp(regexpStr, 'g');

    const regexpUnescapeStr = `${this.prefix}${this.unescapePrefix}(.+?)${this.unescapeSuffix}${
      this.suffix
    }`;
    this.regexpUnescape = new RegExp(regexpUnescapeStr, 'g');

    const nestingRegexpStr = `${this.nestingPrefix}(.+?)${this.nestingSuffix}`;
    this.nestingRegexp = new RegExp(nestingRegexpStr, 'g');
  }

  interpolate(str, data, lng, options) {
    let match;
    let value;
    let replaces;

    const defaultData =
      (this.options && this.options.interpolation && this.options.interpolation.defaultVariables) ||
      {};

    function regexSafe(val) {
      return val.replace(/\$/g, '$$$$');
    }

    const handleFormat = key => {
      if (key.indexOf(this.formatSeparator) < 0) {
        const path = utils.getPathWithDefaults(data, defaultData, key);
        return this.alwaysFormat ? this.format(path, undefined, lng) : path;
      }

      const p = key.split(this.formatSeparator);
      const k = p.shift().trim();
      const f = p.join(this.formatSeparator).trim();

      return this.format(utils.getPathWithDefaults(data, defaultData, k), f, lng, options);
    };

    this.resetRegExp();

    const missingInterpolationHandler =
      (options && options.missingInterpolationHandler) || this.options.missingInterpolationHandler;

    replaces = 0;
    // unescape if has unescapePrefix/Suffix
    /* eslint no-cond-assign: 0 */
    while ((match = this.regexpUnescape.exec(str))) {
      value = handleFormat(match[1].trim());
      if (value === undefined) {
        if (typeof missingInterpolationHandler === 'function') {
          const temp = missingInterpolationHandler(str, match, options);
          value = typeof temp === 'string' ? temp : '';
        } else {
          this.logger.warn(`missed to pass in variable ${match[1]} for interpolating ${str}`);
          value = '';
        }
      } else if (typeof value !== 'string' && !this.useRawValueToEscape) {
        value = utils.makeString(value);
      }
      str = str.replace(match[0], regexSafe(value));
      this.regexpUnescape.lastIndex = 0;
      replaces++;
      if (replaces >= this.maxReplaces) {
        break;
      }
    }

    replaces = 0;
    // regular escape on demand
    while ((match = this.regexp.exec(str))) {
      value = handleFormat(match[1].trim());
      if (value === undefined) {
        if (typeof missingInterpolationHandler === 'function') {
          const temp = missingInterpolationHandler(str, match, options);
          value = typeof temp === 'string' ? temp : '';
        } else {
          this.logger.warn(`missed to pass in variable ${match[1]} for interpolating ${str}`);
          value = '';
        }
      } else if (typeof value !== 'string' && !this.useRawValueToEscape) {
        value = utils.makeString(value);
      }
      value = this.escapeValue ? regexSafe(this.escape(value)) : regexSafe(value);
      str = str.replace(match[0], value);
      this.regexp.lastIndex = 0;
      replaces++;
      if (replaces >= this.maxReplaces) {
        break;
      }
    }
    return str;
  }

  nest(str, fc, options = {}) {
    let match;
    let value;

    let clonedOptions = { ...options };
    clonedOptions.applyPostProcessor = false; // avoid post processing on nested lookup
    delete clonedOptions.defaultValue; // assert we do not get a endless loop on interpolating defaultValue again and again

    // if value is something like "myKey": "lorem $(anotherKey, { "count": {{aValueInOptions}} })"
    function handleHasOptions(key, inheritedOptions) {
      const sep = this.nestingOptionsSeparator;
      if (key.indexOf(sep) < 0) return key;

      const c = key.split(new RegExp(`${sep}[ ]*{`));

      let optionsString = `{${c[1]}`;
      key = c[0];
      optionsString = this.interpolate(optionsString, clonedOptions);
      optionsString = optionsString.replace(/'/g, '"');

      try {
        clonedOptions = JSON.parse(optionsString);

        if (inheritedOptions) clonedOptions = { ...inheritedOptions, ...clonedOptions };
      } catch (e) {
        this.logger.warn(`failed parsing options string in nesting for key ${key}`, e);
        return `${key}${sep}${optionsString}`;
      }

      // assert we do not get a endless loop on interpolating defaultValue again and again
      delete clonedOptions.defaultValue;
      return key;
    }

    // regular escape on demand
    while ((match = this.nestingRegexp.exec(str))) {
      value = fc(handleHasOptions.call(this, match[1].trim(), clonedOptions), clonedOptions);

      // is only the nesting key (key1 = '$(key2)') return the value without stringify
      if (value && match[0] === str && typeof value !== 'string') return value;

      // no string to include or empty
      if (typeof value !== 'string') value = utils.makeString(value);
      if (!value) {
        this.logger.warn(`missed to resolve ${match[1]} for nesting ${str}`);
        value = '';
      }
      // Nested keys should not be escaped by default #854
      // value = this.escapeValue ? regexSafe(utils.escape(value)) : regexSafe(value);
      str = str.replace(match[0], value);
      this.regexp.lastIndex = 0;
    }
    return str;
  }
}

export default Interpolator;
<commit_msg>Allow for formatting of translations.<commit_after>import * as utils from './utils.js';
import baseLogger from './logger.js';

class Interpolator {
  constructor(options = {}) {
    this.logger = baseLogger.create('interpolator');

    this.options = options;
    this.format = (options.interpolation && options.interpolation.format) || (value => value);
    this.init(options);
  }

  /* eslint no-param-reassign: 0 */
  init(options = {}) {
    if (!options.interpolation) options.interpolation = { escapeValue: true };

    const iOpts = options.interpolation;

    this.escape = iOpts.escape !== undefined ? iOpts.escape : utils.escape;
    this.escapeValue = iOpts.escapeValue !== undefined ? iOpts.escapeValue : true;
    this.useRawValueToEscape =
      iOpts.useRawValueToEscape !== undefined ? iOpts.useRawValueToEscape : false;

    this.prefix = iOpts.prefix ? utils.regexEscape(iOpts.prefix) : iOpts.prefixEscaped || '{{';
    this.suffix = iOpts.suffix ? utils.regexEscape(iOpts.suffix) : iOpts.suffixEscaped || '}}';

    this.formatSeparator = iOpts.formatSeparator
      ? iOpts.formatSeparator
      : iOpts.formatSeparator || ',';

    this.unescapePrefix = iOpts.unescapeSuffix ? '' : iOpts.unescapePrefix || '-';
    this.unescapeSuffix = this.unescapePrefix ? '' : iOpts.unescapeSuffix || '';

    this.nestingPrefix = iOpts.nestingPrefix
      ? utils.regexEscape(iOpts.nestingPrefix)
      : iOpts.nestingPrefixEscaped || utils.regexEscape('$t(');
    this.nestingSuffix = iOpts.nestingSuffix
      ? utils.regexEscape(iOpts.nestingSuffix)
      : iOpts.nestingSuffixEscaped || utils.regexEscape(')');

    this.nestingOptionsSeparator = iOpts.nestingOptionsSeparator
      ? iOpts.nestingOptionsSeparator
      : iOpts.nestingOptionsSeparator || ',';

    this.maxReplaces = iOpts.maxReplaces ? iOpts.maxReplaces : 1000;

    this.alwaysFormat = iOpts.alwaysFormat !== undefined ? iOpts.alwaysFormat : false;

    // the regexp
    this.resetRegExp();
  }

  reset() {
    if (this.options) this.init(this.options);
  }

  resetRegExp() {
    // the regexp
    const regexpStr = `${this.prefix}(.+?)${this.suffix}`;
    this.regexp = new RegExp(regexpStr, 'g');

    const regexpUnescapeStr = `${this.prefix}${this.unescapePrefix}(.+?)${this.unescapeSuffix}${
      this.suffix
    }`;
    this.regexpUnescape = new RegExp(regexpUnescapeStr, 'g');

    const nestingRegexpStr = `${this.nestingPrefix}(.+?)${this.nestingSuffix}`;
    this.nestingRegexp = new RegExp(nestingRegexpStr, 'g');
  }

  interpolate(str, data, lng, options) {
    let match;
    let value;
    let replaces;

    const defaultData =
      (this.options && this.options.interpolation && this.options.interpolation.defaultVariables) ||
      {};

    function regexSafe(val) {
      return val.replace(/\$/g, '$$$$');
    }

    const handleFormat = key => {
      if (key.indexOf(this.formatSeparator) < 0) {
        const path = utils.getPathWithDefaults(data, defaultData, key);
        return this.alwaysFormat ? this.format(path, undefined, lng) : path;
      }

      const p = key.split(this.formatSeparator);
      const k = p.shift().trim();
      const f = p.join(this.formatSeparator).trim();

      return this.format(utils.getPathWithDefaults(data, defaultData, k), f, lng, options);
    };

    this.resetRegExp();

    const missingInterpolationHandler =
      (options && options.missingInterpolationHandler) || this.options.missingInterpolationHandler;

    replaces = 0;
    // unescape if has unescapePrefix/Suffix
    /* eslint no-cond-assign: 0 */
    while ((match = this.regexpUnescape.exec(str))) {
      value = handleFormat(match[1].trim());
      if (value === undefined) {
        if (typeof missingInterpolationHandler === 'function') {
          const temp = missingInterpolationHandler(str, match, options);
          value = typeof temp === 'string' ? temp : '';
        } else {
          this.logger.warn(`missed to pass in variable ${match[1]} for interpolating ${str}`);
          value = '';
        }
      } else if (typeof value !== 'string' && !this.useRawValueToEscape) {
        value = utils.makeString(value);
      }
      str = str.replace(match[0], regexSafe(value));
      this.regexpUnescape.lastIndex = 0;
      replaces++;
      if (replaces >= this.maxReplaces) {
        break;
      }
    }

    replaces = 0;
    // regular escape on demand
    while ((match = this.regexp.exec(str))) {
      value = handleFormat(match[1].trim());
      if (value === undefined) {
        if (typeof missingInterpolationHandler === 'function') {
          const temp = missingInterpolationHandler(str, match, options);
          value = typeof temp === 'string' ? temp : '';
        } else {
          this.logger.warn(`missed to pass in variable ${match[1]} for interpolating ${str}`);
          value = '';
        }
      } else if (typeof value !== 'string' && !this.useRawValueToEscape) {
        value = utils.makeString(value);
      }
      value = this.escapeValue ? regexSafe(this.escape(value)) : regexSafe(value);
      str = str.replace(match[0], value);
      this.regexp.lastIndex = 0;
      replaces++;
      if (replaces >= this.maxReplaces) {
        break;
      }
    }
    return str;
  }

  nest(str, fc, options = {}) {
    let match;
    let value;

    let clonedOptions = { ...options };
    clonedOptions.applyPostProcessor = false; // avoid post processing on nested lookup
    delete clonedOptions.defaultValue; // assert we do not get a endless loop on interpolating defaultValue again and again

    // if value is something like "myKey": "lorem $(anotherKey, { "count": {{aValueInOptions}} })"
    function handleHasOptions(key, inheritedOptions) {
      const sep = this.nestingOptionsSeparator;
      if (key.indexOf(sep) < 0) return key;

      const c = key.split(new RegExp(`${sep}[ ]*{`));

      let optionsString = `{${c[1]}`;
      key = c[0];
      optionsString = this.interpolate(optionsString, clonedOptions);
      optionsString = optionsString.replace(/'/g, '"');

      try {
        clonedOptions = JSON.parse(optionsString);

        if (inheritedOptions) clonedOptions = { ...inheritedOptions, ...clonedOptions };
      } catch (e) {
        this.logger.warn(`failed parsing options string in nesting for key ${key}`, e);
        return `${key}${sep}${optionsString}`;
      }

      // assert we do not get a endless loop on interpolating defaultValue again and again
      delete clonedOptions.defaultValue;
      return key;
    }

    // regular escape on demand
    while ((match = this.nestingRegexp.exec(str))) {
      let formatters = [];

      /**
       * If there is more than one parameter (contains the format separator). E.g.:
       *   - t(a, b)
       *   - t(a, b, c)
       *
       * And those parameters are not dynamic values (parameters do not include curly braces). E.g.:
       *   - Not t(a, { "key": "{{variable}}" })
       *   - Not t(a, b, {"keyA": "valueA", "keyB": "valueB"})
       */
      if (match[0].includes(this.formatSeparator) && !/{.*}/.test(match[1])) {
        [match[1], ...formatters] = match[1].split(this.formatSeparator).map(elem => elem.trim());
      }

      value = fc(handleHasOptions.call(this, match[1].trim(), clonedOptions), clonedOptions);

      // is only the nesting key (key1 = '$(key2)') return the value without stringify
      if (value && match[0] === str && typeof value !== 'string') return value;

      // no string to include or empty
      if (typeof value !== 'string') value = utils.makeString(value);
      if (!value) {
        this.logger.warn(`missed to resolve ${match[1]} for nesting ${str}`);
        value = '';
      }

      value = formatters.reduce((v, f) => this.format(v, f, options.lng, options), value.trim());

      // Nested keys should not be escaped by default #854
      // value = this.escapeValue ? regexSafe(utils.escape(value)) : regexSafe(value);
      str = str.replace(match[0], value);
      this.regexp.lastIndex = 0;
    }
    return str;
  }
}

export default Interpolator;
<|endoftext|>


<commit_before>'use strict';

/* Controllers */

angular.module('ten20Angular').
  controller('UserCtrl', ['$scope', '$http', '$timeout', '$window', '$modal', function ($scope, $http, $timeout, $window, $modal) {
  // init user and trackers information
  $scope.user = {};
  $scope.newTracker = {};
  $scope.trackerLoaded = false;
  $scope.trackers = [];
  $scope.adsFree = true; // initially hide ads

  // update features
  function _getFeature() {
    $http.get('/features').success(function(data) {
      if (data.adFree) {
        $scope.adsFree = true;
      } else {
        $scope.adsFree = false;
      }
    }).error(function(error) {
      $scope.adsFree = false;
    });
  }

  $scope.adsShow = function() {
    var desktopWin = true;
    var status = { desk: false, mobile: false};

    if ($window.innerWidth < 600) {
      desktopWin = false;
    }

    if (!$scope.adsFree) {
      if (desktopWin) {
        status.desk = true;
      } else {
        status.mobile = true;
      }
    }

    return status;
  };

  // callback for add tracker modal box
  $scope.addTracker = function() {
    $scope.trackers.push($scope.newTracker);
    $scope.newTracker = {};
  };

  // callback for tracker setting modal box
  function updateSetting(t) {
    $scope.$broadcast('TrackerUpdate', t);
    $scope.$broadcast('PathUpdate', t);
    $http({
      method: 'PATCH',
      url: '/trackers/'+t._id,
      data: {
        iconColor: t.iconColor
      }
    });
  };

  function deleteTracker(t) {
    for (var i = 0; i < $scope.trackers.length; i++) {
      if ($scope.trackers[i]._id === t._id) {
        $scope.trackers.splice(i, 1);
        $http.delete('/tracker/' + t._id);
      }
    };
  }

  $scope.openSetting = function(t) {

    var SettingCtrl = function($scope, $modalInstance) {
      var setInstance = $modalInstance;
      $scope.data = t;

      $scope.ok = function() {
        updateSetting($scope.data);
        $modalInstance.close();
      };

      $scope.cancel = function () {
        $modalInstance.dismiss('cancel');
      };

      $scope.deleteTracker = function(tracker) {

        var DeleteCtrl = function($scope, $modalInstance) {
          $scope.t = tracker;

          $scope.ok = function() {
            deleteTracker(tracker);
            $modalInstance.close();
            setInstance.close();
          };

          $scope.cancel = function () {
            $modalInstance.dismiss('cancel');
          };

        };

        var deleteModal = $modal.open({
          templateUrl: '/templates/deleteTracker.html',
          controller: DeleteCtrl,
          windowClass: 'small'
        });

      };
    };

    var setModal = $modal.open({
      templateUrl: '/templates/trackerSetting.html',
      controller: SettingCtrl,
      windowClass: 'small'
    });

  };

  // get user account info
  $scope.init = function() {
    $http.get('/user/info').success(function(userinfo) {
      $scope.user = userinfo;
      _getFeature();
      initTrackers();
    }).error(function(data, status, headers, config) {
      if (status === 401) {
        angular.element(document.body).find('.bottom-nav .signin').click();
      }
    });
  }

  // get trackers info
  function initTrackers() {
    $http.get('/trackers').success(function(data) {
      $scope.trackers = data.items;
      // hack icon color format
      for (var i = 0; i < $scope.trackers.length; i++) {
        if ($scope.trackers[i].iconColor) {
          if ($scope.trackers[i].iconColor.charAt(0) !== '#') {
            $scope.trackers[i].iconColor = '#' + $scope.trackers[i].iconColor;
          }
        } else {
          $scope.trackers[i].iconColor = '#ff6600';
        }
      };
      $scope.trackerLoaded = true;
      $scope.$broadcast('InitTrackers');
      getMessages();
    }).error(function() {
      $scope.trackerLoaded = true;
    });
  }

  $scope.init();

  var delay = 1; // delay seconds for get trip message
  function getMessages() {
    var start = new Date().valueOf();
    $http.get('/message/notify').success(function (tracker) {
      var newTracker = true;
      var oldPos = {};

      // server response time less than One secend
      if ((new Date().valueOf()) - start < 5000) {
        delay *= 5;
      } else {
        // reset delay
        delay = 1;
      }

      console.log(tracker.name + ' udpated');
      // update or add tracker
      for (var i = 0; i < $scope.trackers.length; i++) {
        if ($scope.trackers[i]._id === tracker._id) {
          // add point to recent
          if ($scope.trackers[i].lastMessage.location) {
            $scope.trackers[i].recent = $scope.trackers[i].recent || {msgs:[]};
            $scope.trackers[i].recent.msgs.unshift(
              $scope.trackers[i].lastMessage.location);
            $scope.trackers[i].recent.msgs.splice(
              $scope.trackers[i].recent.msgs.length - 1, 1);

            // only update path when active tracker's trip pane is not show
            if (!$scope.activeTracker ||
                $scope.activeTracker._id !== $scope.trackers[i]._id ||
                !$scope.trackers[i].tripActive) {

                  $scope.trackers[i].path = $scope.trackers[i].recent.msgs;
                  $scope.$broadcast('PathUpdate', $scope.trackers[i], false);
             }
          }
          // override with new info
          for (var key in tracker) {
            $scope.trackers[i][key] = tracker[key];
          }

          newTracker = false;
        }
      }

      if (newTracker) {
        if (newTracker.iconColor) {
          if ( newTracker.iconColor.charAt(0) !== '#') {
            newTracker.iconColor = '#' + newTracker.iconColor;
          }
        } else {
          newTracker.iconColor = '#ff6600';
        }

        $scope.trackers.push(tracker);
      }

      $scope.$broadcast('TrackerUpdate', tracker);
      $timeout(getMessages, delay * 1000);

    }).error(function() {
      delay *= 2;
      $timeout(getMessages, delay * 1000);
    });
  }

  // refresh tracker
  $scope.refreshTracker = function(t) {
    $scope.activeTracker = t;
    $scope.$broadcast('FocusTracker', t);
    $scope.recentMsg(t);
  };

  // load recent msg of a tracker
  $scope.recentMsg = function(t) {

    if (t.recent && t.recent.msgs.length !== 0) {
      t.path = t.recent.msgs;
      $scope.$broadcast('PathUpdate', t, true);
      return;
    }

    var url = '/recent_messages?sortBy=_id$desc&trackerId=' + t._id;
    console.log('recent message url ' + url);
    $http.get(url).success(function(data) {
      console.log('------recent_message-----');
      t.recent = t.recent || {};
      t.recent.msgs = _filterValidMsg(data.items);
      if (t.recent.msgs.length > 1) {
        t.path = t.recent.msgs;
        $scope.$broadcast('PathUpdate', t, true);
      }
    });
  }

  $scope.onTripTimeSet = function(newOne, oldOne) {
    var date,
        url = '/trips?limit=1&sortBy=_id$asc&trackerId=' +
          $scope.activeTracker._id,
        tracker = $scope.activeTracker;

    tracker.trips.error = '';
    tracker.trips.loading = true;
    tracker.trips.data = [];

    date = moment(tracker.trips.search.toString()).toISOString();
    url += '&endTime=after$date:' + date;
    url += '&startTime=before$date:' + date;

    $http.get(url).success(function (documents) {
      if (documents.items.length > 0) {
        filtered = _simplifyTripMsg(documents.items[0]);
        if (filtered.messages.length === 0) {
          tracker.trips.loading = false;
          tracker.trips.error = 'trips data invalid';
          $timeout(function() { tracker.trips.error = ''; }, 10 * 1000);
          return;
        }
        trips.data.push(filtered);
        tracker.trips.loading = false;
        tracker.trips.error = '';
      } else {
        tracker.trips.loading = false;
        tracker.trips.error = 'no trip data at this time';
        $timeout(function() { tracker.trips.error = ''; }, 10 * 1000);
      }
    }).error(function(data) {
      tracker.trips.error = data;
      tracker.trips.loading = false;
      $timeout(function() { tracker.trips.error = ''; }, 5000);
    });
  };

  // load trips of at tracker
  $scope.showTrip = function(t, index) {
      t.path = t.trips.data[index].messages;
      t.trips.active = index;
      $scope.$broadcast('PathUpdate', t, true);
  };

  $scope.loadTrips = function (tracker, init) {
    var BATCH = 3; // load trip counts per time

    if (tracker.trips && tracker.trips.search !== '') {
      return;
    }

    if (tracker.trips && tracker.trips.error !== '') {
      return;
    }

    // prevent load more if user secondly click trip tab
    if (init && tracker.trips &&
        tracker.trips.data.length !==0) {
      $scope.showTrip(tracker, 0);
      return;
    }

    tracker.trips ? null: tracker.trips = { data:[], destCnt: 0, error: '' , search: ''};
    tracker.trips.destCnt = tracker.trips.data.length + BATCH;
    tracker.trips.loading = true;
    tracker.trips.error = '';

    _getOneTrip(tracker);
  };

  // get one trip from server
  function _getOneTrip(tracker) {
    var trips = tracker.trips;
    var before = '';
    var url, filtered;

    if (trips.data.length > 0) {
      before = '&_id=before$' + trips.data[trips.data.length - 1]._id;
    }

    url = '/trips?sortBy=_id$desc&limit=1&trackerId=' + tracker._id + before;

    $http.get(url).success(function (documents) {
      if (documents.items.length > 0) {
        filtered = _simplifyTripMsg(documents.items[0]);
        if (filtered.messages.length === 0) {
          tracker.trips.loading = false;
          tracker.trips.error = 'trips data invalid, retry later';
          $timeout(function() { tracker.trips.error = ''; }, 10 * 1000);
          return;
        }
        trips.data.push(filtered);
        // get next trip
        if (trips.data.length < tracker.trips.destCnt) {
          _getOneTrip(tracker);
        } else {
          tracker.trips.loading = false;
        }
        tracker.trips.error = '';
      } else {
        tracker.trips.loading = false;
        tracker.trips.error = 'no more trips, retry later';
        $timeout(function() { tracker.trips.error = ''; }, 10 * 1000);
      }
    }).error(function(data) {
      tracker.trips.error = data;
      tracker.trips.loading = false;
      $timeout(function() { tracker.trips.error = ''; }, 5000);
    });
  };

  function _simplifyTripMsg(trip) {
    // cut useless info in trip messages
    trip.messages = _filterValidMsg(trip.messages);

    return trip;
  }

  function _filterValidMsg(data) {
    var validMsg = [];

    // filter out useless messages
    for (var i = 0; i < data.length; i++) {
      if (data[i].message && data[i].message.location
          && data[i].message.location.latitude) {
        if (!data[i].message.location.timestamp) {
          data[i].message.location.timestamp =
            data[i].receivedTime || data[i].message.receiveTime;
        }
        validMsg.push(data[i].message.location);
      }
    }

    return validMsg;
  }

}]);

<commit_msg>Patch up date search tracker.trips.search filtered appeared to be defined in global scope trips was undefined (assume this is supposed to be tracker.trips)<commit_after>'use strict';

/* Controllers */

angular.module('ten20Angular').
  controller('UserCtrl', ['$scope', '$http', '$timeout', '$window', '$modal', function ($scope, $http, $timeout, $window, $modal) {
  // init user and trackers information
  $scope.user = {};
  $scope.newTracker = {};
  $scope.trackerLoaded = false;
  $scope.trackers = [];
  $scope.adsFree = true; // initially hide ads

  // update features
  function _getFeature() {
    $http.get('/features').success(function(data) {
      if (data.adFree) {
        $scope.adsFree = true;
      } else {
        $scope.adsFree = false;
      }
    }).error(function(error) {
      $scope.adsFree = false;
    });
  }

  $scope.adsShow = function() {
    var desktopWin = true;
    var status = { desk: false, mobile: false};

    if ($window.innerWidth < 600) {
      desktopWin = false;
    }

    if (!$scope.adsFree) {
      if (desktopWin) {
        status.desk = true;
      } else {
        status.mobile = true;
      }
    }

    return status;
  };

  // callback for add tracker modal box
  $scope.addTracker = function() {
    $scope.trackers.push($scope.newTracker);
    $scope.newTracker = {};
  };

  // callback for tracker setting modal box
  function updateSetting(t) {
    $scope.$broadcast('TrackerUpdate', t);
    $scope.$broadcast('PathUpdate', t);
    $http({
      method: 'PATCH',
      url: '/trackers/'+t._id,
      data: {
        iconColor: t.iconColor
      }
    });
  };

  function deleteTracker(t) {
    for (var i = 0; i < $scope.trackers.length; i++) {
      if ($scope.trackers[i]._id === t._id) {
        $scope.trackers.splice(i, 1);
        $http.delete('/tracker/' + t._id);
      }
    };
  }

  $scope.openSetting = function(t) {

    var SettingCtrl = function($scope, $modalInstance) {
      var setInstance = $modalInstance;
      $scope.data = t;

      $scope.ok = function() {
        updateSetting($scope.data);
        $modalInstance.close();
      };

      $scope.cancel = function () {
        $modalInstance.dismiss('cancel');
      };

      $scope.deleteTracker = function(tracker) {

        var DeleteCtrl = function($scope, $modalInstance) {
          $scope.t = tracker;

          $scope.ok = function() {
            deleteTracker(tracker);
            $modalInstance.close();
            setInstance.close();
          };

          $scope.cancel = function () {
            $modalInstance.dismiss('cancel');
          };

        };

        var deleteModal = $modal.open({
          templateUrl: '/templates/deleteTracker.html',
          controller: DeleteCtrl,
          windowClass: 'small'
        });

      };
    };

    var setModal = $modal.open({
      templateUrl: '/templates/trackerSetting.html',
      controller: SettingCtrl,
      windowClass: 'small'
    });

  };

  // get user account info
  $scope.init = function() {
    $http.get('/user/info').success(function(userinfo) {
      $scope.user = userinfo;
      _getFeature();
      initTrackers();
    }).error(function(data, status, headers, config) {
      if (status === 401) {
        angular.element(document.body).find('.bottom-nav .signin').click();
      }
    });
  }

  // get trackers info
  function initTrackers() {
    $http.get('/trackers').success(function(data) {
      $scope.trackers = data.items;
      // hack icon color format
      for (var i = 0; i < $scope.trackers.length; i++) {
        if ($scope.trackers[i].iconColor) {
          if ($scope.trackers[i].iconColor.charAt(0) !== '#') {
            $scope.trackers[i].iconColor = '#' + $scope.trackers[i].iconColor;
          }
        } else {
          $scope.trackers[i].iconColor = '#ff6600';
        }
      };
      $scope.trackerLoaded = true;
      $scope.$broadcast('InitTrackers');
      getMessages();
    }).error(function() {
      $scope.trackerLoaded = true;
    });
  }

  $scope.init();

  var delay = 1; // delay seconds for get trip message
  function getMessages() {
    var start = new Date().valueOf();
    $http.get('/message/notify').success(function (tracker) {
      var newTracker = true;
      var oldPos = {};

      // server response time less than One secend
      if ((new Date().valueOf()) - start < 5000) {
        delay *= 5;
      } else {
        // reset delay
        delay = 1;
      }

      console.log(tracker.name + ' udpated');
      // update or add tracker
      for (var i = 0; i < $scope.trackers.length; i++) {
        if ($scope.trackers[i]._id === tracker._id) {
          // add point to recent
          if ($scope.trackers[i].lastMessage.location) {
            $scope.trackers[i].recent = $scope.trackers[i].recent || {msgs:[]};
            $scope.trackers[i].recent.msgs.unshift(
              $scope.trackers[i].lastMessage.location);
            $scope.trackers[i].recent.msgs.splice(
              $scope.trackers[i].recent.msgs.length - 1, 1);

            // only update path when active tracker's trip pane is not show
            if (!$scope.activeTracker ||
                $scope.activeTracker._id !== $scope.trackers[i]._id ||
                !$scope.trackers[i].tripActive) {

                  $scope.trackers[i].path = $scope.trackers[i].recent.msgs;
                  $scope.$broadcast('PathUpdate', $scope.trackers[i], false);
             }
          }
          // override with new info
          for (var key in tracker) {
            $scope.trackers[i][key] = tracker[key];
          }

          newTracker = false;
        }
      }

      if (newTracker) {
        if (newTracker.iconColor) {
          if ( newTracker.iconColor.charAt(0) !== '#') {
            newTracker.iconColor = '#' + newTracker.iconColor;
          }
        } else {
          newTracker.iconColor = '#ff6600';
        }

        $scope.trackers.push(tracker);
      }

      $scope.$broadcast('TrackerUpdate', tracker);
      $timeout(getMessages, delay * 1000);

    }).error(function() {
      delay *= 2;
      $timeout(getMessages, delay * 1000);
    });
  }

  // refresh tracker
  $scope.refreshTracker = function(t) {
    $scope.activeTracker = t;
    $scope.$broadcast('FocusTracker', t);
    $scope.recentMsg(t);
  };

  // load recent msg of a tracker
  $scope.recentMsg = function(t) {

    if (t.recent && t.recent.msgs.length !== 0) {
      t.path = t.recent.msgs;
      $scope.$broadcast('PathUpdate', t, true);
      return;
    }

    var url = '/recent_messages?sortBy=_id$desc&trackerId=' + t._id;
    console.log('recent message url ' + url);
    $http.get(url).success(function(data) {
      console.log('------recent_message-----');
      t.recent = t.recent || {};
      t.recent.msgs = _filterValidMsg(data.items);
      if (t.recent.msgs.length > 1) {
        t.path = t.recent.msgs;
        $scope.$broadcast('PathUpdate', t, true);
      }
    });
  }

  $scope.onTripTimeSet = function(newOne, oldOne) {
      var date,
        url = '/trips?limit=1&sortBy=_id$asc&trackerId=' +
          $scope.activeTracker._id,
        tracker = $scope.activeTracker;

    tracker.trips.error = '';
    tracker.trips.loading = true;
    tracker.trips.data = [];

    date = moment(newOne).toISOString();
    url += '&endTime=after$date:' + date;
    url += '&startTime=before$date:' + date;

    $http.get(url).success(function (documents) {
      if (documents.items.length > 0) {
        var filtered = _simplifyTripMsg(documents.items[0]);
        if (filtered.messages.length === 0) {
          tracker.trips.loading = false;
          tracker.trips.error = 'trips data invalid';
          $timeout(function() { tracker.trips.error = ''; }, 10 * 1000);
          return;
        }
        tracker.trips.data.push(filtered);
        tracker.trips.loading = false;
        tracker.trips.error = '';
      } else {
        tracker.trips.loading = false;
        tracker.trips.error = 'no trip data at this time';
        $timeout(function() { tracker.trips.error = ''; }, 10 * 1000);
      }
    }).error(function(data) {
      tracker.trips.error = data;
      tracker.trips.loading = false;
      $timeout(function() { tracker.trips.error = ''; }, 5000);
    });
  };

  // load trips of at tracker
  $scope.showTrip = function(t, index) {
      t.path = t.trips.data[index].messages;
      t.trips.active = index;
      $scope.$broadcast('PathUpdate', t, true);
  };

  $scope.loadTrips = function (tracker, init) {
    var BATCH = 3; // load trip counts per time

    if (tracker.trips && tracker.trips.search !== '') {
      return;
    }

    if (tracker.trips && tracker.trips.error !== '') {
      return;
    }

    // prevent load more if user secondly click trip tab
    if (init && tracker.trips &&
        tracker.trips.data.length !==0) {
      $scope.showTrip(tracker, 0);
      return;
    }

    tracker.trips ? null: tracker.trips = { data:[], destCnt: 0, error: '' , search: ''};
    tracker.trips.destCnt = tracker.trips.data.length + BATCH;
    tracker.trips.loading = true;
    tracker.trips.error = '';

    _getOneTrip(tracker);
  };

  // get one trip from server
  function _getOneTrip(tracker) {
    var trips = tracker.trips;
    var before = '';
    var url, filtered;

    if (trips.data.length > 0) {
      before = '&_id=before$' + trips.data[trips.data.length - 1]._id;
    }

    url = '/trips?sortBy=_id$desc&limit=1&trackerId=' + tracker._id + before;

    $http.get(url).success(function (documents) {
      if (documents.items.length > 0) {
        filtered = _simplifyTripMsg(documents.items[0]);
        if (filtered.messages.length === 0) {
          tracker.trips.loading = false;
          tracker.trips.error = 'trips data invalid, retry later';
          $timeout(function() { tracker.trips.error = ''; }, 10 * 1000);
          return;
        }
        trips.data.push(filtered);
        // get next trip
        if (trips.data.length < tracker.trips.destCnt) {
          _getOneTrip(tracker);
        } else {
          tracker.trips.loading = false;
        }
        tracker.trips.error = '';
      } else {
        tracker.trips.loading = false;
        tracker.trips.error = 'no more trips, retry later';
        $timeout(function() { tracker.trips.error = ''; }, 10 * 1000);
      }
    }).error(function(data) {
      tracker.trips.error = data;
      tracker.trips.loading = false;
      $timeout(function() { tracker.trips.error = ''; }, 5000);
    });
  };

  function _simplifyTripMsg(trip) {
    // cut useless info in trip messages
    trip.messages = _filterValidMsg(trip.messages);

    return trip;
  }

  function _filterValidMsg(data) {
    var validMsg = [];

    // filter out useless messages
    for (var i = 0; i < data.length; i++) {
      if (data[i].message && data[i].message.location
          && data[i].message.location.latitude) {
        if (!data[i].message.location.timestamp) {
          data[i].message.location.timestamp =
            data[i].receivedTime || data[i].message.receiveTime;
        }
        validMsg.push(data[i].message.location);
      }
    }

    return validMsg;
  }

}]);

<|endoftext|>


<commit_before>$(function () {
	$('[data-toggle="popover"]').popover();
});

require ([
	'dojo/dom',
	'dojo/dom-class',
	'dojo/dom-attr',
	'dojo/on',
	'dojo/request/xhr',
	'dojo/json',
	'dojo/query',

	'put-selector/put',

	'ace/ace',

	'dojo/domReady!'
], function (
	dom,
	domClass,
	domAttr,
	on,
	xhr,
	json,
	query,

	put,

	ace
) {
	var MAX_CHUNK_SIZE = 50 * 1024;

	var inputs = query ('input.js-style-definition'),
		styleForm = dom.byId ('style-form'),
		editorElement = put (inputs[0].parentNode, 'div[style="position: relative; width: 100%; height: 400px;"] div.form-control[style="width: 100%; height: 400px;"]'),
		initialValue = '';

	inputs.forEach (function (input) {
		initialValue += input.value;
	});

	var editor = window.ace.edit (editorElement);

	editor.getSession ().setMode ('ace/mode/xml');
	editor.setValue (initialValue, -1);
	editor.getSession ().addGutterDecoration (1, 'text-danger');

	on (styleForm, 'submit', function (e) {
		var parent = inputs[0].parentNode,
			value = editor.getValue (),
			i = 0;

		inputs = query ('input.js-style-definition', parent);

		while (value.length > 0) {
			var chunk;

			if (value.length > MAX_CHUNK_SIZE) {
				chunk = value.substring (0, MAX_CHUNK_SIZE);
				value = value.substring (MAX_CHUNK_SIZE);
			} else {
				chunk = value;
				value = '';
			}

			// Create a textarea if required:
			var currentInput;
			if (i < inputs.length) {
				currentInput = inputs[i];
			} else {
				currentInput = put (parent, 'input.js-style-definition[type="hidden"]');
				domAttr.set (currentInput, 'name', 'definition[]');
			}

			currentInput.value = chunk;

			++ i;
		}

		for (; i < inputs.length; ++ i) {
			put (inputs[i], '!');
		}
	});

	var styleEditorElement = editorElement;

	window._geopublisherFileUploadCallback = function (content) {
		styleEditorElement.value = content;
	};

	function sendFile (file) {
		if (!window.XMLHttpRequest) {
			return;
		}

		var xmlRequest = new XMLHttpRequest ();
		xmlRequest.open ('POST', jsRoutes.controllers.Styles.handleFileUploadRaw ().url, true);
		xmlRequest.setRequestHeader ("Content-Type", file.type);
		xmlRequest.send (file);

		xmlRequest.onload = function () {
			var data = json.parse (this.responseText);
			if (data.valid) {
				editor.setValue (data.textContent, -1);
			}
		};
	}

	var overlay = null;

	on (styleEditorElement, 'dragenter', function (e) {
		// Do nothing if the transfer doesn't include files or if XMLHttpRequest is not supported by the browser:
		if (overlay !== null || !e.dataTransfer || !e.dataTransfer.types || !window.XMLHttpRequest) {
			return;
		}
		var hasFiles = false;
		for (var i = 0; i < e.dataTransfer.types.length; ++ i) {
			if (e.dataTransfer.types[i] == 'Files') {
				hasFiles = true;
				break;
			}
		}
		if (!hasFiles) {
			return;
		}

		e.stopPropagation ();
		e.preventDefault ();

		overlay = put (styleEditorElement.parentNode, 'div[class="dnd-overlay"]');

		function endDrag () {
			put ('!', overlay);
			overlay = null;
		}

		on (overlay, 'dragenter', function (e) {
			e.stopPropagation ();
			e.preventDefault ();
		});
		on (overlay, 'dragover', function (e) {
			e.stopPropagation ();
			e.preventDefault ();
		});
		on (overlay, 'dragleave', function (e) {
			e.stopPropagation ();
			e.preventDefault ();

			endDrag ();
		});
		on (overlay, 'drop', function (e) {
			e.preventDefault ();
			e.stopPropagation ();

			endDrag ();

			if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length >= 1) {
				sendFile (e.dataTransfer.files[0]);
			}
		});
	});
});<commit_msg>The inline upload form for styles changes the value of the ACE editor.<commit_after>$(function () {
	$('[data-toggle="popover"]').popover();
});

require ([
	'dojo/dom',
	'dojo/dom-class',
	'dojo/dom-attr',
	'dojo/on',
	'dojo/request/xhr',
	'dojo/json',
	'dojo/query',

	'put-selector/put',

	'ace/ace',

	'dojo/domReady!'
], function (
	dom,
	domClass,
	domAttr,
	on,
	xhr,
	json,
	query,

	put,

	ace
) {
	var MAX_CHUNK_SIZE = 50 * 1024;

	var inputs = query ('input.js-style-definition'),
		styleForm = dom.byId ('style-form'),
		editorElement = put (inputs[0].parentNode, 'div[style="position: relative; width: 100%; height: 400px;"] div.form-control[style="width: 100%; height: 400px;"]'),
		initialValue = '';

	inputs.forEach (function (input) {
		initialValue += input.value;
	});

	var editor = window.ace.edit (editorElement);

	editor.getSession ().setMode ('ace/mode/xml');
	editor.setValue (initialValue, -1);
	editor.getSession ().addGutterDecoration (1, 'text-danger');

	on (styleForm, 'submit', function (e) {
		var parent = inputs[0].parentNode,
			value = editor.getValue (),
			i = 0;

		inputs = query ('input.js-style-definition', parent);

		while (value.length > 0) {
			var chunk;

			if (value.length > MAX_CHUNK_SIZE) {
				chunk = value.substring (0, MAX_CHUNK_SIZE);
				value = value.substring (MAX_CHUNK_SIZE);
			} else {
				chunk = value;
				value = '';
			}

			// Create a textarea if required:
			var currentInput;
			if (i < inputs.length) {
				currentInput = inputs[i];
			} else {
				currentInput = put (parent, 'input.js-style-definition[type="hidden"]');
				domAttr.set (currentInput, 'name', 'definition[]');
			}

			currentInput.value = chunk;

			++ i;
		}

		for (; i < inputs.length; ++ i) {
			put (inputs[i], '!');
		}
	});

	var styleEditorElement = editorElement;

	window._geopublisherFileUploadCallback = function (content) {
		editor.setValue (content, -1);
	};

	function sendFile (file) {
		if (!window.XMLHttpRequest) {
			return;
		}

		var xmlRequest = new XMLHttpRequest ();
		xmlRequest.open ('POST', jsRoutes.controllers.Styles.handleFileUploadRaw ().url, true);
		xmlRequest.setRequestHeader ("Content-Type", file.type);
		xmlRequest.send (file);

		xmlRequest.onload = function () {
			var data = json.parse (this.responseText);
			if (data.valid) {
				editor.setValue (data.textContent, -1);
			}
		};
	}

	var overlay = null;

	on (styleEditorElement, 'dragenter', function (e) {
		// Do nothing if the transfer doesn't include files or if XMLHttpRequest is not supported by the browser:
		if (overlay !== null || !e.dataTransfer || !e.dataTransfer.types || !window.XMLHttpRequest) {
			return;
		}
		var hasFiles = false;
		for (var i = 0; i < e.dataTransfer.types.length; ++ i) {
			if (e.dataTransfer.types[i] == 'Files') {
				hasFiles = true;
				break;
			}
		}
		if (!hasFiles) {
			return;
		}

		e.stopPropagation ();
		e.preventDefault ();

		overlay = put (styleEditorElement.parentNode, 'div[class="dnd-overlay"]');

		function endDrag () {
			put ('!', overlay);
			overlay = null;
		}

		on (overlay, 'dragenter', function (e) {
			e.stopPropagation ();
			e.preventDefault ();
		});
		on (overlay, 'dragover', function (e) {
			e.stopPropagation ();
			e.preventDefault ();
		});
		on (overlay, 'dragleave', function (e) {
			e.stopPropagation ();
			e.preventDefault ();

			endDrag ();
		});
		on (overlay, 'drop', function (e) {
			e.preventDefault ();
			e.stopPropagation ();

			endDrag ();

			if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length >= 1) {
				sendFile (e.dataTransfer.files[0]);
			}
		});
	});
});<|endoftext|>


<commit_before>var keyMirror = require('keymirror');
var api = require('../mixins/api.jsx').api;

var Types = keyMirror({
    SET_SCHEDULE: null,
    SET_SCHEDULE_FETCHING: null,
    SET_SCHEDULE_ERROR: null
});

module.exports.scheduleReducer = function (state, action) {
    if (typeof state === 'undefined') {
        state = {
            timeSlots: [],
            day: ''
        };
    }
    switch (action.type) {
    case Types.SET_SCHEDULE:
        return action.schedule;
    case Types.SET_SCHEDULE_FETCHING:
        return state;
    case Types.SET_SCHEDULE_ERROR:
        return state;
    default:
        return state;
    }
};

module.exports.setSchedule = function (schedule) {
    return {
        type: Types.SET_SCHEDULE,
        schedule: schedule
    };
};

module.exports.setScheduleFetching = function () {
    return {
        type: Types.SET_SCHEDULE_FETCHING,
        fetching: true
    };
};

module.exports.setScheduleError = function (error) {
    return {
        type: Types.SET_SCHEDULE_ERROR,
        error: error
    };
};

module.exports.startGetSchedule = function (day) {
    return function (dispatch) {
        dispatch(module.exports.setScheduleFetching());
        dispatch(module.exports.getDaySchedule(day));
    };
};

// group periods of time by start time
module.exports.sortTimeSlots = function (timeSlot1, timeSlot2) {
    var timeSlot1Am = (timeSlot1.time.substr(timeSlot1.time.length - 1, timeSlot1.time.length) === 'a') ? true : false;
    var timeSlot2Am = (timeSlot2.time.substr(timeSlot2.time.length - 1, timeSlot2.time.length) === 'a') ? true : false;
    var timeSlot1Time = parseInt(timeSlot1.time.substr(0, timeSlot1.time.length - 1));
    var timeSlot2Time = parseInt(timeSlot2.time.substr(0, timeSlot2.time.length - 1));

    if (timeSlot1Time < timeSlot2Time) {
        if (timeSlot2Am && !timeSlot1Am) {
            return 1;
        } else {
            return -1;
        }
    } else {
        if (timeSlot1Am && !timeSlot2Am) {
            return -1;
        } else {
            return 1;
        }
    }
};

/**
 * Gets the schedule for the given day from the api
 * @param  {String} day  Day of the conference (Thursday, Friday or Satrurday)
 *
 *  @return {Object}     Schedule for the day, broken into chunks
 */
module.exports.getDaySchedule = function (day) {
    return function (dispatch) {
        api({
            uri: '/conference/schedule/' + day
        }, function (err, body) {
            if (err) {
                dispatch(module.exports.setScheduleError(err));
                return;
            }

            if (typeof body !== 'undefined') {
                var columns = body.columns;
                var rows = body.rows || [];
                // Group events by the time period in which they occur (for presentation)
                var scheduleByTimeSlot = rows.reduce(function (prev, cur) {
                    var cleanedRow = {};
                    for (var i = 0; i < columns.length; i++) {
                        if (cur[i].length > 0) {
                            cleanedRow[columns[i]] = cur[i];
                        }
                    }
                    cleanedRow['uri'] = '/conference/' + cleanedRow.rowid + '/details';
                    var timeSlot = cleanedRow.Chunk;
                    if (typeof prev.timeSlots[timeSlot] === 'undefined') {
                        prev.timeSlots[timeSlot] = [cleanedRow];
                        prev.info.push({
                            name: timeSlot,
                            time: cleanedRow.Start
                        });
                    } else {
                        prev.timeSlots[timeSlot].push(cleanedRow);
                    }
                    return prev;
                }, {timeSlots: [], info: []});

                scheduleByTimeSlot.info.sort(module.exports.sortTimeSlots);
                var schedule = scheduleByTimeSlot.info.map(function (timeSlot) {
                    return {
                        info: timeSlot,
                        items: scheduleByTimeSlot.timeSlots[timeSlot.name]
                    };
                });
                dispatch(module.exports.setSchedule({
                    timeSlots: schedule,
                    day: day
                }));
                return;
            } else {
                dispatch(module.exports.setScheduleError('An unexpected error occurred'));
                return;
            }
        });
    };
};
<commit_msg>Fix schedule formatting<commit_after>var keyMirror = require('keymirror');
var api = require('../mixins/api.jsx').api;

var Types = keyMirror({
    SET_SCHEDULE: null,
    SET_SCHEDULE_FETCHING: null,
    SET_SCHEDULE_ERROR: null
});

module.exports.scheduleReducer = function (state, action) {
    if (typeof state === 'undefined') {
        state = {
            timeSlots: [],
            day: ''
        };
    }
    switch (action.type) {
    case Types.SET_SCHEDULE:
        return action.schedule;
    case Types.SET_SCHEDULE_FETCHING:
        return state;
    case Types.SET_SCHEDULE_ERROR:
        return state;
    default:
        return state;
    }
};

module.exports.setSchedule = function (schedule) {
    return {
        type: Types.SET_SCHEDULE,
        schedule: schedule
    };
};

module.exports.setScheduleFetching = function () {
    return {
        type: Types.SET_SCHEDULE_FETCHING,
        fetching: true
    };
};

module.exports.setScheduleError = function (error) {
    return {
        type: Types.SET_SCHEDULE_ERROR,
        error: error
    };
};

module.exports.startGetSchedule = function (day) {
    return function (dispatch) {
        dispatch(module.exports.setScheduleFetching());
        dispatch(module.exports.getDaySchedule(day));
    };
};

// group periods of time by start time
module.exports.sortTimeSlots = function (timeSlot1, timeSlot2) {
    var timeSlot1Am = (timeSlot1.time.substr(timeSlot1.time.length - 1, timeSlot1.time.length) === 'a') ? true : false;
    var timeSlot2Am = (timeSlot2.time.substr(timeSlot2.time.length - 1, timeSlot2.time.length) === 'a') ? true : false;
    var timeSlot1Time = parseInt(timeSlot1.time.substr(0, timeSlot1.time.length - 1));
    var timeSlot2Time = parseInt(timeSlot2.time.substr(0, timeSlot2.time.length - 1));

    // convert to 24-hour for sorting
    if (timeSlot1Time !== 12 && !timeSlot1Am) {
        timeSlot1Time = timeSlot1Time + 12;
    }
    if (timeSlot2Time !== 12 && !timeSlot2Am) {
        timeSlot2Time = timeSlot2Time + 12;
    }

    if (timeSlot1Time < timeSlot2Time) {
        return -1;
    } else {
        return 1;
    }
};

/**
 * Gets the schedule for the given day from the api
 * @param  {String} day  Day of the conference (Thursday, Friday or Satrurday)
 *
 *  @return {Object}     Schedule for the day, broken into chunks
 */
module.exports.getDaySchedule = function (day) {
    return function (dispatch) {
        api({
            uri: '/conference/schedule/' + day
        }, function (err, body) {
            if (err) {
                dispatch(module.exports.setScheduleError(err));
                return;
            }

            if (typeof body !== 'undefined') {
                var columns = body.columns;
                var rows = body.rows || [];
                // Group events by the time period in which they occur (for presentation)
                var scheduleByTimeSlot = rows.reduce(function (prev, cur) {
                    var cleanedRow = {};
                    for (var i = 0; i < columns.length; i++) {
                        if (cur[i].length > 0) {
                            cleanedRow[columns[i]] = cur[i];
                        }
                    }
                    cleanedRow['uri'] = '/conference/' + cleanedRow.rowid + '/details';
                    var timeSlot = cleanedRow.Chunk + cleanedRow.Start;
                    if (typeof prev.timeSlots[timeSlot] === 'undefined') {
                        prev.timeSlots[timeSlot] = [cleanedRow];
                        prev.info.push({
                            name: cleanedRow.Chunk,
                            time: cleanedRow.Start
                        });
                    } else {
                        prev.timeSlots[timeSlot].push(cleanedRow);
                    }
                    return prev;
                }, {timeSlots: [], info: []});

                scheduleByTimeSlot.info.sort(module.exports.sortTimeSlots);
                var schedule = scheduleByTimeSlot.info.map(function (timeSlot) {
                    return {
                        info: timeSlot,
                        items: scheduleByTimeSlot.timeSlots[timeSlot.name + timeSlot.time]
                    };
                });
                dispatch(module.exports.setSchedule({
                    timeSlots: schedule,
                    day: day
                }));
                return;
            } else {
                dispatch(module.exports.setScheduleError('An unexpected error occurred'));
                return;
            }
        });
    };
};
<|endoftext|>