<commit_before>//
//  This file stolen from the Node.js source v0.8.16
//  We can't depend on having the basic Node.js module available
//  when we are running in the browser or ruby (eg, tilt-jade)
//

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

var isWindows = process.platform === 'win32';

// resolves . and .. elements in a path array with directory names there
// must be no slashes, empty elements, or device names (c:\) in the array
// (so also no leading and trailing slashes - it does not distinguish
// relative and absolute paths)
function normalizeArray(parts, allowAboveRoot) {
  // if the path tries to go above the root, `up` ends up > 0
  var up = 0;
  for (var i = parts.length - 1; i >= 0; i--) {
    var last = parts[i];
    if (last == '.') {
      parts.splice(i, 1);
    } else if (last === '..') {
      parts.splice(i, 1);
      up++;
    } else if (up) {
      parts.splice(i, 1);
      up--;
    }
  }

  // if the path is allowed to go above the root, restore leading ..s
  if (allowAboveRoot) {
    for (; up--; up) {
      parts.unshift('..');
    }
  }

  return parts;
}

if (isWindows) {
  // Regex to split a windows path into three parts: [*, device, slash,
  // tail] windows-only
  var splitDeviceRe =
      /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/][^\\\/]+)?([\\\/])?([\s\S]*?)$/;

  // Regex to split the tail part of the above into [*, dir, basename, ext]
  var splitTailRe =
      /^([\s\S]+[\\\/](?!$)|[\\\/])?((?:\.{1,2}$|[\s\S]+?)?(\.[^.\/\\]*)?)$/;

  // Function to split a filename into [root, dir, basename, ext]
  // windows version
  var splitPath = function(filename) {
    // Separate device+slash from tail
    var result = splitDeviceRe.exec(filename),
        device = (result[1] || '') + (result[2] || ''),
        tail = result[3] || '';
    // Split the tail into dir, basename and extension
    var result2 = splitTailRe.exec(tail),
        dir = result2[1] || '',
        basename = result2[2] || '',
        ext = result2[3] || '';
    return [device, dir, basename, ext];
  };

  // path.resolve([from ...], to)
  // windows version
  exports.resolve = function() {
    var resolvedDevice = '',
        resolvedTail = '',
        resolvedAbsolute = false;

    for (var i = arguments.length - 1; i >= -1; i--) {
      var path;
      if (i >= 0) {
        path = arguments[i];
      } else if (!resolvedDevice) {
        path = process.cwd();
      } else {
        // Windows has the concept of drive-specific current working
        // directories. If we've resolved a drive letter but not yet an
        // absolute path, get cwd for that drive. We're sure the device is not
        // an unc path at this points, because unc paths are always absolute.
        path = process.env['=' + resolvedDevice];
        // Verify that a drive-local cwd was found and that it actually points
        // to our drive. If not, default to the drive's root.
        if (!path || path.slice(0, 3).toLowerCase() !==
            resolvedDevice.toLowerCase() + '\\') {
          path = resolvedDevice + '\\';
        }
      }

      // Skip empty and invalid entries
      if (typeof path !== 'string' || !path) {
        continue;
      }

      var result = splitDeviceRe.exec(path),
          device = result[1] || '',
          isUnc = device && device.charAt(1) !== ':',
          isAbsolute = !!result[2] || isUnc, // UNC paths are always absolute
          tail = result[3];

      if (device &&
          resolvedDevice &&
          device.toLowerCase() !== resolvedDevice.toLowerCase()) {
        // This path points to another device so it is not applicable
        continue;
      }

      if (!resolvedDevice) {
        resolvedDevice = device;
      }
      if (!resolvedAbsolute) {
        resolvedTail = tail + '\\' + resolvedTail;
        resolvedAbsolute = isAbsolute;
      }

      if (resolvedDevice && resolvedAbsolute) {
        break;
      }
    }

    // Replace slashes (in UNC share name) by backslashes
    resolvedDevice = resolvedDevice.replace(/\//g, '\\');

    // At this point the path should be resolved to a full absolute path,
    // but handle relative paths to be safe (might happen when process.cwd()
    // fails)

    // Normalize the tail path

    function f(p) {
      return !!p;
    }

    resolvedTail = normalizeArray(resolvedTail.split(/[\\\/]+/).filter(f),
                                  !resolvedAbsolute).join('\\');

    return (resolvedDevice + (resolvedAbsolute ? '\\' : '') + resolvedTail) ||
           '.';
  };

  // windows version
  exports.normalize = function(path) {
    var result = splitDeviceRe.exec(path),
        device = result[1] || '',
        isUnc = device && device.charAt(1) !== ':',
        isAbsolute = !!result[2] || isUnc, // UNC paths are always absolute
        tail = result[3],
        trailingSlash = /[\\\/]$/.test(tail);

    // Normalize the tail path
    tail = normalizeArray(tail.split(/[\\\/]+/).filter(function(p) {
      return !!p;
    }), !isAbsolute).join('\\');

    if (!tail && !isAbsolute) {
      tail = '.';
    }
    if (tail && trailingSlash) {
      tail += '\\';
    }

    // Convert slashes to backslashes when `device` points to an UNC root.
    device = device.replace(/\//g, '\\');

    return device + (isAbsolute ? '\\' : '') + tail;
  };

  // windows version
  exports.join = function() {
    function f(p) {
      return p && typeof p === 'string';
    }

    var paths = Array.prototype.slice.call(arguments, 0).filter(f);
    var joined = paths.join('\\');

    // Make sure that the joined path doesn't start with two slashes
    // - it will be mistaken for an unc path by normalize() -
    // unless the paths[0] also starts with two slashes
    if (/^[\\\/]{2}/.test(joined) && !/^[\\\/]{2}/.test(paths[0])) {
      joined = joined.slice(1);
    }

    return exports.normalize(joined);
  };

  // path.relative(from, to)
  // it will solve the relative path from 'from' to 'to', for instance:
  // from = 'C:\\orandea\\test\\aaa'
  // to = 'C:\\orandea\\impl\\bbb'
  // The output of the function should be: '..\\..\\impl\\bbb'
  // windows version
  exports.relative = function(from, to) {
    from = exports.resolve(from);
    to = exports.resolve(to);

    // windows is not case sensitive
    var lowerFrom = from.toLowerCase();
    var lowerTo = to.toLowerCase();

    function trim(arr) {
      var start = 0;
      for (; start < arr.length; start++) {
        if (arr[start] !== '') break;
      }

      var end = arr.length - 1;
      for (; end >= 0; end--) {
        if (arr[end] !== '') break;
      }

      if (start > end) return [];
      return arr.slice(start, end - start + 1);
    }

    var toParts = trim(to.split('\\'));

    var lowerFromParts = trim(lowerFrom.split('\\'));
    var lowerToParts = trim(lowerTo.split('\\'));

    var length = Math.min(lowerFromParts.length, lowerToParts.length);
    var samePartsLength = length;
    for (var i = 0; i < length; i++) {
      if (lowerFromParts[i] !== lowerToParts[i]) {
        samePartsLength = i;
        break;
      }
    }

    if (samePartsLength == 0) {
      return to;
    }

    var outputParts = [];
    for (var i = samePartsLength; i < lowerFromParts.length; i++) {
      outputParts.push('..');
    }

    outputParts = outputParts.concat(toParts.slice(samePartsLength));

    return outputParts.join('\\');
  };

  exports.sep = '\\';

} else /* posix */ {

  // Split a filename into [root, dir, basename, ext], unix version
  // 'root' is just a slash, or nothing.
  var splitPathRe =
      /^(\/?)([\s\S]+\/(?!$)|\/)?((?:\.{1,2}$|[\s\S]+?)?(\.[^.\/]*)?)$/;
  var splitPath = function(filename) {
    var result = splitPathRe.exec(filename);
    return [result[1] || '', result[2] || '', result[3] || '', result[4] || ''];
  };

  // path.resolve([from ...], to)
  // posix version
  exports.resolve = function() {
    var resolvedPath = '',
        resolvedAbsolute = false;

    for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
      var path = (i >= 0) ? arguments[i] : process.cwd();

      // Skip empty and invalid entries
      if (typeof path !== 'string' || !path) {
        continue;
      }

      resolvedPath = path + '/' + resolvedPath;
      resolvedAbsolute = path.charAt(0) === '/';
    }

    // At this point the path should be resolved to a full absolute path, but
    // handle relative paths to be safe (might happen when process.cwd() fails)

    // Normalize the path
    resolvedPath = normalizeArray(resolvedPath.split('/').filter(function(p) {
      return !!p;
    }), !resolvedAbsolute).join('/');

    return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
  };

  // path.normalize(path)
  // posix version
  exports.normalize = function(path) {
    var isAbsolute = path.charAt(0) === '/',
        trailingSlash = path.slice(-1) === '/';

    // Normalize the path
    path = normalizeArray(path.split('/').filter(function(p) {
      return !!p;
    }), !isAbsolute).join('/');

    if (!path && !isAbsolute) {
      path = '.';
    }
    if (path && trailingSlash) {
      path += '/';
    }

    return (isAbsolute ? '/' : '') + path;
  };

  // posix version
  exports.join = function() {
    var paths = Array.prototype.slice.call(arguments, 0);
    return exports.normalize(paths.filter(function(p, index) {
      return p && typeof p === 'string';
    }).join('/'));
  };

  // path.relative(from, to)
  // posix version
  exports.relative = function(from, to) {
    from = exports.resolve(from).substr(1);
    to = exports.resolve(to).substr(1);

    function trim(arr) {
      var start = 0;
      for (; start < arr.length; start++) {
        if (arr[start] !== '') break;
      }

      var end = arr.length - 1;
      for (; end >= 0; end--) {
        if (arr[end] !== '') break;
      }

      if (start > end) return [];
      return arr.slice(start, end - start + 1);
    }

    var fromParts = trim(from.split('/'));
    var toParts = trim(to.split('/'));

    var length = Math.min(fromParts.length, toParts.length);
    var samePartsLength = length;
    for (var i = 0; i < length; i++) {
      if (fromParts[i] !== toParts[i]) {
        samePartsLength = i;
        break;
      }
    }

    var outputParts = [];
    for (var i = samePartsLength; i < fromParts.length; i++) {
      outputParts.push('..');
    }

    outputParts = outputParts.concat(toParts.slice(samePartsLength));

    return outputParts.join('/');
  };

  exports.sep = '/';
}

exports.dirname = function(path) {
  var result = splitPath(path),
      root = result[0],
      dir = result[1];

  if (!root && !dir) {
    // No dirname whatsoever
    return '.';
  }

  if (dir) {
    // It has a dirname, strip trailing slash
    dir = dir.substring(0, dir.length - 1);
  }

  return root + dir;
};

exports.basename = function(path, ext) {
  var f = splitPath(path)[2];
  // TODO: make this comparison case-insensitive on windows?
  if (ext && f.substr(-1 * ext.length) === ext) {
    f = f.substr(0, f.length - ext.length);
  }
  return f;
};

exports.extname = function(path) {
  return splitPath(path)[3];
};

if (isWindows) {
  exports._makeLong = function(path) {
    path = '' + path;
    if (!path) {
      return '';
    }

    var resolvedPath = exports.resolve(path);

    if (resolvedPath.match(/^[a-zA-Z]\:\\/)) {
      // path is local filesystem path, which needs to be converted
      // to long UNC path.
      return '\\\\?\\' + resolvedPath;
    } else if (resolvedPath.match(/^\\\\[^?.]/)) {
      // path is network UNC path, which needs to be converted
      // to long UNC path.
      return '\\\\?\\UNC\\' + resolvedPath.substring(2);
    }

    return path;
  };
} else {
  exports._makeLong = function(path) {
    return path;
  };
}
<commit_msg>add hack for the 'process undefined' pain<commit_after>//
//  This file stolen from the Node.js source v0.8.16
//  We can't depend on having the basic Node.js module available
//  when we are running in the browser or ruby (eg, tilt-jade)
//

// Total Hack.  Sometimes process doesn't exist when running under Rails ExecJs. Then again, sometimes it does
if (typeof process == 'undefined') {
  process = {
    platform: 'notWindows',
    cwd: function () { return '/'; },
    env: {}
  };
}

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

var isWindows = process.platform === 'win32';

