<commit_before>var xtabs = require("../"),
    should = require("should");

describe("xtabs", function() {
  describe("factor", function() {
    it("a vector (array)", function() {
      var x = ["Male", "Female", "Female", "Female", "Male", "Female"],
          factoredX = xtabs.factor(x);
      factoredX.length.should.equal(x.length);
      factoredX.slice(0, x.length).should.eql([0, 1, 1, 1, 0, 1]);
      factoredX.levels.should.eql(["Male", "Female"]);
    });

    it("isFactor", function() {
      var x = ["Male", "Female", "Female", "Female", "Male", "Female"],
          factoredX = xtabs.factor(x);
      xtabs.isFactor(factoredX).should.be.true;
    });

    it("pushing", function() {
      var x = ["Male", "Female", "Female", "Female", "Male", "Female"],
          factoredX = xtabs.factor(x);
      factoredX.push(1);
      factoredX.length.should.equal(x.length + 1);
      factoredX[factoredX.length - 1].should.equal(1);
    });

    it("convert back to string", function() {
      var x = ["Male", "Female", "Female", "Female", "Male", "Female"],
          factoredX = xtabs.factor(x);
      xtabs.asString(factoredX).should.be.eql(x);
    });

    it("with NA (aka null)", function() {
      var x = ["Male", "Female", null, "Female", null, "Female"],
          factoredX = xtabs.factor(x, true);
      factoredX.levels.should.eql(["Male", "Female", null]);
      factoredX.slice(0, x.length).should.eql([0, 1, 2, 1, 2, 1]);
    });

    it("without NA (aka null)", function() {
      var x = ["Male", "Female", null, "Female", null, "Female"],
          factoredX = xtabs.factor(x);
      factoredX.levels.should.eql(["Male", "Female"]);
      factoredX.slice(0, x.length).should.eql([0, 1, null, 1, null, 1]);
    });

    it("with 'null' string and without null", function() {
      var x = ["Male", "Female", "Female", "null", "Female", "Male", null, "Female"],
          factoredX = xtabs.factor(x);
      factoredX.length.should.equal(x.length);
      factoredX.slice(0, x.length).should.eql([0, 1, 1, 2, 1, 0, null, 1]);
      factoredX.levels.should.eql(["Male", "Female", "null"]);
    });

    it("with 'null' string and with null", function() {
      var x = ["Male", "Female", "null", "Female", null, "Female"],
          factoredX = xtabs.factor(x, true);
      factoredX.levels.should.eql(["Male", "Female", "null", null]);
      factoredX.slice(0, x.length).should.eql([0, 1, 2, 1, 3, 1]);
    });

    it("convert back to string with null used", function() {
      var x = ["Male", "Female", null, "Female", null, "Female"],
          factoredX = xtabs.factor(x, true);
      xtabs.asString(factoredX).should.be.eql(x);
    });

    it("convert back to string with null not used", function() {
      var x = ["Male", "Female", null, "Female", null, "Female"],
          factoredX = xtabs.factor(x);
      xtabs.asString(factoredX).should.be.eql(x);
    });
  });

  it("diffrent length data", function() {
    var data = {
      department: ["MIS", "MIS", "HR", "TR", null, "TR", "MIS"],
      gender: ["M", "F", null, "M", "F", "M"]
    };
    (function() {
      xtabs.table(data, "department", "gender");
    }).should.throw();
  });

  it("single factor", function() {
    var x = xtabs.factor(["Male", "Female", "Female", "Female", "Male", "Female"]),
        t = xtabs.table(x);
    t.get("Male").should.equal(2);
    t.get("Female").should.equal(4);
    t.get(0).should.equal(2);
    t.get(1).should.equal(4);
    t.dim.should.eql([2]);
    t.dimnames[0].names.should.eql(["Male", "Female"]);
  });

  it("single vector (array)", function() {
    var x = ["Male", "Female", "Female", "Female", "Male", "Female"],
        t = xtabs.table(x);
    t.get("Male").should.equal(2);
    t.get("Female").should.equal(4);
    t.get(0).should.equal(2);
    t.get(1).should.equal(4);
    t.dim.should.eql([2]);
    t.dimnames[0].names.should.eql(["Male", "Female"]);
  });

  it("single factor with 'null' string", function() {
    var x = xtabs.factor(["Male", "Female", "Female", "Female", "null", "Male", null, "Female"]),
        t = xtabs.table(x);
    t.get("Male").should.equal(2);
    t.get("Female").should.equal(4);
    t.get("null").should.equal(1);
    t.get(0).should.equal(2);
    t.get(1).should.equal(4);
    t.get(2).should.equal(1);
    t.dim.should.eql([3]);
    t.dimnames[0].names.should.eql(["Male", "Female", "null"]);
  });

  it("single factor counting NA (aka null)", function() {
    var x = xtabs.factor(["Male", "Female", "Female", "Female", "null", "Male", null, "Female"]),
        t = xtabs.table(x, true);
    t.get("Male").should.equal(2);
    t.get("Female").should.equal(4);
    t.get("null").should.equal(1);
    t.get(null).should.equal(1);
    t.get(0).should.equal(2);
    t.get(1).should.equal(4);
    t.get(2).should.equal(1);
    t.get(3).should.equal(1);
    t.dim.should.eql([4]);
    t.dimnames[0].names.should.eql(["Male", "Female", "null", null]);
  });

  it("single factor not counting NA", function() {
    var x = xtabs.factor(["Male", "Female", "Female", "Female", "null", "Male", null, "Female"], true),
        t = xtabs.table(x, false);
    t.get("Male").should.equal(2);
    t.get("Female").should.equal(4);
    t.get("null").should.equal(1);
    t.get(null).should.throw();
  });

  it("data frame (an object with array members)", function() {
    var data = {
      gender: xtabs.factor(["M", "F", "M", null, "F", "M", "M"])
    };
    var t = xtabs.table(data, "gender");
    t.dim.should.eql([2]);
    t.dimnames[0].dim.should.equal("gender");
    t.dimnames[0].names.should.eql(["M", "F"]);
    t.get("M").should.equal(4);
    t.get("F").should.equal(2);
    t.get(0).should.equal(4);
    t.get(1).should.equal(2);
  });

  it("data frame with 2 variables", function() {
    var data = {
      department: xtabs.factor(["MIS", "MIS", "HR", "TR", null, "TR", "MIS"]),
      gender: xtabs.factor(["M", "F", null, "M", "F", "M", "M"])
    };
    var t = xtabs.table(data, "department", "gender");
    t.dim.should.eql([3, 2]);
    t.get("HR", "M").should.equal(0);
    t.get("HR", "F").should.equal(0);
    t.get(undefined, "M").get("TR").should.equal(2);
    t.get(undefined, "M").get(2).should.equal(2);
    t.get(0).dim.should.eql([2]);
    t.get(0, undefined).dimnames[0].names.should.eql(["M", "F"]);
  });

  it("data frame with 3 variables", function() {
    var data = {
      department: xtabs.factor(["MIS", "MIS", "HR", "TR", null, "TR", "MIS"]),
      team: xtabs.factor(["Oversea", "PO", "HR", "Tech", null, "Tech", "PO"]),
      gender: xtabs.factor(["M", "F", null, "M", "F", "M", "M"])
    };
    var t = xtabs.table(data, "department", "team", "gender");
    t.dim.should.eql([3, 4, 2]);
    t.get("TR", "Tech", "M").should.equal(2);
    t.get(undefined, "Tech", "M").get("TR").should.equal(2);
    t.get(undefined, undefined, "M").get("TR").get("Tech").should.equal(2);
  });

  it("data frame counting NA (aka null)", function() {
    var data = {
      department: xtabs.factor(["MIS", "MIS", "HR", "TR", null, "TR", "MIS"]),
      gender: xtabs.factor(["M", "F", null, "M", "F", "M", "M"])
    };
    var t = xtabs.table(data, "department", "gender", true);
    t.dim.should.eql([4, 3]);
    t.get(null).dim.should.eql([3]);
  });

  describe("Table#get", function() {
    var data = {
      department: xtabs.factor(["MIS", "MIS", "HR", "TR", null, "TR", "MIS"]),
      gender: xtabs.factor(["M", "F", null, "M", "F", "M", "M"])
    };
    var t = xtabs.table(data, "department", "gender");

    it("with no arguments", function() {
      t.get().should.equal(t);
    });

    it("with wrong variable", function() {
      (function() {
        t.get("NonExistentDepartment");
      }).should.throw();
    });

    it("different rows using variable name", function() {
      t.get(["MIS", "TR"], "M").dim.should.eql([2]);
    });

    it("different rows using variable index", function() {
      t.get([0, 2], [0, 1]).dim.should.eql([2, 2]);
    });
  });
});
<commit_msg>Added tests for addmargins function.<commit_after>var xtabs = require("../"),
    should = require("should");

