<commit_before>/**
 * Copyright 2013 Facebook, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
"use strict";

var fs = require('fs');
var path = require('path');
var url = require('url');

var Chart = require('./Chart');
var Modularizer = require('./Modularizer');
var TimingData = require('./TimingData');

var consts = require('./consts');
var renderReactPage = require('./renderReactPage');

var convertSourceMap = require('convert-source-map');

/**
 * What is a "Router" for `react-page-middleware`?
 *
 * A `react-page-middleware` `Router` object helps generate entire responses for URLs.
 * `react-page-middleware` understands dependencies between files, and
 * bundles/transforms your resources for you, but your `Router` fills in some
 * of the application specific details - such as how URLs map to a root JS
 * module, and how to render a page for a particular type of URL request.
 *
 * A `Router object must contain:
 *
 * `decideRoute` method:
 *
 * - Accepts a URL from a server request.
 * - Asynchronously determines a "route" object describing at least two things:
 * -- Absolute path to the root JS module corresponding to the request.
 * -- The content type of the response.
 * -- The determined route may also include as many other things as your router
 *  would like.
 *
 * `routePackageHandler` method:
 *
 * - Accepts a `Package` object (automatically generated for you based on the
 *   `route` object you previously decided from `decideRoute`.
 * - Generates a page response text.
 * - Can make great use of the features in the `Package` object, including
 *   source maps etc.
 * - Can use that package to deliver a static resource to the client, or can
 *   run that package on the server to generate markup on the server.
 */

/**
 * More formally:
 * @typedef Config: Stringmap;
 *
 * @typedef RouteData {
 *  contentType: string,           // Content-type header field
 *  rootModuleAbsolutePath: string // Absolute path to "root" JS module
 *  // (whatever other fields you want: "additional props" etc.
 * };
 *
 * @typedef Router {
 *   decideRoute: (
 *     buildConfig:Config,
 *     reqURL:string,
 *     next:(err, RouteData)->void
 *   ) -> void,
 *   routePackageHandler: (
 *     buildConfig:Config,
 *     route:RouteData,
 *     rootModuleID:string,
 *     ppackage:Package,
 *     next:(err, bundleText:string)->void
 *   ) -> void
 * };
 *
 */

/**
 * The "DefaultRouter" contains some nice features worth emulating:
 *
 * - Looks at `buildConfig` for a field `sourceMapsType` (either 'inline' or
 *   'linked') that instructs page generation on how to link source maps -
 *   should they be sent in the bundle, or downloaded lazily when you open your
 *   debugger?
 * - Looks at `buildConfig.useBrowserBuiltins` to know whether or not to
 *   include common node.js functionality in the bundle even if ran in the
 *   browser.
 * - Server rendering of pages: Routes `mypage/path/to/component.html` to
 *   `mypage/path/to/component.js` and expects to find a React component there
 *   to render.
 * - Looks for "tags" in the path, which hint to include additional code in the
 *   generated bundle:
 * -- runModule: Puts `require('YourModule')` at the end of the bundle so it is
 *  ran automatically.
 * -- includeRequire: Controls whether or not to include the require runtim. If
 *  a previous bundle already defined it you know you don't have to.
 */

/**
 * Define your own custom router:
 * - Simply adhere to the above definition. If you use `react-page`, you should
 *   be able to simply provide your own router inside the `server.js` file
 *   (pass it to the `react-page-middleware` `provide` method using the object
 *   key `router`).
 */

var devBlock = function(buildConfig) {
  return '__VERSION__ = 0.44; __DEV__ = ' + (buildConfig.dev ? ' true;\n' : 'false;\n');
};

var keyMirror = function(obj) {
  var ret = {};
  var key;
  for (key in obj) {
    if (!obj.hasOwnProperty(key)) {
      continue;
    }
    ret[key] = key;
  }
  return ret;
};

var JS_TYPE = 'application/javascript';
var MAPS_TYPE = 'text';
var HTML_TYPE = 'text/html';

/**
 * Bundle the require implementation
 */
var REQUIRE_RUNTIME_PATH = path.resolve(__dirname, '..', 'polyfill/require.js');
var PROCESS_RUNTIME_PATH = path.resolve(
  __dirname,
  '..',
  'node_modules',
  'browser-builtins',
  'builtin',
  'process.js'
);
var REQUIRE_RUNTIME = fs.readFileSync(REQUIRE_RUNTIME_PATH, 'utf8');
var PROCESS_RUNTIME =
  Modularizer.modularize('process', fs.readFileSync(PROCESS_RUNTIME_PATH, 'utf8'));

var RouteTypes = keyMirror({
  fullPageRender: null,  // requests for `.html` files corresponding to components.
  jsBundle: null,        // requests for `.bundle` files corresponding to components.
  jsMaps: null           // requsts for `.map` files corresponding to components.
});

/**
 * The default router uses the `buildConfig.pageRouteRoot` as a way to prefix
 * all component lookups. For example, in the example `server.js`, the
 * `pageRouteRoot` is set to the `src/pages` directory, so
 * http://localhost:8080/index.html => src/pages/index.js
 * http://localhost:8080/about/index.html => src/pages/index.js
 *
 * The same convention is applied to bundle paths, since each page has exactly
 * one bundle. Each generated HTML page automatically pulls in its JS bundle
 * from the client (you don't worry about this).
 *
 * http://localhost:8080/index.bundle => src/pages/index.js(bundled)
 * http://localhost:8080/about/index.bundle => src/pages/index.js(bundled)
 *
 * If no `.bundle` or `.html` is found at the end of the URL, the default router
 * will append `index.html` to the end, before performing the routing convention
 * listed above.
 *
 * So http://localhost:8080/some/path
 * normalized => http://localhost:8080/some/path/index.html
 * rendered   => http://localhost:8080/some/path/index.js
 * bundled   => http://localhost:8080/some/path/index.bundle
 *
 * @return Route data or null if none applicable.
 */
var _getDefaultRouteData = function(buildConfig, reqURL) {
  var reqPath = url.parse(reqURL).pathname;
  var hasExtension = consts.HAS_EXT_RE.test(reqPath);
  var endsInHTML = consts.PAGE_EXT_RE.test(reqPath);
  var endsInBundle = consts.BUNDLE_EXT_RE.test(reqPath);
  var endsInMap = consts.MAP_EXT_RE.test(reqPath);
  var routeType = endsInHTML || !hasExtension ? RouteTypes.fullPageRender :
    endsInBundle ? RouteTypes.jsBundle :
    endsInMap ? RouteTypes.jsMaps : null;
  if (!routeType || reqPath.indexOf('..') !== -1) {
    return null;
  }
  var contentType = routeType === RouteTypes.fullPageRender ? HTML_TYPE :
    routeType === RouteTypes.jsBundle ? JS_TYPE :
    routeType === RouteTypes.jsMaps ? MAPS_TYPE : null;

  // Normalize localhost/myPage to localhost/myPage/index.html
  var indexNormalizedRequestPath =
    !hasExtension ? path.join(reqPath, '/index.html') : reqPath;
  var allTagsAndExtensionsMatch = reqPath.match(consts.ALL_TAGS_AND_EXT_RE);
  if (!allTagsAndExtensionsMatch) {
    return null;
  }
  var tagsAndExtension = allTagsAndExtensionsMatch && allTagsAndExtensionsMatch[1];
  var tagsAndExtensionSplit = tagsAndExtension && tagsAndExtension.split('.');
  var tags = tagsAndExtensionSplit &&
    tagsAndExtensionSplit.slice(0, tagsAndExtensionSplit.length - 1);

  return {
    /**
     * The only "first class" routing fields that are expected to be returned
     * by all routers.
     */
    contentType: contentType,
    rootModuleAbsolutePath: path.join(
      buildConfig.pageRouteRoot || '',
        // .html => .js, .bundle => js, .map => .js
        indexNormalizedRequestPath.replace(consts.ALL_TAGS_AND_EXT_RE, consts.JS_SRC_EXT)
          .replace(consts.BUNDLE_EXT_RE, consts.JS_SRC_EXT)
          .replace(consts.MAP_EXT_RE, consts.JS_SRC_EXT)
          .replace(consts.LEADING_SLASH_RE, '')
    ),

    /**
     * The remaining fields are anticipated by `DefaultRouter`'s particular
     * `routePackageHandler`.
     */
    type: routeType,
    tags: tags,
    indexNormalizedRequestPath: indexNormalizedRequestPath,
    additionalProps: {requestParams: url.parse(reqURL, true).query}
  };
};