// resolves . and .. elements in a path array with directory names there
// must be no slashes, empty elements, or device names (c:\) in the array
// (so also no leading and trailing slashes - it does not distinguish
// relative and absolute paths)
function normalizeArray(parts, allowAboveRoot) {
  // if the path tries to go above the root, `up` ends up > 0
  var up = 0;
  for (var i = parts.length - 1; i >= 0; i--) {
    var last = parts[i];
    if (last == '.') {
      parts.splice(i, 1);
    } else if (last === '..') {
      parts.splice(i, 1);
      up++;
    } else if (up) {
      parts.splice(i, 1);
      up--;
    }
  }

  // if the path is allowed to go above the root, restore leading ..s
  if (allowAboveRoot) {
    for (; up--; up) {
      parts.unshift('..');
    }
  }

  return parts;
}

if (isWindows) {
  // Regex to split a windows path into three parts: [*, device, slash,
  // tail] windows-only
  var splitDeviceRe =
      /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/][^\\\/]+)?([\\\/])?([\s\S]*?)$/;

  // Regex to split the tail part of the above into [*, dir, basename, ext]
  var splitTailRe =
      /^([\s\S]+[\\\/](?!$)|[\\\/])?((?:\.{1,2}$|[\s\S]+?)?(\.[^.\/\\]*)?)$/;

  // Function to split a filename into [root, dir, basename, ext]
  // windows version
  var splitPath = function(filename) {
    // Separate device+slash from tail
    var result = splitDeviceRe.exec(filename),
        device = (result[1] || '') + (result[2] || ''),
        tail = result[3] || '';
    // Split the tail into dir, basename and extension
    var result2 = splitTailRe.exec(tail),
        dir = result2[1] || '',
        basename = result2[2] || '',
        ext = result2[3] || '';
    return [device, dir, basename, ext];
  };

  // path.resolve([from ...], to)
  // windows version
  exports.resolve = function() {
    var resolvedDevice = '',
        resolvedTail = '',
        resolvedAbsolute = false;

    for (var i = arguments.length - 1; i >= -1; i--) {
      var path;
      if (i >= 0) {
        path = arguments[i];
      } else if (!resolvedDevice) {
        path = process.cwd();
      } else {
        // Windows has the concept of drive-specific current working
        // directories. If we've resolved a drive letter but not yet an
        // absolute path, get cwd for that drive. We're sure the device is not
        // an unc path at this points, because unc paths are always absolute.
        path = process.env['=' + resolvedDevice];
        // Verify that a drive-local cwd was found and that it actually points
        // to our drive. If not, default to the drive's root.
        if (!path || path.slice(0, 3).toLowerCase() !==
            resolvedDevice.toLowerCase() + '\\') {
          path = resolvedDevice + '\\';
        }
      }

      // Skip empty and invalid entries
      if (typeof path !== 'string' || !path) {
        continue;
      }

      var result = splitDeviceRe.exec(path),
          device = result[1] || '',
          isUnc = device && device.charAt(1) !== ':',
          isAbsolute = !!result[2] || isUnc, // UNC paths are always absolute
          tail = result[3];

      if (device &&
          resolvedDevice &&
          device.toLowerCase() !== resolvedDevice.toLowerCase()) {
        // This path points to another device so it is not applicable
        continue;
      }

      if (!resolvedDevice) {
        resolvedDevice = device;
      }
      if (!resolvedAbsolute) {
        resolvedTail = tail + '\\' + resolvedTail;
        resolvedAbsolute = isAbsolute;
      }

      if (resolvedDevice && resolvedAbsolute) {
        break;
      }
    }

    // Replace slashes (in UNC share name) by backslashes
    resolvedDevice = resolvedDevice.replace(/\//g, '\\');

    // At this point the path should be resolved to a full absolute path,
    // but handle relative paths to be safe (might happen when process.cwd()
    // fails)

    // Normalize the tail path

    function f(p) {
      return !!p;
    }

    resolvedTail = normalizeArray(resolvedTail.split(/[\\\/]+/).filter(f),
                                  !resolvedAbsolute).join('\\');

    return (resolvedDevice + (resolvedAbsolute ? '\\' : '') + resolvedTail) ||
           '.';
  };

  // windows version
  exports.normalize = function(path) {
    var result = splitDeviceRe.exec(path),
        device = result[1] || '',
        isUnc = device && device.charAt(1) !== ':',
        isAbsolute = !!result[2] || isUnc, // UNC paths are always absolute
        tail = result[3],
        trailingSlash = /[\\\/]$/.test(tail);

    // Normalize the tail path
    tail = normalizeArray(tail.split(/[\\\/]+/).filter(function(p) {
      return !!p;
    }), !isAbsolute).join('\\');

    if (!tail && !isAbsolute) {
      tail = '.';
    }
    if (tail && trailingSlash) {
      tail += '\\';
    }

    // Convert slashes to backslashes when `device` points to an UNC root.
    device = device.replace(/\//g, '\\');

    return device + (isAbsolute ? '\\' : '') + tail;
  };

  // windows version
  exports.join = function() {
    function f(p) {
      return p && typeof p === 'string';
    }

    var paths = Array.prototype.slice.call(arguments, 0).filter(f);
    var joined = paths.join('\\');

    // Make sure that the joined path doesn't start with two slashes
    // - it will be mistaken for an unc path by normalize() -
    // unless the paths[0] also starts with two slashes
    if (/^[\\\/]{2}/.test(joined) && !/^[\\\/]{2}/.test(paths[0])) {
      joined = joined.slice(1);
    }

    return exports.normalize(joined);
  };

  // path.relative(from, to)
  // it will solve the relative path from 'from' to 'to', for instance:
  // from = 'C:\\orandea\\test\\aaa'
  // to = 'C:\\orandea\\impl\\bbb'
  // The output of the function should be: '..\\..\\impl\\bbb'
  // windows version
  exports.relative = function(from, to) {
    from = exports.resolve(from);
    to = exports.resolve(to);

    // windows is not case sensitive
    var lowerFrom = from.toLowerCase();
    var lowerTo = to.toLowerCase();

    function trim(arr) {
      var start = 0;
      for (; start < arr.length; start++) {
        if (arr[start] !== '') break;
      }

      var end = arr.length - 1;
      for (; end >= 0; end--) {
        if (arr[end] !== '') break;
      }

      if (start > end) return [];
      return arr.slice(start, end - start + 1);
    }

    var toParts = trim(to.split('\\'));

    var lowerFromParts = trim(lowerFrom.split('\\'));
    var lowerToParts = trim(lowerTo.split('\\'));

    var length = Math.min(lowerFromParts.length, lowerToParts.length);
    var samePartsLength = length;
    for (var i = 0; i < length; i++) {
      if (lowerFromParts[i] !== lowerToParts[i]) {
        samePartsLength = i;
        break;
      }
    }

    if (samePartsLength == 0) {
      return to;
    }

    var outputParts = [];
    for (var i = samePartsLength; i < lowerFromParts.length; i++) {
      outputParts.push('..');
    }

    outputParts = outputParts.concat(toParts.slice(samePartsLength));

    return outputParts.join('\\');
  };

  exports.sep = '\\';

} else /* posix */ {

  // Split a filename into [root, dir, basename, ext], unix version
  // 'root' is just a slash, or nothing.
  var splitPathRe =
      /^(\/?)([\s\S]+\/(?!$)|\/)?((?:\.{1,2}$|[\s\S]+?)?(\.[^.\/]*)?)$/;
  var splitPath = function(filename) {
    var result = splitPathRe.exec(filename);
    return [result[1] || '', result[2] || '', result[3] || '', result[4] || ''];
  };

  // path.resolve([from ...], to)
  // posix version
  exports.resolve = function() {
    var resolvedPath = '',
        resolvedAbsolute = false;

    for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
      var path = (i >= 0) ? arguments[i] : process.cwd();

      // Skip empty and invalid entries
      if (typeof path !== 'string' || !path) {
        continue;
      }

      resolvedPath = path + '/' + resolvedPath;
      resolvedAbsolute = path.charAt(0) === '/';
    }

    // At this point the path should be resolved to a full absolute path, but
    // handle relative paths to be safe (might happen when process.cwd() fails)

    // Normalize the path
    resolvedPath = normalizeArray(resolvedPath.split('/').filter(function(p) {
      return !!p;
    }), !resolvedAbsolute).join('/');

    return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
  };

  // path.normalize(path)
  // posix version
  exports.normalize = function(path) {
    var isAbsolute = path.charAt(0) === '/',
        trailingSlash = path.slice(-1) === '/';

    // Normalize the path
    path = normalizeArray(path.split('/').filter(function(p) {
      return !!p;
    }), !isAbsolute).join('/');

    if (!path && !isAbsolute) {
      path = '.';
    }
    if (path && trailingSlash) {
      path += '/';
    }

    return (isAbsolute ? '/' : '') + path;
  };

  // posix version
  exports.join = function() {
    var paths = Array.prototype.slice.call(arguments, 0);
    return exports.normalize(paths.filter(function(p, index) {
      return p && typeof p === 'string';
    }).join('/'));
  };

  // path.relative(from, to)
  // posix version
  exports.relative = function(from, to) {
    from = exports.resolve(from).substr(1);
    to = exports.resolve(to).substr(1);

    function trim(arr) {
      var start = 0;
      for (; start < arr.length; start++) {
        if (arr[start] !== '') break;
      }

      var end = arr.length - 1;
      for (; end >= 0; end--) {
        if (arr[end] !== '') break;
      }

      if (start > end) return [];
      return arr.slice(start, end - start + 1);
    }

    var fromParts = trim(from.split('/'));
    var toParts = trim(to.split('/'));

    var length = Math.min(fromParts.length, toParts.length);
    var samePartsLength = length;
    for (var i = 0; i < length; i++) {
      if (fromParts[i] !== toParts[i]) {
        samePartsLength = i;
        break;
      }
    }

    var outputParts = [];
    for (var i = samePartsLength; i < fromParts.length; i++) {
      outputParts.push('..');
    }

    outputParts = outputParts.concat(toParts.slice(samePartsLength));

    return outputParts.join('/');
  };

  exports.sep = '/';
}

exports.dirname = function(path) {
  var result = splitPath(path),
      root = result[0],
      dir = result[1];

  if (!root && !dir) {
    // No dirname whatsoever
    return '.';
  }

  if (dir) {
    // It has a dirname, strip trailing slash
    dir = dir.substring(0, dir.length - 1);
  }

  return root + dir;
};

exports.basename = function(path, ext) {
  var f = splitPath(path)[2];
  // TODO: make this comparison case-insensitive on windows?
  if (ext && f.substr(-1 * ext.length) === ext) {
    f = f.substr(0, f.length - ext.length);
  }
  return f;
};

exports.extname = function(path) {
  return splitPath(path)[3];
};

if (isWindows) {
  exports._makeLong = function(path) {
    path = '' + path;
    if (!path) {
      return '';
    }

    var resolvedPath = exports.resolve(path);

    if (resolvedPath.match(/^[a-zA-Z]\:\\/)) {
      // path is local filesystem path, which needs to be converted
      // to long UNC path.
      return '\\\\?\\' + resolvedPath;
    } else if (resolvedPath.match(/^\\\\[^?.]/)) {
      // path is network UNC path, which needs to be converted
      // to long UNC path.
      return '\\\\?\\UNC\\' + resolvedPath.substring(2);
    }

    return path;
  };
} else {
  exports._makeLong = function(path) {
    return path;
  };
}
<|endoftext|>


<commit_before>'use strict';

var util = require('util');

var templateLoader = require('../util/template-loader');
var dot = require('dot');
dot.templateSettings.strip = false;

var id = require('../util/id');
var QueryBuilder = require('../filter/query-builder');
var Validator = require('./validator');

var TYPE = require('./type');
module.exports.TYPE = TYPE;
var GEOMETRY = require('./geometry');
module.exports.GEOMETRY = GEOMETRY;

var STATUS = {
    // transitional status
    PENDING: 'pending',
    WAITING: 'waiting',
    RUNNING: 'running',

    // final status
    FAILED: 'failed',
    READY: 'ready'
};

module.exports.STATUS = STATUS;

var NODE_RESERVED_KEYWORDS = {
    type: 1,
    typeSignature: 1,
    params: 1,
    status: 1,
    errorMessage: 1,
    filters: 1,
    owner: 1,
    schema: 1,
    columns: 1,
    cacheQuery: 1,
    cacheQueryTimeout: 1,
    queuedWork: 1,
    lazy: 1,
    updatedAt: 1,
    validateAndBuild: 1,
    json: 1,
    attributesForId: 1,
    version: 1,
    nodes: 1,
    inputNodes: 1,
    attrs: 1,
    tags: 1,
    setStatusFromInputNodes: 1
};