describe("xtabs", function() {
  describe("factor", function() {
    it("a vector (array)", function() {
      var x = ["Male", "Female", "Female", "Female", "Male", "Female"],
          factoredX = xtabs.factor(x);
      factoredX.length.should.equal(x.length);
      factoredX.slice(0, x.length).should.eql([0, 1, 1, 1, 0, 1]);
      factoredX.levels.should.eql(["Male", "Female"]);
    });

    it("isFactor", function() {
      var x = ["Male", "Female", "Female", "Female", "Male", "Female"],
          factoredX = xtabs.factor(x);
      xtabs.isFactor(factoredX).should.be.true;
    });

    it("pushing", function() {
      var x = ["Male", "Female", "Female", "Female", "Male", "Female"],
          factoredX = xtabs.factor(x);
      factoredX.push(1);
      factoredX.length.should.equal(x.length + 1);
      factoredX[factoredX.length - 1].should.equal(1);
    });

    it("convert back to string", function() {
      var x = ["Male", "Female", "Female", "Female", "Male", "Female"],
          factoredX = xtabs.factor(x);
      xtabs.asString(factoredX).should.be.eql(x);
    });

    it("with NA (aka null)", function() {
      var x = ["Male", "Female", null, "Female", null, "Female"],
          factoredX = xtabs.factor(x, true);
      factoredX.levels.should.eql(["Male", "Female", null]);
      factoredX.slice(0, x.length).should.eql([0, 1, 2, 1, 2, 1]);
    });

    it("without NA (aka null)", function() {
      var x = ["Male", "Female", null, "Female", null, "Female"],
          factoredX = xtabs.factor(x);
      factoredX.levels.should.eql(["Male", "Female"]);
      factoredX.slice(0, x.length).should.eql([0, 1, null, 1, null, 1]);
    });

    it("with 'null' string and without null", function() {
      var x = ["Male", "Female", "Female", "null", "Female", "Male", null, "Female"],
          factoredX = xtabs.factor(x);
      factoredX.length.should.equal(x.length);
      factoredX.slice(0, x.length).should.eql([0, 1, 1, 2, 1, 0, null, 1]);
      factoredX.levels.should.eql(["Male", "Female", "null"]);
    });

    it("with 'null' string and with null", function() {
      var x = ["Male", "Female", "null", "Female", null, "Female"],
          factoredX = xtabs.factor(x, true);
      factoredX.levels.should.eql(["Male", "Female", "null", null]);
      factoredX.slice(0, x.length).should.eql([0, 1, 2, 1, 3, 1]);
    });

    it("convert back to string with null used", function() {
      var x = ["Male", "Female", null, "Female", null, "Female"],
          factoredX = xtabs.factor(x, true);
      xtabs.asString(factoredX).should.be.eql(x);
    });

    it("convert back to string with null not used", function() {
      var x = ["Male", "Female", null, "Female", null, "Female"],
          factoredX = xtabs.factor(x);
      xtabs.asString(factoredX).should.be.eql(x);
    });
  });

  it("diffrent length data", function() {
    var data = {
      department: ["MIS", "MIS", "HR", "TR", null, "TR", "MIS"],
      gender: ["M", "F", null, "M", "F", "M"]
    };
    (function() {
      xtabs.table(data, "department", "gender");
    }).should.throw();
  });

  it("single factor", function() {
    var x = xtabs.factor(["Male", "Female", "Female", "Female", "Male", "Female"]),
        t = xtabs.table(x);
    t.get("Male").should.equal(2);
    t.get("Female").should.equal(4);
    t.get(0).should.equal(2);
    t.get(1).should.equal(4);
    t.dim.should.eql([2]);
    t.dimnames[0].names.should.eql(["Male", "Female"]);
  });

  it("single vector (array)", function() {
    var x = ["Male", "Female", "Female", "Female", "Male", "Female"],
        t = xtabs.table(x);
    t.get("Male").should.equal(2);
    t.get("Female").should.equal(4);
    t.get(0).should.equal(2);
    t.get(1).should.equal(4);
    t.dim.should.eql([2]);
    t.dimnames[0].names.should.eql(["Male", "Female"]);
  });

  it("single factor with 'null' string", function() {
    var x = xtabs.factor(["Male", "Female", "Female", "Female", "null", "Male", null, "Female"]),
        t = xtabs.table(x);
    t.get("Male").should.equal(2);
    t.get("Female").should.equal(4);
    t.get("null").should.equal(1);
    t.get(0).should.equal(2);
    t.get(1).should.equal(4);
    t.get(2).should.equal(1);
    t.dim.should.eql([3]);
    t.dimnames[0].names.should.eql(["Male", "Female", "null"]);
  });

  it("single factor counting NA (aka null)", function() {
    var x = xtabs.factor(["Male", "Female", "Female", "Female", "null", "Male", null, "Female"]),
        t = xtabs.table(x, true);
    t.get("Male").should.equal(2);
    t.get("Female").should.equal(4);
    t.get("null").should.equal(1);
    t.get(null).should.equal(1);
    t.get(0).should.equal(2);
    t.get(1).should.equal(4);
    t.get(2).should.equal(1);
    t.get(3).should.equal(1);
    t.dim.should.eql([4]);
    t.dimnames[0].names.should.eql(["Male", "Female", "null", null]);
  });

  it("single factor not counting NA", function() {
    var x = xtabs.factor(["Male", "Female", "Female", "Female", "null", "Male", null, "Female"], true),
        t = xtabs.table(x, false);
    t.get("Male").should.equal(2);
    t.get("Female").should.equal(4);
    t.get("null").should.equal(1);
    t.get(null).should.throw();
  });

  it("data frame (an object with array members)", function() {
    var data = {
      gender: xtabs.factor(["M", "F", "M", null, "F", "M", "M"])
    };
    var t = xtabs.table(data, "gender");
    t.dim.should.eql([2]);
    t.dimnames[0].dim.should.equal("gender");
    t.dimnames[0].names.should.eql(["M", "F"]);
    t.get("M").should.equal(4);
    t.get("F").should.equal(2);
    t.get(0).should.equal(4);
    t.get(1).should.equal(2);
  });

  it("data frame with 2 variables", function() {
    var data = {
      department: xtabs.factor(["MIS", "MIS", "HR", "TR", null, "TR", "MIS"]),
      gender: xtabs.factor(["M", "F", null, "M", "F", "M", "M"])
    };
    var t = xtabs.table(data, "department", "gender");
    t.dim.should.eql([3, 2]);
    t.get("HR", "M").should.equal(0);
    t.get("HR", "F").should.equal(0);
    t.get(undefined, "M").get("TR").should.equal(2);
    t.get(undefined, "M").get(2).should.equal(2);
    t.get(0).dim.should.eql([2]);
    t.get(0, undefined).dimnames[0].names.should.eql(["M", "F"]);
  });

  it("data frame with 3 variables", function() {
    var data = {
      department: xtabs.factor(["MIS", "MIS", "HR", "TR", null, "TR", "MIS"]),
      team: xtabs.factor(["Oversea", "PO", "HR", "Tech", null, "Tech", "PO"]),
      gender: xtabs.factor(["M", "F", null, "M", "F", "M", "M"])
    };
    var t = xtabs.table(data, "department", "team", "gender");
    t.dim.should.eql([3, 4, 2]);
    t.get("TR", "Tech", "M").should.equal(2);
    t.get(undefined, "Tech", "M").get("TR").should.equal(2);
    t.get(undefined, undefined, "M").get("TR").get("Tech").should.equal(2);
  });

  it("data frame counting NA (aka null)", function() {
    var data = {
      department: xtabs.factor(["MIS", "MIS", "HR", "TR", null, "TR", "MIS"]),
      gender: xtabs.factor(["M", "F", null, "M", "F", "M", "M"])
    };
    var t = xtabs.table(data, "department", "gender", true);
    t.dim.should.eql([4, 3]);
    t.get(null).dim.should.eql([3]);
  });

  describe("Table#get", function() {
    var data = {
      department: xtabs.factor(["MIS", "MIS", "HR", "TR", null, "TR", "MIS"]),
      gender: xtabs.factor(["M", "F", null, "M", "F", "M", "M"])
    };
    var t = xtabs.table(data, "department", "gender");

    it("with no arguments", function() {
      t.get().should.equal(t);
    });

    it("with wrong variable", function() {
      (function() {
        t.get("NonExistentDepartment");
      }).should.throw();
    });

    it("different rows using variable name", function() {
      t.get(["MIS", "TR"], "M").dim.should.eql([2]);
    });

    it("different rows using variable index", function() {
      t.get([0, 2], [0, 1]).dim.should.eql([2, 2]);
    });
  });

  describe("addmargins", function() {
    var data = {
      department: xtabs.factor(["MIS", "MIS", "HR", "TR", null, "TR", "MIS"]),
      team: xtabs.factor(["Oversea", "PO", "HR", "Tech", null, "Tech", "PO"]),
      gender: xtabs.factor(["M", "F", null, "M", "F", "M", "M"])
    };
    var t = xtabs.table(data, "department", "team", "gender");

    it("add a row sum", function() {
      var table = xtabs.addmargins(t, [0, 1], {n: "sum", f: jstat.sum});
      var misSum = table.get("MIS", "sum");
      misSum.get("M").should.equal(2);
      var techSum = table.get("sum", "Tech");
      techSum.get("M").should.equal(2);
      techSum.get("F").should.equal(0);
    });

    it("add a row sum and mean", function() {
      var table = xtabs.addmargins(t, [0, 1], [[{n: "sum", f: jstat.sum}, {n: "mean", f: jstat.mean}], [{n: "sum", f: jstat.sum}]]);
      var misSum = table.get("MIS", "sum");
      misSum.get("M").should.equal(2);
      var techSum = table.get("sum", "Tech");
      techSum.get("M").should.equal(2);
      techSum.get("F").should.equal(0);
    });
  });
});
<|endoftext|>


<commit_before>const { COINS } = require('@lamassu/coins')
const _ = require('lodash/fp')

const kraken = require('../exchange/kraken')
const bitstamp = require('../exchange/bitstamp')
const itbit = require('../exchange/itbit')
const binanceus = require('../exchange/binanceus')
const cex = require('../exchange/cex')
const ftx = require('../exchange/ftx')
const bitpay = require('../ticker/bitpay')
const binance = require('../exchange/binance')

const { BTC, BCH, DASH, ETH, LTC, ZEC } = COINS

const ALL = {
  cex: cex,
  ftx: ftx,
  binanceus: binanceus,
  kraken: kraken,
  bitstamp: bitstamp,
  itbit: itbit,
  bitpay: bitpay,
  coinbase: {
    CRYPTO: [BTC, ETH, LTC, DASH, ZEC, BCH],
    FIAT: 'ALL_CURRENCIES'
  },
  binance: binance
}

function buildMarket (fiatCode, cryptoCode, serviceName) {
  if (!_.includes(cryptoCode, ALL[serviceName].CRYPTO)) {
    throw new Error('Unsupported crypto: ' + cryptoCode)
  }
  const fiatSupported = ALL[serviceName].FIAT
  if (fiatSupported !== 'ALL_CURRENCIES' && !_.includes(fiatCode, fiatSupported)) {
    throw new Error('Unsupported fiat: ' + fiatCode)
  }
  return cryptoCode + '/' + fiatCode
}

function verifyFiatSupport (fiatCode, serviceName) {
  const fiat = ALL[serviceName].FIAT
  return fiat === 'ALL_CURRENCIES' ? true : _.includes(fiatCode, fiat)
}

function isConfigValid (config, fields) {
  const values = _.map(it => _.get(it)(config))(fields)
  return _.every(it => it || it === 0)(values)
}

module.exports = { buildMarket, ALL, verifyFiatSupport, isConfigValid }
<commit_msg>fix: set default market for fiat exchanges to EUR<commit_after>const { COINS } = require('@lamassu/coins')
const _ = require('lodash/fp')

const kraken = require('../exchange/kraken')
const bitstamp = require('../exchange/bitstamp')
const itbit = require('../exchange/itbit')
const binanceus = require('../exchange/binanceus')
const cex = require('../exchange/cex')
const ftx = require('../exchange/ftx')
const bitpay = require('../ticker/bitpay')
const binance = require('../exchange/binance')
const logger = require('../../logger')

const { BTC, BCH, DASH, ETH, LTC, ZEC } = COINS

const ALL = {
  cex: cex,
  ftx: ftx,
  binanceus: binanceus,
  kraken: kraken,
  bitstamp: bitstamp,
  itbit: itbit,
  bitpay: bitpay,
  coinbase: {
    CRYPTO: [BTC, ETH, LTC, DASH, ZEC, BCH],
    FIAT: 'ALL_CURRENCIES'
  },
  binance: binance
}

function buildMarket (fiatCode, cryptoCode, serviceName) {
  if (!_.includes(cryptoCode, ALL[serviceName].CRYPTO)) {
    throw new Error('Unsupported crypto: ' + cryptoCode)
  }
  const fiatSupported = ALL[serviceName].FIAT
  if (fiatSupported !== 'ALL_CURRENCIES' && !_.includes(fiatCode, fiatSupported)) {
    logger.info('Building a market for an unsupported fiat. Defaulting to EUR market')
    return cryptoCode + '/' + 'EUR'
  }
  return cryptoCode + '/' + fiatCode
}

function verifyFiatSupport (fiatCode, serviceName) {
  const fiat = ALL[serviceName].FIAT
  return fiat === 'ALL_CURRENCIES' ? true : _.includes(fiatCode, fiat)
}

function isConfigValid (config, fields) {
  const values = _.map(it => _.get(it)(config))(fields)
  return _.every(it => it || it === 0)(values)
}

module.exports = { buildMarket, ALL, verifyFiatSupport, isConfigValid }
<|endoftext|>


<commit_before>const { dirname } = require('path')

const ExtendableError = require('es6-error')
const findUp = require('find-up')
const readPkg = require('read-pkg')

const caches = require('./caches')

const KNOWN_LINTERS = new Set([
  'happiness',
  'onelint',
  'semistandard',
  'standard',
  'uber-standard'
])

class MissingLinterError extends ExtendableError {
  constructor (message = 'No supported linter found') {
    super(message)
  }
}