/**
 * @param {object} buildConfig Options for building.
 * @param {RouteData} route RouteData specifying root module etc.
 * @param {string} Root module ID: we use this with the `runModule` tag.
 * @param {Package} ppackage Appends module system etc.
 */
var preparePackage = function(buildConfig, route, rootModuleID, ppackage) {
  var devStr = devBlock(buildConfig);
  var processCode = buildConfig.useBrowserBuiltins ?
    (PROCESS_RUNTIME + '\nprocess = require("process");') : '\nprocess = {env:{}};';
  processCode +=
    '\nprocess.env.NODE_ENV = "' + (buildConfig.dev ? 'development' : 'production') + '";\n';
  ppackage.unshift(PROCESS_RUNTIME_PATH, processCode, processCode);
  if (route.tags.indexOf(consts.INCLUDE_REQUIRE_TAG) !== -1) {
    ppackage.unshift(REQUIRE_RUNTIME_PATH, REQUIRE_RUNTIME, REQUIRE_RUNTIME);
  }
  ppackage.unshift('/dynamically-generated.js', devStr, devStr);
  if (route.tags.indexOf(consts.RUN_MODULE_TAG) !== -1) {
    var moduleRunnerSource = "require('" + rootModuleID + "');null;";
    ppackage.push("PackageRun" + rootModuleID, moduleRunnerSource, moduleRunnerSource);
  }
};

var routePackageHandler = function(buildConfig, route, rootModuleID, ppackage, next) {
  preparePackage(buildConfig, route, rootModuleID, ppackage);
  if (route.type === RouteTypes.fullPageRender) {
    renderComponentPackage(buildConfig, route, rootModuleID, ppackage, next);
    TimingData.data.serveEnd = Date.now();
    if (buildConfig.logTiming) {
      Chart.logPageServeTime(TimingData.data);
    }
  } else if (route.type === RouteTypes.jsBundle) {
    next(null, computeJSBundle(buildConfig, route, ppackage));
    TimingData.data.serveEnd = Date.now();
    if (buildConfig.logTiming) {
      Chart.logBundleServeTime(TimingData.data);
    }
  } else if (route.type === RouteTypes.jsMaps) {
    next(null, computeJSBundleMapsFile(buildConfig, route, ppackage));
    TimingData.data.serveEnd = Date.now();
    if (buildConfig.logTiming) {
      Chart.logBundleServeTime(TimingData.data);
    }
  } else {
    next(new Error('unrecognized route'));
  }
};

var decideRoute = function(buildConfig, reqURL, next) {
  if (!buildConfig.pageRouteRoot) {
    return next(new Error('Must specify default router root'));
  } else {
    var routerData =  _getDefaultRouteData(buildConfig, reqURL);
    return next(routerData ? null : new Error('No route for:' + reqURL), routerData);
  }
};

/**
 * Handles generating "an entire page" of markup for a given route and
 * corresponding `Package`.
 *
 * - First, generates the markup and renders it server side. Will never generate
 *   source maps, unless an error occurs - then they are generated lazily.
 * - That markup is sent, then another request is made for the JS. If
 *   `useSourceMaps` is true in the `buildConfig`, then these will be generated
 *   at this point - it does not block initial page render since server
 *   rendering happens without them.
 *
 * @param {object} buildConfig Build config options.
 * @param {Route} route Contains information about the component to render
 * based on URL.
 * @param {String} rootModuleID, id of the root module for given route.
 * @param {Package} Contains information about all dependencies for given route.
 * @param {function} next When complete.
 */
var renderComponentPackage = function(buildConfig, route, rootModuleID, ppackage, next) {
  var jsBundleText = computeJSBundle(buildConfig, route, ppackage);
  var props =  route.additionalProps || {};
  renderReactPage({
    serverRender: buildConfig.serverRender,
    indexNormalizedRequestPath: route.indexNormalizedRequestPath,
    rootModuleID: rootModuleID,
    props: props,
    bundleText: jsBundleText,
    ppackage: ppackage,
    /**
     * @param {Error} err Error that occured.
     * @param {string} markup Markup result.
     */
    done: function(err, markup) {
      TimingData.data.markupEnd = Date.now();
      next(err, markup);
      if (buildConfig.logTiming) {
        Chart.logSummary(route.indexNormalizedRequestPath, ppackage.resourceCount());
      }
    }
  });
};

var computeJSBundle = function(buildConfig, route, ppackage) {
  var useSourceMaps = buildConfig.useSourceMaps;
  var sourceMapsType = buildConfig.sourceMapsType;
  var inlineSourceMaps = sourceMapsType === 'inline';
  var footerContent = null;
  var packageText = ppackage.getSealedText(inlineSourceMaps);
  TimingData.data.concatEnd = Date.now();
  if (useSourceMaps) {
    footerContent = inlineSourceMaps ?
      // Inline them in a comment
      convertSourceMap.fromObject(ppackage.getSealedSourceMaps().toJSON()).toComment() :
      // Link them by replacing only the extension:
      // path/File.tag.bundle -> path/File.tag.map
      '\/\/@ sourceMappingURL=' + route.indexNormalizedRequestPath.replace(
        consts.BUNDLE_EXT_RE,
        consts.MAP_EXT
      );
  }
  TimingData.data.sourceMapEnd = Date.now();
  return (footerContent ? packageText + '\n' + footerContent : packageText) + ' ';
};

var computeJSBundleMapsFile = function(buildConfig, route, ppackage) {
  TimingData.data.concatEnd = Date.now();
  var bundleText = ppackage.getSealedSourceMaps().toString();
  TimingData.data.sourceMapEnd = Date.now();
  return bundleText;
};

var DefaultRouter = {
  decideRoute: decideRoute,
  routePackageHandler: routePackageHandler
};

module.exports = DefaultRouter;
<commit_msg>[react-page-middleware] Route based on normalized path - fixes bug.<commit_after>/**
 * Copyright 2013 Facebook, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
"use strict";

var fs = require('fs');
var path = require('path');
var url = require('url');

var Chart = require('./Chart');
var Modularizer = require('./Modularizer');
var TimingData = require('./TimingData');

var consts = require('./consts');
var renderReactPage = require('./renderReactPage');

var convertSourceMap = require('convert-source-map');

/**
 * What is a "Router" for `react-page-middleware`?
 *
 * A `react-page-middleware` `Router` object helps generate entire responses for URLs.
 * `react-page-middleware` understands dependencies between files, and
 * bundles/transforms your resources for you, but your `Router` fills in some
 * of the application specific details - such as how URLs map to a root JS
 * module, and how to render a page for a particular type of URL request.
 *
 * A `Router object must contain:
 *
 * `decideRoute` method:
 *
 * - Accepts a URL from a server request.
 * - Asynchronously determines a "route" object describing at least two things:
 * -- Absolute path to the root JS module corresponding to the request.
 * -- The content type of the response.
 * -- The determined route may also include as many other things as your router
 *  would like.
 *
 * `routePackageHandler` method:
 *
 * - Accepts a `Package` object (automatically generated for you based on the
 *   `route` object you previously decided from `decideRoute`.
 * - Generates a page response text.
 * - Can make great use of the features in the `Package` object, including
 *   source maps etc.
 * - Can use that package to deliver a static resource to the client, or can
 *   run that package on the server to generate markup on the server.
 */

/**
 * More formally:
 * @typedef Config: Stringmap;
 *
 * @typedef RouteData {
 *  contentType: string,           // Content-type header field
 *  rootModuleAbsolutePath: string // Absolute path to "root" JS module
 *  // (whatever other fields you want: "additional props" etc.
 * };
 *
 * @typedef Router {
 *   decideRoute: (
 *     buildConfig:Config,
 *     reqURL:string,
 *     next:(err, RouteData)->void
 *   ) -> void,
 *   routePackageHandler: (
 *     buildConfig:Config,
 *     route:RouteData,
 *     rootModuleID:string,
 *     ppackage:Package,
 *     next:(err, bundleText:string)->void
 *   ) -> void
 * };
 *
 */

