<commit_before>/**
 * Copyright (c) 2019-present, GraphQL Foundation
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow strict
 */

// A Function, which when given an Array of keys, returns a Promise of an Array
// of values or Errors.
export type BatchLoadFn<K, V> =
  (keys: $ReadOnlyArray<K>) => Promise<$ReadOnlyArray<V | Error>>;

// Optionally turn off batching or caching or provide a cache key function or a
// custom cache instance.
export type Options<K, V, C = K> = {
  batch?: boolean;
  maxBatchSize?: number;
  batchScheduleFn?: (callback: () => void) => void;
  cache?: boolean;
  cacheKeyFn?: (key: K) => C;
  cacheMap?: CacheMap<C, Promise<V>> | null;
};

// If a custom cache is provided, it must be of this type (a subset of ES6 Map).
export type CacheMap<K, V> = {
  get(key: K): V | void;
  set(key: K, value: V): any;
  delete(key: K): any;
  clear(): any;
};

/**
 * A `DataLoader` creates a public API for loading data from a particular
 * data back-end with unique keys such as the `id` column of a SQL table or
 * document name in a MongoDB database, given a batch loading function.
 *
 * Each `DataLoader` instance contains a unique memoized cache. Use caution when
 * used in long-lived applications or those which serve many users with
 * different access permissions and consider creating a new instance per
 * web request.
 */
class DataLoader<K, V, C = K> {
  constructor(
    batchLoadFn: BatchLoadFn<K, V>,
    options?: Options<K, V, C>
  ) {
    if (typeof batchLoadFn !== 'function') {
      throw new TypeError(
        'DataLoader must be constructed with a function which accepts ' +
        `Array<key> and returns Promise<Array<value>>, but got: ${batchLoadFn}.`
      );
    }
    this._batchLoadFn = batchLoadFn;
    this._maxBatchSize = getValidMaxBatchSize(options);
    this._batchScheduleFn = getValidBatchScheduleFn(options);
    this._cacheKeyFn = getValidCacheKeyFn(options);
    this._cacheMap = getValidCacheMap(options);
    this._batch = null;
  }

  // Private
  _batchLoadFn: BatchLoadFn<K, V>;
  _maxBatchSize: number;
  _batchScheduleFn: (() => void) => void;
  _cacheKeyFn: K => C;
  _cacheMap: CacheMap<C, Promise<V>> | null;
  _batch: Batch<K, V> | null;

  /**
   * Loads a key, returning a `Promise` for the value represented by that key.
   */
  load(key: K): Promise<V> {
    if (key === null || key === undefined) {
      throw new TypeError(
        'The loader.load() function must be called with a value,' +
        `but got: ${String(key)}.`
      );
    }

    var batch = getCurrentBatch(this);
    var cacheMap = this._cacheMap;
    var cacheKey = this._cacheKeyFn(key);

    // If caching and there is a cache-hit, return cached Promise.
    if (cacheMap) {
      var cachedPromise = cacheMap.get(cacheKey);
      if (cachedPromise) {
        var cacheHits = batch.cacheHits || (batch.cacheHits = []);
        return new Promise(resolve => {
          cacheHits.push(() => resolve(cachedPromise));
        });
      }
    }

    // Otherwise, produce a new Promise for this key, and enqueue it to be
    // dispatched along with the current batch.
    batch.keys.push(key);
    var promise = new Promise((resolve, reject) => {
      batch.callbacks.push({ resolve, reject });
    });

    // If caching, cache this promise.
    if (cacheMap) {
      cacheMap.set(cacheKey, promise);
    }

    return promise;
  }

  /**
   * Loads multiple keys, promising an array of values:
   *
   *     var [ a, b ] = await myLoader.loadMany([ 'a', 'b' ]);
   *
   * This is similar to the more verbose:
   *
   *     var [ a, b ] = await Promise.all([
   *       myLoader.load('a'),
   *       myLoader.load('b')
   *     ]);
   *
   * However it is different in the case where any load fails. Where
   * Promise.all() would reject, loadMany() always resolves, however each result
   * is either a value or an Error instance.
   *
   *     var [ a, b, c ] = await myLoader.loadMany([ 'a', 'b', 'badkey' ]);
   *     // c instanceof Error
   *
   */
  loadMany(keys: $ReadOnlyArray<K>): Promise<Array<V | Error>> {
    if (!isArrayLike(keys)) {
      throw new TypeError(
        'The loader.loadMany() function must be called with Array<key> ' +
        `but got: ${(keys: any)}.`
      );
    }
    // Support ArrayLike by using only minimal property access
    const loadPromises = [];
    for (let i = 0; i < keys.length; i++) {
      loadPromises.push(this.load(keys[i]).catch(error => error));
    }
    return Promise.all(loadPromises);
  }

  /**
   * Clears the value at `key` from the cache, if it exists. Returns itself for
   * method chaining.
   */
  clear(key: K): this {
    var cacheMap = this._cacheMap;
    if (cacheMap) {
      var cacheKey = this._cacheKeyFn(key);
      cacheMap.delete(cacheKey);
    }
    return this;
  }

  /**
   * Clears the entire cache. To be used when some event results in unknown
   * invalidations across this particular `DataLoader`. Returns itself for
   * method chaining.
   */
  clearAll(): this {
    var cacheMap = this._cacheMap;
    if (cacheMap) {
      cacheMap.clear();
    }
    return this;
  }

  /**
   * Adds the provided key and value to the cache. If the key already
   * exists, no change is made. Returns itself for method chaining.
   *
   * To prime the cache with an error at a key, provide an Error instance.
   */
  prime(key: K, value: V | Error): this {
    var cacheMap = this._cacheMap;
    if (cacheMap) {
      var cacheKey = this._cacheKeyFn(key);

      // Only add the key if it does not already exist.
      if (cacheMap.get(cacheKey) === undefined) {
        // Cache a rejected promise if the value is an Error, in order to match
        // the behavior of load(key).
        var promise;
        if (value instanceof Error) {
          promise = Promise.reject(value);
          // Since this is a case where an Error is intentionally being primed
          // for a given key, we want to disable unhandled promise rejection.
          promise.catch(() => {});
        } else {
          promise = Promise.resolve(value);
        }
        cacheMap.set(cacheKey, promise);
      }
    }
    return this;
  }
}

// Private: Enqueue a Job to be executed after all "PromiseJobs" Jobs.
//
// ES6 JavaScript uses the concepts Job and JobQueue to schedule work to occur
// after the current execution context has completed:
// http://www.ecma-international.org/ecma-262/6.0/#sec-jobs-and-job-queues
//
// Node.js uses the `process.nextTick` mechanism to implement the concept of a
// Job, maintaining a global FIFO JobQueue for all Jobs, which is flushed after
// the current call stack ends.
//
// When calling `then` on a Promise, it enqueues a Job on a specific
// "PromiseJobs" JobQueue which is flushed in Node as a single Job on the
// global JobQueue.
//
// DataLoader batches all loads which occur in a single frame of execution, but
// should include in the batch all loads which occur during the flushing of the
// "PromiseJobs" JobQueue after that same execution frame.
//
// In order to avoid the DataLoader dispatch Job occuring before "PromiseJobs",
// A Promise Job is created with the sole purpose of enqueuing a global Job,
// ensuring that it always occurs after "PromiseJobs" ends.
//
// Node.js's job queue is unique. Browsers do not have an equivalent mechanism
// for enqueuing a job to be performed after promise microtasks and before the
// next macrotask. For browser environments, a macrotask is used (via
// setImmediate or setTimeout) at a potential performance penalty.
var enqueuePostPromiseJob =
  typeof process === 'object' && typeof process.nextTick === 'function' ?
    function (fn) {
      if (!resolvedPromise) {
        resolvedPromise = Promise.resolve();
      }
      resolvedPromise.then(() => process.nextTick(fn));
    } :
    setImmediate || setTimeout;

// Private: cached resolved Promise instance
var resolvedPromise;

// Private: Describes a batch of requests
type Batch<K, V> = {
  hasDispatched: boolean,
  keys: Array<K>,
  callbacks: Array<{
    resolve: (value: V) => void;
    reject: (error: Error) => void;
  }>,
  cacheHits?: Array<() => void>
}

// Private: Either returns the current batch, or creates and schedules a
// dispatch of a new batch for the given loader.
function getCurrentBatch<K, V>(loader: DataLoader<K, V, any>): Batch<K, V> {
  // If there is an existing batch which has not yet dispatched and is within
  // the limit of the batch size, then return it.
  var existingBatch = loader._batch;
  if (
    existingBatch !== null &&
    !existingBatch.hasDispatched &&
    existingBatch.keys.length < loader._maxBatchSize &&
    (!existingBatch.cacheHits ||
      existingBatch.cacheHits.length < loader._maxBatchSize)
  ) {
    return existingBatch;
  }

  // Otherwise, create a new batch for this loader.
  var newBatch = { hasDispatched: false, keys: [], callbacks: [] };

  // Store it on the loader so it may be reused.
  loader._batch = newBatch;

  // Then schedule a task to dispatch this batch of requests.
  loader._batchScheduleFn(() => dispatchBatch(loader, newBatch));

  return newBatch;
}

function dispatchBatch<K, V>(
  loader: DataLoader<K, V, any>,
  batch: Batch<K, V>
) {
  // Mark this batch as having been dispatched.
  batch.hasDispatched = true;

  // If there's nothing to load, resolve any cache hits and return early.
  if (batch.keys.length === 0) {
    resolveCacheHits(batch);
    return;
  }

  // Call the provided batchLoadFn for this loader with the batch's keys and
  // with the loader as the `this` context.
  var batchPromise = loader._batchLoadFn(batch.keys);

  // Assert the expected response from batchLoadFn
  if (!batchPromise || typeof batchPromise.then !== 'function') {
    return failedDispatch(loader, batch, new TypeError(
      'DataLoader must be constructed with a function which accepts ' +
      'Array<key> and returns Promise<Array<value>>, but the function did ' +
      `not return a Promise: ${String(batchPromise)}.`
    ));
  }

  // Await the resolution of the call to batchLoadFn.
  batchPromise.then(values => {

    // Assert the expected resolution from batchLoadFn.
    if (!isArrayLike(values)) {
      throw new TypeError(
        'DataLoader must be constructed with a function which accepts ' +
        'Array<key> and returns Promise<Array<value>>, but the function did ' +
        `not return a Promise of an Array: ${String(values)}.`
      );
    }
    if (values.length !== batch.keys.length) {
      throw new TypeError(
        'DataLoader must be constructed with a function which accepts ' +
        'Array<key> and returns Promise<Array<value>>, but the function did ' +
        'not return a Promise of an Array of the same length as the Array ' +
        'of keys.' +
        `\n\nKeys:\n${String(batch.keys)}` +
        `\n\nValues:\n${String(values)}`
      );
    }

    // Resolve all cache hits in the same micro-task as freshly loaded values.
    resolveCacheHits(batch);

    // Step through values, resolving or rejecting each Promise in the batch.
    for (var i = 0; i < batch.callbacks.length; i++) {
      var value = values[i];
      if (value instanceof Error) {
        batch.callbacks[i].reject(value);
      } else {
        batch.callbacks[i].resolve(value);
      }
    }
  }).catch(error => failedDispatch(loader, batch, error));
}