class MissingPackageError extends ExtendableError {
  constructor (message = 'No package.json found') {
    super(message)
  }
}

const packagePaths = caches.add(new Map())
const options = caches.add(new Map())

function getPackagePath (file) {
  const cwd = dirname(file)
  if (packagePaths.has(cwd)) return packagePaths.get(cwd)

  const promise = findUp('package.json', { cwd })
  packagePaths.set(cwd, promise)
  promise.then(packagePath => {
    if (!packagePath && packagePaths.get(cwd) === promise) {
      packagePaths.delete(cwd)
    }
  })
  return promise
}

function getOptions (packagePath) {
  if (options.has(packagePath)) return options.get(packagePath)

  const promise = (packagePath && readPkg(packagePath) || Promise.resolve(null))
    .then(pkg => {
      if (!pkg) throw new MissingPackageError()
      const standardEngineConfiguration = {}

      // Parse standard-engine configuration
      if (pkg['standard-engine']) {
        if (typeof pkg['standard-engine'] === 'string') {
          standardEngineConfiguration.linterName = pkg['standard-engine']
        }

        const isSkip = pkg['standard-engine'].skipPackageJson
        standardEngineConfiguration.skipPackageJson = (isSkip || false)
      }

      // Try to find another package.json up the tree
      if (standardEngineConfiguration.skipPackageJson) {
        return getPackagePath(dirname(packagePath)).then(getOptions)
      }

      // Try to find default linterName
      if (!standardEngineConfiguration.linterName) {
        if (pkg.devDependencies) {
          for (const dep of Object.keys(pkg.devDependencies)) {
            if (KNOWN_LINTERS.has(dep)) {
              standardEngineConfiguration.linterName = dep
              break
            }
          }
        }
      }

      if (!standardEngineConfiguration.linterName) {
        throw new MissingLinterError()
      }

      // Support scoped packages. Assume their `cmd` (and thus options key) is
      // configured as the package name *without* the scope prefix.
      let optionsKey = standardEngineConfiguration.linterName
      if (optionsKey.includes('/')) {
        optionsKey = optionsKey.split('/')[1]
      }

      const options = pkg[optionsKey] || {}
      const { ignore: ignoreGlobs = [] } = options

      return {
        ignoreGlobs,
        linterName: standardEngineConfiguration.linterName,
        projectRoot: dirname(packagePath)
      }
    })
  options.set(packagePath, promise)
  promise.catch(() => options.delete(packagePath))
  return promise
}

function findOptions (file) {
  return getPackagePath(file).then(getOptions)
}

exports = module.exports = findOptions

exports.MissingLinterError = MissingLinterError
exports.MissingPackageError = MissingPackageError
<commit_msg>:ok_hand: simplify options parsing<commit_after>const { dirname } = require('path')

const ExtendableError = require('es6-error')
const findUp = require('find-up')
const readPkg = require('read-pkg')

const caches = require('./caches')

const KNOWN_LINTERS = new Set([
  'happiness',
  'onelint',
  'semistandard',
  'standard',
  'uber-standard'
])

class MissingLinterError extends ExtendableError {
  constructor (message = 'No supported linter found') {
    super(message)
  }
}

class MissingPackageError extends ExtendableError {
  constructor (message = 'No package.json found') {
    super(message)
  }
}

const packagePaths = caches.add(new Map())
const options = caches.add(new Map())

function getPackagePath (file) {
  const cwd = dirname(file)
  if (packagePaths.has(cwd)) return packagePaths.get(cwd)

  const promise = findUp('package.json', { cwd })
  packagePaths.set(cwd, promise)
  promise.then(packagePath => {
    if (!packagePath && packagePaths.get(cwd) === promise) {
      packagePaths.delete(cwd)
    }
  })
  return promise
}

function getOptions (packagePath) {
  if (options.has(packagePath)) return options.get(packagePath)

  const promise = (packagePath && readPkg(packagePath) || Promise.resolve(null))
    .then(pkg => {
      if (!pkg) throw new MissingPackageError()

      const defaultOpts = {
        linterName: undefined,
        skipPackageJson: false
      }
      let opts = pkg['standard-engine'] || defaultOpts
      if (typeof opts === 'string') {
        opts = Object.assign({}, defaultOpts, {
          linterName: pkg['standard-engine']
        })
      } else if (typeof opts === 'object') {
        opts.skipPackageJson = (opts.skipPackageJson === true)
      }

      // Try to find another package.json up the tree
      if (opts.skipPackageJson) {
        return getPackagePath(dirname(packagePath)).then(getOptions)
      }

      // Try to find default linterName
      if (!opts.linterName) {
        if (pkg.devDependencies) {
          for (const dep of Object.keys(pkg.devDependencies)) {
            if (KNOWN_LINTERS.has(dep)) {
              opts.linterName = dep
              break
            }
          }
        }
      }

      if (!opts.linterName) {
        throw new MissingLinterError()
      }

      // Support scoped packages. Assume their `cmd` (and thus options key) is
      // configured as the package name *without* the scope prefix.
      let optionsKey = opts.linterName
      if (optionsKey.includes('/')) {
        optionsKey = optionsKey.split('/')[1]
      }

      const options = pkg[optionsKey] || {}
      const { ignore: ignoreGlobs = [] } = options

      return {
        ignoreGlobs,
        linterName: opts.linterName,
        projectRoot: dirname(packagePath)
      }
    })
  options.set(packagePath, promise)
  promise.catch(() => options.delete(packagePath))
  return promise
}

function findOptions (file) {
  return getPackagePath(file).then(getOptions)
}

exports = module.exports = findOptions

exports.MissingLinterError = MissingLinterError
exports.MissingPackageError = MissingPackageError
<|endoftext|>


<commit_before>// @flow
import * as electron from 'electron';
import fs from 'fs';
import fsx from 'fs-extra';
import childProcess from 'child_process';
import { getTempDir, isDevelopment, isWindows, PLUGIN_PATH } from '../common/constants';
import mkdirp from 'mkdirp';
import path from 'path';

export default async function(lookupName: string): Promise<void> {
  return new Promise(async (resolve, reject) => {
    let info: Object = {};
    try {
      info = await _isInsomniaPlugin(lookupName);

      // Get actual module name without version suffixes and things
      const moduleName = info.name;

      const pluginDir = path.join(PLUGIN_PATH, moduleName);

      // Make plugin directory
      mkdirp.sync(pluginDir);

      // Download the module
      const request = electron.remote.net.request(info.dist.tarball);
      request.on('error', err => {
        reject(new Error(`Failed to make plugin request ${info.dist.tarball}: ${err.message}`));
      });

      const { tmpDir } = await _installPluginToTmpDir(lookupName);
      console.log(`[plugins] Moving plugin from ${tmpDir} to ${pluginDir}`);

      // Move entire module to plugins folder
      fsx.moveSync(path.join(tmpDir, moduleName), pluginDir, {
        overwrite: true,
      });

      // Move each dependency into node_modules folder
      const pluginModulesDir = path.join(pluginDir, 'node_modules');
      mkdirp.sync(pluginModulesDir);
      for (const name of fs.readdirSync(tmpDir)) {
        const src = path.join(tmpDir, name);
        if (name === moduleName || !fs.statSync(src).isDirectory()) {
          continue;
        }

        const dest = path.join(pluginModulesDir, name);
        fsx.moveSync(src, dest, { overwrite: true });
      }
    } catch (err) {
      reject(err);
      return;
    }

    resolve();
  });
}

async function _isInsomniaPlugin(lookupName: string): Promise<Object> {
  return new Promise((resolve, reject) => {
    console.log(`[plugins] Fetching module info from npm`);
    childProcess.execFile(
      escape(process.execPath),
      [
        '--no-deprecation', // Because Yarn still uses `new Buffer()`
        _getYarnPath(),
        'info',
        lookupName,
        '--json',
      ],
      {
        timeout: 5 * 60 * 1000,
        maxBuffer: 1024 * 1024,
        shell: true,
        env: {
          NODE_ENV: 'production',
          ELECTRON_RUN_AS_NODE: 'true',
        },
      },
      (err, stdout, stderr) => {
        if (stderr) {
          reject(new Error(`Yarn error ${stderr.toString()}`));
          return;
        }

        let yarnOutput;
        try {
          yarnOutput = JSON.parse(stdout.toString());
        } catch (ex) {
          // Output is not JSON. Check if yarn/electron terminated with non-zero exit code.
          // In certain environments electron can exit with error even if output is OK.
          // Parsing is attemted before checking exit code as workaround for false errors.
          if (err) {
            reject(new Error(`${lookupName} npm error: ${err.message}`));
          } else {
            reject(new Error(`Yarn response not JSON: ${ex.message}`));
          }
          return;
        }

        const data = yarnOutput.data;
        if (!data.hasOwnProperty('insomnia')) {
          reject(new Error(`"${lookupName}" not a plugin! Package missing "insomnia" attribute`));
          return;
        }

        console.log(`[plugins] Detected Insomnia plugin ${data.name}`);

        resolve({
          insomnia: data.insomnia,
          name: data.name,
          version: data.version,
          dist: {
            shasum: data.dist.shasum,
            tarball: data.dist.tarball,
          },
        });
      },
    );
  });
}

async function _installPluginToTmpDir(lookupName: string): Promise<{ tmpDir: string }> {
  return new Promise((resolve, reject) => {
    const tmpDir = path.join(getTempDir(), `${lookupName}-${Date.now()}`);
    mkdirp.sync(tmpDir);
    console.log(`[plugins] Installing plugin to ${tmpDir}`);
    childProcess.execFile(
      escape(process.execPath),
      [
        '--no-deprecation', // Because Yarn still uses `new Buffer()`
        _getYarnPath(),
        'add',
        lookupName,
        '--modules-folder',
        escape(tmpDir),
        '--cwd',
        escape(tmpDir),
        '--no-lockfile',
        '--production',
        '--no-progress',
      ],
      {
        timeout: 5 * 60 * 1000,
        maxBuffer: 1024 * 1024,
        cwd: tmpDir,
        shell: true, // Some package installs require a shell
        env: {
          NODE_ENV: 'production',
          ELECTRON_RUN_AS_NODE: 'true',
        },
      },
      (err, stdout, stderr) => {
        // Check yarn/electron process exit code.
        // In certain environments electron can exit with error even if the command was perfomed sucesfully.
        // Checking for sucess message in output is a workaround for false errors.
        if (err && !stdout.toString().includes('success')) {
          reject(new Error(`${lookupName} install error: ${err.message}`));
          return;
        }

        if (stderr) {
          reject(new Error(`Yarn error ${stderr.toString()}`));
          return;
        }

        resolve({ tmpDir });
      },
    );
  });
}

function _getYarnPath() {
  const { app } = electron.remote || electron;

  // TODO: This is brittle. Make finding this more robust.
  if (isDevelopment()) {
    return path.resolve(app.getAppPath(), './bin/yarn-standalone.js');
  } else {
    return path.resolve(app.getAppPath(), '../bin/yarn-standalone.js');
  }
}