function Node() {
    // args will look like ['owner', {param1: '', param2: ''}, {param1: '', param3: ''}, ...]
    var args = (arguments.length === 1 ? [arguments[0]] : Array.apply(null, arguments));
    this.owner = args.shift();

    this.params = args.reduce(function(reducedParams, arg) {
        Object.keys(arg).forEach(function(paramName) {
            reducedParams[paramName] = arg[paramName];
        });
        return reducedParams;
    }, {});

    this.status = STATUS.PENDING;
    this.errorMessage = null;
    this.queuedWork = false;
    this.cacheQueryTimeout = 0;

    this.filters = this.params.filters || {};

    this.schema = {};
    this.columns = [];

    this.nodes = {};
    this.inputNodes = [];

    this.updatedAt = null;

    this.attrs = {};

    this.json = {};
    this.attributesForId = [];

    this.typeSignature = null;

    this.version = 0;
}

Node.prototype.id = function(skipFilters) {
    var attrsToSkip = {};
    if (skipFilters || false) {
        attrsToSkip.filters = true;
    }

    var json = Object.keys(this.json).reduce(function(json, paramName) {
        if (!attrsToSkip.hasOwnProperty(paramName)) {
            json[paramName] = this.json[paramName];
        }
        return json;
    }.bind(this), {});

    return id({
        // Internal version.
        // Bump this for backwards incompatible changes.
        __version__: 1,
        owner: this.owner,
        json: json,
        version: this.version,
        attrs: this.attributesForId.map(function(attr) {
            return this.hasOwnProperty(attr) ? this[attr] : attr;
        }.bind(this))
    });
};

Node.prototype.cachedNodeId = function() {
    return this.shouldCacheQuery() ? this.id(true) : this.id();
};

Node.prototype.setAttributeToModifyId = function(attr) {
    this.attributesForId.push(attr);
};

Node.prototype.getQuery = function(applyFilters) {
    var query = this.sql();
    if (this.shouldCacheQuery()) {
        query = 'select * from ' + this.getTargetTable();
    }
    if (applyFilters === false) {
        applyFilters = Object.keys(this.filters).reduce(function(skipAllFilters, filterName) {
            skipAllFilters[filterName] = false;
            return skipAllFilters;
        }, {});
    }
    return QueryBuilder.getSql(query, this.filters, applyFilters, this.schema);
};

Node.prototype.getColumns = function(skipTheGeoms) {
    skipTheGeoms = skipTheGeoms || false;
    return (skipTheGeoms) ? skipColumns(this.columns) : this.columns;
};

Node.prototype.getFilters = function() {
    return this.filters;
};

Node.prototype.getOwner = function() {
    return this.owner;
};

Node.prototype.getType = function() {
    return this.type;
};

Node.prototype.getTypeSignature = function() {
    if (this.typeSignature === null) {
        this.typeSignature = id(this.getType()).substring(0, 10);
    }
    return this.typeSignature;
};

var SKIP_COLUMNS = {
    'the_geom': true,
    'the_geom_webmercator': true
};

function skipColumns(columnNames) {
    return columnNames
        .filter(function(columnName) { return !SKIP_COLUMNS[columnName]; });
}

Node.prototype.setColumns = function(columns) {
    this.schema = columns.reduce(function(schema, column) {
        schema[column.name] = column.type;
        return schema;
    }, {});
    this.setColumnsNames(columns.map(function(column) {
        return column.name;
    }));
};

Node.prototype.setColumnsNames = function(columnNames) {
    this.columns = columnNames;
};

Node.prototype.getStatus = function() {
    return this.status;
};

var VALID_STATUS = Object.keys(STATUS).reduce(function(valid, key) {
    valid[STATUS[key]] = true;
    return valid;
}, {});

Node.prototype.setStatus = function(status) {
    if (!VALID_STATUS.hasOwnProperty(status)) {
        return false;
    }
    this.status = status;
};

Node.prototype.getErrorMessage = function() {
    return this.errorMessage;
};

Node.prototype.setErrorMessage = function(errorMessage) {
    this.errorMessage = errorMessage;
};

Node.prototype.getUpdatedAt = function() {
    return this.updatedAt;
};

Node.prototype.setUpdatedAt = function(updatedAt) {
    this.updatedAt = (updatedAt === null) ? null : new Date(updatedAt);
};

Node.prototype.getLastUpdatedAtFromInputNodes = function() {
    if (this.getInputNodes().length === 0) {
        return this.getUpdatedAt();
    }

    return this.getInputNodes().reduce(function(lastUpdatedAt, inputNode) {
        var inputNodeUpdatedAt = inputNode.getLastUpdatedAtFromInputNodes();
        if (lastUpdatedAt === null) {
            lastUpdatedAt = inputNodeUpdatedAt;
        }

        if (lastUpdatedAt !== null &&
            inputNodeUpdatedAt !== null &&
            inputNodeUpdatedAt.getTime() > lastUpdatedAt.getTime()) {

            lastUpdatedAt = inputNodeUpdatedAt;
        }

        return lastUpdatedAt;
    }, null);
};

Node.prototype.isOutdated = function() {
    var lastUpdatedAtFromInputNodes = this.getLastUpdatedAtFromInputNodes();
    var updatedAt = this.getUpdatedAt();
    return updatedAt === null || lastUpdatedAtFromInputNodes === null ||
        (lastUpdatedAtFromInputNodes.getTime() > updatedAt.getTime());
};

function shouldBeInWaitingStatus(inputNodeStatus) {
    return inputNodeStatus === STATUS.PENDING || inputNodeStatus === STATUS.RUNNING;
}
Node.prototype.setStatusFromInputNodes = function() {
    var shouldCacheNode = this.shouldCacheQuery();
    this.status = this.inputNodes.reduce(function(currentNodeStatus, node) {
        if (currentNodeStatus === STATUS.FAILED || currentNodeStatus === STATUS.WAITING) {
            return currentNodeStatus;
        }

        var inputNodeStatus = node.getStatus();

        if (inputNodeStatus === STATUS.FAILED) {
            return STATUS.FAILED;
        }

        if (shouldBeInWaitingStatus(inputNodeStatus)) {
            return STATUS.WAITING;
        }

        return shouldCacheNode ? STATUS.PENDING : STATUS.READY;
    }, STATUS.PENDING);
};

Node.prototype.getInputNodes = function() {
    return this.inputNodes;
};

Node.prototype.shouldCacheQuery = function() {
    return this.cacheQuery;
};

Node.prototype.setQueuedWork = function(queuedWork) {
    this.queuedWork = queuedWork;
};

Node.prototype.didQueueWork = function() {
    return this.queuedWork;
};

Node.prototype.getCacheQueryTimeout = function() {
    return this.cacheQueryTimeout;
};

Node.prototype.setCacheQueryTimeout = function(cacheQueryTimeout) {
    this.cacheQueryTimeout = cacheQueryTimeout;
};

Node.prototype.getTags = function() {
    return this.tags;
};

Node.prototype.ignoreParamForId = function(paramName) {
    delete this.json[paramName];
};

Node.prototype.getTargetTable = function() {
    // Keep name at 60 chars
    // Ref.: https://www.postgresql.org/docs/9.5/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
    return 'analysis_' + this.getTypeSignature() + '_' + this.cachedNodeId();
};

Node.prototype.toJSON = function() {
    return this.json;
};

Node.prototype.toDot = function() {
    return {
        type: this.type,
        nodes: this.nodes,
        attrs: this.attrs
    };
};

module.exports.PARAM = {
    NODE: function() {
        var args = (arguments.length === 1 ? [arguments[0]] : Array.apply(null, arguments));
        return new Validator.Node(args);
    },
    NUMBER: function() {
        return new Validator.Generic(TYPE.NUMBER, Number.isFinite);
    },
    STRING: function() {
        return new Validator.Generic(TYPE.STRING, function (str) {
            return typeof str === 'string';
        });
    },
    BOOLEAN: function() {
        return new Validator.Generic(TYPE.BOOLEAN, function (bool) {
            return typeof bool === 'boolean';
        });
    },
    ENUM: function() {
        var args = (arguments.length === 1 ? [arguments[0]] : Array.apply(null, arguments));
        return new Validator.Enum(args);
    },
    ARRAY: function (validator) {
        if (validator) {
            return new Validator.Generic(TYPE.ARRAY, function(arr) {
                return Array.isArray(arr) && arr.every(validator.isValid);
            }, false, TYPE.ARRAY + '<' + validator.type + '>');
        }
        return new Validator.Generic(TYPE.ARRAY, Array.isArray);
    },
    NULLABLE: function (validator, defaultValue) {
        defaultValue = typeof defaultValue !== 'undefined' ? defaultValue : validator.getDefaultValue();
        return new Validator.Generic(validator.type, function(param) {
            return (param === null || typeof param === 'undefined') || validator.isValid(param);
        }, true, validator.expects, defaultValue, validator.toJSON.bind(validator));
    }
};

module.exports.getChildNodesNames = function(NodeClass) {
    return Object.keys(NodeClass.PARAMS).reduce(function(childNodesNames, paramName) {
        if (NodeClass.PARAMS[paramName].type === TYPE.NODE) {
            childNodesNames.push(paramName);
        }
        return childNodesNames;
    }, []);
};

module.exports.getSqlTemplateFn = function(templateName) {
    return templateLoader.getTemplateFn(__dirname + '/nodes/sql/' + templateName + '.sql');
};

module.exports.template = function(tmpl, c, def) {
    return dot.template(tmpl, c, def);
};

module.exports.create = function createNode(type, expectedParams, options) {
    Object.keys(expectedParams).forEach(function(expectedParamName) {
        if (NODE_RESERVED_KEYWORDS.hasOwnProperty(expectedParamName)) {
            throw new Error('Invalid param name "' + expectedParamName + '". It is a reserved keyword.');
        }
    });

    options = options || {};
    options.cache = options.cache || false;
    options.lazy = options.lazy || false;
    if (!Number.isFinite(options.version)) {
        options.version = 0;
    }
    function BaseNode() {
        Node.apply(this, arguments);
        this.type = type;
        this.cacheQuery = options.cache || false;
        this.lazy = options.lazy || false;
        this.tags = options.tags || [];
        if (options.version > 0) {
            this.version = options.version;
        }
        this.validateAndBuild();
        // `beforeCreate` allows to change/modify the node before it's actually created/returned
        // if it throws an error it will fail to create the node.
        if (options.beforeCreate) {
            options.beforeCreate(this);
        }
    }
    util.inherits(BaseNode, Node);
    BaseNode.prototype.validateAndBuild = function() {
        this.json.type = this.type;
        Object.keys(expectedParams).forEach(function(expectedParamName) {
            var validator = expectedParams[expectedParamName];
            var param = validate(validator, this.params, expectedParamName);
            var value = (param === null || typeof param === 'undefined') ? validator.getDefaultValue() : param;

            this[expectedParamName] = value;
            if (validator.type === TYPE.NODE) {
                var parentNode = param;
                this.nodes[expectedParamName] = parentNode;
                this.inputNodes.push(parentNode);
                var skipFilters = this.lazy;
                this.json[expectedParamName] = parentNode.id(skipFilters);

                if (this.lazy) {
                    this.filters = this.filters || {};
                    var parentNodeFilters = parentNode.getFilters();
                    Object.keys(parentNodeFilters).forEach(function(filterName) {
                        this.filters['__p__' + filterName] = parentNodeFilters[filterName];
                    }.bind(this));
                }
            } else {
                this.attrs[expectedParamName] = value;
                this.json[expectedParamName] = value;
            }
        }.bind(this));
        if (Object.keys(this.filters).length > 0) {
            this.json.filters = this.filters;
        }

        this.setStatusFromInputNodes();
    };
    BaseNode.TYPE = type;
    BaseNode.PARAMS = expectedParams;
    BaseNode.OPTIONS = {
        version: options.version,
        cache: options.cache,
        lazy: options.lazy
    };
    return BaseNode;
};

function validate(validator, params, expectedParamName) {
    if (!validator.nullable && !params.hasOwnProperty(expectedParamName)) {
        throw new Error('Missing required param "' + expectedParamName + '"');
    }

    var param = params[expectedParamName];

    if (!validator.isValid(param)) {
        throw new Error(
            'Invalid type for param "' + expectedParamName + '",' +
            ' expects "' + validator.expects + '" type,' +
            ' got `' + JSON.stringify(param) + '`'
        );
    }

    return param;
}

Node.prototype.computeRequirements = function(databaseService, limits, callback) {
    // By default simply compute maximum of the inputs' number of rows.
    // TODO: if the most common multi-input analysis is some kind of join we should use
    // the product of the input numberOfRows instead
    var self = this;
    var maxRows = Math.max.apply(
        null,
        this.inputNodes.map(function(node) {
          return node.estimatedRequirements.numberOfRows || 0 })
    );
    if (maxRows < 0) {
        maxRows = 0;
    }
    this.estimatedRequirements = {
        numberOfRows: maxRows
    };
    this.limits = {
        maximumNumberOfRows: getNodeLimit(limits, this.getType(), 'maximumNumberOfRows', 1000000)
    };
    return callback(null, this.requirementMessages());
};