// Private: do not cache individual loads if the entire batch dispatch fails,
// but still reject each request so they do not hang.
function failedDispatch<K, V>(
  loader: DataLoader<K, V, any>,
  batch: Batch<K, V>,
  error: Error
) {
  // Cache hits are resolved, even though the batch failed.
  resolveCacheHits(batch);
  for (var i = 0; i < batch.keys.length; i++) {
    loader.clear(batch.keys[i]);
    batch.callbacks[i].reject(error);
  }
}

// Private: Resolves the Promises for any cache hits in this batch.
function resolveCacheHits(batch: Batch<any, any>) {
  if (batch.cacheHits) {
    for (var i = 0; i < batch.cacheHits.length; i++) {
      batch.cacheHits[i]();
    }
  }
}

// Private: given the DataLoader's options, produce a valid max batch size.
function getValidMaxBatchSize(options: ?Options<any, any, any>): number {
  var shouldBatch = !options || options.batch !== false;
  if (!shouldBatch) {
    return 1;
  }
  var maxBatchSize = options && options.maxBatchSize;
  if (maxBatchSize === undefined) {
    return Infinity;
  }
  if (typeof maxBatchSize !== 'number' || maxBatchSize < 1) {
    throw new TypeError(
      `maxBatchSize must be a positive number: ${(maxBatchSize: any)}`
    );
  }
  return maxBatchSize;
}

// Private
function getValidBatchScheduleFn(
  options: ?Options<any, any, any>
): (() => void) => void {
  var batchScheduleFn = options && options.batchScheduleFn;
  if (batchScheduleFn === undefined) {
    return enqueuePostPromiseJob;
  }
  if (typeof batchScheduleFn !== 'function') {
    throw new TypeError(
      `batchScheduleFn must be a function: ${(batchScheduleFn: any)}`
    );
  }
  return batchScheduleFn;
}

// Private: given the DataLoader's options, produce a cache key function.
function getValidCacheKeyFn<K, C>(options: ?Options<K, any, C>): (K => C) {
  var cacheKeyFn = options && options.cacheKeyFn;
  if (cacheKeyFn === undefined) {
    return (key => key: any);
  }
  if (typeof cacheKeyFn !== 'function') {
    throw new TypeError(`cacheKeyFn must be a function: ${(cacheKeyFn: any)}`);
  }
  return cacheKeyFn;
}

// Private: given the DataLoader's options, produce a CacheMap to be used.
function getValidCacheMap<K, V, C>(
  options: ?Options<K, V, C>
): CacheMap<C, Promise<V>> | null {
  var shouldCache = !options || options.cache !== false;
  if (!shouldCache) {
    return null;
  }
  var cacheMap = options && options.cacheMap;
  if (cacheMap === undefined) {
    return new Map();
  }
  if (cacheMap !== null) {
    var cacheFunctions = [ 'get', 'set', 'delete', 'clear' ];
    var missingFunctions = cacheFunctions
      .filter(fnName => cacheMap && typeof cacheMap[fnName] !== 'function');
    if (missingFunctions.length !== 0) {
      throw new TypeError(
        'Custom cacheMap missing methods: ' + missingFunctions.join(', ')
      );
    }
  }
  return cacheMap;
}

// Private
function isArrayLike(x: mixed): boolean {
  return (
    typeof x === 'object' &&
    x !== null &&
    typeof x.length === 'number' &&
    (x.length === 0 ||
      (x.length > 0 && Object.prototype.hasOwnProperty.call(x, x.length - 1)))
  );
}

module.exports = DataLoader;
<commit_msg>Minor: do not return void results from arrow functions<commit_after>/**
 * Copyright (c) 2019-present, GraphQL Foundation
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow strict
 */

// A Function, which when given an Array of keys, returns a Promise of an Array
// of values or Errors.
export type BatchLoadFn<K, V> =
  (keys: $ReadOnlyArray<K>) => Promise<$ReadOnlyArray<V | Error>>;

// Optionally turn off batching or caching or provide a cache key function or a
// custom cache instance.
export type Options<K, V, C = K> = {
  batch?: boolean;
  maxBatchSize?: number;
  batchScheduleFn?: (callback: () => void) => void;
  cache?: boolean;
  cacheKeyFn?: (key: K) => C;
  cacheMap?: CacheMap<C, Promise<V>> | null;
};

// If a custom cache is provided, it must be of this type (a subset of ES6 Map).
export type CacheMap<K, V> = {
  get(key: K): V | void;
  set(key: K, value: V): any;
  delete(key: K): any;
  clear(): any;
};

/**
 * A `DataLoader` creates a public API for loading data from a particular
 * data back-end with unique keys such as the `id` column of a SQL table or
 * document name in a MongoDB database, given a batch loading function.
 *
 * Each `DataLoader` instance contains a unique memoized cache. Use caution when
 * used in long-lived applications or those which serve many users with
 * different access permissions and consider creating a new instance per
 * web request.
 */
class DataLoader<K, V, C = K> {
  constructor(
    batchLoadFn: BatchLoadFn<K, V>,
    options?: Options<K, V, C>
  ) {
    if (typeof batchLoadFn !== 'function') {
      throw new TypeError(
        'DataLoader must be constructed with a function which accepts ' +
        `Array<key> and returns Promise<Array<value>>, but got: ${batchLoadFn}.`
      );
    }
    this._batchLoadFn = batchLoadFn;
    this._maxBatchSize = getValidMaxBatchSize(options);
    this._batchScheduleFn = getValidBatchScheduleFn(options);
    this._cacheKeyFn = getValidCacheKeyFn(options);
    this._cacheMap = getValidCacheMap(options);
    this._batch = null;
  }

  // Private
  _batchLoadFn: BatchLoadFn<K, V>;
  _maxBatchSize: number;
  _batchScheduleFn: (() => void) => void;
  _cacheKeyFn: K => C;
  _cacheMap: CacheMap<C, Promise<V>> | null;
  _batch: Batch<K, V> | null;

  /**
   * Loads a key, returning a `Promise` for the value represented by that key.
   */
  load(key: K): Promise<V> {
    if (key === null || key === undefined) {
      throw new TypeError(
        'The loader.load() function must be called with a value,' +
        `but got: ${String(key)}.`
      );
    }

    var batch = getCurrentBatch(this);
    var cacheMap = this._cacheMap;
    var cacheKey = this._cacheKeyFn(key);

    // If caching and there is a cache-hit, return cached Promise.
    if (cacheMap) {
      var cachedPromise = cacheMap.get(cacheKey);
      if (cachedPromise) {
        var cacheHits = batch.cacheHits || (batch.cacheHits = []);
        return new Promise(resolve => {
          cacheHits.push(() => {
            resolve(cachedPromise);
          });
        });
      }
    }

    // Otherwise, produce a new Promise for this key, and enqueue it to be
    // dispatched along with the current batch.
    batch.keys.push(key);
    var promise = new Promise((resolve, reject) => {
      batch.callbacks.push({ resolve, reject });
    });

    // If caching, cache this promise.
    if (cacheMap) {
      cacheMap.set(cacheKey, promise);
    }

    return promise;
  }

  /**
   * Loads multiple keys, promising an array of values:
   *
   *     var [ a, b ] = await myLoader.loadMany([ 'a', 'b' ]);
   *
   * This is similar to the more verbose:
   *
   *     var [ a, b ] = await Promise.all([
   *       myLoader.load('a'),
   *       myLoader.load('b')
   *     ]);
   *
   * However it is different in the case where any load fails. Where
   * Promise.all() would reject, loadMany() always resolves, however each result
   * is either a value or an Error instance.
   *
   *     var [ a, b, c ] = await myLoader.loadMany([ 'a', 'b', 'badkey' ]);
   *     // c instanceof Error
   *
   */
  loadMany(keys: $ReadOnlyArray<K>): Promise<Array<V | Error>> {
    if (!isArrayLike(keys)) {
      throw new TypeError(
        'The loader.loadMany() function must be called with Array<key> ' +
        `but got: ${(keys: any)}.`
      );
    }
    // Support ArrayLike by using only minimal property access
    const loadPromises = [];
    for (let i = 0; i < keys.length; i++) {
      loadPromises.push(this.load(keys[i]).catch(error => error));
    }
    return Promise.all(loadPromises);
  }

  /**
   * Clears the value at `key` from the cache, if it exists. Returns itself for
   * method chaining.
   */
  clear(key: K): this {
    var cacheMap = this._cacheMap;
    if (cacheMap) {
      var cacheKey = this._cacheKeyFn(key);
      cacheMap.delete(cacheKey);
    }
    return this;
  }

  /**
   * Clears the entire cache. To be used when some event results in unknown
   * invalidations across this particular `DataLoader`. Returns itself for
   * method chaining.
   */
  clearAll(): this {
    var cacheMap = this._cacheMap;
    if (cacheMap) {
      cacheMap.clear();
    }
    return this;
  }

  /**
   * Adds the provided key and value to the cache. If the key already
   * exists, no change is made. Returns itself for method chaining.
   *
   * To prime the cache with an error at a key, provide an Error instance.
   */
  prime(key: K, value: V | Error): this {
    var cacheMap = this._cacheMap;
    if (cacheMap) {
      var cacheKey = this._cacheKeyFn(key);

      // Only add the key if it does not already exist.
      if (cacheMap.get(cacheKey) === undefined) {
        // Cache a rejected promise if the value is an Error, in order to match
        // the behavior of load(key).
        var promise;
        if (value instanceof Error) {
          promise = Promise.reject(value);
          // Since this is a case where an Error is intentionally being primed
          // for a given key, we want to disable unhandled promise rejection.
          promise.catch(() => {});
        } else {
          promise = Promise.resolve(value);
        }
        cacheMap.set(cacheKey, promise);
      }
    }
    return this;
  }
}

// Private: Enqueue a Job to be executed after all "PromiseJobs" Jobs.
//
// ES6 JavaScript uses the concepts Job and JobQueue to schedule work to occur
// after the current execution context has completed:
// http://www.ecma-international.org/ecma-262/6.0/#sec-jobs-and-job-queues
//
// Node.js uses the `process.nextTick` mechanism to implement the concept of a
// Job, maintaining a global FIFO JobQueue for all Jobs, which is flushed after
// the current call stack ends.
//
// When calling `then` on a Promise, it enqueues a Job on a specific
// "PromiseJobs" JobQueue which is flushed in Node as a single Job on the
// global JobQueue.
//
// DataLoader batches all loads which occur in a single frame of execution, but
// should include in the batch all loads which occur during the flushing of the
// "PromiseJobs" JobQueue after that same execution frame.
//
// In order to avoid the DataLoader dispatch Job occuring before "PromiseJobs",
// A Promise Job is created with the sole purpose of enqueuing a global Job,
// ensuring that it always occurs after "PromiseJobs" ends.
//
// Node.js's job queue is unique. Browsers do not have an equivalent mechanism
// for enqueuing a job to be performed after promise microtasks and before the
// next macrotask. For browser environments, a macrotask is used (via
// setImmediate or setTimeout) at a potential performance penalty.
var enqueuePostPromiseJob =
  typeof process === 'object' && typeof process.nextTick === 'function' ?
    function (fn) {
      if (!resolvedPromise) {
        resolvedPromise = Promise.resolve();
      }
      resolvedPromise.then(() => {
        process.nextTick(fn);
      });
    } :
    setImmediate || setTimeout;