function escape(p) {
  if (isWindows()) {
    // Quote for Windows paths
    return `"${p}"`;
  } else {
    // Escape with backslashes for Unix paths
    return p.replace(/(\s+)/g, '\\$1');
  }
}
<commit_msg>Escape yarn paths (#1699)<commit_after>// @flow
import * as electron from 'electron';
import fs from 'fs';
import fsx from 'fs-extra';
import childProcess from 'child_process';
import { getTempDir, isDevelopment, isWindows, PLUGIN_PATH } from '../common/constants';
import mkdirp from 'mkdirp';
import path from 'path';

export default async function(lookupName: string): Promise<void> {
  return new Promise(async (resolve, reject) => {
    let info: Object = {};
    try {
      info = await _isInsomniaPlugin(lookupName);

      // Get actual module name without version suffixes and things
      const moduleName = info.name;

      const pluginDir = path.join(PLUGIN_PATH, moduleName);

      // Make plugin directory
      mkdirp.sync(pluginDir);

      // Download the module
      const request = electron.remote.net.request(info.dist.tarball);
      request.on('error', err => {
        reject(new Error(`Failed to make plugin request ${info.dist.tarball}: ${err.message}`));
      });

      const { tmpDir } = await _installPluginToTmpDir(lookupName);
      console.log(`[plugins] Moving plugin from ${tmpDir} to ${pluginDir}`);

      // Move entire module to plugins folder
      fsx.moveSync(path.join(tmpDir, moduleName), pluginDir, {
        overwrite: true,
      });

      // Move each dependency into node_modules folder
      const pluginModulesDir = path.join(pluginDir, 'node_modules');
      mkdirp.sync(pluginModulesDir);
      for (const name of fs.readdirSync(tmpDir)) {
        const src = path.join(tmpDir, name);
        if (name === moduleName || !fs.statSync(src).isDirectory()) {
          continue;
        }

        const dest = path.join(pluginModulesDir, name);
        fsx.moveSync(src, dest, { overwrite: true });
      }
    } catch (err) {
      reject(err);
      return;
    }

    resolve();
  });
}

async function _isInsomniaPlugin(lookupName: string): Promise<Object> {
  return new Promise((resolve, reject) => {
    console.log(`[plugins] Fetching module info from npm`);
    childProcess.execFile(
      escape(process.execPath),
      [
        '--no-deprecation', // Because Yarn still uses `new Buffer()`
        escape(_getYarnPath()),
        'info',
        lookupName,
        '--json',
      ],
      {
        timeout: 5 * 60 * 1000,
        maxBuffer: 1024 * 1024,
        shell: true,
        env: {
          NODE_ENV: 'production',
          ELECTRON_RUN_AS_NODE: 'true',
        },
      },
      (err, stdout, stderr) => {
        if (stderr) {
          reject(new Error(`Yarn error ${stderr.toString()}`));
          return;
        }

        let yarnOutput;
        try {
          yarnOutput = JSON.parse(stdout.toString());
        } catch (ex) {
          // Output is not JSON. Check if yarn/electron terminated with non-zero exit code.
          // In certain environments electron can exit with error even if output is OK.
          // Parsing is attemted before checking exit code as workaround for false errors.
          if (err) {
            reject(new Error(`${lookupName} npm error: ${err.message}`));
          } else {
            reject(new Error(`Yarn response not JSON: ${ex.message}`));
          }
          return;
        }

        const data = yarnOutput.data;
        if (!data.hasOwnProperty('insomnia')) {
          reject(new Error(`"${lookupName}" not a plugin! Package missing "insomnia" attribute`));
          return;
        }

        console.log(`[plugins] Detected Insomnia plugin ${data.name}`);

        resolve({
          insomnia: data.insomnia,
          name: data.name,
          version: data.version,
          dist: {
            shasum: data.dist.shasum,
            tarball: data.dist.tarball,
          },
        });
      },
    );
  });
}

async function _installPluginToTmpDir(lookupName: string): Promise<{ tmpDir: string }> {
  return new Promise((resolve, reject) => {
    const tmpDir = path.join(getTempDir(), `${lookupName}-${Date.now()}`);
    mkdirp.sync(tmpDir);
    console.log(`[plugins] Installing plugin to ${tmpDir}`);
    childProcess.execFile(
      escape(process.execPath),
      [
        '--no-deprecation', // Because Yarn still uses `new Buffer()`
        escape(_getYarnPath()),
        'add',
        lookupName,
        '--modules-folder',
        escape(tmpDir),
        '--cwd',
        escape(tmpDir),
        '--no-lockfile',
        '--production',
        '--no-progress',
      ],
      {
        timeout: 5 * 60 * 1000,
        maxBuffer: 1024 * 1024,
        cwd: tmpDir,
        shell: true, // Some package installs require a shell
        env: {
          NODE_ENV: 'production',
          ELECTRON_RUN_AS_NODE: 'true',
        },
      },
      (err, stdout, stderr) => {
        // Check yarn/electron process exit code.
        // In certain environments electron can exit with error even if the command was perfomed sucesfully.
        // Checking for sucess message in output is a workaround for false errors.
        if (err && !stdout.toString().includes('success')) {
          reject(new Error(`${lookupName} install error: ${err.message}`));
          return;
        }

        if (stderr) {
          reject(new Error(`Yarn error ${stderr.toString()}`));
          return;
        }

        resolve({ tmpDir });
      },
    );
  });
}

function _getYarnPath() {
  const { app } = electron.remote || electron;

  // TODO: This is brittle. Make finding this more robust.
  if (isDevelopment()) {
    return path.resolve(app.getAppPath(), './bin/yarn-standalone.js');
  } else {
    return path.resolve(app.getAppPath(), '../bin/yarn-standalone.js');
  }
}

function escape(p) {
  if (isWindows()) {
    // Quote for Windows paths
    return `"${p}"`;
  } else {
    // Escape with backslashes for Unix paths
    return p.replace(/(\s+)/g, '\\$1');
  }
}
<|endoftext|>


<commit_before>module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    pkg: '<json:package.json>',
    test: {
      files: ['test/**/*.js']
    },
    lint: {
      files: ['grunt.js', 'lib/**/*.js', 'test/**/*.js']
    },
    watch: {
      files: '<config:lint.files>',
      tasks: 'default'
    },
    jshint: {
      options: {
        curly: true,
        eqeqeq: true,
        immed: true,
        latedef: true,
        newcap: true,
        noarg: true,
        sub: true,
        undef: true,
        boss: true,
        eqnull: true,
        node: true
      },
      globals: {
        exports: true
      }
    }
  });

  // Default task.
  grunt.registerTask('default', 'lint test');

};<commit_msg>switched of jshint error for immediately invoked functions<commit_after>module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    pkg: '<json:package.json>',
    test: {
      files: ['test/**/*.js']
    },
    lint: {
      files: ['grunt.js', 'lib/**/*.js', 'test/**/*.js']
    },
    watch: {
      files: '<config:lint.files>',
      tasks: 'default'
    },
    jshint: {
      options: {
        curly: true,
        eqeqeq: true,
        immed: false,
        latedef: true,
        newcap: true,
        noarg: true,
        sub: true,
        undef: true,
        boss: true,
        eqnull: true,
        node: true
      },
      globals: {
        exports: true
      }
    }
  });

  // Default task.
  grunt.registerTask('default', 'lint test');

};
<|endoftext|>


<commit_before>#!/usr/bin/env node
const {join} = require('path')
const filesize = require('filesize')
const {table} = require('table')

// ensure that K and B values line up vertically
const filesizeConfig = {symbols: {KB: 'K'}}
const prettySize = bytes => filesize(bytes, filesizeConfig)

function getBundles(path) {
  const meta = require(join(path, './dist/meta.json'))
  let metaBundles = Object.values(meta.bundles)

  // fitler out support bundles, since they don't generate CSS
  metaBundles = metaBundles.filter(bundle => !isSupportBundleName(bundle.name))
  const bundles = {}
  for (const bundle of metaBundles) {
    const entry = {
      name: bundle.name,
      path: bundle.css,
      stats: require(join(path, `./${bundle.stats}`))
    }
    bundles[bundle.name] = entry
  }

  return bundles
}

const tableOptions = {
  singleLine: true,
  border: {
    topBody: '',
    topJoin: '',
    topLeft: '',
    topRight: '',

    bottomBody: '',
    bottomJoin: '',
    bottomLeft: '',
    bottomRight: '',

    bodyLeft: '|',
    bodyRight: '|',
    bodyJoin: '|',

    joinBody: '',
    joinLeft: '',
    joinRight: '',
    joinJoin: ''
  }
}

// const sortByName = (a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0)
const sortByGZipSize = (a, b) => b[3] - a[3]
// const sortByRawSize = (a, b) => b[5] - a[5]
const posNeg = v => (v > 0 ? '+ ' : v < 0 ? '- ' : '')

;(async () => {
  const currentBundles = getBundles(join(__dirname, '../'))
  const latestBundles = getBundles(join(__dirname, '../tmp/node_modules/@primer/css/'))

  let data = []

  // Build the rows
  for (const name of Object.keys(currentBundles)) {
    const current = currentBundles[name]
    const latest = latestBundles[name]
    data.push([
      current.name,
      current.stats.selectors.total,
      current.stats.selectors.total - latest.stats.selectors.total,
      current.stats.gzipSize,
      current.stats.gzipSize - latest.stats.gzipSize,
      current.stats.size,
      current.stats.size - latest.stats.size,
      current.path
    ])
  }

  // Sort the data
  data = data.sort(sortByGZipSize)

  // Beautify the data
  data = data.map(row => {
    row[2] = posNeg(row[2]) + `${row[2]}`.replace('-', '')
    row[3] = prettySize(row[3])
    row[4] = posNeg(row[4]) + `${prettySize(row[4])}`.replace('-', '')
    row[5] = prettySize(row[5])
    row[6] = posNeg(row[6]) + `${prettySize(row[6])}`.replace('-', '')
    return row
  })

  // Adding header
  data = [
    ['name', 'selectors', '±', 'gzip size', '±', 'raw size', '±', 'path'],
    [':-', '-:', '-:', '-:', '-:', '-:', '-:', ':-']
  ].concat(data)

  console.log(table(data, tableOptions))
})()

function isSupportBundleName(bundleName) {
  // "support", "marketing-support", and any future ones?
  return bundleName.endsWith('support')
}
<commit_msg>Don't show bundles that haven't changed<commit_after>#!/usr/bin/env node
const {join} = require('path')
const filesize = require('filesize')
const {table} = require('table')

// ensure that K and B values line up vertically
const filesizeConfig = {symbols: {KB: 'K'}}
const prettySize = bytes => filesize(bytes, filesizeConfig)

function getBundles(path) {
  const meta = require(join(path, './dist/meta.json'))
  let metaBundles = Object.values(meta.bundles)

  // fitler out support bundles, since they don't generate CSS
  metaBundles = metaBundles.filter(bundle => !isSupportBundleName(bundle.name))
  const bundles = {}
  for (const bundle of metaBundles) {
    const entry = {
      name: bundle.name,
      path: bundle.css,
      stats: require(join(path, `./${bundle.stats}`))
    }
    bundles[bundle.name] = entry
  }

  return bundles
}

const tableOptions = {
  singleLine: true,
  border: {
    topBody: '',
    topJoin: '',
    topLeft: '',
    topRight: '',

    bottomBody: '',
    bottomJoin: '',
    bottomLeft: '',
    bottomRight: '',

    bodyLeft: '|',
    bodyRight: '|',
    bodyJoin: '|',

    joinBody: '',
    joinLeft: '',
    joinRight: '',
    joinJoin: ''
  }
}

// const sortByName = (a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0)
const sortByGZipSize = (a, b) => b[3] - a[3]
// const sortByRawSize = (a, b) => b[5] - a[5]
const posNeg = v => (v > 0 ? '+ ' : v < 0 ? '- ' : '')