Node.prototype.requirementMessages = function() {
    var messages = [];
    if (this.estimatedRequirements.numberOfRows > this.limits.maximumNumberOfRows) {
        messages.push('too many result rows');
    }
    return messages;
};

Node.prototype.validateRequirements = function(callback) {
    var messages = this.requirementMessages();
    var err;
    if (messages.length > 0) {
        this.status = STATUS.FAILED;
        this.errorMessage = messages.join('\n');
        err = new Error(this.errorMessage);
    }
    callback(err);
};

function getNodeLimit(globalLimits, nodeType, limitName, defaultValue) {
    var limit = (globalLimits[nodeType] == undefined) ?
                globalLimits[limitName] :
                globalLimits[nodeType][limitName];
    return limit || defaultValue;
};

module.exports.getNodeLimit = getNodeLimit;
<commit_msg>:lipstick:<commit_after>'use strict';

var util = require('util');

var templateLoader = require('../util/template-loader');
var dot = require('dot');
dot.templateSettings.strip = false;

var id = require('../util/id');
var QueryBuilder = require('../filter/query-builder');
var Validator = require('./validator');

var TYPE = require('./type');
module.exports.TYPE = TYPE;
var GEOMETRY = require('./geometry');
module.exports.GEOMETRY = GEOMETRY;

var STATUS = {
    // transitional status
    PENDING: 'pending',
    WAITING: 'waiting',
    RUNNING: 'running',

    // final status
    FAILED: 'failed',
    READY: 'ready'
};

module.exports.STATUS = STATUS;

var NODE_RESERVED_KEYWORDS = {
    type: 1,
    typeSignature: 1,
    params: 1,
    status: 1,
    errorMessage: 1,
    filters: 1,
    owner: 1,
    schema: 1,
    columns: 1,
    cacheQuery: 1,
    cacheQueryTimeout: 1,
    queuedWork: 1,
    lazy: 1,
    updatedAt: 1,
    validateAndBuild: 1,
    json: 1,
    attributesForId: 1,
    version: 1,
    nodes: 1,
    inputNodes: 1,
    attrs: 1,
    tags: 1,
    setStatusFromInputNodes: 1
};

function Node() {
    // args will look like ['owner', {param1: '', param2: ''}, {param1: '', param3: ''}, ...]
    var args = (arguments.length === 1 ? [arguments[0]] : Array.apply(null, arguments));
    this.owner = args.shift();

    this.params = args.reduce(function(reducedParams, arg) {
        Object.keys(arg).forEach(function(paramName) {
            reducedParams[paramName] = arg[paramName];
        });
        return reducedParams;
    }, {});

    this.status = STATUS.PENDING;
    this.errorMessage = null;
    this.queuedWork = false;
    this.cacheQueryTimeout = 0;

    this.filters = this.params.filters || {};

    this.schema = {};
    this.columns = [];

    this.nodes = {};
    this.inputNodes = [];

    this.updatedAt = null;

    this.attrs = {};

    this.json = {};
    this.attributesForId = [];

    this.typeSignature = null;

    this.version = 0;
}

Node.prototype.id = function(skipFilters) {
    var attrsToSkip = {};
    if (skipFilters || false) {
        attrsToSkip.filters = true;
    }

    var json = Object.keys(this.json).reduce(function(json, paramName) {
        if (!attrsToSkip.hasOwnProperty(paramName)) {
            json[paramName] = this.json[paramName];
        }
        return json;
    }.bind(this), {});

    return id({
        // Internal version.
        // Bump this for backwards incompatible changes.
        __version__: 1,
        owner: this.owner,
        json: json,
        version: this.version,
        attrs: this.attributesForId.map(function(attr) {
            return this.hasOwnProperty(attr) ? this[attr] : attr;
        }.bind(this))
    });
};

Node.prototype.cachedNodeId = function() {
    return this.shouldCacheQuery() ? this.id(true) : this.id();
};

Node.prototype.setAttributeToModifyId = function(attr) {
    this.attributesForId.push(attr);
};

Node.prototype.getQuery = function(applyFilters) {
    var query = this.sql();
    if (this.shouldCacheQuery()) {
        query = 'select * from ' + this.getTargetTable();
    }
    if (applyFilters === false) {
        applyFilters = Object.keys(this.filters).reduce(function(skipAllFilters, filterName) {
            skipAllFilters[filterName] = false;
            return skipAllFilters;
        }, {});
    }
    return QueryBuilder.getSql(query, this.filters, applyFilters, this.schema);
};

Node.prototype.getColumns = function(skipTheGeoms) {
    skipTheGeoms = skipTheGeoms || false;
    return (skipTheGeoms) ? skipColumns(this.columns) : this.columns;
};

Node.prototype.getFilters = function() {
    return this.filters;
};

Node.prototype.getOwner = function() {
    return this.owner;
};

Node.prototype.getType = function() {
    return this.type;
};

Node.prototype.getTypeSignature = function() {
    if (this.typeSignature === null) {
        this.typeSignature = id(this.getType()).substring(0, 10);
    }
    return this.typeSignature;
};

var SKIP_COLUMNS = {
    'the_geom': true,
    'the_geom_webmercator': true
};

function skipColumns(columnNames) {
    return columnNames
        .filter(function(columnName) { return !SKIP_COLUMNS[columnName]; });
}

Node.prototype.setColumns = function(columns) {
    this.schema = columns.reduce(function(schema, column) {
        schema[column.name] = column.type;
        return schema;
    }, {});
    this.setColumnsNames(columns.map(function(column) {
        return column.name;
    }));
};

Node.prototype.setColumnsNames = function(columnNames) {
    this.columns = columnNames;
};

Node.prototype.getStatus = function() {
    return this.status;
};

var VALID_STATUS = Object.keys(STATUS).reduce(function(valid, key) {
    valid[STATUS[key]] = true;
    return valid;
}, {});

Node.prototype.setStatus = function(status) {
    if (!VALID_STATUS.hasOwnProperty(status)) {
        return false;
    }
    this.status = status;
};

Node.prototype.getErrorMessage = function() {
    return this.errorMessage;
};

Node.prototype.setErrorMessage = function(errorMessage) {
    this.errorMessage = errorMessage;
};

Node.prototype.getUpdatedAt = function() {
    return this.updatedAt;
};

Node.prototype.setUpdatedAt = function(updatedAt) {
    this.updatedAt = (updatedAt === null) ? null : new Date(updatedAt);
};

Node.prototype.getLastUpdatedAtFromInputNodes = function() {
    if (this.getInputNodes().length === 0) {
        return this.getUpdatedAt();
    }

    return this.getInputNodes().reduce(function(lastUpdatedAt, inputNode) {
        var inputNodeUpdatedAt = inputNode.getLastUpdatedAtFromInputNodes();
        if (lastUpdatedAt === null) {
            lastUpdatedAt = inputNodeUpdatedAt;
        }

        if (lastUpdatedAt !== null &&
            inputNodeUpdatedAt !== null &&
            inputNodeUpdatedAt.getTime() > lastUpdatedAt.getTime()) {

            lastUpdatedAt = inputNodeUpdatedAt;
        }

        return lastUpdatedAt;
    }, null);
};

Node.prototype.isOutdated = function() {
    var lastUpdatedAtFromInputNodes = this.getLastUpdatedAtFromInputNodes();
    var updatedAt = this.getUpdatedAt();
    return updatedAt === null || lastUpdatedAtFromInputNodes === null ||
        (lastUpdatedAtFromInputNodes.getTime() > updatedAt.getTime());
};

function shouldBeInWaitingStatus(inputNodeStatus) {
    return inputNodeStatus === STATUS.PENDING || inputNodeStatus === STATUS.RUNNING;
}
Node.prototype.setStatusFromInputNodes = function() {
    var shouldCacheNode = this.shouldCacheQuery();
    this.status = this.inputNodes.reduce(function(currentNodeStatus, node) {
        if (currentNodeStatus === STATUS.FAILED || currentNodeStatus === STATUS.WAITING) {
            return currentNodeStatus;
        }

        var inputNodeStatus = node.getStatus();

        if (inputNodeStatus === STATUS.FAILED) {
            return STATUS.FAILED;
        }

        if (shouldBeInWaitingStatus(inputNodeStatus)) {
            return STATUS.WAITING;
        }

        return shouldCacheNode ? STATUS.PENDING : STATUS.READY;
    }, STATUS.PENDING);
};

Node.prototype.getInputNodes = function() {
    return this.inputNodes;
};

Node.prototype.shouldCacheQuery = function() {
    return this.cacheQuery;
};

Node.prototype.setQueuedWork = function(queuedWork) {
    this.queuedWork = queuedWork;
};

Node.prototype.didQueueWork = function() {
    return this.queuedWork;
};

Node.prototype.getCacheQueryTimeout = function() {
    return this.cacheQueryTimeout;
};

Node.prototype.setCacheQueryTimeout = function(cacheQueryTimeout) {
    this.cacheQueryTimeout = cacheQueryTimeout;
};

Node.prototype.getTags = function() {
    return this.tags;
};

Node.prototype.ignoreParamForId = function(paramName) {
    delete this.json[paramName];
};

Node.prototype.getTargetTable = function() {
    // Keep name at 60 chars
    // Ref.: https://www.postgresql.org/docs/9.5/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
    return 'analysis_' + this.getTypeSignature() + '_' + this.cachedNodeId();
};

Node.prototype.toJSON = function() {
    return this.json;
};

Node.prototype.toDot = function() {
    return {
        type: this.type,
        nodes: this.nodes,
        attrs: this.attrs
    };
};

module.exports.PARAM = {
    NODE: function() {
        var args = (arguments.length === 1 ? [arguments[0]] : Array.apply(null, arguments));
        return new Validator.Node(args);
    },
    NUMBER: function() {
        return new Validator.Generic(TYPE.NUMBER, Number.isFinite);
    },
    STRING: function() {
        return new Validator.Generic(TYPE.STRING, function (str) {
            return typeof str === 'string';
        });
    },
    BOOLEAN: function() {
        return new Validator.Generic(TYPE.BOOLEAN, function (bool) {
            return typeof bool === 'boolean';
        });
    },
    ENUM: function() {
        var args = (arguments.length === 1 ? [arguments[0]] : Array.apply(null, arguments));
        return new Validator.Enum(args);
    },
    ARRAY: function (validator) {
        if (validator) {
            return new Validator.Generic(TYPE.ARRAY, function(arr) {
                return Array.isArray(arr) && arr.every(validator.isValid);
            }, false, TYPE.ARRAY + '<' + validator.type + '>');
        }
        return new Validator.Generic(TYPE.ARRAY, Array.isArray);
    },
    NULLABLE: function (validator, defaultValue) {
        defaultValue = typeof defaultValue !== 'undefined' ? defaultValue : validator.getDefaultValue();
        return new Validator.Generic(validator.type, function(param) {
            return (param === null || typeof param === 'undefined') || validator.isValid(param);
        }, true, validator.expects, defaultValue, validator.toJSON.bind(validator));
    }
};

module.exports.getChildNodesNames = function(NodeClass) {
    return Object.keys(NodeClass.PARAMS).reduce(function(childNodesNames, paramName) {
        if (NodeClass.PARAMS[paramName].type === TYPE.NODE) {
            childNodesNames.push(paramName);
        }
        return childNodesNames;
    }, []);
};

module.exports.getSqlTemplateFn = function(templateName) {
    return templateLoader.getTemplateFn(__dirname + '/nodes/sql/' + templateName + '.sql');
};

module.exports.template = function(tmpl, c, def) {
    return dot.template(tmpl, c, def);
};