// Private: cached resolved Promise instance
var resolvedPromise;

// Private: Describes a batch of requests
type Batch<K, V> = {
  hasDispatched: boolean,
  keys: Array<K>,
  callbacks: Array<{
    resolve: (value: V) => void;
    reject: (error: Error) => void;
  }>,
  cacheHits?: Array<() => void>
}

// Private: Either returns the current batch, or creates and schedules a
// dispatch of a new batch for the given loader.
function getCurrentBatch<K, V>(loader: DataLoader<K, V, any>): Batch<K, V> {
  // If there is an existing batch which has not yet dispatched and is within
  // the limit of the batch size, then return it.
  var existingBatch = loader._batch;
  if (
    existingBatch !== null &&
    !existingBatch.hasDispatched &&
    existingBatch.keys.length < loader._maxBatchSize &&
    (!existingBatch.cacheHits ||
      existingBatch.cacheHits.length < loader._maxBatchSize)
  ) {
    return existingBatch;
  }

  // Otherwise, create a new batch for this loader.
  var newBatch = { hasDispatched: false, keys: [], callbacks: [] };

  // Store it on the loader so it may be reused.
  loader._batch = newBatch;

  // Then schedule a task to dispatch this batch of requests.
  loader._batchScheduleFn(() => {
    dispatchBatch(loader, newBatch);
  });

  return newBatch;
}

function dispatchBatch<K, V>(
  loader: DataLoader<K, V, any>,
  batch: Batch<K, V>
) {
  // Mark this batch as having been dispatched.
  batch.hasDispatched = true;

  // If there's nothing to load, resolve any cache hits and return early.
  if (batch.keys.length === 0) {
    resolveCacheHits(batch);
    return;
  }

  // Call the provided batchLoadFn for this loader with the batch's keys and
  // with the loader as the `this` context.
  var batchPromise = loader._batchLoadFn(batch.keys);

  // Assert the expected response from batchLoadFn
  if (!batchPromise || typeof batchPromise.then !== 'function') {
    return failedDispatch(loader, batch, new TypeError(
      'DataLoader must be constructed with a function which accepts ' +
      'Array<key> and returns Promise<Array<value>>, but the function did ' +
      `not return a Promise: ${String(batchPromise)}.`
    ));
  }

  // Await the resolution of the call to batchLoadFn.
  batchPromise.then(values => {

    // Assert the expected resolution from batchLoadFn.
    if (!isArrayLike(values)) {
      throw new TypeError(
        'DataLoader must be constructed with a function which accepts ' +
        'Array<key> and returns Promise<Array<value>>, but the function did ' +
        `not return a Promise of an Array: ${String(values)}.`
      );
    }
    if (values.length !== batch.keys.length) {
      throw new TypeError(
        'DataLoader must be constructed with a function which accepts ' +
        'Array<key> and returns Promise<Array<value>>, but the function did ' +
        'not return a Promise of an Array of the same length as the Array ' +
        'of keys.' +
        `\n\nKeys:\n${String(batch.keys)}` +
        `\n\nValues:\n${String(values)}`
      );
    }

    // Resolve all cache hits in the same micro-task as freshly loaded values.
    resolveCacheHits(batch);

    // Step through values, resolving or rejecting each Promise in the batch.
    for (var i = 0; i < batch.callbacks.length; i++) {
      var value = values[i];
      if (value instanceof Error) {
        batch.callbacks[i].reject(value);
      } else {
        batch.callbacks[i].resolve(value);
      }
    }
  }).catch(error => {
    failedDispatch(loader, batch, error);
  });
}

// Private: do not cache individual loads if the entire batch dispatch fails,
// but still reject each request so they do not hang.
function failedDispatch<K, V>(
  loader: DataLoader<K, V, any>,
  batch: Batch<K, V>,
  error: Error
) {
  // Cache hits are resolved, even though the batch failed.
  resolveCacheHits(batch);
  for (var i = 0; i < batch.keys.length; i++) {
    loader.clear(batch.keys[i]);
    batch.callbacks[i].reject(error);
  }
}

// Private: Resolves the Promises for any cache hits in this batch.
function resolveCacheHits(batch: Batch<any, any>) {
  if (batch.cacheHits) {
    for (var i = 0; i < batch.cacheHits.length; i++) {
      batch.cacheHits[i]();
    }
  }
}

// Private: given the DataLoader's options, produce a valid max batch size.
function getValidMaxBatchSize(options: ?Options<any, any, any>): number {
  var shouldBatch = !options || options.batch !== false;
  if (!shouldBatch) {
    return 1;
  }
  var maxBatchSize = options && options.maxBatchSize;
  if (maxBatchSize === undefined) {
    return Infinity;
  }
  if (typeof maxBatchSize !== 'number' || maxBatchSize < 1) {
    throw new TypeError(
      `maxBatchSize must be a positive number: ${(maxBatchSize: any)}`
    );
  }
  return maxBatchSize;
}

// Private
function getValidBatchScheduleFn(
  options: ?Options<any, any, any>
): (() => void) => void {
  var batchScheduleFn = options && options.batchScheduleFn;
  if (batchScheduleFn === undefined) {
    return enqueuePostPromiseJob;
  }
  if (typeof batchScheduleFn !== 'function') {
    throw new TypeError(
      `batchScheduleFn must be a function: ${(batchScheduleFn: any)}`
    );
  }
  return batchScheduleFn;
}

// Private: given the DataLoader's options, produce a cache key function.
function getValidCacheKeyFn<K, C>(options: ?Options<K, any, C>): (K => C) {
  var cacheKeyFn = options && options.cacheKeyFn;
  if (cacheKeyFn === undefined) {
    return (key => key: any);
  }
  if (typeof cacheKeyFn !== 'function') {
    throw new TypeError(`cacheKeyFn must be a function: ${(cacheKeyFn: any)}`);
  }
  return cacheKeyFn;
}

// Private: given the DataLoader's options, produce a CacheMap to be used.
function getValidCacheMap<K, V, C>(
  options: ?Options<K, V, C>
): CacheMap<C, Promise<V>> | null {
  var shouldCache = !options || options.cache !== false;
  if (!shouldCache) {
    return null;
  }
  var cacheMap = options && options.cacheMap;
  if (cacheMap === undefined) {
    return new Map();
  }
  if (cacheMap !== null) {
    var cacheFunctions = [ 'get', 'set', 'delete', 'clear' ];
    var missingFunctions = cacheFunctions
      .filter(fnName => cacheMap && typeof cacheMap[fnName] !== 'function');
    if (missingFunctions.length !== 0) {
      throw new TypeError(
        'Custom cacheMap missing methods: ' + missingFunctions.join(', ')
      );
    }
  }
  return cacheMap;
}

// Private
function isArrayLike(x: mixed): boolean {
  return (
    typeof x === 'object' &&
    x !== null &&
    typeof x.length === 'number' &&
    (x.length === 0 ||
      (x.length > 0 && Object.prototype.hasOwnProperty.call(x, x.length - 1)))
  );
}

module.exports = DataLoader;
<|endoftext|>


<commit_before>import extendReactClass from './extendReactClass';
import wrapStatelessFunction from './wrapStatelessFunction';

let decoratorConstructor,
    functionConstructor;

/**
 * When used as a function.
 *
 * @param {Function} Component
 * @param {Object} defaultStyles CSS Modules class map.
 * @param {Object} options {@link https://github.com/gajus/react-css-modules#options}
 * @return {Function}
 */
functionConstructor = (Component, defaultStyles, options) => {
    let decoratedClass;

    decoratedClass = Component.isReactClass ?
        extendReactClass(Component, defaultStyles, options) :
        wrapStatelessFunction(Component, defaultStyles, options);

    if (Component.displayName) {
        decoratedClass.displayName = Component.displayName;
    } else {
        decoratedClass.displayName = Component.name;
    }

    return decoratedClass;
};

/**
 * When used as a ES7 decorator.
 *
 * @param {Object} defaultStyles CSS Modules class map.
 * @param {Object} options {@link https://github.com/gajus/react-css-modules#options}
 * @return {Function}
 */
decoratorConstructor = (defaultStyles, options) => {
    return (Component) => {
        return functionConstructor(Component, defaultStyles, options);
    };
};

export default (...args) => {
    if (typeof args[0] === 'function') {
        return functionConstructor(args[0], args[1], args[2]);
    } else {
        return decoratorConstructor(args[0], args[1]);
    }
};
<commit_msg>A smarter way of determining of class is a React.Component. https://github.com/gajus/react-css-modules/pull/41<commit_after>import extendReactClass from './extendReactClass';
import wrapStatelessFunction from './wrapStatelessFunction';

let decoratorConstructor,
    functionConstructor,
    isReactComponent;

/**
 * Determines if the given object has the signature of a class that inherits React.Component.
 *
 * @param {*} Component
 * @return {Boolean}
 */
isReactComponent = (Component) => {
    return 'prototype' in Component && typeof Component.prototype.render === 'function';
};

/**
 * When used as a function.
 *
 * @param {Function} Component
 * @param {Object} defaultStyles CSS Modules class map.
 * @param {Object} options {@link https://github.com/gajus/react-css-modules#options}
 * @return {Function}
 */
functionConstructor = (Component, defaultStyles, options) => {
    let decoratedClass;

    if (isReactComponent(Component)) {
        decoratedClass = extendReactClass(Component, defaultStyles, options);
    } else {
        decoratedClass = wrapStatelessFunction(Component, defaultStyles, options);
    }

    if (Component.displayName) {
        decoratedClass.displayName = Component.displayName;
    } else {
        decoratedClass.displayName = Component.name;
    }

    return decoratedClass;
};

/**
 * When used as a ES7 decorator.
 *
 * @param {Object} defaultStyles CSS Modules class map.
 * @param {Object} options {@link https://github.com/gajus/react-css-modules#options}
 * @return {Function}
 */
decoratorConstructor = (defaultStyles, options) => {
    return (Component) => {
        return functionConstructor(Component, defaultStyles, options);
    };
};

export default (...args) => {
    if (typeof args[0] === 'function') {
        return functionConstructor(args[0], args[1], args[2]);
    } else {
        return decoratorConstructor(args[0], args[1]);
    }
};
<|endoftext|>


<commit_before>if (SERVER_SIDE){
	var key          = '_request_local_storage_'
	,   cls          = require('continuation-local-storage').createNamespace(key)
	,   bind         = cls.bind.bind(cls)
	,   patch        = func => func(cls)
	,   getContainer = () => cls.get(key)
	,   startRequest = (start) => { cls.run(() => {cls.set(key, []); start()}) }
} else {
	var container    = []
	,   bind         = f  => f
	,   patch        = () => {}
	,   getContainer = () => container
	,   startRequest = () => container = []
}