;(async () => {
  const currentBundles = getBundles(join(__dirname, '../'))
  const latestBundles = getBundles(join(__dirname, '../tmp/node_modules/@primer/css/'))

  let data = []

  // Build the rows
  for (const name of Object.keys(currentBundles)) {
    const current = currentBundles[name]
    const latest = latestBundles[name]

    const delta = [
      current.stats.selectors.total - latest.stats.selectors.total,
      current.stats.gzipSize - latest.stats.gzipSize,
      current.stats.size - latest.stats.size
    ].reduce((a, b) => a + b, 0)

    if (delta !== 0) {
      data.push([
        current.name,
        current.stats.selectors.total,
        current.stats.selectors.total - latest.stats.selectors.total,
        current.stats.gzipSize,
        current.stats.gzipSize - latest.stats.gzipSize,
        current.stats.size,
        current.stats.size - latest.stats.size
      ])
    }
  }

  // Sort the data
  data = data.sort(sortByGZipSize)

  // Beautify the data
  data = data.map(row => {
    row[2] = posNeg(row[2]) + `${row[2]}`.replace('-', '')
    row[3] = prettySize(row[3])
    row[4] = posNeg(row[4]) + `${prettySize(row[4])}`.replace('-', '')
    row[5] = prettySize(row[5])
    row[6] = posNeg(row[6]) + `${prettySize(row[6])}`.replace('-', '')
    return row
  })

  // Adding header
  data = [
    ['name', 'selectors', '±', 'gzip size', '±', 'raw size', '±'],
    [':-', '-:', '-:', '-:', '-:', '-:', '-:']
  ].concat(data)

  console.log(table(data, tableOptions))
})()

function isSupportBundleName(bundleName) {
  // "support", "marketing-support", and any future ones?
  return bundleName.endsWith('support')
}
<|endoftext|>


<commit_before>// NNTP group
//

'use strict';

const Mongoose = require('mongoose');
const Schema   = Mongoose.Schema;

module.exports = function (N, collectionName) {

  let Group = new Schema({
    name:       String,
    source:     Schema.ObjectId,

    // content type (usually, 'forum')
    type:       String,

    // min visible post index (default max_index+1 means that group is empty)
    min_index:  { type: Number, 'default': 1 },

    // max visible post index
    max_index:  { type: Number, 'default': 0 },

    // Message counter. We can't use min/max directly,
    // because last message can be deleted.
    last_index: { type: Number, 'default': 0 }
  }, {
    versionKey: false
  });

  // Indexes
  //////////////////////////////////////////////////////////////////////////////

  // find a group by name (GROUP command)
  Group.index({ name: 1 });

  // find a group for a forum section
  Group.index({ source: 1 });

  N.wire.on('init:models', function emit_init_Group() {
    return N.wire.emit('init:models.' + collectionName, Group);
  });

  N.wire.on('init:models.' + collectionName, function init_model_Group(schema) {
    N.models[collectionName] = Mongoose.model(collectionName, schema);
  });
};
<commit_msg>eslint: change quote-props rule, run eslint --fix<commit_after>// NNTP group
//

'use strict';

const Mongoose = require('mongoose');
const Schema   = Mongoose.Schema;

module.exports = function (N, collectionName) {

  let Group = new Schema({
    name:       String,
    source:     Schema.ObjectId,

    // content type (usually, 'forum')
    type:       String,

    // min visible post index (default max_index+1 means that group is empty)
    min_index:  { type: Number, default: 1 },

    // max visible post index
    max_index:  { type: Number, default: 0 },

    // Message counter. We can't use min/max directly,
    // because last message can be deleted.
    last_index: { type: Number, default: 0 }
  }, {
    versionKey: false
  });

  // Indexes
  //////////////////////////////////////////////////////////////////////////////

  // find a group by name (GROUP command)
  Group.index({ name: 1 });

  // find a group for a forum section
  Group.index({ source: 1 });

  N.wire.on('init:models', function emit_init_Group() {
    return N.wire.emit('init:models.' + collectionName, Group);
  });

  N.wire.on('init:models.' + collectionName, function init_model_Group(schema) {
    N.models[collectionName] = Mongoose.model(collectionName, schema);
  });
};
<|endoftext|>


<commit_before>'use strict';

const fs = require('fs');
const path = require('path');
const util = require('util');

const rr = require('recursive-readdir');
const yaml = require('js-yaml');
const _ = require('lodash');
const oao = require('./index.js');
const common = require('./common.js');
const sd = require('./schema_deref.js');
const empty = require('./empty.js');
const tags = require('./tags.js');
const security = require('./security.js');
const munge = require('./munge.js');

const argv = require('yargs')
	.usage('testRunner [options] [{path-to-specs}]')
	.count('verbose')
	.alias('v','verbose')
	.describe('verbose','increase verbosity')
	.boolean('dump')
	.alias('d','dump')
	.describe('dump','dump expanded specs to a.json and b.json')
	.boolean('no-blacklist')
	.alias('b','no-blacklist')
	.describe('no-blacklist','turn off the blacklist')
	.boolean('validate')
	.describe('validate','use swagger-parser to validate specs')
	//.boolean('alternative')
	//.describe('alternative','use alternative dereferencing algorithm')
	//.alias('a','alternative')
	.boolean('compress')
	.alias('c','compress')
	.describe('compress','compress models')
	.boolean('inline')
	.alias('i','inline')
	.describe('inline','inline $refs where possible')
	.help('h')
	.alias('h', 'help')
	.strict()
	.version()
	.argv;

var SwaggerParser = require('swagger-parser');

var red = process.env.NODE_DISABLE_COLORS ? '' : '\x1b[31m';
var green = process.env.NODE_DISABLE_COLORS ? '' : '\x1b[32m';
var normal = process.env.NODE_DISABLE_COLORS ? '' : '\x1b[0m';

var pass = 0;
var fail = 0;
var invalid = 0;
var pending = 0;
var failures = [];

var blacklist = require(path.resolve('./blacklist.json'));

var pathspec = argv._.length>0 ? argv._[0] : '../openapi-directory/APIs/';

var options = argv;
options.preserveTags = true;

function processSpec(src){
	var result = false;
	var exp = _.cloneDeep(src);
	exp = empty.optimise(exp,options); // as not a reversible operation
	exp = tags.optimise(exp,options); // as not a reversible operation
	exp = security.optimise(exp,options); // as not a reversible operation
	exp = sd.expand(exp,options);
	exp = munge.munge(exp,options); // (re)instates optional objects/arrays
	var expStr = JSON.stringify(exp,null,2);

	var defo = _.cloneDeep(src);
	defo = oao.defaultOptimisations(defo,options);
	if (argv.compress) {
		defo = oao.nonDefaultOptimisations(defo,options);
	}
	//if (argv.alternative) {
		// hmm, takes a callback
		// could use async library:series http://stackoverflow.com/a/9884496/139404
		//defo = SwaggerParser.dereference(defo, [options], [callback]);
	//}
	//else {
	defo = sd.expand(defo,options);
	//}
	defo = munge.munge(defo,options); // (re)instates optional objects/arrays
	var defoStr = JSON.stringify(defo,null,2);

	if (_.isEqual(defo,exp)) {
		console.log(green+'  Matches when expanded'+normal);
		pass++;
		result = true;
	}
	else {
		console.log(red+'  Mismatch of expanded versions'+normal);
		fail++;
		if (argv.dump) {
			fs.writeFileSync('./a.json',expStr,'utf8');
			fs.writeFileSync('./b.json',defoStr,'utf8');
		}
	}
	return result;
}

function check(file) {
	var result = true;
	var components = file.split(path.sep);

	if ((components[components.length-1].endsWith('.yaml')) || (components[components.length-1].endsWith('.json'))) {
		console.log(file);

		var srcStr = fs.readFileSync(path.resolve(file),'utf8');
		var src;
		try {
			src = yaml.safeLoad(srcStr,{json:true});
		}
		catch (ex) {
			console.log(red+ex.message);
			return true;
		}
		if (!src.swagger && !src.openapi) {
			return true;
		}
		console.log('  %s %s',src.info.title,src.info.version);
		console.log('  %s',src.host);

		if (!argv.noBlacklist) {
			for (var b in blacklist) {
				if ((src.info.title == blacklist[b].title) && (src.info.version === blacklist[b].version) &&
					(src.host == blacklist[b].host)) {
					console.log(red+'  Blacklisted'+normal);
					pending++;
					return true; // so we don't lump in with the final failure list
				}
			}
		}

		if (argv.validate) {
			var validator = new SwaggerParser();
			validator.validate(_.cloneDeep(src), function(err, api) {
				if (validator.api) console.log("API name: %s, Version: %s", validator.api.info.title, validator.api.info.version);
				if (err) {
					console.log(err);
					invalid++;
				}
				else {
					result = processSpec(src);
				}
			});
		}
		else {
			result = processSpec(src);
		}
	}
	return result;
}

rr(pathspec, function (err, files) {
	for (var i in files) {
		if (!check(files[i])) {
			failures.push(files[i]);
		}
	}
});

process.on('exit',function(code) {
	if (failures.length>0) {
		failures.sort();
		console.log(red);
		for (var f in failures) {
			console.log(failures[f]);
		}
		console.log(normal);
	}
	console.log('Tests: %s passing, %s failing, %s invalid, %s pending',pass,fail,invalid,pending);
});
<commit_msg>testRunner; set exitCode for CI<commit_after>'use strict';

const fs = require('fs');
const path = require('path');
const util = require('util');

const rr = require('recursive-readdir');
const yaml = require('js-yaml');
const _ = require('lodash');
const oao = require('./index.js');
const common = require('./common.js');
const sd = require('./schema_deref.js');
const empty = require('./empty.js');
const tags = require('./tags.js');
const security = require('./security.js');
const munge = require('./munge.js');

const argv = require('yargs')
	.usage('testRunner [options] [{path-to-specs}]')
	.count('verbose')
	.alias('v','verbose')
	.describe('verbose','increase verbosity')
	.boolean('dump')
	.alias('d','dump')
	.describe('dump','dump expanded specs to a.json and b.json')
	.boolean('no-blacklist')
	.alias('b','no-blacklist')
	.describe('no-blacklist','turn off the blacklist')
	.boolean('validate')
	.describe('validate','use swagger-parser to validate specs')
	//.boolean('alternative')
	//.describe('alternative','use alternative dereferencing algorithm')
	//.alias('a','alternative')
	.boolean('compress')
	.alias('c','compress')
	.describe('compress','compress models')
	.boolean('inline')
	.alias('i','inline')
	.describe('inline','inline $refs where possible')
	.help('h')
	.alias('h', 'help')
	.strict()
	.version()
	.argv;

var SwaggerParser = require('swagger-parser');

var red = process.env.NODE_DISABLE_COLORS ? '' : '\x1b[31m';
var green = process.env.NODE_DISABLE_COLORS ? '' : '\x1b[32m';
var normal = process.env.NODE_DISABLE_COLORS ? '' : '\x1b[0m';

var pass = 0;
var fail = 0;
var invalid = 0;
var pending = 0;
var failures = [];

var blacklist = require(path.resolve('./blacklist.json'));

var pathspec = argv._.length>0 ? argv._[0] : '../openapi-directory/APIs/';

var options = argv;
options.preserveTags = true;