module.exports.create = function createNode(type, expectedParams, options) {
    Object.keys(expectedParams).forEach(function(expectedParamName) {
        if (NODE_RESERVED_KEYWORDS.hasOwnProperty(expectedParamName)) {
            throw new Error('Invalid param name "' + expectedParamName + '". It is a reserved keyword.');
        }
    });

    options = options || {};
    options.cache = options.cache || false;
    options.lazy = options.lazy || false;
    if (!Number.isFinite(options.version)) {
        options.version = 0;
    }
    function BaseNode() {
        Node.apply(this, arguments);
        this.type = type;
        this.cacheQuery = options.cache || false;
        this.lazy = options.lazy || false;
        this.tags = options.tags || [];
        if (options.version > 0) {
            this.version = options.version;
        }
        this.validateAndBuild();
        // `beforeCreate` allows to change/modify the node before it's actually created/returned
        // if it throws an error it will fail to create the node.
        if (options.beforeCreate) {
            options.beforeCreate(this);
        }
    }
    util.inherits(BaseNode, Node);
    BaseNode.prototype.validateAndBuild = function() {
        this.json.type = this.type;
        Object.keys(expectedParams).forEach(function(expectedParamName) {
            var validator = expectedParams[expectedParamName];
            var param = validate(validator, this.params, expectedParamName);
            var value = (param === null || typeof param === 'undefined') ? validator.getDefaultValue() : param;

            this[expectedParamName] = value;
            if (validator.type === TYPE.NODE) {
                var parentNode = param;
                this.nodes[expectedParamName] = parentNode;
                this.inputNodes.push(parentNode);
                var skipFilters = this.lazy;
                this.json[expectedParamName] = parentNode.id(skipFilters);

                if (this.lazy) {
                    this.filters = this.filters || {};
                    var parentNodeFilters = parentNode.getFilters();
                    Object.keys(parentNodeFilters).forEach(function(filterName) {
                        this.filters['__p__' + filterName] = parentNodeFilters[filterName];
                    }.bind(this));
                }
            } else {
                this.attrs[expectedParamName] = value;
                this.json[expectedParamName] = value;
            }
        }.bind(this));
        if (Object.keys(this.filters).length > 0) {
            this.json.filters = this.filters;
        }

        this.setStatusFromInputNodes();
    };
    BaseNode.TYPE = type;
    BaseNode.PARAMS = expectedParams;
    BaseNode.OPTIONS = {
        version: options.version,
        cache: options.cache,
        lazy: options.lazy
    };
    return BaseNode;
};

function validate(validator, params, expectedParamName) {
    if (!validator.nullable && !params.hasOwnProperty(expectedParamName)) {
        throw new Error('Missing required param "' + expectedParamName + '"');
    }

    var param = params[expectedParamName];

    if (!validator.isValid(param)) {
        throw new Error(
            'Invalid type for param "' + expectedParamName + '",' +
            ' expects "' + validator.expects + '" type,' +
            ' got `' + JSON.stringify(param) + '`'
        );
    }

    return param;
}

Node.prototype.computeRequirements = function(databaseService, limits, callback) {
    // By default simply compute maximum of the inputs' number of rows.
    // TODO: if the most common multi-input analysis is some kind of join we should use
    // the product of the input numberOfRows instead
    var maxRows = Math.max.apply(
        null,
        this.inputNodes.map(function(node) {
          return node.estimatedRequirements.numberOfRows || 0 });
    );
    if (maxRows < 0) {
        maxRows = 0;
    }
    this.estimatedRequirements = {
        numberOfRows: maxRows
    };
    this.limits = {
        maximumNumberOfRows: getNodeLimit(limits, this.getType(), 'maximumNumberOfRows', 1000000)
    };
    return callback(null, this.requirementMessages());
};

Node.prototype.requirementMessages = function() {
    var messages = [];
    if (this.estimatedRequirements.numberOfRows > this.limits.maximumNumberOfRows) {
        messages.push('too many result rows');
    }
    return messages;
};

Node.prototype.validateRequirements = function(callback) {
    var messages = this.requirementMessages();
    var err;
    if (messages.length > 0) {
        this.status = STATUS.FAILED;
        this.errorMessage = messages.join('\n');
        err = new Error(this.errorMessage);
    }
    callback(err);
};

function getNodeLimit(globalLimits, nodeType, limitName, defaultValue) {
    var limit = (globalLimits[nodeType] == undefined) ?
                globalLimits[limitName] :
                globalLimits[nodeType][limitName];
    return limit || defaultValue;
};

module.exports.getNodeLimit = getNodeLimit;
<|endoftext|>


<commit_before>const LocatorUtils = require('@routr/location/utils')
const { isLocalnet } = require('@routr/core/ip_util')
const { equalsIgnoreCase, fixPort } = require('@routr/utils/misc_utils')
const config = require('@routr/core/config_util')()
const { RoutingType } = require('@routr/core/routing_type')

const Request = Java.type('javax.sip.message.Request')
const ContactHeader = Java.type('javax.sip.header.ContactHeader')
const RouteHeader = Java.type('javax.sip.header.RouteHeader')
const CSeqHeader = Java.type('javax.sip.header.CSeqHeader')
const ToHeader = Java.type('javax.sip.header.ToHeader')
const FromHeader = Java.type('javax.sip.header.FromHeader')
const MaxForwardsHeader = Java.type('javax.sip.header.MaxForwardsHeader')
const SipFactory = Java.type('javax.sip.SipFactory')
const headerFactory = SipFactory.getInstance().createHeaderFactory()
const addressFactory = SipFactory.getInstance().createAddressFactory()
const LogManager = Java.type('org.apache.logging.log4j.LogManager')
const LOG = LogManager.getLogger()

const isExternalDevice = r =>
  r && (!r.sentByAddress || r.sentByAddress.endsWith('.invalid'))
const isWebRTCClient = isExternalDevice
const isPublicAddress = h => !isLocalnet(config.spec.localnets, h)
const needsExternAddress = (route, host) =>
  isExternalDevice(route) || isPublicAddress(host)

// Deprecated
const addrHost = a => (a.contains(':') ? a.split(':')[0] : a)
// Deprecated
const addrPort = a => (a.contains(':') ? parseInt(a.split(':')[1]) : 5060)
// Deprecated
const ownedAddresss = originInterfaceAddr =>
  config.spec.externAddr
    ? [
        originInterfaceAddr,
        {
          host: addrHost(config.spec.externAddr),
          port: addrPort(config.spec.externAddr)
        }
      ]
    : [originInterfaceAddr]
const getEdgeAddr = (endpointHost, interfaceHost, route) => {
  const externAddr = config.spec.externAddr
  return externAddr && needsExternAddress(route, endpointHost)
    ? externAddr
    : interfaceHost
}
const getToUser = request => {
  const toHeader = request.getHeader(ToHeader.NAME)
  return toHeader
    .getAddress()
    .getURI()
    .getUser()
}
const getUser = request => request.getRequestURI().getUser()
const configureMaxForwards = request => {
  const requestOut = request.clone()
  const maxForwardsHeader = requestOut.getHeader(MaxForwardsHeader.NAME)
  maxForwardsHeader.decrementMaxForwards()
  return requestOut
}
const configureContact = request => {
  const requestOut = request.clone()
  const contactHeader = requestOut.getHeader(ContactHeader.NAME)
  if (
    contactHeader &&
    config.spec.externAddr &&
    isPublicAddress(requestOut.getRequestURI().getHost())
  ) {
    contactHeader
      .getAddress()
      .getURI()
      .setHost(config.spec.externAddr)
    requestOut.setHeader(contactHeader)
  }
  return requestOut
}
const configureCSeq = request => {
  const requestOut = request.clone()
  // Lower the cseq to match the original request
  if (requestOut.getMethod().equals(Request.INVITE)) {
    const cseq = requestOut.getHeader(CSeqHeader.NAME).getSeqNumber() - 1
    requestOut.getHeader(CSeqHeader.NAME).setSeqNumber(cseq)
  }
  return requestOut
}
const configureProxyAuthorization = request => {
  const requestOut = request.clone()
  requestOut.removeHeader('Proxy-Authorization')
  return requestOut
}
const configureRoute = (request, originInterfaceAddr, targetInterfaceAddr) => {
  const requestOut = request.clone()
  const routeHeader = request.getHeader(RouteHeader.NAME)
  if (routeHeader) {
    // 52.174.241.181
    const host = routeHeader
      .getAddress()
      .getURI()
      .getHost()
    // 5060
    const port = fixPort(
      routeHeader
        .getAddress()
        .getURI()
        .getPort()
    )

    LOG.debug(
      `core.processor.RequestUtils.configureRoute [host = ${host}, port=${port}]`
    )

    const c = [originInterfaceAddr, targetInterfaceAddr].filter(
      a => a.host === host && a.port === port
    ).length

    LOG.debug(
      `core.processor.RequestUtils.configureRoute [owns address? = ${c > 0}]`
    )

    if (c > 0) {
      requestOut.removeFirst(RouteHeader.NAME)
      requestOut.removeFirst(RouteHeader.NAME)
    }
  }
  return requestOut
}
const configureVia = (request, interfaceAddr, transport) => {
  const viaHeader = headerFactory.createViaHeader(
    interfaceAddr.host,
    interfaceAddr.port,
    transport,
    null
  )
  viaHeader.setRPort()
  const requestOut = request.clone()
  requestOut.addFirst(viaHeader)
  return requestOut
}
const buildRecordRoute = addr => {
  const sipURI = addressFactory.createSipURI(null, addr.host)
  sipURI.setPort(addr.port)
  sipURI.setTransportParam(addr.transport)
  sipURI.setLrParam()
  const address = addressFactory.createAddress(sipURI)
  return headerFactory.createRecordRouteHeader(address)
}
// rfc5658
const configureRecordRoute = (
  request,
  inputInterfaceAddr,
  outputInterfaceAddr
) => {
  if (config.spec.recordRoute) {
    const requestOut = request.clone()
    requestOut.addHeader(buildRecordRoute(inputInterfaceAddr))
    // Then we get the advertisedAddr
    if (
      inputInterfaceAddr.host !== outputInterfaceAddr.host &&
      inputInterfaceAddr.port !== outputInterfaceAddr.port &&
      inputInterfaceAddr.transport !== outputInterfaceAddr.transport
    ) {
      requestOut.addHeader(buildRecordRoute(outputInterfaceAddr))
    }
    return requestOut
  }
  return request
}
const configureRequestURI = (request, routeInfo, route) => {
  const requestOut = request.clone()
  let uri

  if (
    routeInfo.getRoutingType() === RoutingType.DOMAIN_EGRESS_ROUTING ||
    routeInfo.getRoutingType() === RoutingType.PEER_EGRESS_ROUTING
  ) {
    uri = `sip:${getToUser(request)}@${route.gwHost}`
  } else if (isWebRTCClient(route)) {
    uri = `sip:${getUser(request)}@${route.received}:${route.rport}`
  } else {
    uri = route.contactURI
  }

  requestOut.setRequestURI(LocatorUtils.aorAsObj(uri))
  return requestOut
}
const configurePrivacy = (request, routeInfo) => {
  const requestOut = request.clone()
  requestOut.removeHeader('Privacy')
  const callee = routeInfo.getCallee()
  if (callee && equalsIgnoreCase(callee.kind, 'agent')) {
    let privacyHeader

    if (
      callee.spec.privacy &&
      equalsIgnoreCase(callee.spec.privacy, 'private')
    ) {
      const originFromHeader = requestOut.getHeader(FromHeader.NAME)
      const fromHeaderAddrs = addressFactory.createAddress(
        `"Anonymous" <sip:anonymous@anonymous.invalid>`
      )
      const fromHeader = headerFactory.createHeader(
        'From',
        fromHeaderAddrs.toString()
      )
      fromHeader.setTag(originFromHeader.getTag())
      requestOut.setHeader(fromHeader)
      privacyHeader = headerFactory.createHeader('Privacy', 'id')
    } else {
      privacyHeader = headerFactory.createHeader('Privacy', 'none')
    }

    requestOut.addHeader(privacyHeader)
  }
  return requestOut
}
const configureIdentity = (request, route) => {
  const requestOut = request.clone()
  if (route.thruGw) {
    const remotePartyIdHeader = headerFactory.createHeader(
      'Remote-Party-ID',
      `<sip:${route.number}@${route.gwHost}>;screen=yes;party=calling`
    )
    const dp = requestOut
      .getHeader(FromHeader.NAME)
      .getAddress()
      .getDisplayName()
    const displayName = dp ? `"${dp}" ` : ''
    const pAssertedIdentity = headerFactory.createHeader(
      'P-Asserted-Identity',
      `${displayName}<sip:${route.number}@${route.gwHost}>`
    )
    requestOut.setHeader(remotePartyIdHeader)
    requestOut.setHeader(pAssertedIdentity)
  }
  return requestOut
}
const configureXHeaders = (request, route) => {
  const requestOut = request.clone()
  if (route.thruGw) {
    const gwRefHeader = headerFactory.createHeader('X-Gateway-Ref', route.gwRef)
    requestOut.setHeader(gwRefHeader)
  }
  return requestOut
}
const isInDialog = request =>
  request.getHeader(ToHeader.NAME).getTag() !== null &&
  request.getHeader(FromHeader.NAME).getTag() !== null