var namespaces         = 0
,   getCountNamespaces = () => namespaces
,   getNamespace       = () => {

	// This will be our return value.
	var getter = (i => () => {
		var container = getContainer();
		if (!container){
			throw new Error("RLS() access outside of request!");
		}
		return container[i] || (container[i] = {});
	})(namespaces++);

	// This is guarded against old versions of node that don't provide the
	// necessary API (bamboo).
	if (SERVER_SIDE && Object.observe) {

		Object.observe(getter, changes => {

			// It's easy to make the mistake of using `RLS.foo`
			// instead of `RLS().foo`, but this is actually a very
			// bad error server-side.  It's a leak across requests.
			throw new Error(`Use of "RLS.${
				changes[0].name
			}" should be "RLS().${
				changes[0].name
			}"!`);
		});
	}

	return getter;
}

module.exports = { getNamespace, getCountNamespaces, startRequest, bind, patch };
<commit_msg>Adding a convenience method to check if RLS() is available for consumption during run time (#4)<commit_after>if (SERVER_SIDE){
	var key          = '_request_local_storage_'
	,   cls          = require('continuation-local-storage').createNamespace(key)
	,   bind         = cls.bind.bind(cls)
	,   patch        = func => func(cls)
	,   getContainer = () => cls.get(key)
	,   startRequest = (start) => { cls.run(() => {cls.set(key, []); start()}) }
} else {
	var container    = []
	,   bind         = f  => f
	,   patch        = () => {}
	,   getContainer = () => container
	,   startRequest = () => container = []
}

var namespaces         = 0
,   getCountNamespaces = () => namespaces
,   getNamespace       = () => {

	// This will be our return value.
	var getter = (i => () => {
		var container = getContainer();
		if (!container){
			throw new Error("RLS() access outside of request!");
		}
		return container[i] || (container[i] = {});
	})(namespaces++);

	getter.isActive = () => !!getContainer();

	// This is guarded against old versions of node that don't provide the
	// necessary API (bamboo).
	if (SERVER_SIDE && Object.observe) {

		Object.observe(getter, changes => {

			// It's easy to make the mistake of using `RLS.foo`
			// instead of `RLS().foo`, but this is actually a very
			// bad error server-side.  It's a leak across requests.
			throw new Error(`Use of "RLS.${
				changes[0].name
			}" should be "RLS().${
				changes[0].name
			}"!`);
		});
	}

	return getter;
}

module.exports = { getNamespace, getCountNamespaces, startRequest, bind, patch };
<|endoftext|>