/**
 * The "DefaultRouter" contains some nice features worth emulating:
 *
 * - Looks at `buildConfig` for a field `sourceMapsType` (either 'inline' or
 *   'linked') that instructs page generation on how to link source maps -
 *   should they be sent in the bundle, or downloaded lazily when you open your
 *   debugger?
 * - Looks at `buildConfig.useBrowserBuiltins` to know whether or not to
 *   include common node.js functionality in the bundle even if ran in the
 *   browser.
 * - Server rendering of pages: Routes `mypage/path/to/component.html` to
 *   `mypage/path/to/component.js` and expects to find a React component there
 *   to render.
 * - Looks for "tags" in the path, which hint to include additional code in the
 *   generated bundle:
 * -- runModule: Puts `require('YourModule')` at the end of the bundle so it is
 *  ran automatically.
 * -- includeRequire: Controls whether or not to include the require runtim. If
 *  a previous bundle already defined it you know you don't have to.
 */

/**
 * Define your own custom router:
 * - Simply adhere to the above definition. If you use `react-page`, you should
 *   be able to simply provide your own router inside the `server.js` file
 *   (pass it to the `react-page-middleware` `provide` method using the object
 *   key `router`).
 */

var devBlock = function(buildConfig) {
  return '__VERSION__ = 0.44; __DEV__ = ' + (buildConfig.dev ? ' true;\n' : 'false;\n');
};

var keyMirror = function(obj) {
  var ret = {};
  var key;
  for (key in obj) {
    if (!obj.hasOwnProperty(key)) {
      continue;
    }
    ret[key] = key;
  }
  return ret;
};

var JS_TYPE = 'application/javascript';
var MAPS_TYPE = 'text';
var HTML_TYPE = 'text/html';

/**
 * Bundle the require implementation
 */
var REQUIRE_RUNTIME_PATH = path.resolve(__dirname, '..', 'polyfill/require.js');
var PROCESS_RUNTIME_PATH = path.resolve(
  __dirname,
  '..',
  'node_modules',
  'browser-builtins',
  'builtin',
  'process.js'
);
var REQUIRE_RUNTIME = fs.readFileSync(REQUIRE_RUNTIME_PATH, 'utf8');
var PROCESS_RUNTIME =
  Modularizer.modularize('process', fs.readFileSync(PROCESS_RUNTIME_PATH, 'utf8'));

var RouteTypes = keyMirror({
  fullPageRender: null,  // requests for `.html` files corresponding to components.
  jsBundle: null,        // requests for `.bundle` files corresponding to components.
  jsMaps: null           // requsts for `.map` files corresponding to components.
});

/**
 * The default router uses the `buildConfig.pageRouteRoot` as a way to prefix
 * all component lookups. For example, in the example `server.js`, the
 * `pageRouteRoot` is set to the `src/pages` directory, so
 * http://localhost:8080/index.html => src/pages/index.js
 * http://localhost:8080/about/index.html => src/pages/index.js
 *
 * The same convention is applied to bundle paths, since each page has exactly
 * one bundle. Each generated HTML page automatically pulls in its JS bundle
 * from the client (you don't worry about this).
 *
 * http://localhost:8080/index.bundle => src/pages/index.js(bundled)
 * http://localhost:8080/about/index.bundle => src/pages/index.js(bundled)
 *
 * If no `.bundle` or `.html` is found at the end of the URL, the default router
 * will append `index.html` to the end, before performing the routing convention
 * listed above.
 *
 * So http://localhost:8080/some/path
 * normalized => http://localhost:8080/some/path/index.html
 * rendered   => http://localhost:8080/some/path/index.js
 * bundled   => http://localhost:8080/some/path/index.bundle
 *
 * @return Route data or null if none applicable.
 */
var _getDefaultRouteData = function(buildConfig, reqURL) {
  var reqPath = url.parse(reqURL).pathname;
  var hasExtension = consts.HAS_EXT_RE.test(reqPath);
  var endsInHTML = consts.PAGE_EXT_RE.test(reqPath);
  var endsInBundle = consts.BUNDLE_EXT_RE.test(reqPath);
  var endsInMap = consts.MAP_EXT_RE.test(reqPath);
  var routeType = endsInHTML || !hasExtension ? RouteTypes.fullPageRender :
    endsInBundle ? RouteTypes.jsBundle :
    endsInMap ? RouteTypes.jsMaps : null;
  if (!routeType || reqPath.indexOf('..') !== -1) {
    return null;
  }
  var contentType = routeType === RouteTypes.fullPageRender ? HTML_TYPE :
    routeType === RouteTypes.jsBundle ? JS_TYPE :
    routeType === RouteTypes.jsMaps ? MAPS_TYPE : null;

  // Normalize localhost/myPage to localhost/myPage/index.html
  var indexNormalizedRequestPath =
    !hasExtension ? path.join(reqPath, '/index.html') : reqPath;
  var allTagsAndExtensionsMatch =
    indexNormalizedRequestPath.match(consts.ALL_TAGS_AND_EXT_RE);
  if (!allTagsAndExtensionsMatch) {
    return null;
  }
  var tagsAndExtension = allTagsAndExtensionsMatch && allTagsAndExtensionsMatch[1];
  var tagsAndExtensionSplit = tagsAndExtension && tagsAndExtension.split('.');
  var tags = tagsAndExtensionSplit &&
    tagsAndExtensionSplit.slice(0, tagsAndExtensionSplit.length - 1);

  return {
    /**
     * The only "first class" routing fields that are expected to be returned
     * by all routers.
     */
    contentType: contentType,
    rootModuleAbsolutePath: path.join(
      buildConfig.pageRouteRoot || '',
        // .html => .js, .bundle => js, .map => .js
        indexNormalizedRequestPath.replace(consts.ALL_TAGS_AND_EXT_RE, consts.JS_SRC_EXT)
          .replace(consts.BUNDLE_EXT_RE, consts.JS_SRC_EXT)
          .replace(consts.MAP_EXT_RE, consts.JS_SRC_EXT)
          .replace(consts.LEADING_SLASH_RE, '')
    ),

    /**
     * The remaining fields are anticipated by `DefaultRouter`'s particular
     * `routePackageHandler`.
     */
    type: routeType,
    tags: tags,
    indexNormalizedRequestPath: indexNormalizedRequestPath,
    additionalProps: {requestParams: url.parse(reqURL, true).query}
  };
};

/**
 * @param {object} buildConfig Options for building.
 * @param {RouteData} route RouteData specifying root module etc.
 * @param {string} Root module ID: we use this with the `runModule` tag.
 * @param {Package} ppackage Appends module system etc.
 */
var preparePackage = function(buildConfig, route, rootModuleID, ppackage) {
  var devStr = devBlock(buildConfig);
  var processCode = buildConfig.useBrowserBuiltins ?
    (PROCESS_RUNTIME + '\nprocess = require("process");') : '\nprocess = {env:{}};';
  processCode +=
    '\nprocess.env.NODE_ENV = "' + (buildConfig.dev ? 'development' : 'production') + '";\n';
  ppackage.unshift(PROCESS_RUNTIME_PATH, processCode, processCode);
  if (route.tags.indexOf(consts.INCLUDE_REQUIRE_TAG) !== -1) {
    ppackage.unshift(REQUIRE_RUNTIME_PATH, REQUIRE_RUNTIME, REQUIRE_RUNTIME);
  }
  ppackage.unshift('/dynamically-generated.js', devStr, devStr);
  if (route.tags.indexOf(consts.RUN_MODULE_TAG) !== -1) {
    var moduleRunnerSource = "require('" + rootModuleID + "');null;";
    ppackage.push("PackageRun" + rootModuleID, moduleRunnerSource, moduleRunnerSource);
  }
};

var routePackageHandler = function(buildConfig, route, rootModuleID, ppackage, next) {
  preparePackage(buildConfig, route, rootModuleID, ppackage);
  if (route.type === RouteTypes.fullPageRender) {
    renderComponentPackage(buildConfig, route, rootModuleID, ppackage, next);
    TimingData.data.serveEnd = Date.now();
    if (buildConfig.logTiming) {
      Chart.logPageServeTime(TimingData.data);
    }
  } else if (route.type === RouteTypes.jsBundle) {
    next(null, computeJSBundle(buildConfig, route, ppackage));
    TimingData.data.serveEnd = Date.now();
    if (buildConfig.logTiming) {
      Chart.logBundleServeTime(TimingData.data);
    }
  } else if (route.type === RouteTypes.jsMaps) {
    next(null, computeJSBundleMapsFile(buildConfig, route, ppackage));
    TimingData.data.serveEnd = Date.now();
    if (buildConfig.logTiming) {
      Chart.logBundleServeTime(TimingData.data);
    }
  } else {
    next(new Error('unrecognized route'));
  }
};

