Code coverage report for api/map_reduce/MapReduce.js

Statements: 65.96% (62 / 94)      Branches: 64.91% (37 / 57)      Functions: 58.33% (7 / 12)      Lines: 65.96% (62 / 94)      Ignored: none     

All files » api/map_reduce/ » MapReduce.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 1821   1 6         6   6       6                             6                     6 278     278         6   6 2     2 2     2 2 104 104 104     104 3   104 4   100     2     2 2 2 4 4           2     2               2   2   2         6 5 5 2   2 2 2           5   5 2 2   2 2 2         3     5           6                 6 6   6         6               6 2     2         6     1  
var debugMode = false;
/*jslint evil: true */
var MapReduceFactory = function(options) {
  options = options || {
    filename: "",
    map: null,
    reduce: null
  };
  options.rows = options.rows || [];
 
  var debug = function() {
    // console.log(arguments);
  };
 
  var sum = function(values) {
    debug("summing ", values);
    if (!values || !values.reduce) {
      debug("this isn't an array");
      return values;
    }
    if (values && typeof values[0] !== "number") {
      debug("using number of values " + values.length);
      return values.length;
    }
    return values.reduce(function(a, b) {
      debug("adding " + a + " " + b);
      return a + b;
    });
  };
  var count = function(keys, values, rereduce) {
    debug("counting" + keys);
    if (rereduce) {
      debug("suming " + values);
      return sum(values);
    } else {
      debug("count  " + values.length);
      return values.length;
    }
  };
 
  options.emit = options.emit || function(key, val) {
    Iif (debugMode) {
      debug(options);
    }
    options.rows.push({
      key: key,
      value: val
    });
  };
  options._count = options._count || count;
 
  options.group = options.group || function(rows) {
    Iif (debugMode) {
      debug(options);
    }
    options.reduce = options.reduce || options._count;
    rows = rows || options.rows;
 
    // Group all rows by their key
    var rowsGroupedById = {};
    rows.map(function(row) {
      var key = row.key;
      var value = row.value;
      Iif (typeof key !== "string") {
        key = JSON.stringify(key);
      }
      if (typeof value !== "string") {
        value = JSON.stringify(value);
      }
      if (!rowsGroupedById[key]) {
        rowsGroupedById[key] = [value];
      } else {
        rowsGroupedById[key].push(value);
      }
    });
    debug("regrouped ", rowsGroupedById);
 
    // Reduce all grouped items
    var rereduceMode = false;
    var reducedRows = [];
    for (var key in rowsGroupedById) {
      Eif (rowsGroupedById.hasOwnProperty(key)) {
        reducedRows.push({
          key: key,
          value: options.reduce(key, rowsGroupedById[key], rereduceMode)
        });
      }
    }
    debug("reduced ", reducedRows);
 
    // Re-reduce all grouped items
    Iif (Object.prototype.toString.call(reducedRows.value) === "[object Array]") {
      rereduceMode = true;
      reducedRows = reducedRows.map(function(groupedRow) {
        groupedRow.value = options.reduce(null, groupedRow.value, rereduceMode);
        return groupedRow;
      });
    }
 
    debug("re-reduced ", reducedRows);
 
    return {
      rows: reducedRows.sort(function(a, b) {
        return a.key - b.key;
      })
    };
  };
 
  options.customMap = function(doc, emit, rows) {
    rows = rows || options.rows || [];
    if (emit) {
      try {
        // ugly way to make sure references to 'emit' in map/reduce bind to the above emit at run time
        eval("options.map = " + options.map.toString() + ";");
        options.mustRescopeEmit = true;
        debug("customizing emit");
      } catch (e) {
        console.warn("Probably running in a Chrome app or other context where eval is not permitted. Using global emit and results for options");
      }
    }
 
    options.map(doc);
 
    if (options.mustRescopeEmit) {
      try {
        emit = options.emit;
        // ugly way to make sure references to 'emit' in map/reduce bind to the above emit at run time
        eval("options.map = " + options.map.toString() + ";");
        options.mustRescopeEmit = false;
        debug("rescoped emit");
      } catch (e) {
        console.warn("Probably running in a Chrome app or other context where eval is not permitted. Using global emit and results for options");
      }
    } else {
      debug("not rescoping emit, already using the default");
    }
 
    return {
      rows: rows
    };
  };
 
  // Acccept a file path
  Iif (!options.map && options.mapFilePath) {
    try {
      options.mapString = require(options.mapFilename)[options.filename];
    } catch (exception) {
      debug("Unable to load the map reduce " + options.mapFilePath, exception.stack);
    }
  }
 
  // Acccept a string
  Eif (!options.map && options.mapString) {
    try {
      /* jshint ignore:start */
      var emit = options.emit;
      /* jshint ignore:end */
 
      // ugly way to make sure references to 'emit' in map/reduce bind to the above emit
      /*jslint evil: true */
      eval("options.map = " + options.mapString.toString() + ";");
    } catch (exception) {
      console.log("Unable to parse the map reduce in this context, most likely eval is not permitted. (The app will function normally, you just can't run the map reduces in a custom scope.) ");
      options.map = function() {
        options.emit("error", "unable to load map reduce");
      };
    }
  }
  if (!options.reduce && options.reduceString) {
    try {
      // ugly way to make sure references to 'emit' in reduce/reduce bind to the above emit
      /*jslint evil: true */
      eval("options.reduce = " + options.reduceString.toString() + ";");
    } catch (exception) {
      console.log("Unable to parse the map reduce in this context, most likely eval is not permitted. (The app will function normally, you just can't run the map reduces in a custom scope.) ");
    }
  }
  return options;
};
 
exports.MapReduceFactory = MapReduceFactory;