<commit_before>module.exports = (function() {
    'use strict';

    // conf.initBump();
    // conf.initMochaTest();
    // conf.initBrowserify();
    // conf.initUglify();
    // conf.initJshint();
    // conf.initLess();
    // conf.initWatch();
    // conf.initWebpack();
    // conf.getBanner();

    function UmxGruntConfig(require, grunt, options) {
        this.options = options || {};
        this.require = require;
        if (!this.require) {
            throw new Error('The "require" method is not defined.');
        }
        this.grunt = grunt;
        if (!this.grunt) {
            throw new Error('A Grunt instance is not defined.');
        }
        var pkg = this.grunt.file.readJSON('package.json');
        pkg.appname = this.options.appname || pkg.appname || pkg.name;
        this.config = {
            pkg : pkg
        };
    }

    UmxGruntConfig.prototype.registerBumpTasks = function() {
        this.grunt.registerTask('inc', [ 'bump-only' ]);
        this.grunt.registerTask('incMinor', [ 'bump-only:minor' ]);
        this.grunt.registerTask('incMajor', [ 'bump-only:major' ]);
    };

    UmxGruntConfig.prototype.initBump = function(options) {
        options = options || {};
        this.config.bump = {
            options : {
                files : [ 'package.json', 'bower.json' ],
                updateConfigs : [],
                commit : true,
                commitMessage : 'Release v%VERSION%',
                commitFiles : [ '.' ],
                createTag : true,
                tagName : 'v%VERSION%',
                tagMessage : 'Version %VERSION%',
                push : false,
                pushTo : 'upstream',
                gitDescribeOptions : '--tags --always --abbrev=1 --dirty=-d'
            }
        };
        this.grunt.loadNpmTasks('grunt-bump');
        if (options.tasks !== false) {
            this.registerBumpTasks();
        }
    };

    UmxGruntConfig.prototype.initLess = function(options) {
        options = options || {};
        this.config.less = {
            main : {
                options : {
                    paths : this._getLessPaths(options)
                },
                files : this._getLessMapping(options)
            }
        };
        this.grunt.loadNpmTasks('grunt-contrib-less');
    };

    UmxGruntConfig.prototype._getLessPaths = function(options) {
        options = options || {};
        var paths = options.lessPaths || options.cssPaths;
        if (!paths) {
            var pkg = this.config.pkg;
            var Path = require('path');
            var cssDir = options.cssDir || 'css';
            var srcMask = Path.resolve(cssDir, './**/*.css');
            var lessMask = Path.resolve(cssDir, './**/*.less');
            paths = [ srcMask, lessMask ];
        }
        return paths;
    };

    UmxGruntConfig.prototype._getLessMapping = function(options) {
        var pkg = this.config.pkg;
        var sourceFile = options.lessFile || options.cssFile;
        if (!sourceFile) {
            var cssDir = options.lessDir || options.cssDir || 'css';
            sourceFile = cssDir + '/index.less';
        }
        var distDir = this._getDistDir(options);
        var distFile = distDir + '/' + pkg.appname + '.css';
        var result = {};
        result[distFile] = sourceFile;
        return result;
    };

    UmxGruntConfig.prototype.initWebpack = function(options) {
        options = options || {};
        var pkg = this.config.pkg;
        var banner = this.getBanner(options);
        var webpack = this.require('webpack');
        var bowerLibs = this._getBowerDir(options);
        this.config.webpack = {
            main : {
                entry : this._getMainFile(options),
                output : {
                    path : this._getDistDir(options),
                    filename : pkg.appname + '.js',
                    library : pkg.appname,
                    libraryTarget : options.target || 'umd'
                },
                externals : options.externals || [ /^[a-z\-0-9]+$/ ],
                resolve : {
                    modulesDirectories : [ "web_modules", "node_modules",
                            "bower_components", "libs" ].concat(bowerLibs),
                    extensions : [ "", ".webpack-loader.js", ".web-loader.js",
                            ".loader.js", ".js" ],
                    packageMains : [ "webpackLoader", "webLoader", "loader",
                            "main" ]
                },
                // devtool : options.devtool || "eval",
                debug : options.debug || true,

                module : options.module || {},
                plugins : [
                        new webpack.BannerPlugin(banner),
                        new webpack.optimize.DedupePlugin(),
                        new webpack.ResolverPlugin(
                                new webpack.ResolverPlugin.DirectoryDescriptionFilePlugin(
                                        "bower.json", [ "main" ])) ]
            }
        };
        this.grunt.loadNpmTasks('grunt-webpack');
    };

    UmxGruntConfig.prototype.initMochaTest = function(options) {
        options = options || {};
        this.config.mochaTest = {
            test : {
                options : {
                    reporter : 'spec'
                },
                src : options.src || //
                [ 'test/**/spec_*.js', 'test/**/*_spec.js' ]
            }
        };
        this.grunt.loadNpmTasks('grunt-mocha-test');
    };

    UmxGruntConfig.prototype.initKarma = function() {
        this.config.karma = {
            unit : {
                configFile : 'karma.conf.js',
                reporters : 'progress'
            },
            // continuous integration mode: run tests once in PhantomJS browser.
            continuous : {
                configFile : 'karma.conf.js',
                singleRun : true,
                reporters : 'progress',
                runnerPort : 9998
            }
        };
        this.grunt.loadNpmTasks('grunt-karma');
    };

    UmxGruntConfig.prototype.initBrowserify = function(options) {
        options = options || {};
        this.config.browserify = {
            standalone : {
                src : [ this._getMainFile(options) ],
                dest : this._getDistDir(options) + '/<%= pkg.appname %>.js',
                options : {
                    exclude : options.exclude || [],
                    bundleOptions : {
                        standalone : '<%= pkg.appname %>'
                    }
                }
            }
        };
        this.grunt.loadNpmTasks('grunt-browserify');
    };

    UmxGruntConfig.prototype.getBanner = function(options) {
        // Project configuration.
        var pkg = this.config.pkg;
        var licenses = '';
        (pkg.licenses || []).forEach(function(l) {
            if (licenses.length) {
                licenses += ', ';
            }
            licenses += l ? l.type || '' : '';
        });
        if (licenses.length) {
            licenses = ' | License: ' + licenses + ' ';
        }
        return '<%= pkg.appname %> v<%= pkg.version %>' + licenses + '\n';
    };

    UmxGruntConfig.prototype.initUglify = function(options) {
        var banner = '/* \n * ' + this.getBanner(options) + ' */\n';
        var distDir = this._getDistDir(options);
        this.config.uglify = {
            options : {
                banner : banner
            },
            browser : {
                src : distDir + '/<%= pkg.appname %>.js',
                dest : distDir + '/<%= pkg.appname %>.min.js'
            }
        };
        this.grunt.loadNpmTasks('grunt-contrib-uglify');
    };

    UmxGruntConfig.prototype.initJshint = function(options) {
        this.config.jshint = {
            files : this._getSourceFiles(options),
            // configure JSHint (documented at
            // http://www.jshint.com/docs/)
            options : {
                // more options here if you want to override JSHint
                // defaults
                globals : {
                    console : true,
                    module : true,
                    require : true
                }
            }
        };
        this.grunt.loadNpmTasks('grunt-contrib-jshint');
    };

    UmxGruntConfig.prototype.initWatch = function(options) {
        options = options || {};
        var files = this._getWatchFiles(options);
        this.config.watch = {
            scripts : {
                files : files,
                tasks : options.watchTasks || options.tasks || [ 'test' ],
                options : {
                    spawn : options.spawn !== false,
                    interrupt : true,
                },
            },
        };
        this.grunt.loadNpmTasks('grunt-contrib-watch');
    };

    UmxGruntConfig.prototype.registerTasks = function() {
        // this would be run by typing "grunt test" on the command line
        this.grunt.registerTask('test', [ 'jshint', 'mochaTest' ]);
        // Default task(s).
        // the default task can be run just by typing "grunt" on the command
        // line
        this.grunt.registerTask('default', [ 'jshint', 'mochaTest',
                'browserify', 'uglify' ]);
    };

    UmxGruntConfig.prototype._getMainFile = function(options) {
        var pkg = this.config.pkg;
        options = options || {};
        return options.main || './' + pkg.main + '/index.js';
    };

    UmxGruntConfig.prototype._getWatchFiles = function(options) {
        if (options.watchFiles)
            return options.watchFiles;
        var result = [];
        var sources = this._getSourceFiles(options);
        if (sources) {
            result = result.concat(sources);
        }
        var less = this._getLessPaths(options);
        if (less) {
            result = result.concat(less);
        }
        return result;
    };

    UmxGruntConfig.prototype._getSourceFiles = function(options) {
        options = options || {};
        if (options.files)
            return options.files;
        var pkg = this.config.pkg;
        var Path = require('path');
        var srcMask = Path.resolve(pkg.main || 'src', './**/*.js');
        return [ srcMask, 'test/**/*.js' ];
    };

    UmxGruntConfig.prototype._getBowerDir = function(optoins) {
        var bowerConf;
        try {
            bowerConf = this.grunt.file.readJSON('.bowerrc');
        } catch (err) {
            bowerConf = {};
        }
        if (!bowerConf.directory) {
            try {
                bowerConf = this.grunt.file.readJSON('bower.json');
            } catch (err) {
            }
        }
        if (!bowerConf.directory) {
            bowerConf.directory = './libs';
        }
        var result = [ bowerConf.directory ];
        return result;
    };

    UmxGruntConfig.prototype._getDistDir = function(options) {
        options = options || {};
        return options.dist || './dist';
    };

    return UmxGruntConfig;
})(this);
<commit_msg>Update WebPack config to generate source maps.<commit_after>module.exports = (function() {
    'use strict';

    // conf.initBump();
    // conf.initMochaTest();
    // conf.initBrowserify();
    // conf.initUglify();
    // conf.initJshint();
    // conf.initLess();
    // conf.initWatch();
    // conf.initWebpack();
    // conf.getBanner();

    function UmxGruntConfig(require, grunt, options) {
        this.options = options || {};
        this.require = require;
        if (!this.require) {
            throw new Error('The "require" method is not defined.');
        }
        this.grunt = grunt;
        if (!this.grunt) {
            throw new Error('A Grunt instance is not defined.');
        }
        var pkg = this.grunt.file.readJSON('package.json');
        pkg.appname = this.options.appname || pkg.appname || pkg.name;
        this.config = {
            pkg : pkg
        };
    }

    UmxGruntConfig.prototype.registerBumpTasks = function() {
        this.grunt.registerTask('inc', [ 'bump-only' ]);
        this.grunt.registerTask('incMinor', [ 'bump-only:minor' ]);
        this.grunt.registerTask('incMajor', [ 'bump-only:major' ]);
    };

    UmxGruntConfig.prototype.initBump = function(options) {
        options = options || {};
        this.config.bump = {
            options : {
                files : [ 'package.json', 'bower.json' ],
                updateConfigs : [],
                commit : true,
                commitMessage : 'Release v%VERSION%',
                commitFiles : [ '.' ],
                createTag : true,
                tagName : 'v%VERSION%',
                tagMessage : 'Version %VERSION%',
                push : false,
                pushTo : 'upstream',
                gitDescribeOptions : '--tags --always --abbrev=1 --dirty=-d'
            }
        };
        this.grunt.loadNpmTasks('grunt-bump');
        if (options.tasks !== false) {
            this.registerBumpTasks();
        }
    };

    UmxGruntConfig.prototype.initLess = function(options) {
        options = options || {};
        this.config.less = {
            main : {
                options : {
                    paths : this._getLessPaths(options)
                },
                files : this._getLessMapping(options)
            }
        };
        this.grunt.loadNpmTasks('grunt-contrib-less');
    };

    UmxGruntConfig.prototype._getLessPaths = function(options) {
        options = options || {};
        var paths = options.lessPaths || options.cssPaths;
        if (!paths) {
            var pkg = this.config.pkg;
            var Path = require('path');
            var cssDir = options.cssDir || 'css';
            var srcMask = Path.resolve(cssDir, './**/*.css');
            var lessMask = Path.resolve(cssDir, './**/*.less');
            paths = [ srcMask, lessMask ];
        }
        return paths;
    };

    UmxGruntConfig.prototype._getLessMapping = function(options) {
        var pkg = this.config.pkg;
        var sourceFile = options.lessFile || options.cssFile;
        if (!sourceFile) {
            var cssDir = options.lessDir || options.cssDir || 'css';
            sourceFile = cssDir + '/index.less';
        }
        var distDir = this._getDistDir(options);
        var distFile = distDir + '/' + pkg.appname + '.css';
        var result = {};
        result[distFile] = sourceFile;
        return result;
    };

    UmxGruntConfig.prototype.initWebpack = function(options) {
        options = options || {};
        var pkg = this.config.pkg;
        var banner = this.getBanner(options);
        var webpack = this.require('webpack');
        var bowerLibs = this._getBowerDir(options);
        this.config.webpack = {
            main : {
                entry : this._getMainFile(options),
                output : {
                    path : this._getDistDir(options),
                    filename : pkg.appname + '.js',
                    library : pkg.appname,
                    libraryTarget : options.target || 'umd'
                },
                externals : options.externals || [ /^[a-z\-0-9]+$/ ],
                resolve : {
                    modulesDirectories : [ "web_modules", "node_modules",
                            "bower_components", "libs" ].concat(bowerLibs),
                    extensions : [ "", ".webpack-loader.js", ".web-loader.js",
                            ".loader.js", ".js" ],
                    packageMains : [ "webpackLoader", "webLoader", "loader",
                            "main" ]
                },
                devtool : options.devtool || "source-map",
                debug : options.debug || true,

                module : options.module || {},
                plugins : [
                        new webpack.BannerPlugin(banner),
                        new webpack.optimize.DedupePlugin(),
                        new webpack.ResolverPlugin(
                                new webpack.ResolverPlugin.DirectoryDescriptionFilePlugin(
                                        "bower.json", [ "main" ])) ]
            }
        };
        this.grunt.loadNpmTasks('grunt-webpack');
    };

    UmxGruntConfig.prototype.initMochaTest = function(options) {
        options = options || {};
        this.config.mochaTest = {
            test : {
                options : {
                    reporter : 'spec'
                },
                src : options.src || //
                [ 'test/**/spec_*.js', 'test/**/*_spec.js' ]
            }
        };
        this.grunt.loadNpmTasks('grunt-mocha-test');
    };

    UmxGruntConfig.prototype.initKarma = function() {
        this.config.karma = {
            unit : {
                configFile : 'karma.conf.js',
                reporters : 'progress'
            },
            // continuous integration mode: run tests once in PhantomJS browser.
            continuous : {
                configFile : 'karma.conf.js',
                singleRun : true,
                reporters : 'progress',
                runnerPort : 9998
            }
        };
        this.grunt.loadNpmTasks('grunt-karma');
    };

    UmxGruntConfig.prototype.initBrowserify = function(options) {
        options = options || {};
        this.config.browserify = {
            standalone : {
                src : [ this._getMainFile(options) ],
                dest : this._getDistDir(options) + '/<%= pkg.appname %>.js',
                options : {
                    exclude : options.exclude || [],
                    bundleOptions : {
                        standalone : '<%= pkg.appname %>'
                    }
                }
            }
        };
        this.grunt.loadNpmTasks('grunt-browserify');
    };

    UmxGruntConfig.prototype.getBanner = function(options) {
        // Project configuration.
        var pkg = this.config.pkg;
        var licenses = '';
        (pkg.licenses || []).forEach(function(l) {
            if (licenses.length) {
                licenses += ', ';
            }
            licenses += l ? l.type || '' : '';
        });
        if (licenses.length) {
            licenses = ' | License: ' + licenses + ' ';
        }
        return '<%= pkg.appname %> v<%= pkg.version %>' + licenses + '\n';
    };

    UmxGruntConfig.prototype.initUglify = function(options) {
        var banner = '/* \n * ' + this.getBanner(options) + ' */\n';
        var distDir = this._getDistDir(options);
        this.config.uglify = {
            options : {
                banner : banner
            },
            browser : {
                src : distDir + '/<%= pkg.appname %>.js',
                dest : distDir + '/<%= pkg.appname %>.min.js'
            }
        };
        this.grunt.loadNpmTasks('grunt-contrib-uglify');
    };

    UmxGruntConfig.prototype.initJshint = function(options) {
        this.config.jshint = {
            files : this._getSourceFiles(options),
            // configure JSHint (documented at
            // http://www.jshint.com/docs/)
            options : {
                // more options here if you want to override JSHint
                // defaults
                globals : {
                    console : true,
                    module : true,
                    require : true
                }
            }
        };
        this.grunt.loadNpmTasks('grunt-contrib-jshint');
    };

    UmxGruntConfig.prototype.initWatch = function(options) {
        options = options || {};
        var files = this._getWatchFiles(options);
        this.config.watch = {
            scripts : {
                files : files,
                tasks : options.watchTasks || options.tasks || [ 'test' ],
                options : {
                    spawn : options.spawn !== false,
                    interrupt : true,
                },
            },
        };
        this.grunt.loadNpmTasks('grunt-contrib-watch');
    };

    UmxGruntConfig.prototype.registerTasks = function() {
        // this would be run by typing "grunt test" on the command line
        this.grunt.registerTask('test', [ 'jshint', 'mochaTest' ]);
        // Default task(s).
        // the default task can be run just by typing "grunt" on the command
        // line
        this.grunt.registerTask('default', [ 'jshint', 'mochaTest',
                'browserify', 'uglify' ]);
    };

    UmxGruntConfig.prototype._getMainFile = function(options) {
        var pkg = this.config.pkg;
        options = options || {};
        return options.main || './' + pkg.main + '/index.js';
    };

    UmxGruntConfig.prototype._getWatchFiles = function(options) {
        if (options.watchFiles)
            return options.watchFiles;
        var result = [];
        var sources = this._getSourceFiles(options);
        if (sources) {
            result = result.concat(sources);
        }
        var less = this._getLessPaths(options);
        if (less) {
            result = result.concat(less);
        }
        return result;
    };

    UmxGruntConfig.prototype._getSourceFiles = function(options) {
        options = options || {};
        if (options.files)
            return options.files;
        var pkg = this.config.pkg;
        var Path = require('path');
        var srcMask = Path.resolve(pkg.main || 'src', './**/*.js');
        return [ srcMask, 'test/**/*.js' ];
    };

    UmxGruntConfig.prototype._getBowerDir = function(optoins) {
        var bowerConf;
        try {
            bowerConf = this.grunt.file.readJSON('.bowerrc');
        } catch (err) {
            bowerConf = {};
        }
        if (!bowerConf.directory) {
            try {
                bowerConf = this.grunt.file.readJSON('bower.json');
            } catch (err) {
            }
        }
        if (!bowerConf.directory) {
            bowerConf.directory = './libs';
        }
        var result = [ bowerConf.directory ];
        return result;
    };

    UmxGruntConfig.prototype._getDistDir = function(options) {
        options = options || {};
        return options.dist || './dist';
    };

    return UmxGruntConfig;
})(this);
<|endoftext|>


<commit_before>import Hapi from 'hapi';
import Steam from 'steam-web';

var steam = new Steam({
    apiKey: process.env.STEAM_WEB_API_KEY,
    format: 'json',
});

const server = new Hapi.Server({
    connections: {
        router: {
            stripTrailingSlash: true,
        },
    },
});
server.connection({ port: 3000 });
server.start(() => console.log('Server running at:', server.info.uri));

// /api/stats
server.route({
    method: 'GET',
    path: '/api/stats',
    handler: (request, reply) => {
        return reply(new Promise((resolve, reject) => {
            steam.getPlayerSummaries({
                steamids: [process.env.STEAM_USER_ID],
                callback: function cb (err, data) {
                    if (err) {
                        reject(err);
                    }
                    resolve(data);
                },
            });
        }));
    },
});

// /api/friends
server.route({
    method: 'GET',
    path: '/api/friends',
    handler: (request, reply) => {
        return reply(new Promise((resolve, reject) => {
            steam.getFriendList({
                steamid: process.env.STEAM_USER_ID,
                relationship: 'all',
                callback: function cb (err, data) {
                    if (err) {
                        reject(err);
                    }
                    resolve(data);
                },
            });
        }));
    },
});