function processSpec(src){
	var result = false;
	var exp = _.cloneDeep(src);
	exp = empty.optimise(exp,options); // as not a reversible operation
	exp = tags.optimise(exp,options); // as not a reversible operation
	exp = security.optimise(exp,options); // as not a reversible operation
	exp = sd.expand(exp,options);
	exp = munge.munge(exp,options); // (re)instates optional objects/arrays
	var expStr = JSON.stringify(exp,null,2);

	var defo = _.cloneDeep(src);
	defo = oao.defaultOptimisations(defo,options);
	if (argv.compress) {
		defo = oao.nonDefaultOptimisations(defo,options);
	}
	//if (argv.alternative) {
		// hmm, takes a callback
		// could use async library:series http://stackoverflow.com/a/9884496/139404
		//defo = SwaggerParser.dereference(defo, [options], [callback]);
	//}
	//else {
	defo = sd.expand(defo,options);
	//}
	defo = munge.munge(defo,options); // (re)instates optional objects/arrays
	var defoStr = JSON.stringify(defo,null,2);

	if (_.isEqual(defo,exp)) {
		console.log(green+'  Matches when expanded'+normal);
		pass++;
		result = true;
	}
	else {
		console.log(red+'  Mismatch of expanded versions'+normal);
		fail++;
		if (argv.dump) {
			fs.writeFileSync('./a.json',expStr,'utf8');
			fs.writeFileSync('./b.json',defoStr,'utf8');
		}
	}
	return result;
}

function check(file) {
	var result = true;
	var components = file.split(path.sep);

	if ((components[components.length-1].endsWith('.yaml')) || (components[components.length-1].endsWith('.json'))) {
		console.log(file);

		var srcStr = fs.readFileSync(path.resolve(file),'utf8');
		var src;
		try {
			src = yaml.safeLoad(srcStr,{json:true});
		}
		catch (ex) {
			console.log(red+ex.message);
			return true;
		}
		if (!src.swagger && !src.openapi) {
			return true;
		}
		console.log('  %s %s',src.info.title,src.info.version);
		console.log('  %s',src.host);

		if (!argv.noBlacklist) {
			for (var b in blacklist) {
				if ((src.info.title == blacklist[b].title) && (src.info.version === blacklist[b].version) &&
					(src.host == blacklist[b].host)) {
					console.log(red+'  Blacklisted'+normal);
					pending++;
					return true; // so we don't lump in with the final failure list
				}
			}
		}

		if (argv.validate) {
			var validator = new SwaggerParser();
			validator.validate(_.cloneDeep(src), function(err, api) {
				if (validator.api) console.log("API name: %s, Version: %s", validator.api.info.title, validator.api.info.version);
				if (err) {
					console.log(err);
					invalid++;
				}
				else {
					result = processSpec(src);
				}
			});
		}
		else {
			result = processSpec(src);
		}
	}
	return result;
}

rr(pathspec, function (err, files) {
	for (var i in files) {
		if (!check(files[i])) {
			failures.push(files[i]);
		}
	}
});

process.on('exit',function(code) {
	if (failures.length>0) {
		failures.sort();
		console.log(red);
		for (var f in failures) {
			console.log(failures[f]);
		}
		console.log(normal);
	}
	console.log('Tests: %s passing, %s failing, %s invalid, %s pending',pass,fail,invalid,pending);
	process.exitCode = ((fail === 0) && (pass > 0)) ? 0 : 1;
});
<|endoftext|>