var decideRoute = function(buildConfig, reqURL, next) {
  if (!buildConfig.pageRouteRoot) {
    return next(new Error('Must specify default router root'));
  } else {
    var routerData =  _getDefaultRouteData(buildConfig, reqURL);
    return next(routerData ? null : new Error('No route for:' + reqURL), routerData);
  }
};

/**
 * Handles generating "an entire page" of markup for a given route and
 * corresponding `Package`.
 *
 * - First, generates the markup and renders it server side. Will never generate
 *   source maps, unless an error occurs - then they are generated lazily.
 * - That markup is sent, then another request is made for the JS. If
 *   `useSourceMaps` is true in the `buildConfig`, then these will be generated
 *   at this point - it does not block initial page render since server
 *   rendering happens without them.
 *
 * @param {object} buildConfig Build config options.
 * @param {Route} route Contains information about the component to render
 * based on URL.
 * @param {String} rootModuleID, id of the root module for given route.
 * @param {Package} Contains information about all dependencies for given route.
 * @param {function} next When complete.
 */
var renderComponentPackage = function(buildConfig, route, rootModuleID, ppackage, next) {
  var jsBundleText = computeJSBundle(buildConfig, route, ppackage);
  var props =  route.additionalProps || {};
  renderReactPage({
    serverRender: buildConfig.serverRender,
    indexNormalizedRequestPath: route.indexNormalizedRequestPath,
    rootModuleID: rootModuleID,
    props: props,
    bundleText: jsBundleText,
    ppackage: ppackage,
    /**
     * @param {Error} err Error that occured.
     * @param {string} markup Markup result.
     */
    done: function(err, markup) {
      TimingData.data.markupEnd = Date.now();
      next(err, markup);
      if (buildConfig.logTiming) {
        Chart.logSummary(route.indexNormalizedRequestPath, ppackage.resourceCount());
      }
    }
  });
};

var computeJSBundle = function(buildConfig, route, ppackage) {
  var useSourceMaps = buildConfig.useSourceMaps;
  var sourceMapsType = buildConfig.sourceMapsType;
  var inlineSourceMaps = sourceMapsType === 'inline';
  var footerContent = null;
  var packageText = ppackage.getSealedText(inlineSourceMaps);
  TimingData.data.concatEnd = Date.now();
  if (useSourceMaps) {
    footerContent = inlineSourceMaps ?
      // Inline them in a comment
      convertSourceMap.fromObject(ppackage.getSealedSourceMaps().toJSON()).toComment() :
      // Link them by replacing only the extension:
      // path/File.tag.bundle -> path/File.tag.map
      '\/\/@ sourceMappingURL=' + route.indexNormalizedRequestPath.replace(
        consts.BUNDLE_EXT_RE,
        consts.MAP_EXT
      );
  }
  TimingData.data.sourceMapEnd = Date.now();
  return (footerContent ? packageText + '\n' + footerContent : packageText) + ' ';
};

var computeJSBundleMapsFile = function(buildConfig, route, ppackage) {
  TimingData.data.concatEnd = Date.now();
  var bundleText = ppackage.getSealedSourceMaps().toString();
  TimingData.data.sourceMapEnd = Date.now();
  return bundleText;
};

var DefaultRouter = {
  decideRoute: decideRoute,
  routePackageHandler: routePackageHandler
};

module.exports = DefaultRouter;
<|endoftext|>


<commit_before>var sinon = require('sinon');
var chai = require('chai');
var expect = chai.expect;
var Sequelize = require('sequelize');
var dbModels = require('../../../server/db/database.js');
var Song = dbModels.Song;
var GroupController = require('../../../server/models/group.js');
var SongController = require('../../../server/models/song.js');
dbModels.db.options.logging = false;

// The `clearDB` helper function, when invoked, will clear the database
var clearDB = function (done) {
  dbModels.db.query('DELETE from SONGS where true')
    .spread(function(results, metadata) {
      done();
    });
};

var songReq = {
  body: {
    title: 'Margaritaville',
    description: 'Wasted again',
    groupId: '1'
  }
};

var groupReq = {
  body: {
    name: 'Safety Talk'
  }
};

describe('Song Controller', function () {

  // Connect to database before any tests
  before(function (done) {
    var res = {};
    res.json = function(jsonresponse) {
      console.log(JSON.stringify(jsonresponse));
      songReq.body.groupId = jsonresponse.id;
      done();
    };

    Song.sync({force: true})
      .then(function() {
        return Group.sync({force: true});
      })
      .then(function() {
        GroupController.createGroup(groupReq, res);
        // done();
      });
  });

  describe ('add song', function() {
  // Clear database before each test and then seed it with example `users` so that you can run tests
    beforeEach(function (done) {
      clearDB(function () {
        done();
      });
    });
    it('should call res.json to return a json object', function (done) {

      var res = {};

      // make my own damn spy
      res.json = function(jsonresponse) {
        expect(jsonresponse).to.have.property('title');
        done();
      };

      res.send = function(err) {
        console.error(err);
      };
      // var spy = res.json = sinon.stub();
      SongController.addSong(songReq, res);
    });

    it('should create a new song in the database', function (done) {
      var res = {};
      res.send = function(err) {
        console.error(err);
      };
      res.json = function(jsonresponse) {
        dbModels.db.query('SELECT * FROM songs WHERE title = :title ', { replacements: {title: songReq.body.title}, type: Sequelize.QueryTypes.SELECT})
        .then( function(songs) {
          expect(songs[0].title).to.equal(songReq.body.title);
          done();
        });
      };
      SongController.addSong(songReq, res, function() {});
    });
  });
});<commit_msg>Import Group model in song test<commit_after>var sinon = require('sinon');
var chai = require('chai');
var expect = chai.expect;
var Sequelize = require('sequelize');
var dbModels = require('../../../server/db/database.js');
var Song = dbModels.Song;
var Group = dbModels.Song;
var GroupController = require('../../../server/models/group.js');
var SongController = require('../../../server/models/song.js');
dbModels.db.options.logging = false;

// The `clearDB` helper function, when invoked, will clear the database
var clearDB = function (done) {
  dbModels.db.query('DELETE from SONGS where true')
    .spread(function(results, metadata) {
      done();
    });
};

var songReq = {
  body: {
    title: 'Margaritaville',
    description: 'Wasted again',
    groupId: '1'
  }
};

var groupReq = {
  body: {
    name: 'Safety Talk'
  }
};

describe('Song Controller', function () {

  // Connect to database before any tests
  before(function (done) {
    var res = {};
    res.json = function(jsonresponse) {
      console.log(JSON.stringify(jsonresponse));
      songReq.body.groupId = jsonresponse.id;
      done();
    };

    Song.sync({force: true})
      .then(function() {
        return Group.sync({force: true});
      })
      .then(function() {
        GroupController.createGroup(groupReq, res);
        // done();
      });
  });

  describe ('add song', function() {
  // Clear database before each test and then seed it with example `users` so that you can run tests
    beforeEach(function (done) {
      clearDB(function () {
        done();
      });
    });
    it('should call res.json to return a json object', function (done) {

      var res = {};

      // make my own damn spy
      res.json = function(jsonresponse) {
        expect(jsonresponse).to.have.property('title');
        done();
      };

      res.send = function(err) {
        console.error(err);
      };
      // var spy = res.json = sinon.stub();
      SongController.addSong(songReq, res);
    });

    it('should create a new song in the database', function (done) {
      var res = {};
      res.send = function(err) {
        console.error(err);
      };
      res.json = function(jsonresponse) {
        dbModels.db.query('SELECT * FROM songs WHERE title = :title ', { replacements: {title: songReq.body.title}, type: Sequelize.QueryTypes.SELECT})
        .then( function(songs) {
          expect(songs[0].title).to.equal(songReq.body.title);
          done();
        });
      };
      SongController.addSong(songReq, res, function() {});
    });
  });
});<|endoftext|>


<commit_before>/* global i18n, describe, it, beforeEach, expect */
/* jshint globalstrict: true, devel: true */

"use strict";