// /api/games/owned
server.route({
    method: 'GET',
    path: '/api/games/owned',
    handler: (request, reply) => {
        return reply(new Promise((resolve, reject) => {
            steam.getOwnedGames({
                steamid: process.env.STEAM_USER_ID,
                callback: function cb (err, data) {
                    if (err) {
                        reject(err);
                    }
                    resolve(data);
                },
            });
        }));
    },
});

// /api/games/recent
server.route({
    method: 'GET',
    path: '/api/games/recent',
    handler: (request, reply) => {
        return reply(new Promise((resolve, reject) => {
            steam.getRecentlyPlayedGames({
                steamid: process.env.STEAM_USER_ID,
                callback: function cb (err, data) {
                    if (err) {
                        reject(err);
                    }
                    resolve(data);
                },
            });
        }));
    },
});

// /api/game/{id}/stats
server.route({
    method: 'GET',
    path: '/api/game/{id}/stats',
    handler: (request, reply) => {
        return reply(new Promise((resolve, reject) => {
            steam.getUserStatsForGame({
                steamid: process.env.STEAM_USER_ID,
                appid: request.params.id,
                callback: function cb (err, data) {
                    if (err) {
                        reject(err);
                    }
                    resolve(data);
                },
            });
        }));
    },
});

// /api/game/{id}/achievements
server.route({
    method: 'GET',
    path: '/api/game/{id}/achievements',
    handler: (request, reply) => {
        return reply(new Promise((resolve, reject) => {
            steam.getPlayerAchievements({
                steamid: process.env.STEAM_USER_ID,
                appid: request.params.id,
                l: 'en',
                callback: function cb (err, data) {
                    if (err) {
                        reject(err);
                    }
                    resolve(data);
                },
            });
        }));
    },
});
<commit_msg>port env<commit_after>import Hapi from 'hapi';
import Steam from 'steam-web';

var steam = new Steam({
    apiKey: process.env.STEAM_WEB_API_KEY,
    format: 'json',
});

const server = new Hapi.Server({
    connections: {
        router: {
            stripTrailingSlash: true,
        },
    },
});
server.connection({ port: process.env.PORT || 3000 });
server.start(() => console.log('Server running at:', server.info.uri));

// /api/stats
server.route({
    method: 'GET',
    path: '/api/stats',
    handler: (request, reply) => {
        return reply(new Promise((resolve, reject) => {
            steam.getPlayerSummaries({
                steamids: [process.env.STEAM_USER_ID],
                callback: function cb (err, data) {
                    if (err) {
                        reject(err);
                    }
                    resolve(data);
                },
            });
        }));
    },
});

// /api/friends
server.route({
    method: 'GET',
    path: '/api/friends',
    handler: (request, reply) => {
        return reply(new Promise((resolve, reject) => {
            steam.getFriendList({
                steamid: process.env.STEAM_USER_ID,
                relationship: 'all',
                callback: function cb (err, data) {
                    if (err) {
                        reject(err);
                    }
                    resolve(data);
                },
            });
        }));
    },
});

// /api/games/owned
server.route({
    method: 'GET',
    path: '/api/games/owned',
    handler: (request, reply) => {
        return reply(new Promise((resolve, reject) => {
            steam.getOwnedGames({
                steamid: process.env.STEAM_USER_ID,
                callback: function cb (err, data) {
                    if (err) {
                        reject(err);
                    }
                    resolve(data);
                },
            });
        }));
    },
});

// /api/games/recent
server.route({
    method: 'GET',
    path: '/api/games/recent',
    handler: (request, reply) => {
        return reply(new Promise((resolve, reject) => {
            steam.getRecentlyPlayedGames({
                steamid: process.env.STEAM_USER_ID,
                callback: function cb (err, data) {
                    if (err) {
                        reject(err);
                    }
                    resolve(data);
                },
            });
        }));
    },
});

// /api/game/{id}/stats
server.route({
    method: 'GET',
    path: '/api/game/{id}/stats',
    handler: (request, reply) => {
        return reply(new Promise((resolve, reject) => {
            steam.getUserStatsForGame({
                steamid: process.env.STEAM_USER_ID,
                appid: request.params.id,
                callback: function cb (err, data) {
                    if (err) {
                        reject(err);
                    }
                    resolve(data);
                },
            });
        }));
    },
});

// /api/game/{id}/achievements
server.route({
    method: 'GET',
    path: '/api/game/{id}/achievements',
    handler: (request, reply) => {
        return reply(new Promise((resolve, reject) => {
            steam.getPlayerAchievements({
                steamid: process.env.STEAM_USER_ID,
                appid: request.params.id,
                l: 'en',
                callback: function cb (err, data) {
                    if (err) {
                        reject(err);
                    }
                    resolve(data);
                },
            });
        }));
    },
});
<|endoftext|>


<commit_before>// Define search commands. Depends on dialog.js or another
// implementation of the openDialog method.

// Replace works a little oddly -- it will do the replace on the next
// Ctrl-G (or whatever is bound to findNext) press. You prevent a
// replace by making sure the match is no longer selected when hitting
// Ctrl-G.

(function(mod) {
  if (typeof exports == "object" && typeof module == "object") // CommonJS
    mod(require("../../lib/codemirror"), require("./searchcursor"), require("../dialog/dialog"));
  else if (typeof define == "function" && define.amd) // AMD
    define(["../../lib/codemirror", "./searchcursor", "../dialog/dialog"], mod);
  else // Plain browser env
    mod(CodeMirror);
})(function(CodeMirror) {
  "use strict";
  function searchOverlay(query, caseInsensitive) {
    var startChar;
    if (typeof query == "string") {
      startChar = query.charAt(0);
      query = new RegExp("^" + query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"),
                         caseInsensitive ? "i" : "");
    } else {
      query = new RegExp("^(?:" + query.source + ")", query.ignoreCase ? "i" : "");
    }
    return {token: function(stream) {
      if (stream.match(query)) return "searching";
      while (!stream.eol()) {
        stream.next();
        if (startChar && !caseInsensitive)
          stream.skipTo(startChar) || stream.skipToEnd();
        if (stream.match(query, false)) break;
      }
    }};
  }

  function SearchState() {
    this.posFrom = this.posTo = this.query = null;
    this.overlay = null;
  }
  function getSearchState(cm) {
    return cm.state.search || (cm.state.search = new SearchState());
  }
  function queryCaseInsensitive(query) {
    return typeof query == "string" && query == query.toLowerCase();
  }
  function getSearchCursor(cm, query, pos) {
    // Heuristic: if the query string is all lowercase, do a case insensitive search.
    return cm.getSearchCursor(query, pos, queryCaseInsensitive(query));
  }
  function dialog(cm, text, shortText, deflt, f) {
    if (cm.openDialog) cm.openDialog(text, f, {value: deflt});
    else f(prompt(shortText, deflt));
  }
  function confirmDialog(cm, text, shortText, fs) {
    if (cm.openConfirm) cm.openConfirm(text, fs);
    else if (confirm(shortText)) fs[0]();
  }
  function parseQuery(query) {
    var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
    if (isRE) {
      query = new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i");
      if (query.test("")) query = /x^/;
    } else if (query == "") {
      query = /x^/;
    }
    return query;
  }
  var queryDialog =
    'Search: <input type="text" style="width: 10em"/> (Use /re/ syntax for regexp search)';
  function doSearch(cm, rev) {
    var state = getSearchState(cm);
    if (state.query) return findNext(cm, rev);
    dialog(cm, queryDialog, "Search for:", cm.getSelection(), function(query) {
      cm.operation(function() {
        if (!query || state.query) return;
        state.query = parseQuery(query);
        cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query));
        state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query));
        cm.addOverlay(state.overlay);
        state.posFrom = state.posTo = cm.getCursor();
        findNext(cm, rev);
      });
    });
  }
  function findNext(cm, rev) {cm.operation(function() {
    var state = getSearchState(cm);
    var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);
    if (!cursor.find(rev)) {
      cursor = getSearchCursor(cm, state.query, rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0));
      if (!cursor.find(rev)) return;
    }
    cm.setSelection(cursor.from(), cursor.to());
    cm.scrollIntoView({from: cursor.from(), to: cursor.to()});
    state.posFrom = cursor.from(); state.posTo = cursor.to();
  });}
  function clearSearch(cm) {cm.operation(function() {
    var state = getSearchState(cm);
    if (!state.query) return;
    state.query = null;
    cm.removeOverlay(state.overlay);
  });}

  var replaceQueryDialog =
    'Replace: <input type="text" style="width: 10em"/> (Use /re/ syntax for regexp search)';
  var replacementQueryDialog = 'With: <input type="text" style="width: 10em"/>';
  var doReplaceConfirm = "Replace? <button>Yes</button> <button>No</button> <button>Stop</button>";
  function replace(cm, all) {
    dialog(cm, replaceQueryDialog, "Replace:", cm.getSelection(), function(query) {
      if (!query) return;
      query = parseQuery(query);
      dialog(cm, replacementQueryDialog, "Replace with:", "", function(text) {
        if (all) {
          cm.operation(function() {
            for (var cursor = getSearchCursor(cm, query); cursor.findNext();) {
              if (typeof query != "string") {
                var match = cm.getRange(cursor.from(), cursor.to()).match(query);
                cursor.replace(text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
              } else cursor.replace(text);
            }
          });
        } else {
          clearSearch(cm);
          var cursor = getSearchCursor(cm, query, cm.getCursor());
          var advance = function() {
            var start = cursor.from(), match;
            if (!(match = cursor.findNext())) {
              cursor = getSearchCursor(cm, query);
              if (!(match = cursor.findNext()) ||
                  (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return;
            }
            cm.setSelection(cursor.from(), cursor.to());
            cm.scrollIntoView({from: cursor.from(), to: cursor.to()});
            confirmDialog(cm, doReplaceConfirm, "Replace?",
                          [function() {doReplace(match);}, advance]);
          };
          var doReplace = function(match) {
            cursor.replace(typeof query == "string" ? text :
                           text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
            advance();
          };
          advance();
        }
      });
    });
  }

  CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);};
  CodeMirror.commands.findNext = doSearch;
  CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);};
  CodeMirror.commands.clearSearch = clearSearch;
  CodeMirror.commands.replace = replace;
  CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);};
});
<commit_msg>[search addon] Fix highlighting around \b markers in regexp search<commit_after>// Define search commands. Depends on dialog.js or another
// implementation of the openDialog method.

// Replace works a little oddly -- it will do the replace on the next
// Ctrl-G (or whatever is bound to findNext) press. You prevent a
// replace by making sure the match is no longer selected when hitting
// Ctrl-G.