<commit_before>module.exports = function (grunt) {
	grunt.initConfig({
		/*
			deletes dist and temp directories
			the temp directory is used during the build process
			the dist directory contains the artifacts of the build
			these directories should be deleted before subsequent builds
		*/
		delete: {
			reset: {
				files: ['./dist/']
			},
			temp: {
				files: ['./temp/']
			}
		},

		/*
			contains linting rules for CoffeeScript files
		*/
		coffeeLint: {
			scripts: {
				files: ['./src/scripts/**/*.coffee'],
				indentation: {
					value: 1,
					level: 'error'
				},
				max_line_length: {
					level: 'ignore'
				},
				no_tabs: {
					level: 'ignore'
				}
			},
			tests: {
				files: ['./test/scripts/**/*.coffee'],
				indentation: '<config:coffeeLint.scripts.indentation>',
				max_line_length: '<config:coffeeLint.scripts.max_line_length>',
				no_tabs: '<config:coffeeLint.scripts.no_tabs>',
			}
		},

		/*
			compiles CoffeeScript (.coffee) files to JavaScript (.js)
		*/
		coffee: {
			scripts: {
				files: {
					'./temp/scripts/': './src/scripts/**/*.coffee'
				},
				bare: true
			},
			tests: {
				files: {
					'./test/scripts/': './test/scripts/**/*.coffee'
				},
				bare: true
			}
		},

		/*
			compiles LESS (.less) files to CSS (.css)
		*/
		less: {
			styles: {
				files: {
					'./temp/styles/styles.css': './src/styles/styles.less'
				}
			}
		},

		/*
			compiles template files (.template) to HTML (.html)

			.template files are essentially html; however, you can take advantage
			of features provided by grunt such as underscore templating

			the example below demonstrates the use of the environment attribute
			in 'prod' the concatenated and minified scripts are used along with a unique QueryString parameter to address browser caching
			in environments other than 'prod' the individual files are used and loaded with RequireJS

			<% if (config.environment === 'prod') { %>

			<% } else { %>

			<% } %>
		*/
		template: {
			views: {
				files: {
					'./temp/views/': './src/views/**/*.template'
				}
			},
			dev: {
				files: {
					'./temp/index.html': './src/index.template'
				},
				environment: 'dev'
			},
			prod: {
				files: '<config:template.dev.files>',
				environment: 'prod'
			}
		},

		/*
			creates a single file consisting of multiple html (views) files surrounded by script tags

			for example, take the following two files,
			/temp/views/people.html (compiled from /src/views/people.template)

			/temp/views/repos.html (compiled from /src/views/repos.html)

			AngularJS will interpret inlined scripts with type of "text/ng-template" in lieu of retrieving the view from the server
			the id of the script tag must match the path requested
			since the path includes the temp directory, this must be trimmed

			the output of the configuration below is:
			/temp/views/views.html

			now the views.html file can be included in the application and avoid making requests to the server for the templates
		*/
		inlineTemplate: {
			views: {
				files: {
					'./temp/views/views.html': './temp/views/**/*.html'
				},
				type: 'text/ng-template',
				trim: 'temp'
			}
		},

		/*
			copies directories and files from one location to another
		*/
		copy: {
			// copies libs and img directories to temp
			temp: {
				files: {
					'./temp/scripts/libs/': './src/scripts/libs/',
					'./temp/img/': './src/img/'
				}
			},
			// copies the contents of the temp directory to the dist directory
			// in 'dev' individual files are used
			dev: {
				files: {
					'./dist/': './temp/'
				}
			},
			// copies select files from the temp directory to the dist directory
			// in 'prod' minified files are used along with img and libs
			// the dist artifacts contain only the files necessary to run the application
			prod: {
				files: {
					'./dist/img/': './temp/img/',
					'./dist/scripts/': './temp/scripts/scripts.min.js',
					'./dist/scripts/libs': ['./temp/scripts/libs/html5shiv-printshiv.js', './temp/scripts/libs/json2.js'],
					'./dist/styles/': './temp/styles/styles.min.css',
					'./dist/index.html': './temp/index.min.html'
				}
			},
			// task is run when a watched script is modified
			scripts: {
				files: {
					'./dist/scripts/': './temp/scripts/'
				}
			},
			// task is run when a watched style is modified
			styles: {
				files: {
					'./dist/styles/': './temp/styles/'
				}
			},
			// task is run when the watched index.template file is modified
			index: {
				files: {
					'./dist/': './temp/index.html'
				}
			},
			// task is run when a watched view is modified
			views: {
				files: {
					'./dist/views/': './temp/views/'
				}
			}
		},

		/*
			RequireJS optimizer configuration for both scripts and styles
			this configuration is only used in the 'prod' build
			the optimizer will scan the main file, walk the dependency tree, and write the output in dependent sequence to a single file
			since RequireJS is not being used outside of the main file, RequireJS is not needed for final output and is excluded
			RequireJS is still used for the 'dev' build
			the main file is used only to establish the proper loading sequence
		*/
		requirejs: {
			scripts: {
				baseUrl: './temp/scripts/',
				findNestedDependencies: true,
				logLevel: 0,
				mainConfigFile: './temp/scripts/main.js',
				name: 'main',
				// exclude main from the final output to avoid the dependency on RequireJS at runtime
				onBuildWrite: function (moduleName, path, contents) {
					var modulesToExclude = ['main'],
						shouldExcludeModule = modulesToExclude.indexOf(moduleName) >= 0;

					if (shouldExcludeModule) {
						return '';
					}

					return contents;
				},
				optimize: 'uglify',
				out: './temp/scripts/scripts.min.js',
				preserveLicenseComments: false,
				skipModuleInsertion: true,
				uglify: {
					no_mangle: false
				}
			},
			styles: {
				baseUrl: './temp/styles/',
				cssIn: './temp/styles/styles.css',
				logLevel: 0,
				optimizeCss: 'standard',
				out: './temp/styles/styles.min.css'
			}
		},

		/*
			minifies index.html
			extra white space and comments will be removed
			content within
 tags will be left unchanged
			IE conditional comments will be left unchanged
			as of this writing, the output is reduced by over 14%
		*/
		minifyHtml: {
			prod: {
				files: {
					'./temp/index.min.html': './temp/index.html'
				}
			}
		},

		/*
			sets up file watchers and runs tasks when watched files are changed
		*/
		watch: {
			scripts: {
				files: './src/scripts/**/*.coffee',
				tasks: 'coffeeLint:scripts coffee:scripts copy:scripts reload'
			},
			styles: {
				files: './src/styles/**/*.less',
				tasks: 'less copy:styles reload'
			},
			index: {
				files: './src/index.template',
				tasks: 'template:dev copy:index reload'
			},
			views: {
				files: './src/views/**/*.template',
				tasks: 'template:views copy:views reload'
			}
		},

		/*
			runs a web server at the specified port
			can optionally watch for changes to the file referenced in the watch setting
			the web server will automatically restart once the changes have been saved
		*/
		server: {
			app: {
				src: './server.coffee',
				port: 3005,
				watch: './routes.coffee'
			}
		},

		/*
			leverages the LiveReload browser plugin to automatically reload the browser when watched files have changed

			as of this writing, Chrome, Firefox, and Safari are supported

			get the plugin:
			here http://help.livereload.com/kb/general-use/browser-extensions
		*/
		reload: {
			liveReload: true,
			port: 35729
		}
	});

	grunt.loadNpmTasks('grunt-hustler');
	grunt.loadNpmTasks('grunt-reload');

	grunt.registerTask('unit-tests', 'run the testacular test driver on jasmine unit tests', function () {
		var done = this.async();

		require('child_process').exec('./node_modules/testacular/bin/testacular start ./testacular.conf.js --single-run', function (err, stdout) {
			grunt.log.write(stdout);
			done(err);
		});
	});

	grunt.registerTask('default', [
		'delete',
		'coffeeLint:scripts',
		'coffee:scripts',
		'less',
		'template:views',
		'template:dev',
		'copy:temp',
		'copy:dev',
		'delete:temp'
	]);

	grunt.registerTask('dev', [
		'default',
		'reload',
		'watch'
	]);

	grunt.registerTask('prod', [
		'delete',
		'coffeeLint:scripts',
		'coffee:scripts',
		'less',
		'template:views',
		'inlineTemplate',
		'template:prod',
		'copy:temp',
		'requirejs',
		'minifyHtml',
		'copy:prod',
		'delete:temp'
	]);

	grunt.registerTask('test', [
		'coffeeLint:tests',
		'coffee:tests',
		'default',
		'unit-tests'
	]);
};<commit_msg>improved grunt.js documentation<commit_after>// Build configurations.
module.exports = function (grunt) {
	grunt.initConfig({
		/*
			Deletes dist and temp directories.
			The temp directory is used during the build process.
			The dist directory contains the artifacts of the build.
			These directories should be deleted before subsequent builds.
		*/
		delete: {
			dist: {
				files: ['./dist/']
			},
			temp: {
				files: ['./temp/']
			}
		},

		// CoffeeScript linting rules.
		coffeeLint: {
			scripts: {
				files: ['./src/scripts/**/*.coffee', './test/scripts/**/*.coffee'],
				// Use one tab for indentation.
				indentation: {
					value: 1,
					level: 'error'
				},
				// No maximum line length.
				max_line_length: {
					level: 'ignore'
				},
				// Using tabs should not result in an error.
				no_tabs: {
					level: 'ignore'
				}
			}
		},

		// Compile CoffeeScript (.coffee) files to JavaScript (.js).
		coffee: {
			scripts: {
				files: {
					'./temp/scripts/': './src/scripts/**/*.coffee',
					'./test/scripts/': './test/scripts/**/*.coffee'
				},
				// Don't include a surrounding IIFE in the compiled output.
				bare: true
			}
		},

		// Compile LESS (.less) files to CSS (.css).
		less: {
			styles: {
				files: {
					'./temp/styles/styles.css': './src/styles/styles.less'
				}
			}
		},

		/*
			Compile template files (.template) to HTML (.html).

			.template files are essentially html; however, you can take advantage of features provided by grunt such as underscore templating.

			The example below demonstrates the use of the environment configuration setting.
			In 'prod' the concatenated and minified scripts are used along with a unique QueryString parameter to address browser caching.
			In environments other than 'prod' the individual files are used and loaded with RequireJS.

			<% if (config.environment === 'prod') { %>

			<% } else { %>

			<% } %>
		*/
		template: {
			views: {
				files: {
					'./temp/views/': './src/views/**/*.template'
				}
			},
			dev: {
				files: {
					'./temp/index.html': './src/index.template'
				},
				environment: 'dev'
			},
			prod: {
				files: '<config:template.dev.files>',
				environment: 'prod'
			}
		},

		/*
			Creates a single file consisting of multiple views (html) files surrounded by script tags.

			For example, take the following two files:

			AngularJS will interpret inlined scripts with type of "text/ng-template" in lieu of retrieving the view from the server.
			The id of the script tag must match the path requested.
			Since the path includes the temp directory, this must be trimmed.

			The output of the configuration below is:

			Now the views.html file can be included in the application and avoid making requests to the server for the views.
		*/
		inlineTemplate: {
			views: {
				files: {
					'./temp/views/views.html': './temp/views/**/*.html'
				},
				type: 'text/ng-template',
				trim: 'temp'
			}
		},

		// Copies directories and files from one location to another.
		copy: {
			// Copies libs and img directories to temp.
			temp: {
				files: {
					'./temp/scripts/libs/': './src/scripts/libs/',
					'./temp/img/': './src/img/'
				}
			},
			/*
				Copies the contents of the temp directory to the dist directory.
				In 'dev' individual files are used.
			*/
			dev: {
				files: {
					'./dist/': './temp/'
				}
			},
			/*
				Copies select files from the temp directory to the dist directory.
				In 'prod' minified files are used along with img and libs.
				The dist artifacts contain only the files necessary to run the application.
			*/
			prod: {
				files: {
					'./dist/img/': './temp/img/',
					'./dist/scripts/': './temp/scripts/scripts.min.js',
					'./dist/scripts/libs': ['./temp/scripts/libs/html5shiv-printshiv.js', './temp/scripts/libs/json2.js'],
					'./dist/styles/': './temp/styles/styles.min.css',
					'./dist/index.html': './temp/index.min.html'
				}
			},
			// Task is run when a watched script is modified.
			scripts: {
				files: {
					'./dist/scripts/': './temp/scripts/'
				}
			},
			// Task is run when a watched style is modified.
			styles: {
				files: {
					'./dist/styles/': './temp/styles/'
				}
			},
			// Task is run when the watched index.template file is modified.
			index: {
				files: {
					'./dist/': './temp/index.html'
				}
			},
			// Task is run when a watched view is modified.
			views: {
				files: {
					'./dist/views/': './temp/views/'
				}
			}
		},

		/*
			RequireJS optimizer configuration for both scripts and styles.
			This configuration is only used in the 'prod' build.
			The optimizer will scan the main file, walk the dependency tree, and write the output in dependent sequence to a single file.
			Since RequireJS is not being used outside of the main file or for dependency resolution (this is handled by AngularJS), RequireJS is not needed for final output and is excluded.
			RequireJS is still used for the 'dev' build.
			The main file is used only to establish the proper loading sequence.
		*/
		requirejs: {
			scripts: {
				baseUrl: './temp/scripts/',
				findNestedDependencies: true,
				logLevel: 0,
				mainConfigFile: './temp/scripts/main.js',
				name: 'main',
				// Exclude main from the final output to avoid the dependency on RequireJS at runtime.
				onBuildWrite: function (moduleName, path, contents) {
					var modulesToExclude = ['main'],
						shouldExcludeModule = modulesToExclude.indexOf(moduleName) >= 0;

					if (shouldExcludeModule) {
						return '';
					}

					return contents;
				},
				optimize: 'uglify',
				out: './temp/scripts/scripts.min.js',
				preserveLicenseComments: false,
				skipModuleInsertion: true,
				uglify: {
					// Let uglifier replace variables to further reduce file size.
					no_mangle: false
				}
			},
			styles: {
				baseUrl: './temp/styles/',
				cssIn: './temp/styles/styles.css',
				logLevel: 0,
				optimizeCss: 'standard',
				out: './temp/styles/styles.min.css'
			}
		},

		/*
			Minifiy index.html.
			Extra white space and comments will be removed.
			Content within
 tags will be left unchanged.
			IE conditional comments will be left unchanged.
			As of this writing, the output is reduced by over 14%.
		*/
		minifyHtml: {
			prod: {
				files: {
					'./temp/index.min.html': './temp/index.html'
				}
			}
		},

		// Sets up file watchers and runs tasks when watched files are changed.
		watch: {
			scripts: {
				files: './src/scripts/**/*.coffee',
				tasks: 'coffeeLint:scripts coffee:scripts copy:scripts reload'
			},
			styles: {
				files: './src/styles/**/*.less',
				tasks: 'less copy:styles reload'
			},
			index: {
				files: './src/index.template',
				tasks: 'template:dev copy:index reload'
			},
			views: {
				files: './src/views/**/*.template',
				tasks: 'template:views copy:views reload'
			}
		},

		/*
			Runs a web server at the specified port.
			Can optionally watch for changes to the file referenced in the watch setting.
			The web server will automatically restart once the changes have been saved.
		*/
		server: {
			app: {
				src: './server.coffee',
				port: 3005,
				watch: './routes.coffee'
			}
		},

		/*
			Leverages the LiveReload browser plugin to automatically reload the browser when watched files have changed.

			As of this writing, Chrome, Firefox, and Safari are supported.

			Get the plugin:
			here http://help.livereload.com/kb/general-use/browser-extensions
		*/
		reload: {
			liveReload: true,
			port: 35729
		}
	});

	/*
		Register grunt tasks supplied by grunt-hustler.
		Referenced in package.json.
		https://github.com/CaryLandholt/grunt-hustler
	*/
	grunt.loadNpmTasks('grunt-hustler');

	/*
		Register grunt tasks supplied by grunt-reload.
		Referenced in package.json.
		https://github.com/webxl/grunt-reload
	*/
	grunt.loadNpmTasks('grunt-reload');

	// A task to run unit tests in testacular.
	grunt.registerTask('unit-tests', 'run the testacular test driver on jasmine unit tests', function () {
		var done = this.async();

		require('child_process').exec('./node_modules/testacular/bin/testacular start ./testacular.conf.js --single-run', function (err, stdout) {
			grunt.log.write(stdout);
			done(err);
		});
	});

	/*
		Compiles the app with non-optimized build settings and places the build artifacts in the dist directory.
		Enter the following command at the command line to execute this build task:
		grunt
	*/
	grunt.registerTask('default', [
		'delete',
		'coffeeLint',
		'coffee',
		'less',
		'template:views',
		'template:dev',
		'copy:temp',
		'copy:dev',
		'delete:temp'
	]);

	/*
		Compiles the app with non-optimized build settings, places the build artifacts in the dist directory, and watches for file changes.
		Enter the following command at the command line to execute this build task:
		grunt dev
	*/
	grunt.registerTask('dev', [
		'default',
		'reload',
		'watch'
	]);

	/*
		Compiles the app with optimized build settings and places the build artifacts in the dist directory.
		Enter the following command at the command line to execute this build task:
		grunt prod
	*/
	grunt.registerTask('prod', [
		'delete',
		'coffeeLint',
		'coffee',
		'less',
		'template:views',
		'inlineTemplate',
		'template:prod',
		'copy:temp',
		'requirejs',
		'minifyHtml',
		'copy:prod',
		'delete:temp'
	]);

	/*
		Compiles the app with non-optimized build settings, places the build artifacts in the dist directory, and runs unit tests.
		Enter the following command at the command line to execute this build task:
		grunt test
	*/
	grunt.registerTask('test', [
		'default',
		'unit-tests'
	]);
};<|endoftext|>