describe("light-i18n", function() {
  describe("#translate(ele)", function() {
    var ele;

    beforeEach(function() {
      ele = document.createElement("div");
    });

    it("should work when property is not nested", function(done) {
      ele.setAttribute("data-i18n", "test");

      i18n.translate(ele).then(function() {
        expect(ele.innerHTML).to.be.equal("test1");
        done();
      });
    });

    it("should remove previous content", function(done) {
      ele.setAttribute("data-i18n", "test");
      ele.innerHTML = "test123";

      i18n.translate(ele).then(function() {
        expect(ele.childNodes.length).to.be.equal(1);
        expect(ele.innerHTML).to.be.equal("test1");
        done();
      });
    });

    it("should set lang attribute", function(done) {
      ele.setAttribute("data-i18n", "test");
      ele.lang = "de";

      i18n.translate(ele).then(function() {
        expect(ele.lang).to.be.equal(i18n.language);
        done();
      });
    });

    it("should not change anything when undefined", function(done) {
      ele.setAttribute("data-i18n", "test42");
      ele.innerHTML = "test43";

      i18n.translate(ele).then(function() {
        expect(ele.innerHTML).to.be.equal("test43");
        done();
      });
    });

    it("should warn & not change anything when object", function(done) {
      var warn = console.warn;
      ele.setAttribute("data-i18n", "test2");
      ele.innerHTML = "test43";

      console.warn = function(_, ele2, path) {
        expect(ele2).to.be.equal(ele);
        expect(path).to.be.equal("test2");
        console.warn = warn;
        done();
      };
      i18n.translate(ele);
    });

    it("should work when nested", function(done) {
      ele.setAttribute("data-i18n", "test2.test4");

      i18n.translate(ele).then(function() {
        expect(ele.innerHTML).to.be.equal("test5");
        done();
      });
    });

    it("should work when nested", function(done) {
      ele.setAttribute("data-i18n", "test2.test4");

      i18n.translate(ele).then(function() {
        expect(ele.innerHTML).to.be.equal("test5");
        done();
      });
    });

    it("should work when number", function(done) {
      ele.setAttribute("data-i18n", "test2.test3");

      i18n.translate(ele).then(function() {
        expect(ele.innerHTML).to.be.equal("42");
        done();
      });
    });

    it("should not translate if lang attribute matches dialect", function(done) {
      // "test": "test1"
      ele.setAttribute("data-i18n", "test");
      ele.innerHTML = "test";
      ele.lang = navigator.userLanguage || navigator.language;

      i18n.translate(ele).then(function() {
        expect(ele.innerHTML).to.be.equal("test");
        done();
      });
    });

    it("should add every element of an array as paragraph", function(done) {
      // "testArray": ["test1", "test2"]
      ele.setAttribute("data-i18n", "testArray");

      i18n.translate(ele).then(function() {
        expect(ele.childNodes.length).to.be.equal(2);
        expect(ele.childNodes[0].nodeName).to.be.equal("P");
        expect(ele.childNodes[1].nodeName).to.be.equal("P");
        expect(ele.childNodes[0].innerHTML).to.be.equal("test1");
        expect(ele.childNodes[1].innerHTML).to.be.equal("test2");
        done();
      });
    });
  });
});
<commit_msg>Fix test case when path matches object<commit_after>/* global i18n, describe, it, beforeEach, expect */
/* jshint globalstrict: true, devel: true */

"use strict";

describe("light-i18n", function() {
  describe("#translate(ele)", function() {
    var ele;

    beforeEach(function() {
      ele = document.createElement("div");
    });

    it("should work when property is not nested", function(done) {
      ele.setAttribute("data-i18n", "test");

      i18n.translate(ele).then(function() {
        expect(ele.innerHTML).to.be.equal("test1");
        done();
      });
    });

    it("should remove previous content", function(done) {
      ele.setAttribute("data-i18n", "test");
      ele.innerHTML = "test123";

      i18n.translate(ele).then(function() {
        expect(ele.childNodes.length).to.be.equal(1);
        expect(ele.innerHTML).to.be.equal("test1");
        done();
      });
    });

    it("should set lang attribute", function(done) {
      ele.setAttribute("data-i18n", "test");
      ele.lang = "de";

      i18n.translate(ele).then(function() {
        expect(ele.lang).to.be.equal(i18n.language);
        done();
      });
    });

    it("should not change anything when undefined", function(done) {
      ele.setAttribute("data-i18n", "test42");
      ele.innerHTML = "test43";

      i18n.translate(ele).then(function() {
        expect(ele.innerHTML).to.be.equal("test43");
        done();
      });
    });

    it("should warn & not change anything when object", function(done) {
      var warn = console.warn;
      ele.setAttribute("data-i18n", "test2");
      ele.innerHTML = "test43";

      console.warn = function(_, ele2, path) {
        expect(ele2).to.be.equal(ele);
        expect(path).to.be.equal("test2");
        expect(ele.innerHTML).to.be.equal("test43");
        console.warn = warn;
        done();
      };
      i18n.translate(ele);
    });

    it("should work when nested", function(done) {
      ele.setAttribute("data-i18n", "test2.test4");

      i18n.translate(ele).then(function() {
        expect(ele.innerHTML).to.be.equal("test5");
        done();
      });
    });

    it("should work when nested", function(done) {
      ele.setAttribute("data-i18n", "test2.test4");

      i18n.translate(ele).then(function() {
        expect(ele.innerHTML).to.be.equal("test5");
        done();
      });
    });

    it("should work when number", function(done) {
      ele.setAttribute("data-i18n", "test2.test3");

      i18n.translate(ele).then(function() {
        expect(ele.innerHTML).to.be.equal("42");
        done();
      });
    });

    it("should not translate if lang attribute matches dialect", function(done) {
      // "test": "test1"
      ele.setAttribute("data-i18n", "test");
      ele.innerHTML = "test";
      ele.lang = navigator.userLanguage || navigator.language;

      i18n.translate(ele).then(function() {
        expect(ele.innerHTML).to.be.equal("test");
        done();
      });
    });

    it("should add every element of an array as paragraph", function(done) {
      // "testArray": ["test1", "test2"]
      ele.setAttribute("data-i18n", "testArray");

      i18n.translate(ele).then(function() {
        expect(ele.childNodes.length).to.be.equal(2);
        expect(ele.childNodes[0].nodeName).to.be.equal("P");
        expect(ele.childNodes[1].nodeName).to.be.equal("P");
        expect(ele.childNodes[0].innerHTML).to.be.equal("test1");
        expect(ele.childNodes[1].innerHTML).to.be.equal("test2");
        done();
      });
    });
  });
});
<|endoftext|>


<commit_before>/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2014 Broad Institute
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 *  Define parsers for bed-like files  (.bed, .gff, .vcf, etc).  A parser should implement 2 methods
 *
 *     parseHeader(data) - return an object representing a header or metadata.  Details are format specific
 *
 *     parseFeatures(data) - return an array of features
 *
 */