module.exports.getEdgeAddr = getEdgeAddr
module.exports.configureRoute = configureRoute
module.exports.configureVia = configureVia
module.exports.configureProxyAuthorization = configureProxyAuthorization
module.exports.configureRequestURI = configureRequestURI
module.exports.configureMaxForwards = configureMaxForwards
module.exports.configureContact = configureContact
module.exports.configurePrivacy = configurePrivacy
module.exports.configureRecordRoute = configureRecordRoute
module.exports.configureIdentity = configureIdentity
module.exports.configureXHeaders = configureXHeaders
module.exports.configureCSeq = configureCSeq
module.exports.isInDialog = isInDialog
<commit_msg>Updated configRequestURI to also use contractURI for WebRTC clients<commit_after>const LocatorUtils = require('@routr/location/utils')
const { isLocalnet } = require('@routr/core/ip_util')
const { equalsIgnoreCase, fixPort } = require('@routr/utils/misc_utils')
const config = require('@routr/core/config_util')()
const { RoutingType } = require('@routr/core/routing_type')

const Request = Java.type('javax.sip.message.Request')
const ContactHeader = Java.type('javax.sip.header.ContactHeader')
const RouteHeader = Java.type('javax.sip.header.RouteHeader')
const CSeqHeader = Java.type('javax.sip.header.CSeqHeader')
const ToHeader = Java.type('javax.sip.header.ToHeader')
const FromHeader = Java.type('javax.sip.header.FromHeader')
const MaxForwardsHeader = Java.type('javax.sip.header.MaxForwardsHeader')
const SipFactory = Java.type('javax.sip.SipFactory')
const headerFactory = SipFactory.getInstance().createHeaderFactory()
const addressFactory = SipFactory.getInstance().createAddressFactory()
const LogManager = Java.type('org.apache.logging.log4j.LogManager')
const LOG = LogManager.getLogger()

const isExternalDevice = r =>
  r && (!r.sentByAddress || r.sentByAddress.endsWith('.invalid'))
// Deprecated
const isWebRTCClient = isExternalDevice
const isPublicAddress = h => !isLocalnet(config.spec.localnets, h)
const needsExternAddress = (route, host) =>
  isExternalDevice(route) || isPublicAddress(host)

// Deprecated
const addrHost = a => (a.contains(':') ? a.split(':')[0] : a)
// Deprecated
const addrPort = a => (a.contains(':') ? parseInt(a.split(':')[1]) : 5060)
// Deprecated
const ownedAddresss = originInterfaceAddr =>
  config.spec.externAddr
    ? [
        originInterfaceAddr,
        {
          host: addrHost(config.spec.externAddr),
          port: addrPort(config.spec.externAddr)
        }
      ]
    : [originInterfaceAddr]
const getEdgeAddr = (endpointHost, interfaceHost, route) => {
  const externAddr = config.spec.externAddr
  return externAddr && needsExternAddress(route, endpointHost)
    ? externAddr
    : interfaceHost
}
const getToUser = request => {
  const toHeader = request.getHeader(ToHeader.NAME)
  return toHeader
    .getAddress()
    .getURI()
    .getUser()
}
const getUser = request => request.getRequestURI().getUser()
const configureMaxForwards = request => {
  const requestOut = request.clone()
  const maxForwardsHeader = requestOut.getHeader(MaxForwardsHeader.NAME)
  maxForwardsHeader.decrementMaxForwards()
  return requestOut
}
const configureContact = request => {
  const requestOut = request.clone()
  const contactHeader = requestOut.getHeader(ContactHeader.NAME)
  if (
    contactHeader &&
    config.spec.externAddr &&
    isPublicAddress(requestOut.getRequestURI().getHost())
  ) {
    contactHeader
      .getAddress()
      .getURI()
      .setHost(config.spec.externAddr)
    requestOut.setHeader(contactHeader)
  }
  return requestOut
}
const configureCSeq = request => {
  const requestOut = request.clone()
  // Lower the cseq to match the original request
  if (requestOut.getMethod().equals(Request.INVITE)) {
    const cseq = requestOut.getHeader(CSeqHeader.NAME).getSeqNumber() - 1
    requestOut.getHeader(CSeqHeader.NAME).setSeqNumber(cseq)
  }
  return requestOut
}
const configureProxyAuthorization = request => {
  const requestOut = request.clone()
  requestOut.removeHeader('Proxy-Authorization')
  return requestOut
}
const configureRoute = (request, originInterfaceAddr, targetInterfaceAddr) => {
  const requestOut = request.clone()
  const routeHeader = request.getHeader(RouteHeader.NAME)
  if (routeHeader) {
    // 52.174.241.181
    const host = routeHeader
      .getAddress()
      .getURI()
      .getHost()
    // 5060
    const port = fixPort(
      routeHeader
        .getAddress()
        .getURI()
        .getPort()
    )

    LOG.debug(
      `core.processor.RequestUtils.configureRoute [host = ${host}, port=${port}]`
    )

    const c = [originInterfaceAddr, targetInterfaceAddr].filter(
      a => a.host === host && a.port === port
    ).length

    LOG.debug(
      `core.processor.RequestUtils.configureRoute [owns address? = ${c > 0}]`
    )

    if (c > 0) {
      requestOut.removeFirst(RouteHeader.NAME)
      requestOut.removeFirst(RouteHeader.NAME)
    }
  }
  return requestOut
}
const configureVia = (request, interfaceAddr, transport) => {
  const viaHeader = headerFactory.createViaHeader(
    interfaceAddr.host,
    interfaceAddr.port,
    transport,
    null
  )
  viaHeader.setRPort()
  const requestOut = request.clone()
  requestOut.addFirst(viaHeader)
  return requestOut
}
const buildRecordRoute = addr => {
  const sipURI = addressFactory.createSipURI(null, addr.host)
  sipURI.setPort(addr.port)
  sipURI.setTransportParam(addr.transport)
  sipURI.setLrParam()
  const address = addressFactory.createAddress(sipURI)
  return headerFactory.createRecordRouteHeader(address)
}
// rfc5658
const configureRecordRoute = (
  request,
  originInterfaceAddr,
  targetInterfaceAddr
) => {
  if (config.spec.recordRoute) {
    const requestOut = request.clone()
    requestOut.addHeader(buildRecordRoute(originInterfaceAddr))

    LOG.debug(
      `core.processor.RequestUtils.configureRecordRoute [Adding originInterfaceAddr -> ${JSON.stringify(
        originInterfaceAddr
      )}]`
    )

    // Add the targetInterfaceAddr only if it is different to the
    // originInterfaceAddr
    if (
      originInterfaceAddr.host !== targetInterfaceAddr.host ||
      originInterfaceAddr.port !== targetInterfaceAddr.port ||
      originInterfaceAddr.transport !== targetInterfaceAddr.transport
    ) {
      LOG.debug(
        `core.processor.RequestUtils.configureRecordRoute [Adding targetInterfaceAddr -> ${JSON.stringify(
          targetInterfaceAddr
        )}]`
      )
      requestOut.addHeader(buildRecordRoute(targetInterfaceAddr))
    }
    return requestOut
  }
  return request
}
const configureRequestURI = (request, routeInfo, route) => {
  const requestOut = request.clone()
  let uri

  if (
    routeInfo &&
    (routeInfo.getRoutingType() === RoutingType.DOMAIN_EGRESS_ROUTING ||
      routeInfo.getRoutingType() === RoutingType.PEER_EGRESS_ROUTING)
  ) {
    uri = `sip:${getToUser(request)}@${route.gwHost}`
    //} else if (isWebRTCClient(route)) {
    //  uri = `sip:${getUser(request)}@${route.received}:${route.rport}`
    //  LOG.debug('XXXXX uri=' + uri)
    //
  } else {
    uri = route.contactURI
  }

  requestOut.setRequestURI(LocatorUtils.aorAsObj(uri))
  return requestOut
}
const configurePrivacy = (request, routeInfo) => {
  const requestOut = request.clone()
  requestOut.removeHeader('Privacy')
  const callee = routeInfo.getCallee()
  if (callee && equalsIgnoreCase(callee.kind, 'agent')) {
    let privacyHeader

    if (
      callee.spec.privacy &&
      equalsIgnoreCase(callee.spec.privacy, 'private')
    ) {
      const originFromHeader = requestOut.getHeader(FromHeader.NAME)
      const fromHeaderAddrs = addressFactory.createAddress(
        `"Anonymous" <sip:anonymous@anonymous.invalid>`
      )
      const fromHeader = headerFactory.createHeader(
        'From',
        fromHeaderAddrs.toString()
      )
      fromHeader.setTag(originFromHeader.getTag())
      requestOut.setHeader(fromHeader)
      privacyHeader = headerFactory.createHeader('Privacy', 'id')
    } else {
      privacyHeader = headerFactory.createHeader('Privacy', 'none')
    }

    requestOut.addHeader(privacyHeader)
  }
  return requestOut
}
const configureIdentity = (request, route) => {
  const requestOut = request.clone()
  if (route.thruGw) {
    const remotePartyIdHeader = headerFactory.createHeader(
      'Remote-Party-ID',
      `<sip:${route.number}@${route.gwHost}>;screen=yes;party=calling`
    )
    const dp = requestOut
      .getHeader(FromHeader.NAME)
      .getAddress()
      .getDisplayName()
    const displayName = dp ? `"${dp}" ` : ''
    const pAssertedIdentity = headerFactory.createHeader(
      'P-Asserted-Identity',
      `${displayName}<sip:${route.number}@${route.gwHost}>`
    )
    requestOut.setHeader(remotePartyIdHeader)
    requestOut.setHeader(pAssertedIdentity)
  }
  return requestOut
}
const configureXHeaders = (request, route) => {
  const requestOut = request.clone()
  if (route.thruGw) {
    const gwRefHeader = headerFactory.createHeader('X-Gateway-Ref', route.gwRef)
    requestOut.setHeader(gwRefHeader)
  }
  return requestOut
}
const isInDialog = request =>
  request.getHeader(ToHeader.NAME).getTag() !== null &&
  request.getHeader(FromHeader.NAME).getTag() !== null

module.exports.getEdgeAddr = getEdgeAddr
module.exports.configureRoute = configureRoute
module.exports.configureVia = configureVia
module.exports.configureProxyAuthorization = configureProxyAuthorization
module.exports.configureRequestURI = configureRequestURI
module.exports.configureMaxForwards = configureMaxForwards
module.exports.configureContact = configureContact
module.exports.configurePrivacy = configurePrivacy
module.exports.configureRecordRoute = configureRecordRoute
module.exports.configureIdentity = configureIdentity
module.exports.configureXHeaders = configureXHeaders
module.exports.configureCSeq = configureCSeq
module.exports.isInDialog = isInDialog
<|endoftext|>


<commit_before>var _; //globals

/* This section uses a functional extension known as Underscore.js - http://documentcloud.github.com/underscore/
     "Underscore is a utility-belt library for JavaScript that provides a lot of the functional programming support
      that you would expect in Prototype.js (or Ruby), but without extending any of the built-in JavaScript objects.
      It's the tie to go along with jQuery's tux."
 */