<commit_before>(function () {
    var cogClass = function () {};
    cogClass.prototype.exec = function (params, request, response) {
        var oops = this.utils.apiError;
        var page = this.page;
        var sys = this.sys;
        var classID = params.classid;
        var quizNumber = params.quizno;
        var commenter = this.sys.validCommenter(params).name;
        var commenterID = this.sys.validCommenter(params).id;
        // This gets us a clean list of wrong answers, with rubric, correct and wrong choices, and the stringID and text for each,
        // plus a count of the frequency of each error
        //
        // quizNumber  questionNumber  rubricID  rubricText  correct  correctID  correctText   wrong  wrongID  wrongText   COUNT(res.studentID)
        // ----------  --------------  --------  ----------  -------  ---------  ------------  -----  -------  ----------  --------------------
        // 3           1               3         How high a  1        30         A lot.        0      16       Ten meter   2
        // 3           1               3         How high a  1        30         A lot.        2      43       Many.       1
        //
        var sql = "SELECT qq.questionNumber,"
            + "res.rubricID,r.string AS rubricText,"
            + "res.correct,res.correctID,cc.string AS correctText,"
            + "res.wrong,res.wrongID,ww.string AS wrongText,"
            + "COUNT(res.studentID) AS count,"
            + "COUNT(res.comment) AS commentCount,"
            + "group_concat(distinct res.lang) AS langs "
            + "FROM quizzes AS qz "
            + 'JOIN questions AS qq USING(quizID) '
            + "JOIN classes AS cls ON cls.classID=qz.classID "
            + "LEFT JOIN ("
            +     "SELECT DISTINCT que.questionID,que.questionNumber,que.correct,ach.choice AS wrong,"
            +     'qch.stringID AS correctID,'
            +     'ach.stringID AS wrongID,'
            +     'que.stringID AS rubricID,'
            +     'ans.studentID,'
            +     "com.choiceID AS comment,"
            +     "stu.lang "

            +     'FROM quizzes AS qui '
            +     'JOIN questions AS que USING(quizID) '
            +     'JOIN answers AS ans ON ans.questionID=que.questionID '
            +     "JOIN students AS stu ON stu.studentID=ans.studentID "
            +     'JOIN choices AS qch ON qch.questionID=que.questionID AND qch.choice=que.correct '
            +     'JOIN choices AS ach ON ach.questionID=que.questionID AND ach.choice=ans.choice '
            +     "LEFT JOIN comments AS com ON com.choiceID=ach.choiceID "
            +     "WHERE NOT ans.choice=que.correct AND qui.classID=? AND qui.quizNumber=? "
            +     "GROUP BY qui.quizNumber,que.questionNumber,ans.choice,ans.studentID"
            + ") as res ON res.questionID=qq.questionID "
            + "LEFT JOIN strings AS r ON r.stringID=res.rubricID "
            + "LEFT JOIN strings AS cc ON cc.stringID=res.correctID "
            + "LEFT JOIN strings AS ww ON ww.stringID=res.wrongID "
            + "WHERE qz.classID=? AND qz.quizNumber=? AND res.questionNumber IS NOT NULL "
            + "GROUP BY qz.quizNumber,res.questionNumber,res.wrong "
            + "ORDER BY commentCount,count DESC,langs;";
        var mistakeCount = 0;
        var data = {commenter:commenter,commenterID:commenterID,mistakes:[]};
        // ZZZ console.log("SQL: "+sql);
        sys.db.all(sql,[classID,quizNumber,classID,quizNumber],function(err,rows){
            if (err||!rows) {return oops(response,err,'**quiz/quizmistakes(1)')};
            mistakeCount += rows.length;
            if (!mistakeCount) {
                response.writeHead(200, {'Content-Type': 'application/json'});
                response.end(JSON.stringify(data));
            }
            for (var i=0,ilen=rows.length;i<ilen;i+=1) {
                var row = rows[i];
                var questionNumber = row.questionNumber;
                var wrongChoice = row.wrong;
                var langs = row.langs;
                var obj = {
                    questionNumber: questionNumber,
                    wrongChoice: wrongChoice,
                    langs:langs,
                    rubric: row.rubricText,
                    correct: row.correctText,
                    wrong: row.wrongText,
                    count: row.count,
                    comments:[]
                };
                getComments(classID,quizNumber,questionNumber,wrongChoice,obj);
            }
        });
        function getComments (classID,quizNumber,questionNumber,wrongChoice,obj) {
            var sql = 'SELECT c.adminID AS commenterID,'
                + 'admin.name AS commenter,'
                + 's.string AS comment '
                + 'FROM quizzes AS qz '
                + 'NATURAL JOIN questions as que '
                + 'JOIN choices AS ch ON ch.questionID=que.questionID '
                + 'JOIN comments AS c ON c.choiceID=ch.choiceID '
                + 'JOIN admin AS admin USING(adminID) '
                + 'LEFT JOIN strings as s ON s.stringID=c.stringID '
                + 'WHERE classID=? AND quizNumber=? AND questionNumber=? AND ch.choice=?'
            //console.log("SQL: "+sql);
            console.log("READ: "+classID+" "+quizNumber+" "+questionNumber+" "+wrongChoice);
            sys.db.all(sql,[classID,quizNumber,questionNumber,wrongChoice],function(err,rows){
                if (err||!rows) {return oops(response,err,'**quiz/quizmistakes(2)')};
                for (var i=0,ilen=rows.length;i<ilen;i+=1) {
                    var row = rows[i];
                    obj.comments.push({commenter:row.commenter,comment:row.comment,commenterID:row.commenterID});
                    if (commenterID === row.commenterID) {
                        obj.hasCommenterComment = true;
                    }
                }
                data.mistakes.push(obj);
                mistakeCount += -1;
                if (!mistakeCount) {
                    response.writeHead(200, {'Content-Type': 'application/json'});
                    response.end(JSON.stringify(data));
                }
            });
        }
    }
    exports.cogClass = cogClass;
})();
<commit_msg>Build sequence object synchronously<commit_after>(function () {
    var cogClass = function () {};
    cogClass.prototype.exec = function (params, request, response) {
        var oops = this.utils.apiError;
        var page = this.page;
        var sys = this.sys;
        var classID = params.classid;
        var quizNumber = params.quizno;
        var commenter = this.sys.validCommenter(params).name;
        var commenterID = this.sys.validCommenter(params).id;
        // This gets us a clean list of wrong answers, with rubric, correct and wrong choices, and the stringID and text for each,
        // plus a count of the frequency of each error
        //
        // quizNumber  questionNumber  rubricID  rubricText  correct  correctID  correctText   wrong  wrongID  wrongText   COUNT(res.studentID)
        // ----------  --------------  --------  ----------  -------  ---------  ------------  -----  -------  ----------  --------------------
        // 3           1               3         How high a  1        30         A lot.        0      16       Ten meter   2
        // 3           1               3         How high a  1        30         A lot.        2      43       Many.       1
        //
        var sql = "SELECT qq.questionNumber,"
            + "res.rubricID,r.string AS rubricText,"
            + "res.correct,res.correctID,cc.string AS correctText,"
            + "res.wrong,res.wrongID,ww.string AS wrongText,"
            + "COUNT(res.studentID) AS count,"
            + "COUNT(res.comment) AS commentCount,"
            + "group_concat(distinct res.lang) AS langs "
            + "FROM quizzes AS qz "
            + 'JOIN questions AS qq USING(quizID) '
            + "JOIN classes AS cls ON cls.classID=qz.classID "
            + "LEFT JOIN ("
            +     "SELECT DISTINCT que.questionID,que.questionNumber,que.correct,ach.choice AS wrong,"
            +     'qch.stringID AS correctID,'
            +     'ach.stringID AS wrongID,'
            +     'que.stringID AS rubricID,'
            +     'ans.studentID,'
            +     "com.choiceID AS comment,"
            +     "stu.lang "

            +     'FROM quizzes AS qui '
            +     'JOIN questions AS que USING(quizID) '
            +     'JOIN answers AS ans ON ans.questionID=que.questionID '
            +     "JOIN students AS stu ON stu.studentID=ans.studentID "
            +     'JOIN choices AS qch ON qch.questionID=que.questionID AND qch.choice=que.correct '
            +     'JOIN choices AS ach ON ach.questionID=que.questionID AND ach.choice=ans.choice '
            +     "LEFT JOIN comments AS com ON com.choiceID=ach.choiceID "
            +     "WHERE NOT ans.choice=que.correct AND qui.classID=? AND qui.quizNumber=? "
            +     "GROUP BY qui.quizNumber,que.questionNumber,ans.choice,ans.studentID"
            + ") as res ON res.questionID=qq.questionID "
            + "LEFT JOIN strings AS r ON r.stringID=res.rubricID "
            + "LEFT JOIN strings AS cc ON cc.stringID=res.correctID "
            + "LEFT JOIN strings AS ww ON ww.stringID=res.wrongID "
            + "WHERE qz.classID=? AND qz.quizNumber=? AND res.questionNumber IS NOT NULL "
            + "GROUP BY qz.quizNumber,res.questionNumber,res.wrong "
            + "ORDER BY commentCount,count DESC,langs;";
        var mistakeCount = 0;
        var data = {commenter:commenter,commenterID:commenterID,mistakes:[]};
        // ZZZ console.log("SQL: "+sql);
        sys.db.all(sql,[classID,quizNumber,classID,quizNumber],function(err,rows){
            if (err||!rows) {return oops(response,err,'**quiz/quizmistakes(1)')};
            mistakeCount += rows.length;
            if (!mistakeCount) {
                response.writeHead(200, {'Content-Type': 'application/json'});
                response.end(JSON.stringify(data));
            }
            for (var i=0,ilen=rows.length;i<ilen;i+=1) {
                var row = rows[i];
                var questionNumber = row.questionNumber;
                var wrongChoice = row.wrong;
                var langs = row.langs;
                var obj = {
                    questionNumber: questionNumber,
                    wrongChoice: wrongChoice,
                    langs:langs,
                    rubric: row.rubricText,
                    correct: row.correctText,
                    wrong: row.wrongText,
                    count: row.count,
                    comments:[]
                };
                data.mistakes.push(obj);
                getComments(classID,quizNumber,questionNumber,wrongChoice,obj);
            }
        });
        function getComments (classID,quizNumber,questionNumber,wrongChoice,obj) {
            var sql = 'SELECT c.adminID AS commenterID,'
                + 'admin.name AS commenter,'
                + 's.string AS comment '
                + 'FROM quizzes AS qz '
                + 'NATURAL JOIN questions as que '
                + 'JOIN choices AS ch ON ch.questionID=que.questionID '
                + 'JOIN comments AS c ON c.choiceID=ch.choiceID '
                + 'JOIN admin AS admin USING(adminID) '
                + 'LEFT JOIN strings as s ON s.stringID=c.stringID '
                + 'WHERE classID=? AND quizNumber=? AND questionNumber=? AND ch.choice=?'
            //console.log("SQL: "+sql);
            console.log("READ: "+classID+" "+quizNumber+" "+questionNumber+" "+wrongChoice);
            sys.db.all(sql,[classID,quizNumber,questionNumber,wrongChoice],function(err,rows){
                if (err||!rows) {return oops(response,err,'**quiz/quizmistakes(2)')};
                for (var i=0,ilen=rows.length;i<ilen;i+=1) {
                    var row = rows[i];
                    obj.comments.push({commenter:row.commenter,comment:row.comment,commenterID:row.commenterID});
                    if (commenterID === row.commenterID) {
                        obj.hasCommenterComment = true;
                    }
                }
                mistakeCount += -1;
                if (!mistakeCount) {
                    response.writeHead(200, {'Content-Type': 'application/json'});
                    response.end(JSON.stringify(data));
                }
            });
        }
    }
    exports.cogClass = cogClass;
})();
<|endoftext|>


<commit_before>//test runner
module.exports = {
    run: function(tests) {
        for(var i in tests) {
            if (tests.hasOwnProperty(i)) {
                var fixture = tests[i];
                for(var t in fixture){
                    if (fixture.hasOwnProperty(t)) {
                        try {
                            var res = fixture[t]();
                            if (res === undefined) {
                                console.log(i + ' ' + t + ' ...ok');
                            } else {
                                console.log(i + ' ' + t + ': ' + res + ' ...ok');
                            }
                        } catch (ex) {
                            console.log(i + ' ' + t + ' ...failed!');
                            console.log(ex);
                        }
                    }
                }
            }
        }
    },
    areEqual: function (a,b) {
        if (a !== b) {
            throw "failed assert, wanted '"+a+"', got '"+b+"'.";
        }
    }
}<commit_msg>assert.fail<commit_after>//test runner
module.exports = {
    run: function(tests) {
        for(var i in tests) {
            if (tests.hasOwnProperty(i)) {
                var fixture = tests[i];
                for(var t in fixture){
                    if (fixture.hasOwnProperty(t)) {
                        try {
                            var res = fixture[t]();
                            if (res === undefined) {
                                console.log(i + ' ' + t + ' ...ok');
                            } else {
                                console.log(i + ' ' + t + ': ' + res + ' ...ok');
                            }
                        } catch (ex) {
                            console.log(i + ' ' + t + ' ...failed!');
                            console.log(ex);
                        }
                    }
                }
            }
        }
    },
    areEqual: function (a,b) {
        if (a !== b) {
            throw "failed assert, wanted '"+a+"', got '"+b+"'.";
        }
    },
    fail: function(a) {
        throw "failed assert, "+a;
    }
};<|endoftext|>