var igv = (function (igv) {

    var maxFeatureCount = Number.MAX_VALUE;    // For future use,  controls downsampling

    /**
     * A factory function.  Return a parser for the given file type.
     */
    igv.FeatureParser = function (type, decode) {

        this.type = type;
        this.skipRows = 0;   // The number of fixed header rows to skip.  Override for specific types as needed

        if (decode) {
            this.decode = decode;
        }
        switch (type) {
            case "narrowPeak":
            case "broadPeak":
                this.decode = decodePeak;
                break;
            case "bedgraph":
                this.decode = decodeBedGraph;
                break;
            case "wig":
                this.decode = decodeWig;
                break;
            case "gff" :
                this.decode = decodeGFF;
                break;
            case "aneu":
                this.decode = decodeAneu;
                break;
            case "FusionJuncSpan":
                // bhaas, needed for FusionInspector view
                this.decode = decodeFusionJuncSpan;
                break;
            case "gtexGWAS":
                this.skipRows = 1;
                break;
            default:
                this.decode = decodeBed;

        }

    };

    igv.FeatureParser.prototype.parseHeader = function (data) {

        var lines = data.splitLines(),
            len = lines.length,
            line,
            i,
            header;

        for (i = 0; i < len; i++) {
            line = lines[i];
            if (line.startsWith("track") || line.startsWith("#") || line.startsWith("browser")) {

                if (line.startsWith("track")) {
                    header = parseTrackLine(line);
                }
            }
            else {
                break;
            }
        }
        return header;
    };

    igv.FeatureParser.prototype.parseFeatures = function (data) {

        if (!data) return null;

        var wig,
            feature,
            lines = data.splitLines(),
            len = lines.length,
            tokens,
            allFeatures = [],
            line,
            i,
            cnt = 0,
            j,
            decode = this.decode,
            type = this.type;

        for (i = this.skipRows; i < len; i++) {
            line = lines[i];
            if (line.startsWith("track") || line.startsWith("#") || line.startsWith("browser")) {
                continue;
            }
            else if (type === "wig" && line.startsWith("fixedStep")) {
                wig = parseFixedStep(line);
                continue;
            }
            else if (type === "wig" && line.startsWith("variableStep")) {
                wig = parseVariableStep(line);
                continue;
            }

            tokens = lines[i].split(/\s+/);
            if (tokens.length < 1) continue;

            feature = decode(tokens, wig);

            if (feature) {
                if (allFeatures.length < maxFeatureCount) {
                    allFeatures.push(feature);
                }
                else {
                    // Reservoir sampling,  conditionally replace existing feature with new one.
                    j = Math.floor(Math.random() * cnt);
                    if (j < maxFeatureCount) {
                        allFeatures[j] = feature;
                    }
                }
                cnt++;
            }
        }

        return allFeatures;
    };

    function decodeAneu(tokens, ignore) {

        var chr, start, end, id, name, tmp, idName, strand, cdStart, feature,
            eStart, eEnd;

        if (tokens.length < 4) return null;

        // console.log("Decoding aneu.tokens="+JSON.stringify(tokens));
        chr = tokens[1];
        start = parseInt(tokens[2]);
        end = tokens.length > 3 ? parseInt(tokens[3]) : start + 1;

        feature = {chr: chr, start: start, end: end};

        if (tokens.length > 4) {
            feature.score = parseFloat(tokens[4]);
            feature.value = feature.score;
        }

        feature.popupData = function () {
            return [{name: "Name", value: feature.name}];
        };

        return feature;

    }

    function parseFixedStep(line) {

        var tokens = line.split(/\s+/),
            cc = tokens[1].split("=")[1],
            ss = parseInt(tokens[2].split("=")[1], 10),
            step = parseInt(tokens[3].split("=")[1], 10),
            span = (tokens.length > 4) ? parseInt(tokens[4].split("=")[1], 10) : 1;

        return {type: "fixedStep", chrom: cc, start: ss, step: step, span: span, index: 0};

    }

    function parseVariableStep(line) {

        var tokens = line.split(/\s+/),
            cc = tokens[1].split("=")[1],
            span = tokens.length > 2 ? parseInt(tokens[2].split("=")[1], 10) : 1;
        return {type: "variableStep", chrom: cc, span: span}

    }

    function parseTrackLine(line) {
        var properties = {},
            tokens = line.split(/(?:")([^"]+)(?:")|([^\s"]+)(?=\s+|$)/g),
            tmp = [],
            i, tk, curr;

        // Clean up tokens array
        for (i = 1; i < tokens.length; i++) {
            if (!tokens[i] || tokens[i].trim().length === 0) continue;

            tk = tokens[i].trim();

            if (tk.endsWith("=") > 0) {
                curr = tk;
            }
            else if (curr) {
                tmp.push(curr + tk);
                curr = undefined;
            }
            else {
                tmp.push(tk);
            }

        }

        tmp.forEach(function (str) {
            if (!str) return;
            var kv = str.split('=', 2);
            if (kv.length == 2) {
                properties[kv[0]] = kv[1];
            }

        });

        return properties;
    }

    function decodeBed(tokens, ignore) {

        var chr, start, end, id, name, tmp, idName, strand, cdStart, exonCount, exonSizes, exonStarts, exons, feature,
            eStart, eEnd;

        if (tokens.length < 3) return null;

        chr = tokens[0];
        start = parseInt(tokens[1]);
        end = tokens.length > 2 ? parseInt(tokens[2]) : start + 1;

        feature = {chr: chr, start: start, end: end, score: 1000};

        if (tokens.length > 3) {
            // Note: these are very special rules for the gencode gene files.
            tmp = tokens[3].replace(/"/g, '');
            idName = tmp.split(';');
            for (var i = 0; i < idName.length; i++) {
                var kv = idName[i].split('=');
                if (kv[0] == "gene_id") {
                    id = kv[1];
                }
                if (kv[0] == "gene_name") {
                    name = kv[1];
                }
            }
            feature.id = id ? id : tmp;
            feature.name = name ? name : tmp;
        }

        if (tokens.length > 4) {
            feature.score = parseFloat(tokens[4]);
        }
        if (tokens.length > 5) {
            feature.strand = tokens[5];
        }
        if (tokens.length > 6) {
            feature.cdStart = parseInt(tokens[6]);
        }
        if (tokens.length > 7) {
            feature.cdEnd = parseInt(tokens[7]);
        }
        if (tokens.length > 8) {
            feature.rgb = tokens[8];
        }
        if (tokens.length > 11) {
            exonCount = parseInt(tokens[9]);
            exonSizes = tokens[10].split(',');
            exonStarts = tokens[11].split(',');
            exons = [];

            for (var i = 0; i < exonCount; i++) {
                eStart = start + parseInt(exonStarts[i]);
                eEnd = eStart + parseInt(exonSizes[i]);
                exons.push({start: eStart, end: eEnd});
            }

            feature.exons = exons;
        }

        feature.popupData = function () {
            return [{name: "Name", value: feature.name}];
        };

        return feature;

    }

    function decodePeak(tokens, ignore) {

        var tokenCount, chr, start, end, strand, name, score, qValue, signal, pValue;

        tokenCount = tokens.length;
        if (tokenCount < 9) {
            return null;
        }

        chr = tokens[0];
        start = parseInt(tokens[1]);
        end = parseInt(tokens[2]);
        name = tokens[3];
        score = parseFloat(tokens[4]);
        strand = tokens[5].trim();
        signal = parseFloat(tokens[6]);
        pValue = parseFloat(tokens[7]);
        qValue = parseFloat(tokens[8]);

        return {
            chr: chr, start: start, end: end, name: name, score: score, strand: strand, signal: signal,
            pValue: pValue, qValue: qValue
        };
    }

    function decodeBedGraph(tokens, ignore) {

        var chr, start, end, value;

        if (tokens.length < 3) return null;

        chr = tokens[0];
        start = parseInt(tokens[1]);
        end = parseInt(tokens[2]);

        value = parseFloat(tokens[3]);

        return {chr: chr, start: start, end: end, value: value};
    }

    function decodeWig(tokens, wig) {

        var ss,
            ee,
            value;

        if (wig.type === "fixedStep") {

            ss = (wig.index * wig.step) + wig.start;
            ee = ss + wig.span;
            value = parseFloat(tokens[0]);
            ++(wig.index);
            return isNaN(value) ? null : {chr: wig.chrom, start: ss, end: ee, value: value};
        }
        else if (wig.type === "variableStep") {

            if (tokens.length < 2) return null;

            ss = parseInt(tokens[0], 10);
            ee = ss + wig.span;
            value = parseFloat(tokens[1]);
            return isNaN(value) ? null : {chr: wig.chrom, start: ss, end: ee, value: value};

        }
        else {
            return decodeBedGraph(tokens);
        }
    }

    function decodeFusionJuncSpan(tokens, ignore) {

        /*
         Format:

         0       #scaffold
         1       fusion_break_name
         2       break_left
         3       break_right
         4       num_junction_reads
         5       num_spanning_frags
         6       spanning_frag_coords

         0       B3GNT1--NPSR1
         1       B3GNT1--NPSR1|2203-10182
         2       2203
         3       10182
         4       189
         5       1138
         6       1860-13757,1798-13819,1391-18127,1443-17174,...

         */

        var chr = tokens[0];
        var fusion_name = tokens[1];
        var junction_left = parseInt(tokens[2]);
        var junction_right = parseInt(tokens[3]);
        var num_junction_reads = parseInt(tokens[4]);
        var num_spanning_frags = parseInt(tokens[5]);

        var spanning_frag_coords_text = tokens[6];

        var feature = {
            chr: chr,
            name: fusion_name,
            junction_left: junction_left,
            junction_right: junction_right,
            num_junction_reads: num_junction_reads,
            num_spanning_frags: num_spanning_frags,
            spanning_frag_coords: [],

            start: -1,
            end: -1
        }; // set start and end later based on min/max of span coords

        var min_coord = junction_left;
        var max_coord = junction_right;

        if (num_spanning_frags > 0) {

            var coord_pairs = spanning_frag_coords_text.split(',');

            for (var i = 0; i < coord_pairs.length; i++) {
                var split_coords = coord_pairs[i].split('-');

                var span_left = split_coords[0];
                var span_right = split_coords[1];

                if (span_left < min_coord) {
                    min_coord = span_left;
                }
                if (span_right > max_coord) {
                    max_coord = span_right;
                }
                feature.spanning_frag_coords.push({left: span_left, right: span_right});

            }
        }

        feature.start = min_coord;
        feature.end = max_coord;

        feature.popupData = function () {
            return [{name: "Name", value: feature.name}];
        };

        return feature;

    }

    function decodeGtexGWAS(tokens, ignore) {

        var tokenCount, chr, start, end, strand, name, score, qValue, signal, pValue;

        tokenCount = tokens.length;
        if (tokenCount < 9) {
            return null;
        }

        chr = tokens[0];
        start = parseInt(tokens[1]);
        end = parseInt(tokens[2]);
        name = tokens[3];
        score = parseFloat(tokens[4]);
        strand = tokens[5].trim();
        signal = parseFloat(tokens[6]);
        pValue = parseFloat(tokens[7]);
        qValue = parseFloat(tokens[8]);

        return {chr: chr, start: start, end: end, name: name, score: score, strand: strand, signal: signal,
            pValue: pValue, qValue: qValue};
    }

    /**
     * Decode a single gff record (1 line in file).  Aggregations such as gene models are constructed at a higher level.
     *      ctg123 . mRNA            1050  9000  .  +  .  ID=mRNA00001;Parent=gene00001
     * @param tokens
     * @param ignore
     * @returns {*}
     */
    function decodeGFF(tokens, ignore) {

        var tokenCount, chr, start, end, strand, type, score, phase, attributeString, id, parent;

        tokenCount = tokens.length;
        if (tokenCount < 9) {
            return null;      // Not a valid gff record
        }

        chr = tokens[0];
        type = tokens[2];
        start = parseInt(tokens[3]) - 1;
        end = parseInt(tokens[4]);
        score = "." === tokens[5] ? 0 : parseFloat(tokens[5]);
        strand = tokens[6];
        phase = "." === tokens[7] ? 0 : parseInt(tokens[7]);
        attributeString = tokens[8];

        // Find ID and Parent
        attributeString.split(';').forEach(function (kv) {
            var t = kv.split('=', 2);
            if(t.length == 2) {
                if("ID" === t[0]) id = t[1];
                else if("Parent"=== t[0]) parent = t[1];
            }

        });

        return {
            id: id,
            parent: parent,
            chr: chr,
            start: start,
            end: end,
            score: score,
            strand: strand,
            attributeString: attributeString,
            popupData: function () {
                var kvs = this.attributeString.split(';'),
                    pd = [];
                kvs.forEach(function (kv) {
                    var t = kv.split('=', 2);
                    if(t.length === 2)
                        pd.push({name: t[0], value: t[1]});
                });
                return pd;
            }

        };
    }

    return igv;
})
(igv || {});
<commit_msg>Extended with decodeGtexGWAS decoding function.<commit_after>/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2014 Broad Institute
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 *  Define parsers for bed-like files  (.bed, .gff, .vcf, etc).  A parser should implement 2 methods
 *
 *     parseHeader(data) - return an object representing a header or metadata.  Details are format specific
 *
 *     parseFeatures(data) - return an array of features
 *
 */