describe("About Higher Order Functions", function () {

  it("should use filter to return array items that meet a criteria", function () {
    var numbers = [1,2,3];
    var odd = _(numbers).filter(function (x) { return x % 2 !== 0 });

    expect(odd).toEqual(FILL_ME_IN);
    expect(odd.length).toBe(FILL_ME_IN);
    expect(numbers.length).toBe(FILL_ME_IN);
  });

  it("should use 'map' to transform each element", function () {
    var numbers = [1, 2, 3];
    var numbersPlus1 = _(numbers).map(function(x) { return x + 1 });

    expect(numbersPlus1).toEqual(FILL_ME_IN);
    expect(numbers).toEqual(FILL_ME_IN);
  });

  it("should use 'reduce' to update the same result on each iteration", function () {
    var numbers = [1, 2, 3];
    var reduction = _(numbers).reduce(
      function(memo, x) {
        //note: memo is the result from last call, and x is the current number
        return memo + x;
      },
      /* initial */ 0
    );

    expect(reduction).toBe(FILL_ME_IN);
    expect(numbers).toEqual(FILL_ME_IN);
  });

  it("should use 'forEach' for simple iteration", function () {
    var numbers = [1,2,3];
    var msg = "";
    var isEven = function (item) {
      msg += (item % 2) === 0;
    };

    _(numbers).forEach(isEven);

    expect(msg).toEqual(FILL_ME_IN);
    expect(numbers).toEqual(FILL_ME_IN);
  });

  it("should use 'all' to test whether all items pass condition", function () {
    var onlyEven = [2,4,6];
    var mixedBag = [2,4,5,6];

    var isEven = function(x) { return x % 2 === 0 };

    expect(_(onlyEven).all(isEven)).toBe(FILL_ME_IN);
    expect(_(mixedBag).all(isEven)).toBe(FILL_ME_IN);
  });

  it("should use 'any' to test if any items passes condition" , function () {
    var onlyEven = [2,4,6];
    var mixedBag = [2,4,5,6];

    var isEven = function(x) { return x % 2 === 0 };

    expect(_(onlyEven).any(isEven)).toBe(FILL_ME_IN);
    expect(_(mixedBag).any(isEven)).toBe(FILL_ME_IN);
  });

  it("should use range to generate an array", function() {
    expect(_.range(3)).toEqual(FILL_ME_IN);
    expect(_.range(1, 4)).toEqual(FILL_ME_IN);
    expect(_.range(0, -4, -1)).toEqual(FILL_ME_IN);
  });

  it("should use flatten to make nested arrays easy to work with", function() {
    expect(_([ [1, 2], [3, 4] ]).flatten()).toEqual(FILL_ME_IN);
  });

  it("should use chain() ... .value() to use multiple higher order functions", function() {
    var result = _([ [0, 1], 2 ]).chain()
                     .flatten()
                     .map(function(x) { return x+1 } )
                     .reduce(function (sum, x) { return sum + x })
                     .value();

    expect(result).toEqual(FILL_ME_IN);
  });

});

<commit_msg>AboutHigherOrderFunctions tests passing<commit_after>var _; //globals

/* This section uses a functional extension known as Underscore.js - http://documentcloud.github.com/underscore/
     "Underscore is a utility-belt library for JavaScript that provides a lot of the functional programming support
      that you would expect in Prototype.js (or Ruby), but without extending any of the built-in JavaScript objects.
      It's the tie to go along with jQuery's tux."
 */
describe("About Higher Order Functions", function () {

  it("should use filter to return array items that meet a criteria", function () {
    var numbers = [1,2,3];
    var odd = _(numbers).filter(function (x) { return x % 2 !== 0 });

    expect(odd).toEqual([1, 3]);
    expect(odd.length).toBe(2);
    expect(numbers.length).toBe(3);
  });

  it("should use 'map' to transform each element", function () {
    var numbers = [1, 2, 3];
    var numbersPlus1 = _(numbers).map(function(x) { return x + 1 });

    expect(numbersPlus1).toEqual([2, 3, 4]);
    expect(numbers).toEqual([1, 2, 3]);
  });

  it("should use 'reduce' to update the same result on each iteration", function () {
    var numbers = [1, 2, 3];
    var reduction = _(numbers).reduce(
      function(memo, x) {
        //note: memo is the result from last call, and x is the current number
        return memo + x;
      },
      /* initial */ 0
    );

    expect(reduction).toBe(6);
    expect(numbers).toEqual([1, 2, 3]);
  });

  it("should use 'forEach' for simple iteration", function () {
    var numbers = [1,2,3];
    var msg = "";
    var isEven = function (item) {
      msg += (item % 2) === 0;
    };

    _(numbers).forEach(isEven);

    expect(msg).toEqual("falsetruefalse");
    expect(numbers).toEqual([1, 2, 3]);
  });

  it("should use 'all' to test whether all items pass condition", function () {
    var onlyEven = [2,4,6];
    var mixedBag = [2,4,5,6];

    var isEven = function(x) { return x % 2 === 0 };

    expect(_(onlyEven).all(isEven)).toBe(true);
    expect(_(mixedBag).all(isEven)).toBe(false);
  });

  it("should use 'any' to test if any items passes condition" , function () {
    var onlyEven = [2,4,6];
    var mixedBag = [2,4,5,6];

    var isEven = function(x) { return x % 2 === 0 };

    expect(_(onlyEven).any(isEven)).toBe(true);
    expect(_(mixedBag).any(isEven)).toBe(true);
  });

  it("should use range to generate an array", function() {
    expect(_.range(3)).toEqual([0, 1, 2]);
    expect(_.range(1, 4)).toEqual([1, 2, 3]);
    expect(_.range(0, -4, -1)).toEqual([0, -1, -2, -3]);
  });

  it("should use flatten to make nested arrays easy to work with", function() {
    expect(_([ [1, 2], [3, 4] ]).flatten()).toEqual([1, 2, 3, 4]);
  });

  it("should use chain() ... .value() to use multiple higher order functions", function() {
    var result = _([ [0, 1], 2 ]).chain()
                     .flatten()
                     .map(function(x) { return x+1 } )
                     .reduce(function (sum, x) { return sum + x })
                     .value();

    expect(result).toEqual(6);
  });

});

<|endoftext|>


<commit_before>/*jslint node: true */
"use strict";

var Util = require('util');
var child_process = require('child_process');
var Events = require('events');
var fs = require('fs');
var assert = require('assert');

var DBus = require('dbus-native');

var DEFAULT_DBUS_ADDRESS_FILENAME = "/tmp/omxplayerdbus";

var DEFAULT_DESTINATION = 'org.mpris.MediaPlayer2.omxplayer';
var DEFAULT_PATH = '/org/mpris/MediaPlayer2';
var DBUS_INTERFACE_PROPERTIES = 'org.freedesktop.DBus.Properties';
var OMXPLAYER_DBUS_INTERFACE_PLAYER = 'org.mpris.MediaPlayer2.Player';

function OMXPlayer(configuration) {
	Events.EventEmitter.call(this);

	configuration = configuration || {};

	this._configuration = configuration;

	this.dbusPath = configuration.dbusPath || DEFAULT_PATH;
	this.dbusDestination = configuration.dbusDestination || DEFAULT_DESTINATION;

	configuration.omxPlayerPath = configuration.omxPlayerPath || "omxplayer";
}

Util.inherits(OMXPlayer, Events.EventEmitter);
module.exports = OMXPlayer;

OMXPlayer.prototype.start = function(moviePath, callback) {
	var configuration = this._configuration;

	var sessionBus = this._sessionBus;
	if (sessionBus) {
		this.emit("closeSessionBus");
		this._sessionBus = null;
		try {
			sessionBus.end();
		} catch (x) {
			console.error(x);
		}
	}

	var omxProcess = this._omxProcess;
	if (omxProcess) {
		this.emit("killOMXPlayer");
		this._omxProcess = null;

		try {
			omxProcess.kill();
			omxProcess.disconnect();
		} catch (x) {
			console.error(x);
		}
	}

	if (!moviePath) {
		return callback();
	}

	if (configuration.omxPlayerPath) {
		var parameters = [ "-b", "-o", "hdmi", "-p" ];
		if (configuration.omxPlayerParams) {
			parameters = parameters.concat(configuration.omxPlayerParams);
		}

		var dbusName = DEFAULT_DESTINATION + process.getgid;
		this.dbusDestination = dbusName;

		parameters.push("--dbus_name", dbusName);

		parameters.push(moviePath);

		console.log("Execute  '" + configuration.omxPlayerPath + "' parameters=", parameters);

		omxProcess = child_process.spawn(configuration.omxPlayerPath, parameters);
		this._omxProcess = omxProcess;

		omxProcess.stderr.on('data', function(data) {
			console.error('omxPlayer.error: ' + data);
		});

		omxProcess.on('close', function(code) {
			console.log('omxProcess is ended');
		});
	}

	if (!configuration.dbus) {
		configuration.dbus = {};

		var dbusAddressFilename = configuration.dbusAddressFilename || DEFAULT_DBUS_ADDRESS_FILENAME;

		if (!fs.existsSync(dbusAddressFilename)) {

			var dbusAddressFilename2 = dbusAddressFilename + "." + process.env.USER;

			if (!fs.existsSync(dbusAddressFilename2)) {
				throw new Error("Can not get the dbus address  (try content of filename " + dbusAddressFilename2 + ")");
			}

			dbusAddressFilename = dbusAddressFilename2;
		}

		configuration.dbus.busAddress = fs.readFileSync(dbusAddressFilename).toString();

		console.info("Dbus address=", configuration.dbus.busAddress);
	}

	var self = this;
	setTimeout(function() {
		self._sessionBus = DBus.sessionBus(configuration.dbus);

		return callback();
	}, 100);
}

OMXPlayer.prototype._dbusProperty = function(propertyName, callback) {
	assert(typeof (callback) === "function", "Invalid callback parameter");

	var params = {
		destination: this.dbusDestination,
		path: this.dbusPath,
		'interface': DBUS_INTERFACE_PROPERTIES,
		member: propertyName
	};

	return this._sessionBus.invoke(params, function(error, value) {
		if (error) {
			return callback(error);
		}

		return callback(null, value);
	});
};

OMXPlayer.prototype._dbusCommand = function(memberName, signature, body, callback) {

	var params = {
		destination: this.dbusDestination,
		path: this.dbusPath,
		'interface': OMXPLAYER_DBUS_INTERFACE_PLAYER,
		member: memberName
	};
	if (signature) {
		params.signature = signature;
	}
	if (body) {
		params.body = body;
	}

	return this._sessionBus.invoke(params, function(error, value) {
		if (error) {
			if (!callback) {
				console.error("DBus command error", error);
				return;
			}
			return callback(error);
		}

		if (!callback) {
			return;
		}

		return callback(null, value);
	});
};

[ "Position",
	"PlaybackStatus",
	"CanQuit",
	"Fullscreen",
	"CanSetFullscreen",
	"CanRaise",
	"HasTrackList",
	"Identity",
	"SupportedUriSchemes",
	"SupportedMimeTypes",
	"CanGoNext",
	"CanGoPrevious",
	"CanSeek",
	"CanControl",
	"CanPlay",
	"CanPause",
	"PlaybackStatus",
	"Position",
	"Aspect",
	"VideoStreamCount",
	"ResWidth",
	"ResHeight",
	"Duration",
	"MinimumRate",
	"MaximumRate",
	"ListSubtitles",
	"ListAudio",
	"ListVideo" ].forEach(function(name) {
	OMXPlayer.prototype["get" + name] = function(callback) {
		return this._dbusProperty(name, callback);
	};
});

[ "Next",
	"Previous",
	"Pause",
	"PlayPause",
	"Stop",
	"Mute",
	"Unmute",
	"HideVideo",
	"UnHideVideo",
	"ShowSubtitles",
	"HideSubtitles",
	"Quit" ].forEach(function(name) {
	OMXPlayer.prototype[name.substring(0, 1).toLowerCase() + name.substring(1)] = function(callback) {
		return this._dbusCommand(name, null, null, callback);
	};
});

OMXPlayer.prototype.seek = function(offset, callback) {
	return this._dbusCommand('Seek', "x", [ offset ], callback);
};

OMXPlayer.prototype.setPosition = function(position, callback) {
	return this._dbusCommand('Position', "sx", [ '', position ], callback);
};

OMXPlayer.prototype.setVolume = function(volume, callback) {
	return this._dbusCommand('Volume', "d", [ volume ], callback);
};

OMXPlayer.prototype.setVideoPos = function(win, callback) {
	return this._dbusCommand('VideoPos', "os", [ '', win ], callback);
};

OMXPlayer.prototype.selectSubtitle = function(index, callback) {
	return this._dbusCommand('SelectSubtitle', "i", [ index ], callback);
};

OMXPlayer.prototype.selectAudio = function(index, callback) {
	return this._dbusCommand('SelectAudio', "i", [ index ], callback);
};

OMXPlayer.prototype.action = function(action, callback) {
	return this._dbusCommand('Action', "i", [ action ], callback);
};

OMXPlayer.prototype.release = function() {

};
<commit_msg>First tests<commit_after>/*jslint node: true */
"use strict";

var Util = require('util');
var child_process = require('child_process');
var Events = require('events');
var fs = require('fs');
var assert = require('assert');

var DBus = require('dbus-native');

var DEFAULT_DBUS_ADDRESS_FILENAME = "/tmp/omxplayerdbus";

var DEFAULT_DESTINATION = 'org.mpris.MediaPlayer2.omxplayer';
var DEFAULT_PATH = '/org/mpris/MediaPlayer2';
var DBUS_INTERFACE_PROPERTIES = 'org.freedesktop.DBus.Properties';
var OMXPLAYER_DBUS_INTERFACE_PLAYER = 'org.mpris.MediaPlayer2.Player';

var dbusIdentifier = 0;