(function(mod) {
  if (typeof exports == "object" && typeof module == "object") // CommonJS
    mod(require("../../lib/codemirror"), require("./searchcursor"), require("../dialog/dialog"));
  else if (typeof define == "function" && define.amd) // AMD
    define(["../../lib/codemirror", "./searchcursor", "../dialog/dialog"], mod);
  else // Plain browser env
    mod(CodeMirror);
})(function(CodeMirror) {
  "use strict";
  function searchOverlay(query, caseInsensitive) {
    if (typeof query == "string")
      query = new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"), caseInsensitive ? "gi" : "g");
    else if (!query.global)
      query = new RegExp(query.source, query.ignoreCase ? "gi" : "g");

    return {token: function(stream) {
      query.lastIndex = stream.pos;
      var match = query.exec(stream.string);
      if (match && match.index == stream.pos) {
        stream.pos += match[0].length;
        return "searching";
      } else if (match) {
        stream.pos = match.index;
      } else {
        stream.skipToEnd();
      }
    }};
  }

  function SearchState() {
    this.posFrom = this.posTo = this.query = null;
    this.overlay = null;
  }
  function getSearchState(cm) {
    return cm.state.search || (cm.state.search = new SearchState());
  }
  function queryCaseInsensitive(query) {
    return typeof query == "string" && query == query.toLowerCase();
  }
  function getSearchCursor(cm, query, pos) {
    // Heuristic: if the query string is all lowercase, do a case insensitive search.
    return cm.getSearchCursor(query, pos, queryCaseInsensitive(query));
  }
  function dialog(cm, text, shortText, deflt, f) {
    if (cm.openDialog) cm.openDialog(text, f, {value: deflt});
    else f(prompt(shortText, deflt));
  }
  function confirmDialog(cm, text, shortText, fs) {
    if (cm.openConfirm) cm.openConfirm(text, fs);
    else if (confirm(shortText)) fs[0]();
  }
  function parseQuery(query) {
    var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
    if (isRE) {
      query = new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i");
      if (query.test("")) query = /x^/;
    } else if (query == "") {
      query = /x^/;
    }
    return query;
  }
  var queryDialog =
    'Search: <input type="text" style="width: 10em"/> (Use /re/ syntax for regexp search)';
  function doSearch(cm, rev) {
    var state = getSearchState(cm);
    if (state.query) return findNext(cm, rev);
    dialog(cm, queryDialog, "Search for:", cm.getSelection(), function(query) {
      cm.operation(function() {
        if (!query || state.query) return;
        state.query = parseQuery(query);
        cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query));
        state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query));
        cm.addOverlay(state.overlay);
        state.posFrom = state.posTo = cm.getCursor();
        findNext(cm, rev);
      });
    });
  }
  function findNext(cm, rev) {cm.operation(function() {
    var state = getSearchState(cm);
    var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);
    if (!cursor.find(rev)) {
      cursor = getSearchCursor(cm, state.query, rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0));
      if (!cursor.find(rev)) return;
    }
    cm.setSelection(cursor.from(), cursor.to());
    cm.scrollIntoView({from: cursor.from(), to: cursor.to()});
    state.posFrom = cursor.from(); state.posTo = cursor.to();
  });}
  function clearSearch(cm) {cm.operation(function() {
    var state = getSearchState(cm);
    if (!state.query) return;
    state.query = null;
    cm.removeOverlay(state.overlay);
  });}

  var replaceQueryDialog =
    'Replace: <input type="text" style="width: 10em"/> (Use /re/ syntax for regexp search)';
  var replacementQueryDialog = 'With: <input type="text" style="width: 10em"/>';
  var doReplaceConfirm = "Replace? <button>Yes</button> <button>No</button> <button>Stop</button>";
  function replace(cm, all) {
    dialog(cm, replaceQueryDialog, "Replace:", cm.getSelection(), function(query) {
      if (!query) return;
      query = parseQuery(query);
      dialog(cm, replacementQueryDialog, "Replace with:", "", function(text) {
        if (all) {
          cm.operation(function() {
            for (var cursor = getSearchCursor(cm, query); cursor.findNext();) {
              if (typeof query != "string") {
                var match = cm.getRange(cursor.from(), cursor.to()).match(query);
                cursor.replace(text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
              } else cursor.replace(text);
            }
          });
        } else {
          clearSearch(cm);
          var cursor = getSearchCursor(cm, query, cm.getCursor());
          var advance = function() {
            var start = cursor.from(), match;
            if (!(match = cursor.findNext())) {
              cursor = getSearchCursor(cm, query);
              if (!(match = cursor.findNext()) ||
                  (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return;
            }
            cm.setSelection(cursor.from(), cursor.to());
            cm.scrollIntoView({from: cursor.from(), to: cursor.to()});
            confirmDialog(cm, doReplaceConfirm, "Replace?",
                          [function() {doReplace(match);}, advance]);
          };
          var doReplace = function(match) {
            cursor.replace(typeof query == "string" ? text :
                           text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
            advance();
          };
          advance();
        }
      });
    });
  }

  CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);};
  CodeMirror.commands.findNext = doSearch;
  CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);};
  CodeMirror.commands.clearSearch = clearSearch;
  CodeMirror.commands.replace = replace;
  CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);};
});
<|endoftext|>


<commit_before>// Draw parameters
var pc_SIZE = 16;

// Legend:
//  #: wall
//   : no cheese
//  .: cheese
//  o: big cheese
//  s: starting point
//  g: ghost starting point
//  _: forbidden for player
var pc_grid_template = [
	"############################",
	"#............##............#",
	"#.####.#####.##.#####.####.#",
	"#o####.#####.##.#####.####o#",
	"#.####.#####.##.#####.####.#",
	"#..........................#",
	"#.####.##.########.##.####.#",
	"#.####.##.########.##.####.#",
	"#......##....##....##......#",
	"######.#####.##.#####.######",
	"######.#####.##.#####.######",
	"######.##..........##.######",
	"######.##.###__###.##.######",
	"######.##.#gggggg#.##.######",
	"      ....#gggggg#....      ",
	"######.##.#gggggg#.##.######",
	"######.##.########.##.######",
	"######.##..........##.######",
	"######.##.########.##.######",
	"######.##.########.##.######",
	"#............##............#",
	"#.####.#####.##.#####.####.#",
	"#o####.#####.##.#####.####o#",
	"#...##.......s........##...#",
	"###.##.##.########.##.##.###",
	"###.##.##.########.##.##.###",
	"#......##....##....##......#",
	"#.##########.##.##########.#",
	"#.##########.##.##########.#",
	"#..........................#",
	"############################",
];
var pc_grid = [];

var pc_LEFT = 0;
var pc_TOP = 1;
var pc_RIGHT = 2;
var pc_BOTTOM = 3;
var pc_FPS = 10;
var pc_FRAMES_PER_CELL = 5;

/**
 * User position
 */

var pc_pacman_x = -1;
var pc_pacman_y = -1;
var pc_pacman_direction = pc_LEFT;

/**
 * Initialize the game
 */

function initGame() {
	// Already launched?
	if (pc_pacman_x != -1 || pc_pacman_y != -1)
		return;

	// Copy the grid into local grid
	pc_grid = pc_grid_template.slice();

	// Find the starting point
	var height = pc_grid.length;
	var width = pc_grid[0].length;
	for (i=0 ; i!=width ; i++) {
		for (j=0 ; j!=height ; j++) {
			if (pc_grid[j][i] == 's') {
				pc_pacman_x = i * pc_FRAMES_PER_CELL;
				pc_pacman_y = j * pc_FRAMES_PER_CELL;
			}
		}
	}

	// Resize canvas
	var canvas = document.getElementById('myCanvas');
	if (! canvas.getContext)
		return;
	var ctx = canvas.getContext('2d');
	var height = pc_grid.length;
	var width = pc_grid[0].length;
	canvas.width = width*pc_SIZE +10;
	canvas.height = height*pc_SIZE +10;

	// Launch the game
	iterateGame();
}

/**
 * Iterate inside the game
 */

function iterateGame() {
	var canvas = document.getElementById('myCanvas');
	if (! canvas.getContext)
		return;
	var ctx = canvas.getContext('2d');

	// Draw game
	drawEmptyGameBoard(canvas, ctx);
	drawPacMan(canvas, ctx);

	setTimeout(iterateGame, 1000/pc_FPS);
}

/**
 * Draw an empty game board
 */

function drawEmptyGameBoard(canvas, ctx) {
	/**
	 * Draw the Game Board based on pc_grid
	 */

	// Retrieve grid dimensions
	var height = pc_grid.length;
	var width = pc_grid[0].length;

	// Draw Game Board
	ctx.beginPath();
	ctx.fillStyle = "white";
	ctx.fillRect(0, 0, width*pc_SIZE +10, height*pc_SIZE +10);
	ctx.fill();

	ctx.beginPath();
	ctx.lineWidth = 3;
	ctx.strokeStyle = "black";
	ctx.moveTo(2, 2);
	ctx.lineTo(2, height*pc_SIZE +8);
	ctx.lineTo(width*pc_SIZE +8, height*pc_SIZE +8);
	ctx.lineTo(width*pc_SIZE +8, 2);
	ctx.closePath();
	ctx.stroke();

	for (i=0 ; i!=width ; i++) {
		for (j=0 ; j!=height ; j++) {
			if (pc_grid[j][i] == '#') {
				ctx.fillStyle = "#777777";
				ctx.fillRect(i*pc_SIZE +5, j*pc_SIZE +5, pc_SIZE, pc_SIZE);
			} else if (pc_grid[j][i] == '.') {
				ctx.beginPath();
				ctx.fillStyle = "#aaaa00";
				ctx.arc((i+.5)*pc_SIZE +5, (j+.5)*pc_SIZE +5, .2*pc_SIZE, 0, 2*Math.PI, false);
				ctx.fill();
			} else if (pc_grid[j][i] == 'o') {
				ctx.beginPath();
				ctx.fillStyle = "#aaaa00";
				ctx.arc((i+.5)*pc_SIZE +5, (j+.5)*pc_SIZE +5, .4*pc_SIZE, 0, 2*Math.PI, false);
				ctx.fill();
			}
		}
	}
}

/**
 * Draw the PacMan
 */

function drawPacMan(canvas, ctx) {
	var pacman_px_x = (1.*pc_pacman_x/pc_FRAMES_PER_CELL +.5)*pc_SIZE +5;
	var pacman_px_y = (1.*pc_pacman_y/pc_FRAMES_PER_CELL +.5)*pc_SIZE +5;

	ctx.beginPath();
	ctx.fillStyle = "#777700";
	ctx.arc(pacman_px_x, pacman_px_y, .45*pc_SIZE, Math.PI/7, -Math.PI/7,false);
	ctx.lineTo(pacman_px_x, pacman_px_y);
	ctx.fill();
}
<commit_msg>PacMan orientation depends on pc_pacman_direction<commit_after>// Draw parameters
var pc_SIZE = 16;

// Legend:
//  #: wall
//   : no cheese
//  .: cheese
//  o: big cheese
//  s: starting point
//  g: ghost starting point
//  _: forbidden for player
var pc_grid_template = [
	"############################",
	"#............##............#",
	"#.####.#####.##.#####.####.#",
	"#o####.#####.##.#####.####o#",
	"#.####.#####.##.#####.####.#",
	"#..........................#",
	"#.####.##.########.##.####.#",
	"#.####.##.########.##.####.#",
	"#......##....##....##......#",
	"######.#####.##.#####.######",
	"######.#####.##.#####.######",
	"######.##..........##.######",
	"######.##.###__###.##.######",
	"######.##.#gggggg#.##.######",
	"      ....#gggggg#....      ",
	"######.##.#gggggg#.##.######",
	"######.##.########.##.######",
	"######.##..........##.######",
	"######.##.########.##.######",
	"######.##.########.##.######",
	"#............##............#",
	"#.####.#####.##.#####.####.#",
	"#o####.#####.##.#####.####o#",
	"#...##.......s........##...#",
	"###.##.##.########.##.##.###",
	"###.##.##.########.##.##.###",
	"#......##....##....##......#",
	"#.##########.##.##########.#",
	"#.##########.##.##########.#",
	"#..........................#",
	"############################",
];
var pc_grid = [];