var igv = (function (igv) {

    var maxFeatureCount = Number.MAX_VALUE;    // For future use,  controls downsampling

    /**
     * A factory function.  Return a parser for the given file type.
     */
    igv.FeatureParser = function (type, decode) {

        this.type = type;
        this.skipRows = 0;   // The number of fixed header rows to skip.  Override for specific types as needed

        if (decode) {
            this.decode = decode;
        }
<<<<<<< HEAD
        switch (type) {
            case "narrowPeak":
            case "broadPeak":
                this.decode = decodePeak;
                break;
            case "bedgraph":
                this.decode = decodeBedGraph;
                break;
            case "wig":
                this.decode = decodeWig;
                break;
            case "gff" :
                this.decode = decodeGFF;
                break;
            case "aneu":
                this.decode = decodeAneu;
                break;
            case "FusionJuncSpan":
                // bhaas, needed for FusionInspector view
                this.decode = decodeFusionJuncSpan;
                break;
            case "gtexGWAS":
                this.skipRows = 1;
                this.decode = decodeGtexGWAS;
                break;
            default:
                this.decode = decodeBed;

        }

    };

    igv.FeatureParser.prototype.parseHeader = function (data) {

        var lines = data.splitLines(),
            len = lines.length,
            line,
            i,
            header;

        for (i = 0; i < len; i++) {
            line = lines[i];
            if (line.startsWith("track") || line.startsWith("#") || line.startsWith("browser")) {

                if (line.startsWith("track")) {
                    header = parseTrackLine(line);
                }
            }
            else {
                break;
            }
        }
        return header;
    };

    igv.FeatureParser.prototype.parseFeatures = function (data) {

        if (!data) return null;

        var wig,
            feature,
            lines = data.splitLines(),
            len = lines.length,
            tokens,
            allFeatures = [],
            line,
            i,
            cnt = 0,
            j,
            decode = this.decode,
            type = this.type;

        for (i = this.skipRows; i < len; i++) {
            line = lines[i];
            if (line.startsWith("track") || line.startsWith("#") || line.startsWith("browser")) {
                continue;
            }
            else if (type === "wig" && line.startsWith("fixedStep")) {
                wig = parseFixedStep(line);
                continue;
            }
            else if (type === "wig" && line.startsWith("variableStep")) {
                wig = parseVariableStep(line);
                continue;
            }

            tokens = lines[i].split(/\s+/);
            if (tokens.length < 1) continue;

            feature = decode(tokens, wig);

            if (feature) {
                if (allFeatures.length < maxFeatureCount) {
                    allFeatures.push(feature);
                }
                else {
                    // Reservoir sampling,  conditionally replace existing feature with new one.
                    j = Math.floor(Math.random() * cnt);
                    if (j < maxFeatureCount) {
                        allFeatures[j] = feature;
                    }
                }
                cnt++;
            }
        }

        return allFeatures;
    };

    function decodeAneu(tokens, ignore) {

        var chr, start, end, id, name, tmp, idName, strand, cdStart, feature,
            eStart, eEnd;

        if (tokens.length < 4) return null;

        // console.log("Decoding aneu.tokens="+JSON.stringify(tokens));
        chr = tokens[1];
        start = parseInt(tokens[2]);
        end = tokens.length > 3 ? parseInt(tokens[3]) : start + 1;

        feature = {chr: chr, start: start, end: end};

        if (tokens.length > 4) {
            feature.score = parseFloat(tokens[4]);
            feature.value = feature.score;
        }

        feature.popupData = function () {
            return [{name: "Name", value: feature.name}];
        };

        return feature;

    }

    function parseFixedStep(line) {

        var tokens = line.split(/\s+/),
            cc = tokens[1].split("=")[1],
            ss = parseInt(tokens[2].split("=")[1], 10),
            step = parseInt(tokens[3].split("=")[1], 10),
            span = (tokens.length > 4) ? parseInt(tokens[4].split("=")[1], 10) : 1;

        return {type: "fixedStep", chrom: cc, start: ss, step: step, span: span, index: 0};

    }

    function parseVariableStep(line) {

        var tokens = line.split(/\s+/),
            cc = tokens[1].split("=")[1],
            span = tokens.length > 2 ? parseInt(tokens[2].split("=")[1], 10) : 1;
        return {type: "variableStep", chrom: cc, span: span}

    }

    function parseTrackLine(line) {
        var properties = {},
            tokens = line.split(/(?:")([^"]+)(?:")|([^\s"]+)(?=\s+|$)/g),
            tmp = [],
            i, tk, curr;

        // Clean up tokens array
        for (i = 1; i < tokens.length; i++) {
            if (!tokens[i] || tokens[i].trim().length === 0) continue;

            tk = tokens[i].trim();

            if (tk.endsWith("=") > 0) {
                curr = tk;
            }
            else if (curr) {
                tmp.push(curr + tk);
                curr = undefined;
            }
            else {
                tmp.push(tk);
            }

        }

        tmp.forEach(function (str) {
            if (!str) return;
            var kv = str.split('=', 2);
            if (kv.length == 2) {
                properties[kv[0]] = kv[1];
            }

        });

        return properties;
    }

    function decodeBed(tokens, ignore) {

        var chr, start, end, id, name, tmp, idName, strand, cdStart, exonCount, exonSizes, exonStarts, exons, feature,
            eStart, eEnd;

        if (tokens.length < 3) return null;

        chr = tokens[0];
        start = parseInt(tokens[1]);
        end = tokens.length > 2 ? parseInt(tokens[2]) : start + 1;

        feature = {chr: chr, start: start, end: end, score: 1000};

        if (tokens.length > 3) {
            // Note: these are very special rules for the gencode gene files.
            tmp = tokens[3].replace(/"/g, '');
            idName = tmp.split(';');
            for (var i = 0; i < idName.length; i++) {
                var kv = idName[i].split('=');
                if (kv[0] == "gene_id") {
                    id = kv[1];
                }
                if (kv[0] == "gene_name") {
                    name = kv[1];
                }
            }
            feature.id = id ? id : tmp;
            feature.name = name ? name : tmp;
        }

        if (tokens.length > 4) {
            feature.score = parseFloat(tokens[4]);
        }
        if (tokens.length > 5) {
            feature.strand = tokens[5];
        }
        if (tokens.length > 6) {
            feature.cdStart = parseInt(tokens[6]);
        }
        if (tokens.length > 7) {
            feature.cdEnd = parseInt(tokens[7]);
        }
        if (tokens.length > 8) {
            feature.rgb = tokens[8];
        }
        if (tokens.length > 11) {
            exonCount = parseInt(tokens[9]);
            exonSizes = tokens[10].split(',');
            exonStarts = tokens[11].split(',');
            exons = [];

            for (var i = 0; i < exonCount; i++) {
                eStart = start + parseInt(exonStarts[i]);
                eEnd = eStart + parseInt(exonSizes[i]);
                exons.push({start: eStart, end: eEnd});
            }

            feature.exons = exons;
        }

        feature.popupData = function () {
            return [{name: "Name", value: feature.name}];
        };

        return feature;

    }

    function decodePeak(tokens, ignore) {

        var tokenCount, chr, start, end, strand, name, score, qValue, signal, pValue;

        tokenCount = tokens.length;
        if (tokenCount < 9) {
            return null;
        }

        chr = tokens[0];
        start = parseInt(tokens[1]);
        end = parseInt(tokens[2]);
        name = tokens[3];
        score = parseFloat(tokens[4]);
        strand = tokens[5].trim();
        signal = parseFloat(tokens[6]);
        pValue = parseFloat(tokens[7]);
        qValue = parseFloat(tokens[8]);

        return {
            chr: chr, start: start, end: end, name: name, score: score, strand: strand, signal: signal,
            pValue: pValue, qValue: qValue
        };
    }

    function decodeBedGraph(tokens, ignore) {

        var chr, start, end, value;

        if (tokens.length < 3) return null;

        chr = tokens[0];
        start = parseInt(tokens[1]);
        end = parseInt(tokens[2]);

        value = parseFloat(tokens[3]);

        return {chr: chr, start: start, end: end, value: value};
    }

    function decodeWig(tokens, wig) {

        var ss,
            ee,
            value;

        if (wig.type === "fixedStep") {

            ss = (wig.index * wig.step) + wig.start;
            ee = ss + wig.span;
            value = parseFloat(tokens[0]);
            ++(wig.index);
            return isNaN(value) ? null : {chr: wig.chrom, start: ss, end: ee, value: value};
        }
        else if (wig.type === "variableStep") {

            if (tokens.length < 2) return null;

            ss = parseInt(tokens[0], 10);
            ee = ss + wig.span;
            value = parseFloat(tokens[1]);
            return isNaN(value) ? null : {chr: wig.chrom, start: ss, end: ee, value: value};

        }
        else {
            return decodeBedGraph(tokens);
        }
    }

    function decodeFusionJuncSpan(tokens, ignore) {

        /*
         Format:

         0       #scaffold
         1       fusion_break_name
         2       break_left
         3       break_right
         4       num_junction_reads
         5       num_spanning_frags
         6       spanning_frag_coords

         0       B3GNT1--NPSR1
         1       B3GNT1--NPSR1|2203-10182
         2       2203
         3       10182
         4       189
         5       1138
         6       1860-13757,1798-13819,1391-18127,1443-17174,...

         */

        var chr = tokens[0];
        var fusion_name = tokens[1];
        var junction_left = parseInt(tokens[2]);
        var junction_right = parseInt(tokens[3]);
        var num_junction_reads = parseInt(tokens[4]);
        var num_spanning_frags = parseInt(tokens[5]);

        var spanning_frag_coords_text = tokens[6];

        var feature = {
            chr: chr,
            name: fusion_name,
            junction_left: junction_left,
            junction_right: junction_right,
            num_junction_reads: num_junction_reads,
            num_spanning_frags: num_spanning_frags,
            spanning_frag_coords: [],

            start: -1,
            end: -1
        }; // set start and end later based on min/max of span coords

        var min_coord = junction_left;
        var max_coord = junction_right;

        if (num_spanning_frags > 0) {

            var coord_pairs = spanning_frag_coords_text.split(',');

            for (var i = 0; i < coord_pairs.length; i++) {
                var split_coords = coord_pairs[i].split('-');

                var span_left = split_coords[0];
                var span_right = split_coords[1];

                if (span_left < min_coord) {
                    min_coord = span_left;
                }
                if (span_right > max_coord) {
                    max_coord = span_right;
                }
                feature.spanning_frag_coords.push({left: span_left, right: span_right});

            }
        }

        feature.start = min_coord;
        feature.end = max_coord;

        feature.popupData = function () {
            return [{name: "Name", value: feature.name}];
        };

        return feature;

    }

    function decodeGtexGWAS(tokens, ignore) {

        var tokenCount, chr, start, end, strand, name, score, qValue, signal, pValue;

        tokenCount = tokens.length;
        if (tokenCount < 8) {
            return null;
        }

        chr = tokens[0];
        start = parseInt(tokens[1]) - 1;
        end = parseInt(tokens[1]) + 100;
        //name = tokens[3];
        //score = parseFloat(tokens[4]);
        //strand = tokens[5].trim();
        //signal = parseFloat(tokens[6]);
        //pValue = parseFloat(tokens[7]);
        //qValue = parseFloat(tokens[8]);

        //return {chr: chr, start: start, end: end, name: name, score: score, strand: strand, signal: signal,
        //    pValue: pValue, qValue: qValue};
        return {chr: chr, start: start, end: end};
    }

    /**
     * Decode a single gff record (1 line in file).  Aggregations such as gene models are constructed at a higher level.
     *      ctg123 . mRNA            1050  9000  .  +  .  ID=mRNA00001;Parent=gene00001
     * @param tokens
     * @param ignore
     * @returns {*}
     */
    function decodeGFF(tokens, ignore) {

        var tokenCount, chr, start, end, strand, type, score, phase, attributeString, id, parent;

        tokenCount = tokens.length;
        if (tokenCount < 9) {
            return null;      // Not a valid gff record
        }

        chr = tokens[0];
        type = tokens[2];
        start = parseInt(tokens[3]) - 1;
        end = parseInt(tokens[4]);
        score = "." === tokens[5] ? 0 : parseFloat(tokens[5]);
        strand = tokens[6];
        phase = "." === tokens[7] ? 0 : parseInt(tokens[7]);
        attributeString = tokens[8];

        // Find ID and Parent
        attributeString.split(';').forEach(function (kv) {
            var t = kv.split('=', 2);
            if(t.length == 2) {
                if("ID" === t[0]) id = t[1];
                else if("Parent"=== t[0]) parent = t[1];
            }

        });

        return {
            id: id,
            parent: parent,
            chr: chr,
            start: start,
            end: end,
            score: score,
            strand: strand,
            attributeString: attributeString,
            popupData: function () {
                var kvs = this.attributeString.split(';'),
                    pd = [];
                kvs.forEach(function (kv) {
                    var t = kv.split('=', 2);
                    if(t.length === 2)
                        pd.push({name: t[0], value: t[1]});
                });
                return pd;
            }

        };
    }

    return igv;
})
(igv || {});
<|endoftext|>