function OMXPlayer(configuration) {
	Events.EventEmitter.call(this);

	configuration = configuration || {};

	this._configuration = configuration;

	this.dbusPath = configuration.dbusPath || DEFAULT_PATH;
	this.dbusDestination = configuration.dbusDestination || DEFAULT_DESTINATION;

	configuration.omxPlayerPath = configuration.omxPlayerPath || "omxplayer";
}

Util.inherits(OMXPlayer, Events.EventEmitter);
module.exports = OMXPlayer;

OMXPlayer.prototype.start = function(moviePath, callback) {
	var configuration = this._configuration;

	var sessionBus = this._sessionBus;
	if (sessionBus) {
		this.emit("closeSessionBus");
		this._sessionBus = null;
		try {
			sessionBus.end();
		} catch (x) {
			console.error(x);
		}
	}

	var omxProcess = this._omxProcess;
	if (omxProcess) {
		this.emit("killOMXPlayer");
		this._omxProcess = null;

		try {
			omxProcess.kill();
			omxProcess.disconnect();
		} catch (x) {
			console.error(x);
		}
	}

	if (!moviePath) {
		return callback();
	}

	if (configuration.omxPlayerPath) {
		var parameters = [ "-b", "-o", "hdmi", "-p" ];
		if (configuration.omxPlayerParams) {
			parameters = parameters.concat(configuration.omxPlayerParams);
		}

		var dbusName = DEFAULT_DESTINATION + "_" + process.getgid() + "_" + (dbusIdentifier++);
		this.dbusDestination = dbusName;

		parameters.push("--dbus_name", dbusName);

		parameters.push(moviePath);

		console.log("Execute  '" + configuration.omxPlayerPath + "' parameters=", parameters);

		omxProcess = child_process.spawn(configuration.omxPlayerPath, parameters);
		this._omxProcess = omxProcess;

		omxProcess.stderr.on('data', function(data) {
			console.error('omxPlayer: ' + data);
		});

		omxProcess.stdout.on('data', function(data) {
			console.log('omxPlayer: ' + data);
		});

		omxProcess.on('close', function(code) {
			console.log('omxProcess is ended');
		});
	}

	if (!configuration.dbus) {
		configuration.dbus = {};

		var dbusAddressFilename = configuration.dbusAddressFilename || DEFAULT_DBUS_ADDRESS_FILENAME;

		if (!fs.existsSync(dbusAddressFilename)) {

			var dbusAddressFilename2 = dbusAddressFilename + "." + process.env.USER;

			if (!fs.existsSync(dbusAddressFilename2)) {
				throw new Error("Can not get the dbus address  (try content of filename " + dbusAddressFilename2 + ")");
			}

			dbusAddressFilename = dbusAddressFilename2;
		}

		configuration.dbus.busAddress = fs.readFileSync(dbusAddressFilename).toString();

		console.info("Dbus address=", configuration.dbus.busAddress);
	}

	var self = this;
	setTimeout(function() {
		self._sessionBus = DBus.sessionBus(configuration.dbus);

		return callback();
	}, 100);
}

OMXPlayer.prototype._dbusProperty = function(propertyName, callback) {
	assert(typeof (callback) === "function", "Invalid callback parameter");

	var params = {
		destination: this.dbusDestination,
		path: this.dbusPath,
		'interface': DBUS_INTERFACE_PROPERTIES,
		member: propertyName
	};

	return this._sessionBus.invoke(params, function(error, value) {
		if (error) {
			return callback(error);
		}

		return callback(null, value);
	});
};

OMXPlayer.prototype._dbusCommand = function(memberName, signature, body, callback) {

	var params = {
		destination: this.dbusDestination,
		path: this.dbusPath,
		'interface': OMXPLAYER_DBUS_INTERFACE_PLAYER,
		member: memberName
	};
	if (signature) {
		params.signature = signature;
	}
	if (body) {
		params.body = body;
	}

	return this._sessionBus.invoke(params, function(error, value) {
		if (error) {
			if (!callback) {
				console.error("DBus command error", error);
				return;
			}
			return callback(error);
		}

		if (!callback) {
			return;
		}

		return callback(null, value);
	});
};

[ "Position",
	"PlaybackStatus",
	"CanQuit",
	"Fullscreen",
	"CanSetFullscreen",
	"CanRaise",
	"HasTrackList",
	"Identity",
	"SupportedUriSchemes",
	"SupportedMimeTypes",
	"CanGoNext",
	"CanGoPrevious",
	"CanSeek",
	"CanControl",
	"CanPlay",
	"CanPause",
	"PlaybackStatus",
	"Position",
	"Aspect",
	"VideoStreamCount",
	"ResWidth",
	"ResHeight",
	"Duration",
	"MinimumRate",
	"MaximumRate",
	"ListSubtitles",
	"ListAudio",
	"ListVideo" ].forEach(function(name) {
	OMXPlayer.prototype["get" + name] = function(callback) {
		return this._dbusProperty(name, callback);
	};
});

[ "Next",
	"Previous",
	"Pause",
	"PlayPause",
	"Stop",
	"Mute",
	"Unmute",
	"HideVideo",
	"UnHideVideo",
	"ShowSubtitles",
	"HideSubtitles",
	"Quit" ].forEach(function(name) {
	OMXPlayer.prototype[name.substring(0, 1).toLowerCase() + name.substring(1)] = function(callback) {
		return this._dbusCommand(name, null, null, callback);
	};
});

OMXPlayer.prototype.seek = function(offset, callback) {
	return this._dbusCommand('Seek', "x", [ offset ], callback);
};

OMXPlayer.prototype.setPosition = function(position, callback) {
	return this._dbusCommand('Position', "sx", [ '', position ], callback);
};

OMXPlayer.prototype.setVolume = function(volume, callback) {
	return this._dbusCommand('Volume', "d", [ volume ], callback);
};

OMXPlayer.prototype.setVideoPos = function(win, callback) {
	return this._dbusCommand('VideoPos', "os", [ '', win ], callback);
};

OMXPlayer.prototype.selectSubtitle = function(index, callback) {
	return this._dbusCommand('SelectSubtitle', "i", [ index ], callback);
};

OMXPlayer.prototype.selectAudio = function(index, callback) {
	return this._dbusCommand('SelectAudio', "i", [ index ], callback);
};

OMXPlayer.prototype.action = function(action, callback) {
	return this._dbusCommand('Action', "i", [ action ], callback);
};

OMXPlayer.prototype.release = function() {

};
<|endoftext|>


<commit_before>var _; //globals

/* This section uses a functional extension known as Underscore.js - http://documentcloud.github.com/underscore/
     "Underscore is a utility-belt library for JavaScript that provides a lot of the functional programming support
      that you would expect in Prototype.js (or Ruby), but without extending any of the built-in JavaScript objects.
      It's the tie to go along with jQuery's tux."
 */
describe("About Higher Order Functions", function () {

  it("should use filter to return array items that meet a criteria", function () {
    var numbers = [1,2,3];
    var odd = _(numbers).filter(function (x) { return x % 2 !== 0 });

    expect(odd).toEqual(FILL_ME_IN);
    expect(odd.length).toBe(FILL_ME_IN);
    expect(numbers.length).toBe(FILL_ME_IN);
  });

  it("should use 'map' to transform each element", function () {
    var numbers = [1, 2, 3];
    var numbersPlus1 = _(numbers).map(function(x) { return x + 1 });

    expect(numbersPlus1).toEqual(FILL_ME_IN);
    expect(numbers).toEqual(FILL_ME_IN);
  });

  it("should use 'reduce' to update the same result on each iteration", function () {
    var numbers = [1, 2, 3];
    var reduction = _(numbers).reduce(
            function(/* result from last call */ memo, /* current */ x) { return memo + x }, /* initial */ 0);

    expect(reduction).toBe(FILL_ME_IN);
    expect(numbers).toEqual(FILL_ME_IN);
  });

  it("should use 'forEach' for simple iteration", function () {
    var numbers = [1,2,3];
    var msg = "";
    var isEven = function (item) {
      msg += (item % 2) === 0;
    };

    _(numbers).forEach(isEven);

    expect(msg).toEqual(FILL_ME_IN);
    expect(numbers).toEqual(FILL_ME_IN);
  });

  it("should use 'all' to test whether all items pass condition", function () {
    var onlyEven = [2,4,6];
    var mixedBag = [2,4,5,6];

    var isEven = function(x) { return x % 2 === 0 };

    expect(_(onlyEven).all(isEven)).toBe(FILL_ME_IN);
    expect(_(mixedBag).all(isEven)).toBe(FILL_ME_IN);
  });

  it("should use 'any' to test if any items passes condition" , function () {
    var onlyEven = [2,4,6];
    var mixedBag = [2,4,5,6];

    var isEven = function(x) { return x % 2 === 0 };

    expect(_(onlyEven).any(isEven)).toBe(FILL_ME_IN);
    expect(_(mixedBag).any(isEven)).toBe(FILL_ME_IN);
  });

  it("should use range to generate an array", function() {
      expect(_.range(3)).toEqual(FILL_ME_IN);
      expect(_.range(1, 4)).toEqual(FILL_ME_IN);
      expect(_.range(0, -4, -1)).toEqual(FILL_ME_IN);
  });

  it("should use flatten to make nested arrays easy to work with", function() {
      expect(_([ [1, 2], [3, 4] ]).flatten()).toEqual(FILL_ME_IN);
  });

  it("should use chain() ... .value() to use multiple higher order functions", function() {
      var result = _([ [0, 1], 2 ]).chain()
                       .flatten()
                       .map(function(x) { return x+1 } )
                       .reduce(function (sum, x) { return sum + x })
                       .value();

      expect(result).toEqual(FILL_ME_IN);
  });

});

<commit_msg>completed 39/53<commit_after>var _; //globals

/* This section uses a functional extension known as Underscore.js - http://documentcloud.github.com/underscore/
     "Underscore is a utility-belt library for JavaScript that provides a lot of the functional programming support
      that you would expect in Prototype.js (or Ruby), but without extending any of the built-in JavaScript objects.
      It's the tie to go along with jQuery's tux."
 */
describe("About Higher Order Functions", function () {

  it("should use filter to return array items that meet a criteria", function () {
    var numbers = [1,2,3];
    var odd = _(numbers).filter(function (x) { return x % 2 !== 0 });

    expect(odd).toEqual([1,3]);
    expect(odd.length).toBe(2);
    expect(numbers.length).toBe(3);
  });

  it("should use 'map' to transform each element", function () {
    var numbers = [1, 2, 3];
    var numbersPlus1 = _(numbers).map(function(x) { return x + 1 });

    expect(numbersPlus1).toEqual([2,3,4]);
    expect(numbers).toEqual([1,2,3]);
  });

  it("should use 'reduce' to update the same result on each iteration", function () {
    var numbers = [1, 2, 3];
    var reduction = _(numbers).reduce(
            function(/* result from last call */ memo, /* current */ x) { return memo + x }, /* initial */ 0);

    expect(reduction).toBe(6);
    expect(numbers).toEqual([1,2,3]);
  });

  it("should use 'forEach' for simple iteration", function () {
    var numbers = [1,2,3];
    var msg = "";
    var isEven = function (item) {
      msg += (item % 2) === 0;
    };

    _(numbers).forEach(isEven);

    expect(msg).toEqual("falsetruefalse");
    expect(numbers).toEqual([1,2,3]);
  });

  it("should use 'all' to test whether all items pass condition", function () {
    var onlyEven = [2,4,6];
    var mixedBag = [2,4,5,6];

    var isEven = function(x) { return x % 2 === 0 };

    expect(_(onlyEven).all(isEven)).toBe(true);
    expect(_(mixedBag).all(isEven)).toBe(false);
  });

  it("should use 'any' to test if any items passes condition" , function () {
    var onlyEven = [2,4,6];
    var mixedBag = [2,4,5,6];

    var isEven = function(x) { return x % 2 === 0 };

    expect(_(onlyEven).any(isEven)).toBe(true);
    expect(_(mixedBag).any(isEven)).toBe(true);
  });

  it("should use range to generate an array", function() {
      expect(_.range(3)).toEqual([0,1,2]);
      expect(_.range(1, 4)).toEqual([1,2,3]);
      expect(_.range(0, -4, -1)).toEqual([0,-1,-2,-3]);
  });

  it("should use flatten to make nested arrays easy to work with", function() {
      expect(_([ [1, 2], [3, 4] ]).flatten()).toEqual([1,2,3,4]);
  });

  it("should use chain() ... .value() to use multiple higher order functions", function() {
      var result = _([ [0, 1], 2 ]).chain()
                       .flatten()
                       .map(function(x) { return x+1 } )
                       .reduce(function (sum, x) { return sum + x })
                       .value();

      expect(result).toEqual(6);
  });

});

<|endoftext|>