var pc_LEFT = 0;
var pc_TOP = 1;
var pc_RIGHT = 2;
var pc_BOTTOM = 3;
var pc_FPS = 10;
var pc_FRAMES_PER_CELL = 5;

/**
 * User position
 */

var pc_pacman_x = -1;
var pc_pacman_y = -1;
var pc_pacman_direction = pc_LEFT;

/**
 * Initialize the game
 */

function initGame() {
	// Already launched?
	if (pc_pacman_x != -1 || pc_pacman_y != -1)
		return;

	// Copy the grid into local grid
	pc_grid = pc_grid_template.slice();

	// Find the starting point
	var height = pc_grid.length;
	var width = pc_grid[0].length;
	for (i=0 ; i!=width ; i++) {
		for (j=0 ; j!=height ; j++) {
			if (pc_grid[j][i] == 's') {
				pc_pacman_x = i * pc_FRAMES_PER_CELL;
				pc_pacman_y = j * pc_FRAMES_PER_CELL;
			}
		}
	}

	// Resize canvas
	var canvas = document.getElementById('myCanvas');
	if (! canvas.getContext)
		return;
	var ctx = canvas.getContext('2d');
	var height = pc_grid.length;
	var width = pc_grid[0].length;
	canvas.width = width*pc_SIZE +10;
	canvas.height = height*pc_SIZE +10;

	// Launch the game
	iterateGame();
}

/**
 * Iterate inside the game
 */

function iterateGame() {
	var canvas = document.getElementById('myCanvas');
	if (! canvas.getContext)
		return;
	var ctx = canvas.getContext('2d');

	// Draw game
	drawEmptyGameBoard(canvas, ctx);
	drawPacMan(canvas, ctx);

	setTimeout(iterateGame, 1000/pc_FPS);
}

/**
 * Draw an empty game board
 */

function drawEmptyGameBoard(canvas, ctx) {
	/**
	 * Draw the Game Board based on pc_grid
	 */

	// Retrieve grid dimensions
	var height = pc_grid.length;
	var width = pc_grid[0].length;

	// Draw Game Board
	ctx.beginPath();
	ctx.fillStyle = "white";
	ctx.fillRect(0, 0, width*pc_SIZE +10, height*pc_SIZE +10);
	ctx.fill();

	ctx.beginPath();
	ctx.lineWidth = 3;
	ctx.strokeStyle = "black";
	ctx.moveTo(2, 2);
	ctx.lineTo(2, height*pc_SIZE +8);
	ctx.lineTo(width*pc_SIZE +8, height*pc_SIZE +8);
	ctx.lineTo(width*pc_SIZE +8, 2);
	ctx.closePath();
	ctx.stroke();

	for (i=0 ; i!=width ; i++) {
		for (j=0 ; j!=height ; j++) {
			if (pc_grid[j][i] == '#') {
				ctx.fillStyle = "#777777";
				ctx.fillRect(i*pc_SIZE +5, j*pc_SIZE +5, pc_SIZE, pc_SIZE);
			} else if (pc_grid[j][i] == '.') {
				ctx.beginPath();
				ctx.fillStyle = "#aaaa00";
				ctx.arc((i+.5)*pc_SIZE +5, (j+.5)*pc_SIZE +5, .2*pc_SIZE, 0, 2*Math.PI, false);
				ctx.fill();
			} else if (pc_grid[j][i] == 'o') {
				ctx.beginPath();
				ctx.fillStyle = "#aaaa00";
				ctx.arc((i+.5)*pc_SIZE +5, (j+.5)*pc_SIZE +5, .4*pc_SIZE, 0, 2*Math.PI, false);
				ctx.fill();
			}
		}
	}
}

/**
 * Draw the PacMan
 */

function drawPacMan(canvas, ctx) {
	var pacman_px_x = (1.*pc_pacman_x/pc_FRAMES_PER_CELL +.5)*pc_SIZE +5;
	var pacman_px_y = (1.*pc_pacman_y/pc_FRAMES_PER_CELL +.5)*pc_SIZE +5;

	ctx.beginPath();
	ctx.fillStyle = "#777700";
	if (pc_pacman_direction == pc_LEFT)
		ctx.arc(pacman_px_x, pacman_px_y, .45*pc_SIZE, Math.PI+Math.PI/7, Math.PI-Math.PI/7,false);
	else if (pc_pacman_direction == pc_TOP)
		ctx.arc(pacman_px_x, pacman_px_y, .45*pc_SIZE, -Math.PI/2+Math.PI/7, -Math.PI/2-Math.PI/7,false);
	else if (pc_pacman_direction == pc_RIGHT)
		ctx.arc(pacman_px_x, pacman_px_y, .45*pc_SIZE, Math.PI/7, -Math.PI/7,false);
	else
		ctx.arc(pacman_px_x, pacman_px_y, .45*pc_SIZE, Math.PI/2+Math.PI/7, Math.PI/2+-Math.PI/7,false);
	ctx.lineTo(pacman_px_x, pacman_px_y);
	ctx.fill();
}
<|endoftext|>


<commit_before>import {Graph} from 'graphlib'
import _ from 'lodash'

var parent = function (graph, outP, inP) {
  if (graph.parent(outP) === graph.parent(inP)) {
    return graph.parent(outP)
  } else if (graph.parent(outP) === inP) {
    return inP
  } else {
    return outP
  }
}

var api = {
  remodelPorts: function (portGraph) {
    var parents = [ ]
    var g = new Graph({ directed: true, compound: true, multigraph: true })

    for (let node of portGraph.nodes()) {
      let lbl = portGraph.node(node)
      lbl.nodeType = 'process'
      g.setNode(node, lbl)
      var parentNode = portGraph.parent(node)
      // if needed, creates the parent node and creates the parent relation
      // also adds the parent node to an array of parent nodes for further usage
      if (parentNode) {
        if (!g.hasNode(parentNode)) {
          g.setNode(parentNode, portGraph.node(parentNode))
        }
        g.setParent(node, parentNode)
        parents.push(parentNode)
      }
    }
    parents = _.unique(parents)

    for (let edge of _.reject(portGraph.edges(), (e) => portGraph.edge(e) && portGraph.edge(e).continuation)) {
      var label = portGraph.edge(edge)
      var outPortName = edge.v + '_PORT_' + label.outPort
      var inPortName = edge.w + '_PORT_' + label.inPort

      // if needed, add the in-port node
      if (portGraph.parent(edge.v) !== edge.w) {
        g.setNode(inPortName, { nodeType: 'inPort', portName: label.inPort, process: edge.w })
        g.setParent(inPortName, parent(portGraph, edge.w, edge.v))
      } else {
        g.setNode(inPortName, { nodeType: 'outPort', portName: label.inPort, hierarchyBorder: true, process: edge.w })
      }
      // if needed, add the out-port node
      if (portGraph.parent(edge.w) !== edge.v) {
        g.setNode(outPortName, { nodeType: 'outPort', portName: label.outPort, process: edge.v })
        g.setParent(outPortName, parent(portGraph, edge.w, edge.v))
      } else {
        g.setNode(outPortName, { nodeType: 'inPort', portName: label.outPort, hierarchyBorder: true, process: edge.v })
      }
      // connect the ports to it's corresponding process nodes
      // hierarchy information still exists, but it is not used anymore
      // That means: No edges from or to a node that is a parent of another node
      if (!_.contains(parents, edge.w)) {
        g.setEdge(inPortName, edge.w)
      }
      if (!_.contains(parents, edge.v)) {
        g.setEdge(edge.v, outPortName)
      }
      // connect the two ports from this edge
      g.setEdge(outPortName, inPortName)
    }
    for (let edge of _.filter(portGraph.edges(), (e) => portGraph.edge(e) && portGraph.edge(e).continuation)) {
      g.setEdge(edge, portGraph.edge(edge))
    }
    return g
  }
}

module.exports = api
<commit_msg>(feature) port nodes now have their type saved<commit_after>import {Graph} from 'graphlib'
import _ from 'lodash'

var parent = function (graph, outP, inP) {
  if (graph.parent(outP) === graph.parent(inP)) {
    return graph.parent(outP)
  } else if (graph.parent(outP) === inP) {
    return inP
  } else {
    return outP
  }
}

var api = {
  remodelPorts: function (portGraph) {
    var parents = [ ]
    var g = new Graph({ directed: true, compound: true, multigraph: true })

    for (let node of portGraph.nodes()) {
      let lbl = portGraph.node(node)
      lbl.nodeType = 'process'
      g.setNode(node, lbl)
      var parentNode = portGraph.parent(node)
      // if needed, creates the parent node and creates the parent relation
      // also adds the parent node to an array of parent nodes for further usage
      if (parentNode) {
        if (!g.hasNode(parentNode)) {
          g.setNode(parentNode, portGraph.node(parentNode))
        }
        g.setParent(node, parentNode)
        parents.push(parentNode)
      }
    }
    parents = _.unique(parents)

    for (let edge of _.reject(portGraph.edges(), (e) => portGraph.edge(e) && portGraph.edge(e).continuation)) {
      var label = portGraph.edge(edge)
      var outPortName = edge.v + '_PORT_' + label.outPort
      var inPortName = edge.w + '_PORT_' + label.inPort

      // if needed, add the in-port node
      if (portGraph.parent(edge.v) !== edge.w) {
        let type = portGraph.node(edge.w).inputPorts[label.inPort]
        g.setNode(inPortName, { nodeType: 'inPort', portName: label.inPort, process: edge.w, type: type })
        g.setParent(inPortName, parent(portGraph, edge.w, edge.v))
      } else {
        g.setNode(inPortName, { nodeType: 'outPort', portName: label.inPort, hierarchyBorder: true, process: edge.w })
      }
      // if needed, add the out-port node
      if (portGraph.parent(edge.w) !== edge.v) {
        let type = portGraph.node(edge.w).inputPorts[label.inPort]
        g.setNode(outPortName, { nodeType: 'outPort', portName: label.outPort, process: edge.v, type: type })
        g.setParent(outPortName, parent(portGraph, edge.w, edge.v))
      } else {
        g.setNode(outPortName, { nodeType: 'inPort', portName: label.outPort, hierarchyBorder: true, process: edge.v })
      }
      // connect the ports to it's corresponding process nodes
      // hierarchy information still exists, but it is not used anymore
      // That means: No edges from or to a node that is a parent of another node
      if (!_.contains(parents, edge.w)) {
        g.setEdge(inPortName, edge.w)
      }
      if (!_.contains(parents, edge.v)) {
        g.setEdge(edge.v, outPortName)
      }
      // connect the two ports from this edge
      g.setEdge(outPortName, inPortName)
    }
    for (let edge of _.filter(portGraph.edges(), (e) => portGraph.edge(e) && portGraph.edge(e).continuation)) {
      g.setEdge(edge, portGraph.edge(edge))
    }
    return g
  }
}

module.exports = api
<|endoftext|>