<commit_before>'use strict'

const {log, BaseKonnector, saveBills, request} = require('cozy-konnector-libs')
const moment = require('moment')
const bluebird = require('bluebird')

let rq = request({
  // debug: true,
  cheerio: true,
  json: false,
  jar: true
})

const baseUrl = 'https://assure.ameli.fr/PortailAS/paiements.do?actionEvt='

module.exports = new BaseKonnector(function fetch (fields) {
  return checkLogin(fields)
  .then(() => logIn(fields))
  .then($ => fetchMainPage($))
  .then($ => parseMainPage($))
  .then(reimbursements => getBills(reimbursements))
  .then(entries => {
    // get custom bank identifier if any
    let identifiers = 'C.P.A.M.'
    if (fields.bank_identifier && fields.bank_identifier.length) {
      identifiers = fields.bank_identifier
    }

    return saveBills(entries, fields.folderPath, {
      timeout: Date.now() + 60 * 1000,
      identifiers,
      dateDelta: 10,
      amountDelta: 0.1
    })
  })
})

const checkLogin = function (fields) {
  log('info', 'Checking the length of the login')
  if (fields.login.length > 13) {
    return Promise.reject(new Error('LOGIN_FAILED'))
  }

  return Promise.resolve()
}

// Procedure to login to Ameli website.
const logIn = function (fields) {
  log('info', 'Now logging in')
  const loginUrl = 'https://assure.ameli.fr/PortailAS/appmanager/PortailAS/assure?_somtc=true'

  const submitUrl = 'https://assure.ameli.fr/PortailAS/appmanager/PortailAS/' +
    'assure?_nfpb=true&_windowLabel=connexioncompte_2&connexioncompte_2_' +
    'actionOverride=/portlets/connexioncompte/validationconnexioncompte&_pageLabel=as_login_page'

  const reimbursementUrl = 'https://assure.ameli.fr/PortailAS/appmanager/PortailAS/assure?_nfpb=true&_pageLabel=as_paiements_page'

  const form = {
    'connexioncompte_2numSecuriteSociale': fields.login,
    'connexioncompte_2codeConfidentiel': fields.password,
    'connexioncompte_2actionEvt': 'connecter',
    'submit': 'Valider'
  }

  return rq({
    url: loginUrl,
    resolveWithFullResponse: true
  })
  // First request to get the cookie
  .then(res => rq({
    method: 'POST',
    form,
    url: submitUrl
  }))
  // Second request to authenticate
  .then($ => {
    if ($('[title="Déconnexion du compte ameli"]').length !== 1) {
      throw new Error('LOGIN_FAILED')
    }

    log('info', 'Correctly logged in')
    return rq(reimbursementUrl)
  })
}

// fetch the HTML page with the list of health cares
const fetchMainPage = function ($) {
  log('info', 'Fetching the list of bills')

  // Get the start and end date to generate the bill's url
  const endDate = $('#paiements_1dateFin').attr('value')

  // We can get the history only 6 months back
  const startDate = moment(endDate, 'DD/MM/YYYY').subtract(6, 'months').format('DD/MM/YYYY')

  const billUrl = `${baseUrl}afficherPaiementsComplementaires&DateDebut=${startDate}&DateFin=${endDate}\
&Beneficiaire=tout_selectionner&afficherReleves=false&afficherIJ=false&afficherInva=false&afficherRentes=false\
&afficherRS=false&indexPaiement=&idNotif=`

  return rq(billUrl)
}

// Parse the fetched page to extract bill data.
const parseMainPage = function ($) {
  const reimbursements = []
  let i = 0

  // Each bloc represents a month that includes 0 to n reimbursement
  $('.blocParMois').each(function () {
    // It would be too easy to get the full date at the same place
    let year = $($(this).find('.rowdate .mois').get(0)).text()
    year = year.split(' ')[1]

    return $(`[id^=lignePaiement${i++}]`).each(function () {
      const month = $($(this).find('.col-date .mois').get(0)).text()
      const day = $($(this).find('.col-date .jour').get(0)).text()
      let date = `${day} ${month} ${year}`
      moment.locale('fr')
      date = moment(date, 'Do MMMM YYYY')

      // Retrieve and extract the infos needed to generate the pdf
      const attrInfos = $(this).attr('onclick')
      const tokens = attrInfos.split("'")

      const idPaiement = tokens[1]
      const naturePaiement = tokens[3]
      const indexGroupe = tokens[5]
      const indexPaiement = tokens[7]

      const detailsUrl = `${baseUrl}chargerDetailPaiements\
&idPaiement=${idPaiement}\
&naturePaiement=${naturePaiement}\
&indexGroupe=${indexGroupe}\
&indexPaiement=${indexPaiement}`

      let lineId = indexGroupe + indexPaiement

      let reimbursement = {
        date,
        lineId,
        detailsUrl,
        isThirdPartyPayer: naturePaiement === 'PAIEMENT_A_UN_TIERS',
        beneficiaries: {}
      }

      reimbursements.push(reimbursement)
    })
  })
  return bluebird.each(reimbursements, reimbursement => {
    return rq(reimbursement.detailsUrl)
    .then($ => parseDetails($, reimbursement))
  })
  .then(() => reimbursements)
}

function parseDetails ($, reimbursement) {
  let currentBeneficiary = null
  reimbursement.link = $('.entete [id^=liendowndecompte]').attr('href')
  $('.container:not(.entete)').each(function () {
    const $beneficiary = $(this).find('[id^=nomBeneficiaire]')
    if ($beneficiary.length > 0) { // a beneficiary container
      currentBeneficiary = $beneficiary.text().trim()
      return null
    }

    // the next container is the list of health cares associated to the beneficiary
    if (currentBeneficiary) {
      parseHealthCares($, this, currentBeneficiary, reimbursement)
      currentBeneficiary = null
    } else {
      // there is some participation remaining for the whole reimbursement
      parseParticipation($, this, reimbursement)
    }
  })
}

function parseAmount (amount) {
  return parseFloat(amount.replace(' €', '').replace(',', '.'))
}

function parseHealthCares ($, container, beneficiary, reimbursement) {
  $(container).find('tr').each(function () {
    if ($(this).find('th').length > 0) {
      return null // ignore header
    }

    let date = $(this).find('[id^=Nature]').html().split('
').pop().trim()
    date = moment(date, 'DD/MM/YYYY')
    const healthCare = {
      prestation: $(this).find('.naturePrestation').text().trim(),
      date,
      montantPayé: parseAmount($(this).find('[id^=montantPaye]').text().trim()),
      baseRemboursement: parseAmount($(this).find('[id^=baseRemboursement]').text().trim()),
      taux: $(this).find('[id^=taux]').text().trim(),
      montantVersé: parseAmount($(this).find('[id^=montantVerse]').text().trim())
    }

    reimbursement.beneficiaries[beneficiary] = reimbursement.beneficiaries[beneficiary] || []
    reimbursement.beneficiaries[beneficiary].push(healthCare)
  })
}

function parseParticipation ($, container, reimbursement) {
  $(container).find('tr').each(function () {
    if ($(this).find('th').length > 0) {
      return null // ignore header
    }

    if (reimbursement.participation) {
      log('warning', 'There is already a participation, this case is not supposed to happend')
    }
    let date = $(this).find('[id^=dateActePFF]').text().trim()
    date = moment(date, 'DD/MM/YYYY')
    reimbursement.participation = {
      prestation: $(this).find('[id^=naturePFF]').text().trim(),
      date,
      montantVersé: parseAmount($(this).find('[id^=montantVerse]').text().trim())
    }
  })
}

function getBills (reimbursements) {
  const bills = []
  reimbursements.forEach(reimbursement => {
    for (const beneficiary in reimbursement.beneficiaries) {
      reimbursement.beneficiaries[beneficiary].forEach(healthCare => {
        bills.push({
          type: 'health',
          subtype: healthCare.prestation,
          beneficiary,
          isThirdPartyPayer: reimbursement.isThirdPartyPayer,
          date: reimbursement.date.toDate(),
          originalDate: healthCare.date.toDate(),
          vendor: 'Ameli',
          amount: healthCare.montantVersé,
          originalAmount: healthCare.montantPayé,
          fileurl: 'https://assure.ameli.fr' + reimbursement.link,
          filename: getFileName(reimbursement.date)
        })
      })
    }

    if (reimbursement.participation) {
      bills.push({
        type: 'health',
        subtype: reimbursement.participation.prestation,
        isThirdPartyPayer: reimbursement.isThirdPartyPayer,
        date: reimbursement.date.toDate(),
        originalDate: reimbursement.participation.date.toDate(),
        vendor: 'Ameli',
        amount: reimbursement.participation.montantVersé,
        fileurl: 'https://assure.ameli.fr' + reimbursement.link,
        filename: getFileName(reimbursement.date)
      })
    }
  })
  return bills
}

function getFileName (date) {
  return `${moment(date).format('YYYYMMDD')}_ameli.pdf`
}
<commit_msg>debug: more detailed bad login detection + more logs<commit_after>'use strict'

const {log, BaseKonnector, saveBills, request} = require('cozy-konnector-libs')
const moment = require('moment')
const bluebird = require('bluebird')

let rq = request({
  // debug: true,
  cheerio: true,
  json: false,
  jar: true
})

const baseUrl = 'https://assure.ameli.fr/PortailAS/paiements.do?actionEvt='

module.exports = new BaseKonnector(function fetch (fields) {
  return checkLogin(fields)
  .then(() => logIn(fields))
  .then($ => fetchMainPage($))
  .then($ => parseMainPage($))
  .then(reimbursements => getBills(reimbursements))
  .then(entries => {
    // get custom bank identifier if any
    let identifiers = 'C.P.A.M.'
    if (fields.bank_identifier && fields.bank_identifier.length) {
      identifiers = fields.bank_identifier
    }

    return saveBills(entries, fields.folderPath, {
      timeout: Date.now() + 60 * 1000,
      identifiers,
      dateDelta: 10,
      amountDelta: 0.1
    })
  })
})

const checkLogin = function (fields) {
  log('info', 'Checking the length of the login')
  if (fields.login.length > 13) {
    log('debug', `Bad login length : ${fields.login.length}`)
    return Promise.reject(new Error('LOGIN_FAILED'))
  }

  return Promise.resolve()
}

// Procedure to login to Ameli website.
const logIn = function (fields) {
  log('info', 'Now logging in')
  const loginUrl = 'https://assure.ameli.fr/PortailAS/appmanager/PortailAS/assure?_somtc=true'

  const submitUrl = 'https://assure.ameli.fr/PortailAS/appmanager/PortailAS/' +
    'assure?_nfpb=true&_windowLabel=connexioncompte_2&connexioncompte_2_' +
    'actionOverride=/portlets/connexioncompte/validationconnexioncompte&_pageLabel=as_login_page'

  const reimbursementUrl = 'https://assure.ameli.fr/PortailAS/appmanager/PortailAS/assure?_nfpb=true&_pageLabel=as_paiements_page'

  const form = {
    'connexioncompte_2numSecuriteSociale': fields.login,
    'connexioncompte_2codeConfidentiel': fields.password,
    'connexioncompte_2actionEvt': 'connecter',
    'submit': 'Valider'
  }

  return rq({
    url: loginUrl,
    resolveWithFullResponse: true
  })
  // First request to get the cookie
  .then(res => rq({
    method: 'POST',
    form,
    url: submitUrl
  }))
  // Second request to authenticate
  .then($ => {
    const $errors = $('#r_errors')
    if ($errors.length > 0) {
      log('debug', $errors.text(), 'These errors where found on screen')
      throw new Error('LOGIN_FAILED')
    }

    if ($('[title="Déconnexion du compte ameli"]').length !== 1) {
      log('debug', $('body').html(), 'No deconnection link found in the html')
      throw new Error('LOGIN_FAILED')
    }

    log('info', 'Correctly logged in')
    return rq(reimbursementUrl)
  })
}

// fetch the HTML page with the list of health cares
const fetchMainPage = function ($) {
  log('info', 'Fetching the list of bills')

  // Get the start and end date to generate the bill's url
  const endDate = $('#paiements_1dateFin').attr('value')

  // We can get the history only 6 months back
  const startDate = moment(endDate, 'DD/MM/YYYY').subtract(6, 'months').format('DD/MM/YYYY')

  const billUrl = `${baseUrl}afficherPaiementsComplementaires&DateDebut=${startDate}&DateFin=${endDate}\
&Beneficiaire=tout_selectionner&afficherReleves=false&afficherIJ=false&afficherInva=false&afficherRentes=false\
&afficherRS=false&indexPaiement=&idNotif=`

  return rq(billUrl)
}

// Parse the fetched page to extract bill data.
const parseMainPage = function ($) {
  const reimbursements = []
  let i = 0

  // Each bloc represents a month that includes 0 to n reimbursement
  $('.blocParMois').each(function () {
    // It would be too easy to get the full date at the same place
    let year = $($(this).find('.rowdate .mois').get(0)).text()
    year = year.split(' ')[1]

    return $(`[id^=lignePaiement${i++}]`).each(function () {
      const month = $($(this).find('.col-date .mois').get(0)).text()
      const day = $($(this).find('.col-date .jour').get(0)).text()
      let date = `${day} ${month} ${year}`
      moment.locale('fr')
      date = moment(date, 'Do MMMM YYYY')

      // Retrieve and extract the infos needed to generate the pdf
      const attrInfos = $(this).attr('onclick')
      const tokens = attrInfos.split("'")

      const idPaiement = tokens[1]
      const naturePaiement = tokens[3]
      const indexGroupe = tokens[5]
      const indexPaiement = tokens[7]

      const detailsUrl = `${baseUrl}chargerDetailPaiements\
&idPaiement=${idPaiement}\
&naturePaiement=${naturePaiement}\
&indexGroupe=${indexGroupe}\
&indexPaiement=${indexPaiement}`

      let lineId = indexGroupe + indexPaiement

      let reimbursement = {
        date,
        lineId,
        detailsUrl,
        isThirdPartyPayer: naturePaiement === 'PAIEMENT_A_UN_TIERS',
        beneficiaries: {}
      }

      reimbursements.push(reimbursement)
    })
  })
  return bluebird.each(reimbursements, reimbursement => {
    return rq(reimbursement.detailsUrl)
    .then($ => parseDetails($, reimbursement))
  })
  .then(() => reimbursements)
}

function parseDetails ($, reimbursement) {
  let currentBeneficiary = null
  reimbursement.link = $('.entete [id^=liendowndecompte]').attr('href')
  $('.container:not(.entete)').each(function () {
    const $beneficiary = $(this).find('[id^=nomBeneficiaire]')
    if ($beneficiary.length > 0) { // a beneficiary container
      currentBeneficiary = $beneficiary.text().trim()
      return null
    }

    // the next container is the list of health cares associated to the beneficiary
    if (currentBeneficiary) {
      parseHealthCares($, this, currentBeneficiary, reimbursement)
      currentBeneficiary = null
    } else {
      // there is some participation remaining for the whole reimbursement
      parseParticipation($, this, reimbursement)
    }
  })
}

function parseAmount (amount) {
  return parseFloat(amount.replace(' €', '').replace(',', '.'))
}

function parseHealthCares ($, container, beneficiary, reimbursement) {
  $(container).find('tr').each(function () {
    if ($(this).find('th').length > 0) {
      return null // ignore header
    }

    let date = $(this).find('[id^=Nature]').html().split('
').pop().trim()
    date = moment(date, 'DD/MM/YYYY')
    const healthCare = {
      prestation: $(this).find('.naturePrestation').text().trim(),
      date,
      montantPayé: parseAmount($(this).find('[id^=montantPaye]').text().trim()),
      baseRemboursement: parseAmount($(this).find('[id^=baseRemboursement]').text().trim()),
      taux: $(this).find('[id^=taux]').text().trim(),
      montantVersé: parseAmount($(this).find('[id^=montantVerse]').text().trim())
    }

    reimbursement.beneficiaries[beneficiary] = reimbursement.beneficiaries[beneficiary] || []
    reimbursement.beneficiaries[beneficiary].push(healthCare)
  })
}

function parseParticipation ($, container, reimbursement) {
  $(container).find('tr').each(function () {
    if ($(this).find('th').length > 0) {
      return null // ignore header
    }

    if (reimbursement.participation) {
      log('warning', 'There is already a participation, this case is not supposed to happend')
    }
    let date = $(this).find('[id^=dateActePFF]').text().trim()
    date = moment(date, 'DD/MM/YYYY')
    reimbursement.participation = {
      prestation: $(this).find('[id^=naturePFF]').text().trim(),
      date,
      montantVersé: parseAmount($(this).find('[id^=montantVerse]').text().trim())
    }
  })
}

function getBills (reimbursements) {
  const bills = []
  reimbursements.forEach(reimbursement => {
    for (const beneficiary in reimbursement.beneficiaries) {
      reimbursement.beneficiaries[beneficiary].forEach(healthCare => {
        bills.push({
          type: 'health',
          subtype: healthCare.prestation,
          beneficiary,
          isThirdPartyPayer: reimbursement.isThirdPartyPayer,
          date: reimbursement.date.toDate(),
          originalDate: healthCare.date.toDate(),
          vendor: 'Ameli',
          amount: healthCare.montantVersé,
          originalAmount: healthCare.montantPayé,
          fileurl: 'https://assure.ameli.fr' + reimbursement.link,
          filename: getFileName(reimbursement.date)
        })
      })
    }

    if (reimbursement.participation) {
      bills.push({
        type: 'health',
        subtype: reimbursement.participation.prestation,
        isThirdPartyPayer: reimbursement.isThirdPartyPayer,
        date: reimbursement.date.toDate(),
        originalDate: reimbursement.participation.date.toDate(),
        vendor: 'Ameli',
        amount: reimbursement.participation.montantVersé,
        fileurl: 'https://assure.ameli.fr' + reimbursement.link,
        filename: getFileName(reimbursement.date)
      })
    }
  })
  return bills
}

function getFileName (date) {
  return `${moment(date).format('YYYYMMDD')}_ameli.pdf`
}
<|endoftext|>


<commit_before>// exports
// -------

exports = module.exports = {
  async: async,
  lazy: lazy,
  maybe: maybe,
  state: state
};

// ASYNC
// -----

// Function -> Function
function async(fn) {
  assert(isGeneratorFunction(fn), 'The argument `fn` must be a GeneratorFunction.');

  run.mflow_generatorFunction = fn;
  return run;

  function run(callback) {
    assert(isFunction(callback), 'The argument `callback` must be a function.');

    var gen = fn();
    next();

    function next(err, rest) {
      var ret;

      if (err) {
        try {
          ret = gen.throw(err);
        } catch (e) {
          callback(e);
          return;
        }
      } else {
        if (arguments.length > 2) {
          rest = [].slice.call(arguments, 1);
        }
        try {
          ret = gen.next(rest);
        } catch (e) {
          callback(e);
          return;
        }
      }

      if (ret.done) {
        callback(null, ret.value);
      } else {
        if (isFunction(ret.value)) {
          var f = normalize(ret.value);
          f(next);
        } else {
          next(new Error('The yieldable value must be a function.'));
        }
      }
    }
  }

  function normalize(obj) {
    if (obj && obj.mflow_generatorFunction) obj = obj.mflow_generatorFunction;
    if (isGeneratorFunction(obj)) obj = async(obj);
    return obj;
  }
}

// [Function fns...] -> Function
async.join = function (fns) {
  if (!Array.isArray(fns)) fns = [].slice.call(arguments);
  for (var i = 0, len = fns.length; i < len; i++) {
    assert(isFunction(fns[i]), 'The argument[' + i + '] must be a function.');
  }
  return run;

  function run(callback) {
    var pending = fns.length;
    var results = Array(pending);
    var finished;

    for (var i = 0, len = fns.length; i < len; i++) {
      fns[i](wrap(i));
    }

    function wrap(i) {
      return function (err, rest) {
        if (finished) return;
        if (err) {
          finished = true;
          callback(err);
        } else {
          if (arguments.length > 2) rest = [].slice.call(arguments, 1);
          results[i] = rest;
          pending--;
          if (pending === 0) callback(null, results);
        }
      };
    }
  };
};

// LAZY
// ----

// Function -> Function
function lazy(fn) {
  assert(isGeneratorFunction(fn), 'The argument `fn` must be a GeneratorFunction.');

  run.mflow_generatorFunction = fn;
  return run;

  var finished;
  var result;

  function run() {
    if (finished) return result;
    var gen = fn();
    var ret = gen.next();
    var value;
    while (!ret.done) {
      value = normalize(ret.value);
      ret = gen.next(value);
    }
    finished = true;
    result = ret.value;
    return result;
  }

  function normalize(obj) {
    if (obj && obj.mflow_generatorFunction) obj = obj.mflow_generatorFunction;
    if (isGeneratorFunction(obj)) obj = lazy(obj);
    if (isFunction(obj)) obj = obj();
    return obj;
  }
}

// MAYBE
// -----

// Function -> Object
function maybe(fn) {
  assert(isGeneratorFunction(fn), 'The argument `fn` must be a GeneratorFunction.');

  var gen = fn();
  var ret = gen.next();
  while (!ret.done) {
    if (ret.value == null) return null;
    ret = gen.next(ret.value);
  }
  return ret.value;
}

// STATE
// -----

// Function -> Function
function state(fn) {
  assert(isGeneratorFunction(fn), 'The argument `fn` must be a GeneratorFunction.');

  run.mflow_generatorFunction = fn;
  return run;

  function run(state) {
    var gen = fn();
    var ret = gen.next();
    var pair;
    var value;
    while (!ret.done) {
      if (isFunction(ret.value)) {
        f = normalize(ret.value);
        pair = f(state);
        if (Array.isArray(pair)) {
          value = pair[0];
          state = pair[1];
        } else {
          value = pair;
        }
        ret = gen.next(value);
      } else {
        ret = gen.throw(new Error("The yieldable value must be a function."));
      }
    }
    return [ret.value, state];
  };

  function normalize(obj) {
    if (obj && obj.mflow_generatorFunction) obj = obj.mflow_generatorFunction;
    if (isGeneratorFunction(obj)) obj = state(obj);
    return obj;
  }
}

// Object -> Function
state.put = function (s) {
  return function () {
    return [null, s];
  }
}

// Void -> Function
state.get = function () {
  return function (s) {
    return [s, s];
  }
}

// Function, Object -> Object
state.eval = function (flow, s) {
  assert(isFunction(flow), 'The argument `flow` must be a function.');
  var pair = flow(s);
  assert(Array.isArray(pair), 'The argument `flow` must return an array.');
  return pair[0];
}

// Function, Object -> Object
state.exec = function (flow, s) {
  assert(isFunction(flow), 'The argument `flow` must be a function.');
  var pair = flow(s);
  assert(Array.isArray(pair), 'The argument `flow` must return an array.');
  return pair[1];
}

// utilities
// ---------

function assert(prerequisite, message) {
  if (prerequisite) return;
  throw new Error(message);
}

function isFunction(obj) {
  return typeof obj === 'function';
}

function isGeneratorFunction(obj) {
  return obj && obj.constructor && 'GeneratorFunction' === obj.constructor.name;
}
<commit_msg>Define a local variable to prevent to be a global variable<commit_after>// exports
// -------

exports = module.exports = {
  async: async,
  lazy: lazy,
  maybe: maybe,
  state: state
};

// ASYNC
// -----

// Function -> Function
function async(fn) {
  assert(isGeneratorFunction(fn), 'The argument `fn` must be a GeneratorFunction.');

  run.mflow_generatorFunction = fn;
  return run;

  function run(callback) {
    assert(isFunction(callback), 'The argument `callback` must be a function.');

    var gen = fn();
    next();

    function next(err, rest) {
      var ret;

      if (err) {
        try {
          ret = gen.throw(err);
        } catch (e) {
          callback(e);
          return;
        }
      } else {
        if (arguments.length > 2) {
          rest = [].slice.call(arguments, 1);
        }
        try {
          ret = gen.next(rest);
        } catch (e) {
          callback(e);
          return;
        }
      }

      if (ret.done) {
        callback(null, ret.value);
      } else {
        if (isFunction(ret.value)) {
          var f = normalize(ret.value);
          f(next);
        } else {
          next(new Error('The yieldable value must be a function.'));
        }
      }
    }
  }

  function normalize(obj) {
    if (obj && obj.mflow_generatorFunction) obj = obj.mflow_generatorFunction;
    if (isGeneratorFunction(obj)) obj = async(obj);
    return obj;
  }
}

// [Function fns...] -> Function
async.join = function (fns) {
  if (!Array.isArray(fns)) fns = [].slice.call(arguments);
  for (var i = 0, len = fns.length; i < len; i++) {
    assert(isFunction(fns[i]), 'The argument[' + i + '] must be a function.');
  }
  return run;

  function run(callback) {
    var pending = fns.length;
    var results = Array(pending);
    var finished;

    for (var i = 0, len = fns.length; i < len; i++) {
      fns[i](wrap(i));
    }

    function wrap(i) {
      return function (err, rest) {
        if (finished) return;
        if (err) {
          finished = true;
          callback(err);
        } else {
          if (arguments.length > 2) rest = [].slice.call(arguments, 1);
          results[i] = rest;
          pending--;
          if (pending === 0) callback(null, results);
        }
      };
    }
  };
};

// LAZY
// ----

// Function -> Function
function lazy(fn) {
  assert(isGeneratorFunction(fn), 'The argument `fn` must be a GeneratorFunction.');

  run.mflow_generatorFunction = fn;
  return run;

  var finished;
  var result;

  function run() {
    if (finished) return result;
    var gen = fn();
    var ret = gen.next();
    var value;
    while (!ret.done) {
      value = normalize(ret.value);
      ret = gen.next(value);
    }
    finished = true;
    result = ret.value;
    return result;
  }

  function normalize(obj) {
    if (obj && obj.mflow_generatorFunction) obj = obj.mflow_generatorFunction;
    if (isGeneratorFunction(obj)) obj = lazy(obj);
    if (isFunction(obj)) obj = obj();
    return obj;
  }
}

// MAYBE
// -----

// Function -> Object
function maybe(fn) {
  assert(isGeneratorFunction(fn), 'The argument `fn` must be a GeneratorFunction.');

  var gen = fn();
  var ret = gen.next();
  while (!ret.done) {
    if (ret.value == null) return null;
    ret = gen.next(ret.value);
  }
  return ret.value;
}

// STATE
// -----

// Function -> Function
function state(fn) {
  assert(isGeneratorFunction(fn), 'The argument `fn` must be a GeneratorFunction.');

  run.mflow_generatorFunction = fn;
  return run;

  function run(state) {
    var gen = fn();
    var ret = gen.next();
    var f;
    var pair;
    var value;
    while (!ret.done) {
      if (isFunction(ret.value)) {
        f = normalize(ret.value);
        pair = f(state);
        if (Array.isArray(pair)) {
          value = pair[0];
          state = pair[1];
        } else {
          value = pair;
        }
        ret = gen.next(value);
      } else {
        ret = gen.throw(new Error("The yieldable value must be a function."));
      }
    }
    return [ret.value, state];
  };

  function normalize(obj) {
    if (obj && obj.mflow_generatorFunction) obj = obj.mflow_generatorFunction;
    if (isGeneratorFunction(obj)) obj = state(obj);
    return obj;
  }
}

// Object -> Function
state.put = function (s) {
  return function () {
    return [null, s];
  }
}

// Void -> Function
state.get = function () {
  return function (s) {
    return [s, s];
  }
}

// Function, Object -> Object
state.eval = function (flow, s) {
  assert(isFunction(flow), 'The argument `flow` must be a function.');
  var pair = flow(s);
  assert(Array.isArray(pair), 'The argument `flow` must return an array.');
  return pair[0];
}

// Function, Object -> Object
state.exec = function (flow, s) {
  assert(isFunction(flow), 'The argument `flow` must be a function.');
  var pair = flow(s);
  assert(Array.isArray(pair), 'The argument `flow` must return an array.');
  return pair[1];
}

// utilities
// ---------

function assert(prerequisite, message) {
  if (prerequisite) return;
  throw new Error(message);
}

function isFunction(obj) {
  return typeof obj === 'function';
}

function isGeneratorFunction(obj) {
  return obj && obj.constructor && 'GeneratorFunction' === obj.constructor.name;
}
<|endoftext|>


<commit_before>'use strict';

var marked = require('marked')
  , wrench = require('wrench')
  , atomify = require('atomify')
  , handlebars = require('handlebars')
  , fs = require('fs')
  , path = require('path')
  , http = require('http')
  , change = require('change-case')
  , _ = require('lodash')
  , js = atomify.js
  , css = atomify.css

var renderer = new marked.Renderer();

renderer.heading = function (text, level) {
  var ret = ''
  if (level === 1) {
    ret = '<h' + level + '>'+text+'</h' + level + '>'
  } else {
    ret = '<h' + level + '>'+text+'</h'+level+'>'
  }
  return ret
}

module.exports = function (dir) {

  var exists = function exists(component, file) {
    var p = path.join(dir, component, file)
    return !!fs.existsSync(p)
  }

  var read = function read(component, file) {
    var p = path.join(dir, component, file)
    return fs.readFileSync(p, 'utf8')
  }

  var scanComponents = function () {
    var files = wrench.readdirSyncRecursive(dir)
      , blackListedComponentNames = ['.DS_Store', 'README.md']
      , dirs = _.unique(_.map(files, function (file) {
          return file.split('/')[0]
        }))
      , components = _.map(dirs, function (component) {
          return {
            name: component
          , pascal: change.pascal(component)
          , readme: exists(component, 'README.md')
          , example: exists(component, 'example')
          , test: exists(component, 'test.js')
          }
        })
      return _.filter(components, function (component){
        return blackListedComponentNames.indexOf(component.name) === -1
      })
  }

  var sendComponentReadme = function (req, res) {
    var component = req.url.split('?')[0].split('/')[1]
      , html = ''

    if (exists(component, 'README.md')) {
      html = marked(read(component, 'README.md'))
    } else {
      html = '
'+component+'

No docs written.
'
    }
    res.end(html)
  }

  var sendComponentExampleJS = function (req, res) {
    var component = req.url.split('?')[0].split('/')[1]

    if (!exists(component, path.join('example', 'entry.js'))) {
      return res.end('no example')
    }

    var opts = {
      entry: path.join(dir, component, 'example', 'entry.js')
    , debug: true // default: `false`
    }

    js(opts, responder('css', res))
  }

  var sendComponentExampleCSS = function (req, res) {
    var component = req.url.split('?')[0].split('/')[1]

    if (!exists(component, path.join('example', 'entry.css'))) {
      return res.end('no example')
    }

    var opts = {
      entry: path.join(dir, component, 'example', 'entry.css')
    , debug: true // default: `false`
    }

    css(opts, responder('css', res))
  }

  var sendComponentExampleHTML = function (req, res) {
    var component = req.url.split('?')[0].split('/')[1]

    var src = '<!doctype html>'
    src += '<meta charset="utf-8">'
    src += '<meta http-equiv="X-UA-Compatible" content="IE=edge">'
    src += '<meta name="viewport" content="initial-scale=1,width=device-width,user-scalable=0,minimal-ui">'
    src += '<title>Example for '+component+'</title>'
    src += '<link rel="stylesheet" href="/'+component+'/example.css">'
    src += ''
    src += ''
    src += ''

    res.setHeader('Content-Type', 'text/html')
    res.end(src)
  }

  var sendDocs = function (req, res) {
    var components = scanComponents()
      , iframe = ''

    for(var i in components) {
      if (components.hasOwnProperty(i)){
        iframe = ''
        if (components[i].example) {
          iframe = ''
        }

        if (components[i].readme) {
          components[i].htmlReadme = iframe + marked(read(components[i].path, 'README.md'), {renderer: renderer})
        } else {
          components[i].htmlReadme = '
'+components[i].name+'
'+iframe+'
No docs written.
'
        }
      }
    }

    var template = handlebars.compile(fs.readFileSync(path.join(__dirname, 'index.html.hbs'), 'utf8'))

    var html = template({
      components: components
    })

    res.end(html)
  }

  var sendCSS = function (req, res) {
    var opts = {
      entry: path.join(__dirname, 'pages', 'docs', 'entry.css')
    , debug: true // default: `false`
    }

    css(opts, responder('css', res))
  }

  var sendJS = function (req, res) {
    var opts = {
      entry: path.join(__dirname, 'pages', 'docs', 'entry.js')
    , debug: true // default: `false`
    }

    js(opts, responder('css', res))
  }

  var responder = function (type, res) {
    return function (err, src) {
      if (err) console.error(err);

      if (!res.headersSent) res.setHeader('Content-Type', 'text/' + type)
      res.end(src)
    }
  }

  var startServer = function () {
    http.createServer(function (req, res) {
      var url = req.url.split('?')[0]

      if (url === '/docs' || url === '/') {
        return sendDocs(req, res)
      }

      if (url === '/style.css') {
        return sendCSS(req, res)
      }

      if (url === '/site.js') {
        return sendJS(req, res)
      }

      if (url.indexOf('readme.html') > -1) {
        return sendComponentReadme(req, res)
      }

      if (url.indexOf('example.html') > -1) {
        return sendComponentExampleHTML(req, res)
      }

      if (url.indexOf('example.js') > -1) {
        return sendComponentExampleJS(req, res)
      }

      if (url.indexOf('example.css') > -1) {
        return sendComponentExampleCSS(req, res)
      }

    }).listen(9001)
    console.info('Server running. Visit http://localhost:9001/ to see the docs')
  }
  startServer()

}
<commit_msg>Allow parsing a dir that has sub-dirs<commit_after>'use strict';

var marked = require('marked')
  , wrench = require('wrench')
  , atomify = require('atomify')
  , handlebars = require('handlebars')
  , fs = require('fs')
  , path = require('path')
  , http = require('http')
  , change = require('change-case')
  , _ = require('lodash')
  , js = atomify.js
  , css = atomify.css

var renderer = new marked.Renderer();

renderer.heading = function (text, level) {
  var ret = ''
  if (level === 1) {
    ret = '<h' + level + '>'+text+'</h' + level + '>'
  } else {
    ret = '<h' + level + '>'+text+'</h'+level+'>'
  }
  return ret
}

module.exports = function (dir) {

  var exists = function exists(component, file) {
    var p = path.join(dir, component, file)
    return !!fs.existsSync(p)
  }

  var read = function read(component, file) {
    var p = path.join(dir, component, file)
    return fs.readFileSync(p, 'utf8')
  }

  var scanComponents = function () {
    var files = wrench.readdirSyncRecursive(dir)
      , blackListedComponentNames = ['.DS_Store', 'README.md']
      , dirs = _.unique(_.compact(_.map(files, function (filePath) {
          var file = path.basename(filePath)

          if (file === 'index.js' || file === 'index.css')
            return path.dirname(filePath)
          else
            return false
        })))
      , components = _.map(dirs, function (component) {
          var pathParts = component.split('/')
            , name = ''
            , section = ''

          if (pathParts.length > 1) {
            name = path.basename(component)
            section = _.initial(pathParts).join('/')
          }
          else {
            name = component
          }

          return {
            name: name
          , section: section
          , path: component
          , pascal: change.pascal(name)
          , readme: exists(component, 'README.md')
          , example: exists(component, 'example')
          , test: exists(component, 'test.js')
          }
        })
      return _.filter(components, function (component){
        return blackListedComponentNames.indexOf(component.name) === -1
      })
  }

  var sendComponentReadme = function (req, res) {
    var component = req.url.split('?')[0].split('/')[1]
      , html = ''

    if (exists(component, 'README.md')) {
      html = marked(read(component, 'README.md'))
    } else {
      html = '
'+component+'

No docs written.
'
    }
    res.end(html)
  }

  var sendComponentExampleJS = function (req, res) {
    var component = req.url.split('?')[0].split('/')[1]

    if (!exists(component, path.join('example', 'entry.js'))) {
      return res.end('no example')
    }

    var opts = {
      entry: path.join(dir, component, 'example', 'entry.js')
    , debug: true // default: `false`
    }

    js(opts, responder('css', res))
  }

  var sendComponentExampleCSS = function (req, res) {
    var component = req.url.split('?')[0].split('/')[1]

    if (!exists(component, path.join('example', 'entry.css'))) {
      return res.end('no example')
    }

    var opts = {
      entry: path.join(dir, component, 'example', 'entry.css')
    , debug: true // default: `false`
    }

    css(opts, responder('css', res))
  }

  var sendComponentExampleHTML = function (req, res) {
    var component = req.url.split('?')[0].split('/')[1]

    var src = '<!doctype html>'
    src += '<meta charset="utf-8">'
    src += '<meta http-equiv="X-UA-Compatible" content="IE=edge">'
    src += '<meta name="viewport" content="initial-scale=1,width=device-width,user-scalable=0,minimal-ui">'
    src += '<title>Example for '+component+'</title>'
    src += '<link rel="stylesheet" href="/'+component+'/example.css">'
    src += ''
    src += ''
    src += ''

    res.setHeader('Content-Type', 'text/html')
    res.end(src)
  }

  var sendDocs = function (req, res) {
    var components = scanComponents()
      , iframe = ''

    for(var i in components) {
      if (components.hasOwnProperty(i)){
        iframe = ''
        if (components[i].example) {
          iframe = ''
        }

        if (components[i].readme) {
          components[i].htmlReadme = iframe + marked(read(components[i].path, 'README.md'), {renderer: renderer})
        } else {
          components[i].htmlReadme = '
'+components[i].name+'
'+iframe+'
No docs written.
'
        }
      }
    }

    var template = handlebars.compile(fs.readFileSync(path.join(__dirname, 'index.html.hbs'), 'utf8'))

    var html = template({
      components: components
    })

    res.end(html)
  }

  var sendCSS = function (req, res) {
    var opts = {
      entry: path.join(__dirname, 'pages', 'docs', 'entry.css')
    , debug: true // default: `false`
    }

    css(opts, responder('css', res))
  }

  var sendJS = function (req, res) {
    var opts = {
      entry: path.join(__dirname, 'pages', 'docs', 'entry.js')
    , debug: true // default: `false`
    }

    js(opts, responder('css', res))
  }

  var responder = function (type, res) {
    return function (err, src) {
      if (err) console.error(err);

      if (!res.headersSent) res.setHeader('Content-Type', 'text/' + type)
      res.end(src)
    }
  }

  var startServer = function () {
    http.createServer(function (req, res) {
      var url = req.url.split('?')[0]

      if (url === '/docs' || url === '/') {
        return sendDocs(req, res)
      }

      if (url === '/style.css') {
        return sendCSS(req, res)
      }

      if (url === '/site.js') {
        return sendJS(req, res)
      }

      if (url.indexOf('readme.html') > -1) {
        return sendComponentReadme(req, res)
      }

      if (url.indexOf('example.html') > -1) {
        return sendComponentExampleHTML(req, res)
      }

      if (url.indexOf('example.js') > -1) {
        return sendComponentExampleJS(req, res)
      }

      if (url.indexOf('example.css') > -1) {
        return sendComponentExampleCSS(req, res)
      }

    }).listen(9001)
    console.info('Server running. Visit http://localhost:9001/ to see the docs')
  }
  startServer()

}
<|endoftext|>


<commit_before>var express = require('express');
var http = require('http');
var path = require('path');

// Create Express webapp.
var app = express();

var agent = path.join(__dirname, '/agent/app');
app.use('/agent', express.static(agent));

/**
 * Redirect application by default to agent
 */
app.get('/', function (request, response) {
    response.redirect('/agent');
});

var server = http.createServer(app);
var port = process.env.PORT || 1347;
server.listen(port, () => {
	console.log('Express server running on *:' + port);
});

console.log("Server running at http://localhost:%d", port);<commit_msg>Test api added<commit_after>var express = require('express');
var http = require('http');
var path = require('path');

// Create Express webapp.
var app = express();

var agent = path.join(__dirname, '/agent/app');
app.use('/agent', express.static(agent));

/**
 * Redirect application by default to agent
 */
app.get('/', function (request, response) {
    response.redirect('/agent');
});

	res.send({"message" : "Success"});

var server = http.createServer(app);
var port = process.env.PORT || 1347;
server.listen(port, () => {
	console.log('Express server running on *:' + port);
});

console.log("Server running at http://localhost:%d", port);<|endoftext|>


<commit_before>'use strict';

var objectAssign = require('object-assign'),
    Fluxex = require('./lib/fluxex');

Fluxex.createApp = function (stores, prototype) {
    var App = function FluxexApp() {
        this.stores = stores;
        Fluxex.apply(this, arguments);
    };

    if (!stores) {
        throw new Error('You should create app with information of stores');
    }

    App.prototype = new Fluxex();
    objectAssign(App.prototype, prototype);
    App.prototype.constructor = App;

    return App;
};

module.exports = Fluxex;
<commit_msg>add mixin<commit_after>'use strict';

var objectAssign = require('object-assign'),
    Fluxex = require('./lib/fluxex');

Fluxex.createApp = function (stores, prototype) {
    var App = function FluxexApp() {
        this.stores = stores;
        Fluxex.apply(this, arguments);
    };

    if (!stores) {
        throw new Error('You should create app with information of stores');
    }

    App.prototype = new Fluxex();
    objectAssign(App.prototype, prototype);
    App.prototype.constructor = App;

    return App;
};

Fluxex.mixin = require('./lib/fluxmixin');

module.exports = Fluxex;
<|endoftext|>


<commit_before>import test from 'tape';
import _ from 'lodash';
import valueEvaluator from '../src/value-evaluator';
import buildValidator from '../src/build-validator';
import {createSimpleScope} from 'bcx-expression-evaluator';

function validatorResolveDummy(rule) {
  if (rule && rule.validate === 'isTrue') {
    const e = valueEvaluator(rule.value || '$value');
    return scope => !!e(scope);
  } else if (rule && rule.mock) {
    let result = {};
    result.isValid = rule.mock === 'pass';
    result.break = !!rule.break;
    return () => result;
  }
  // else return nothing
};

test('buildValidator: builds on raw func', t => {
  function test(value) {
    if (value === 'fail') return 'lorem';
  };

  const validator = buildValidator(test, validatorResolveDummy);
  t.deepEqual(validator(createSimpleScope({$value: 'pass'}, {})),
              {isValid: true, messages: [], break: false});
  t.deepEqual(validator(createSimpleScope({$value: 'fail'}, {})),
              {isValid: false, messages: ['lorem'], break: false});
  t.end();
});

test('buildValidator: builds on raw func with early break on pass', t => {
  function test(value) {
    if (value === 'fail') return 'lorem';
  };

  test.stopValidationChainIfPass = true;

  const validator = buildValidator(test, validatorResolveDummy);
  t.deepEqual(validator(createSimpleScope({$value: 'pass'}, {})),
              {isValid: true, messages: [], break: true});
  t.deepEqual(validator(createSimpleScope({$value: 'fail'}, {})),
              {isValid: false, messages: ['lorem'], break: false});
  t.end();
});

test('buildValidator: builds on raw func with early break on fail', t => {
  function test(value) {
    if (value === 'fail') return 'lorem';
  };

  test.stopValidationChainIfFail = true;

  const validator = buildValidator(test, validatorResolveDummy);
  t.deepEqual(validator(createSimpleScope({$value: 'pass'}, {})),
              {isValid: true, messages: [], break: false});
  t.deepEqual(validator(createSimpleScope({$value: 'fail'}, {})),
              {isValid: false, messages: ['lorem'], break: true});
  t.end();
});

test('buildValidator: wraps bare expression, override $value to test', t => {
  const validator = buildValidator("a > 3", validatorResolveDummy);
  t.deepEqual(validator(createSimpleScope({$value: 1, a: 4}, {})),
              {isValid: true, messages: [], break: false});

  t.deepEqual(validator(createSimpleScope({$value: 1, a: 2}, {})),
              {isValid: false, messages: ['invalid'], break: false});
  t.end();
});

test('buildValidator: wraps regex, override $value to test', t => {
  const validator = buildValidator(/^ok/, validatorResolveDummy);
  t.deepEqual(validator(createSimpleScope({$value: "ok"}, {})),
              {isValid: true, messages: [], break: false});

  t.deepEqual(validator(createSimpleScope({$value: "not ok"}, {})),
              {isValid: false, messages: ['invalid'], break: false});
  t.end();
});

test('buildValidator: resolve known validator', t => {
  let validator;

  validator = buildValidator(
    {validate: 'isTrue', message: "must be true"},
    validatorResolveDummy
  );

  t.deepEqual(validator(createSimpleScope({$value: true}, {})),
              {isValid: true, messages: [], break: false});

  t.deepEqual(validator(createSimpleScope({$value: ""}, {})),
              {isValid: false, messages: ['must be true'], break: false});

  // early break
  validator = buildValidator(
    {validate: 'isTrue', stopValidationChainIfPass: true},
    validatorResolveDummy
  );

  t.deepEqual(validator(createSimpleScope({$value: true}, {})),
              {isValid: true, messages: [], break: true});

  t.deepEqual(validator(createSimpleScope({$value: ""}, {})),
              {isValid: false, messages: ['invalid'], break: false});

  validator = buildValidator(
    {validate: 'isTrue', stopValidationChainIfFail: true},
    validatorResolveDummy
  );

  t.deepEqual(validator(createSimpleScope({$value: true}, {})),
              {isValid: true, messages: [], break: false});

  t.deepEqual(validator(createSimpleScope({$value: ""}, {})),
              {isValid: false, messages: ['invalid'], break: true});

  // customize value (why testing my test dummy code??)
  validator = buildValidator(
    {validate: 'isTrue', value: "$value > 3", message: "a (${$value}) must be greater than 3"},
    validatorResolveDummy
  );

  t.deepEqual(validator(createSimpleScope({$value: 4}, {})),
              {isValid: true, messages: [], break: false});

  t.deepEqual(validator(createSimpleScope({$value: 1}, {})),
              {isValid: false, messages: ['a (1) must be greater than 3'], break: false});

  t.end();
});

test('buildValidator: rejects unknown rule', t => {
  t.throws(() => buildValidator(
    {unknown: 1},
    validatorResolveDummy
  ));
  t.end();
});

test('buildValidator: builds chain of validators', t => {
  let validator;

  validator = buildValidator(
    [],
    validatorResolveDummy
  );
  t.deepEqual(validator(createSimpleScope({}, {})),
              {isValid: true, messages: [], break: false});

  validator = buildValidator(
    [
      {mock: 'pass'},
      {mock: 'pass'},
      {mock: 'pass'}
    ],
    validatorResolveDummy
  );
  t.deepEqual(validator(createSimpleScope({}, {})),
              {isValid: true, messages: [], break: false});

  validator = buildValidator(
    [
      {mock: 'fail'},
      {mock: 'pass'},
      {mock: 'fail', message: 'bar${1+1}'}
    ],
    validatorResolveDummy
  );
  t.deepEqual(validator(createSimpleScope({}, {})),
              {isValid: false, messages: ['invalid', 'bar2'], break: false});

  validator = buildValidator(
    [
      {mock: 'fail', break: true},
      {mock: 'pass'},
      {mock: 'fail', message: 'bar'}
    ],
    validatorResolveDummy
  );
  t.deepEqual(validator(createSimpleScope({}, {})),
              {isValid: false, messages: ['invalid'], break: false});

  validator = buildValidator(
    [
      {mock: 'fail'},
      {mock: 'pass', break: true},
      {mock: 'fail', message: 'bar'}
    ],
    validatorResolveDummy
  );
  t.deepEqual(validator(createSimpleScope({}, {})),
              {isValid: false, messages: ['invalid'], break: false});

  validator = buildValidator(
    [
      {mock: 'pass', break: true},
      {mock: 'fail'},
      {mock: 'fail', message: 'bar'}
    ],
    validatorResolveDummy
  );
  t.deepEqual(validator(createSimpleScope({}, {})),
              {isValid: true, messages: [], break: false});

  t.end();
});

<commit_msg>test transformer on single string.<commit_after>import test from 'tape';
import _ from 'lodash';
import valueEvaluator from '../src/value-evaluator';
import buildValidator from '../src/build-validator';
import {createSimpleScope} from 'bcx-expression-evaluator';

function validatorResolveDummy(rule) {
  if (rule && rule.validate === 'isTrue') {
    const e = valueEvaluator(rule.value || '$value');
    return scope => !!e(scope);
  } else if (rule && rule.mock) {
    let result = {};
    result.isValid = rule.mock === 'pass';
    result.break = !!rule.break;
    return () => result;
  } else if (rule === 'notTrue') {
    const e = valueEvaluator('$value');
    return scope => !e(scope);
  }
  // else return nothing
};

test('buildValidator: builds on raw func', t => {
  function test(value) {
    if (value === 'fail') return 'lorem';
  };

  const validator = buildValidator(test, validatorResolveDummy);
  t.deepEqual(validator(createSimpleScope({$value: 'pass'}, {})),
              {isValid: true, messages: [], break: false});
  t.deepEqual(validator(createSimpleScope({$value: 'fail'}, {})),
              {isValid: false, messages: ['lorem'], break: false});
  t.end();
});

test('buildValidator: builds on raw func with early break on pass', t => {
  function test(value) {
    if (value === 'fail') return 'lorem';
  };

  test.stopValidationChainIfPass = true;

  const validator = buildValidator(test, validatorResolveDummy);
  t.deepEqual(validator(createSimpleScope({$value: 'pass'}, {})),
              {isValid: true, messages: [], break: true});
  t.deepEqual(validator(createSimpleScope({$value: 'fail'}, {})),
              {isValid: false, messages: ['lorem'], break: false});
  t.end();
});

test('buildValidator: builds on raw func with early break on fail', t => {
  function test(value) {
    if (value === 'fail') return 'lorem';
  };

  test.stopValidationChainIfFail = true;

  const validator = buildValidator(test, validatorResolveDummy);
  t.deepEqual(validator(createSimpleScope({$value: 'pass'}, {})),
              {isValid: true, messages: [], break: false});
  t.deepEqual(validator(createSimpleScope({$value: 'fail'}, {})),
              {isValid: false, messages: ['lorem'], break: true});
  t.end();
});

test('buildValidator: wraps bare expression, override $value to test', t => {
  const validator = buildValidator("a > 3", validatorResolveDummy);
  t.deepEqual(validator(createSimpleScope({$value: 1, a: 4}, {})),
              {isValid: true, messages: [], break: false});

  t.deepEqual(validator(createSimpleScope({$value: 1, a: 2}, {})),
              {isValid: false, messages: ['invalid'], break: false});
  t.end();
});

test('buildValidator: transform single string with validatorResolve', t => {
  const validator = buildValidator("notTrue", validatorResolveDummy);
  t.deepEqual(validator(createSimpleScope({$value: 0, a: 4}, {})),
              {isValid: true, messages: [], break: false});

  t.deepEqual(validator(createSimpleScope({$value: 1, a: 2}, {})),
              {isValid: false, messages: ['invalid'], break: false});
  t.end();
});

test('buildValidator: wraps regex, override $value to test', t => {
  const validator = buildValidator(/^ok/, validatorResolveDummy);
  t.deepEqual(validator(createSimpleScope({$value: "ok"}, {})),
              {isValid: true, messages: [], break: false});

  t.deepEqual(validator(createSimpleScope({$value: "not ok"}, {})),
              {isValid: false, messages: ['invalid'], break: false});
  t.end();
});

test('buildValidator: resolve known validator', t => {
  let validator;

  validator = buildValidator(
    {validate: 'isTrue', message: "must be true"},
    validatorResolveDummy
  );

  t.deepEqual(validator(createSimpleScope({$value: true}, {})),
              {isValid: true, messages: [], break: false});

  t.deepEqual(validator(createSimpleScope({$value: ""}, {})),
              {isValid: false, messages: ['must be true'], break: false});

  // early break
  validator = buildValidator(
    {validate: 'isTrue', stopValidationChainIfPass: true},
    validatorResolveDummy
  );

  t.deepEqual(validator(createSimpleScope({$value: true}, {})),
              {isValid: true, messages: [], break: true});

  t.deepEqual(validator(createSimpleScope({$value: ""}, {})),
              {isValid: false, messages: ['invalid'], break: false});

  validator = buildValidator(
    {validate: 'isTrue', stopValidationChainIfFail: true},
    validatorResolveDummy
  );

  t.deepEqual(validator(createSimpleScope({$value: true}, {})),
              {isValid: true, messages: [], break: false});

  t.deepEqual(validator(createSimpleScope({$value: ""}, {})),
              {isValid: false, messages: ['invalid'], break: true});

  // customize value (why testing my test dummy code??)
  validator = buildValidator(
    {validate: 'isTrue', value: "$value > 3", message: "a (${$value}) must be greater than 3"},
    validatorResolveDummy
  );

  t.deepEqual(validator(createSimpleScope({$value: 4}, {})),
              {isValid: true, messages: [], break: false});

  t.deepEqual(validator(createSimpleScope({$value: 1}, {})),
              {isValid: false, messages: ['a (1) must be greater than 3'], break: false});

  t.end();
});

test('buildValidator: rejects unknown rule', t => {
  t.throws(() => buildValidator(
    {unknown: 1},
    validatorResolveDummy
  ));
  t.end();
});

test('buildValidator: builds chain of validators', t => {
  let validator;

  validator = buildValidator(
    [],
    validatorResolveDummy
  );
  t.deepEqual(validator(createSimpleScope({}, {})),
              {isValid: true, messages: [], break: false});

  validator = buildValidator(
    [
      {mock: 'pass'},
      {mock: 'pass'},
      {mock: 'pass'}
    ],
    validatorResolveDummy
  );
  t.deepEqual(validator(createSimpleScope({}, {})),
              {isValid: true, messages: [], break: false});

  validator = buildValidator(
    [
      {mock: 'fail'},
      {mock: 'pass'},
      {mock: 'fail', message: 'bar${1+1}'}
    ],
    validatorResolveDummy
  );
  t.deepEqual(validator(createSimpleScope({}, {})),
              {isValid: false, messages: ['invalid', 'bar2'], break: false});

  validator = buildValidator(
    [
      {mock: 'fail', break: true},
      {mock: 'pass'},
      {mock: 'fail', message: 'bar'}
    ],
    validatorResolveDummy
  );
  t.deepEqual(validator(createSimpleScope({}, {})),
              {isValid: false, messages: ['invalid'], break: false});

  validator = buildValidator(
    [
      {mock: 'fail'},
      {mock: 'pass', break: true},
      {mock: 'fail', message: 'bar'}
    ],
    validatorResolveDummy
  );
  t.deepEqual(validator(createSimpleScope({}, {})),
              {isValid: false, messages: ['invalid'], break: false});

  validator = buildValidator(
    [
      {mock: 'pass', break: true},
      {mock: 'fail'},
      {mock: 'fail', message: 'bar'}
    ],
    validatorResolveDummy
  );
  t.deepEqual(validator(createSimpleScope({}, {})),
              {isValid: true, messages: [], break: false});

  t.end();
});

<|endoftext|>


<commit_before>var Flow = require('./flow');
var utils = require('./utils');
var isPublicPath = new RegExp("^\/[^/]");
var markups = {};
var cbBuffer = {};

// global module instance cache
var instances = {};

engine._r.push(function () {

    //reset caches
    instances = engine.instances = {};
    markups = {};
    cbBuffer = {};

    // reset DOM body
    document.body.innerHTML = '';

    // remove styles
    var styles = document.head.querySelectorAll('link[rel=stylesheet]');
    if (styles) {
        styles.forEach(function (stylesheet) {
            stylesheet.remove();
        });
    }
});

// call callbacks
function callbacks (err, instance, cbs) {
    cbs.forEach(function (callback) {
        callback(err, instance);
    });
}

/**
 * Create a new module instance.
 *
 * @public
 * @param {object} The CommonJS module.
 * @param {object} The composition config.
 * @param {function} Callback handler.
 */
module.exports = function factory (module, config, role, callback, loaderInstance) {

// TODO handle flow calls (stream, options, data)
/*
// check chached instance access
if (engine.instances[name]) {

    if (!utils.roleAccess(engine.instances[name], role)) {
        return callback(engine.log('E', new Error('Access denied for instance:' + name)));
    }

    return callback(null, engine.instances[name]);
}
*/

    // check instance cache
    if (instances[config.name]) {

        // buffer callback
        if (instances[config.name] === 1 || !instances[config.name]._ready) {
            return cbBuffer[config.name].push(callback);
        }

        return callback(null, instances[config.name]);
    }

    // save callback and mark instance as loading
    cbBuffer[config.name] = [callback];
    instances[config.name] = 1;

    // create new flow emitter
    var instance = Flow();
    var countDown = 1;
    var readyHandler = function (err) {
        if (--countDown === 0) {

            if (err) {
                callbacks(err, undefined, cbBuffer[config.name]);
                delete cbBuffer[config.name];
                delete instances[config.name];
                return;
            }

            // setup flow
            if (config.flow) {
                config.flow.forEach(function (flow) {
                    if (flow.constructor === Array) {

                        // create event streams cache
                        if (!instance._flow[flow[0]]) {
                            instance._flow[flow[0]] = []
                        }

                        // push flow stream config to stream cache
                        instance._flow[flow[0]].push(flow.slice(1));
                    }
                });
            }

            // don't init dependency instances
            if (loaderInstance) {

                // save not yet initialized instance in cache
                instances[config.name] = instance;
                return callback(null, instance);
            }

            initInstancesInLoadOrder(instance);
        }
    };

    // extend module instance with module properties
    if (module) {
        Object.keys(module).forEach(function (key) {
            instance[key] = module[key];
        });
    }

    // extend instance
    instance._module = config.module;
    instance._config = config.config || {};
    instance._name = config.name;
    instance._roles = config.roles || {};
    instance._load = config.load;
    instance._flow = {};
    instance._markups = {};

    // collect dependency instance names in loader dependencies
    if (loaderInstance && config.load) {
        loaderInstance._load.concat(config.load);
    }

    // extend with logging methods
    instance.log = require('./logs') || function () {};

    // load styles
    config.styles && loadStyles(config.styles);

    // load markup
    if (config.markup && config.markup.length) {
        ++countDown;
        loadSnippets(instance, config.markup, readyHandler);
    }

    // only client, on the server the module can just use require any file
    if (config.load) {
        countDown += config.load.length;
        config.load.forEach(function (childInstance) {
            engine.load(childInstance, role, readyHandler, loaderInstance || instance);
        });
    }

    readyHandler();
};

/**
 * Setup event flow, emit ready events and after all module instances are ready, emit a route event.
 *
 * @public
 * @param {err} The err string.
 * @param {array} The loaded module instances.
 */
function initInstancesInLoadOrder (instance) {

    // init dependencies
    var dependencies = instance._load;
    if (dependencies) {

        // init instances
        dependencies.forEach(function (dependency) {

            if (!instances[dependency]) {
                return engine.log('E', 'Instance "' + dependency + '" not found.');

            }

            if (instances[dependency].init) {
                instances[dependency].init();
            }

            instances[dependency]._ready = true;
            callbacks(null, instances[dependency], cbBuffer[dependency]);
        });
    }

    // init loader instance
    if (instance.init) {
        instance.init();
    }

    // save module instance in cache
    instances[instance._name] = instance;

    instance._ready = true;
    callbacks(null, instance, cbBuffer[instance._name]);
}

/**
 * Load html snippets.
 *
 * @public
 * @param {array} The array containing html snippet file urls.
 */

function loadSnippets (instance, urls, callback) {

    var count = urls.length;
    var stream = engine.flow('M', true);

    // receive html snipptes
    stream.error(function (stream, options, err) {

        engine.log('E', err);

        // check if all snippets are loaded
        if (--count === 0) {
            stream.end();
            callback();
        }

    }).data(function (stream, options, html) {

        // save html snippet in cache
        instance._markups[html[0]] = html[1];

        // check if all snippets are loaded
        if (--count === 0) {
            stream.end();
            callback();
        }
    });

    // get html snippets
    urls.forEach(function (url) {
        if (!instance._markups[url]) {
            stream.write(null, url);

        } else if (--count === 0) {
            stream.end();
            callback();
        }
    });
}

/**
 * Load css files.
 *
 * @public
 * @param {array} The array containing css file urls.
 */
function loadStyles (urls) {

    urls.forEach(function (url) {

        var link = global.document.createElement('link');

        // append public file prefix
        if (isPublicPath.test(url)) {
            url = '/!' + url;

        // append module file prefix
        } else if (url.indexOf('://') < 0 && url.indexOf('//') !== 0) {
            url = '/@/@/' + url;
        }

        link.setAttribute('rel', 'stylesheet');
        link.setAttribute('href', url);
        global.document.head.appendChild(link);
    });
}
<commit_msg>move markup and style loading to adapter<commit_after>var Flow = require('./flow');
var utils = require('./utils');
var isPublicPath = new RegExp("^\/[^/]");
var markups = {};
var cbBuffer = {};
var instances = {};

function reset () {

    //reset caches
    instances = {};
    markups = {};
    cbBuffer = {};

    // reset DOM body
    document.body.innerHTML = '';

    // remove styles
    var styles = document.head.querySelectorAll('link[rel=stylesheet]');
    if (styles) {
        styles.forEach(function (stylesheet) {
            stylesheet.remove();
        });
    }
}

// call callbacks
function callbacks (err, instance, cbs) {
    cbs.forEach(function (callback) {
        callback(err, instance);
    });
}

/**
 * Create a new module instance.
 *
 * @public
 * @param {object} The CommonJS module.
 * @param {object} The composition config.
 * @param {function} Callback handler.
 */
module.exports = function factory (module, config, role, callback, loaderInstance) {

    // check instance cache
    if (instances[config.name]) {

        if (!utils.roleAccess(instances[config.name], role)) {
            return callback(console.log('E', new Error('Access denied for instance:' + config.name)));
        }

        // buffer callback
        if (instances[config.name] === 1 || !instances[config.name]._ready) {
            return cbBuffer[config.name].push(callback);
        }

        return callback(null, instances[config.name]);
    }

    // get composition
    // get module
    // get markup
    // load styles
    // create instance

    // save callback and mark instance as loading
    cbBuffer[config.name] = [callback];
    instances[config.name] = 1;

    // create new flow emitter
    var instance = Flow();
    var countDown = 1;
    var readyHandler = function (err) {
        if (--countDown === 0) {

            if (err) {
                callbacks(err, undefined, cbBuffer[config.name]);
                delete cbBuffer[config.name];
                delete instances[config.name];
                return;
            }

            // setup flow
            if (config.flow) {
                config.flow.forEach(function (flow) {
                    if (flow.constructor === Array) {

                        // create event streams cache
                        if (!instance._flow[flow[0]]) {
                            instance._flow[flow[0]] = []
                        }

                        // push flow stream config to stream cache
                        instance._flow[flow[0]].push(flow.slice(1));
                    }
                });
            }

            // don't init dependency instances
            if (loaderInstance) {

                // save not yet initialized instance in cache
                instances[config.name] = instance;
                return callback(null, instance);
            }

            initInstancesInLoadOrder(instance);
        }
    };

    // extend module instance with module properties
    if (module) {
        Object.keys(module).forEach(function (key) {
            instance[key] = module[key];
        });
    }

    // extend instance
    instance._module = config.module;
    instance._config = config.config || {};
    instance._name = config.name;
    instance._roles = config.roles || {};
    instance._load = config.load;
    instance._flow = {};
    instance._markups = {};

    // collect dependency instance names in loader dependencies
    if (loaderInstance && config.load) {
        loaderInstance._load.concat(config.load);
    }

    // extend with logging methods
    instance.log = function () {};//require('./logs') ||

    // load styles
    config.styles && Flow.styles(config.styles);

    // load markup
    if (config.markup && config.markup.length) {
        ++countDown;
        Flow.markup(instance, config.markup, readyHandler);
    }

    // only client, on the server the module can just use require any file
    if (config.load) {
        countDown += config.load.length;
        config.load.forEach(function (childInstance) {
            Flow.load(childInstance, role, readyHandler, loaderInstance || instance);
        });
    }

    readyHandler();
};

/**
 * Setup event flow, emit ready events and after all module instances are ready, emit a route event.
 *
 * @public
 * @param {err} The err string.
 * @param {array} The loaded module instances.
 */
function initInstancesInLoadOrder (instance) {

    // init dependencies
    var dependencies = instance._load;
    if (dependencies) {

        // init instances
        dependencies.forEach(function (dependency) {

            if (!instances[dependency]) {
                return console.log('E', 'Instance "' + dependency + '" not found.');

            }

            if (instances[dependency].init) {
                instances[dependency].init();
            }

            instances[dependency]._ready = true;
            callbacks(null, instances[dependency], cbBuffer[dependency]);
        });
    }

    // init loader instance
    if (instance.init) {
        instance.init();
    }

    // save module instance in cache
    instances[instance._name] = instance;

    instance._ready = true;
    callbacks(null, instance, cbBuffer[instance._name]);
}
<|endoftext|>


<commit_before>;(function (root, factory) {

  if (typeof define === 'function' && define.amd) {
    define(['ractive'], factory);
  } else if (typeof exports === 'object') {
    module.exports = factory(require('ractive'));
  } else {
    factory(root.Ractive);
  }

}(this, function (Ractive) {

  var Adaptor = Ractive.adaptors.Ractive = {
    filter: filter,
    wrap: wrap
  };

  /*
   * Advanced options:
   * You can adjust these settings via `Ractive.adaptors.Ractive.maxKeyLength`
   * and so on. There's usually no need to do that, but it may be good for
   * optimizing tests.
   */

  Adaptor.fireWrapEvents = true;
  Adaptor.maxKeyLength = 2048;

  /*
   * Ractive instance accessor methods that will be proxied by Ractive-Ractive.
   */

  var ractiveMethods = {
    add: {
      modifiesData: true,
      mapAllowed: false
    },
    animate: {
      modifiesData: true,
      mapAllowed: true
    },
    get: {
      modifiesData: false,
      mapAllowed: false
    },
    merge: {
      modifiesData: true,
      mapAllowed: false
    },
    observe: {
      modifiesData: false,
      mapAllowed: true
    },
    observeOnce: {
      modifiesData: false,
      mapAllowed: false
    },
    pop: {
      modifiesData: true,
      mapAllowed: false
    },
    push: {
      modifiesData: true,
      mapAllowed: false
    },
    set: {
      modifiesData: true,
      mapAllowed: true
    },
    shift: {
      modifiesData: true,
      mapAllowed: false
    },
    splice: {
      modifiesData: true,
      mapAllowed: false
    },
    subtract: {
      modifiesData: true,
      mapAllowed: false
    },
    toggle: {
      modifiesData: true,
      mapAllowed: false
    },
    unshift: {
      modifiesData: true,
      mapAllowed: false
    },
    update: {
      modifiesData: false,
      mapAllowed: false
    },
    updateModel: {
      modifiesData: false,
      mapAllowed: false
    }
  };

  /*
   * Check if the child is an Ractive instance.
   *
   * Also, if this key has been wrapped before, don't rewrap it. (Happens on
   * deeply-nested values, and .reset() for some reason.)
   */

  function filter (child, keypath, parent) {
    if (!(child instanceof Ractive))
      return false;

    if (parent &&
        parent._ractiveWraps &&
        parent._ractiveWraps[keypath])
      return false;

    return true;
  }

  /*
   * Global write lock.
   * This prevents infinite loops from happening where a parent will set a
   * value on the child, and the child will attempt to write back to the
   * parent, and so on.
   */

  var locked = Adaptor.locked = {};

  function lock (key, fn) {
    if (locked[key]) return;
    try {
      locked[key] = true;
      return fn();
    } finally {
      delete locked[key];
    }
  }

  /*
   * Returns a wrapped Adaptor for Ractive.
   * See: http://docs.ractivejs.org/latest/writing-adaptor-plugins
   */

  function wrap (parent, child, keypath, prefixer) {
    setup();

    return {
      get: get,
      set: set,
      reset: reset,
      teardown: teardown
    };

    /*
     * Initializes the adaptor.
     */

    function setup () {
      hookAccessors();
      checkForRecursion();
      markAsWrapped();
      child.on('change', onChange);

      if (Adaptor.fireWrapEvents) {
        child.fire('wrap', parent, keypath);
        parent.fire('wrapchild', child, keypath);
      }
    }

    function teardown () {
      delete parent._ractiveWraps[keypath];
      updateChildrenPattern();
      unhookAccessors();
      child.off('change', onChange);

      if (Adaptor.fireWrapEvents) {
        child.fire('unwrap', parent, keypath);
        parent.fire('unwrapchild', child, keypath);
      }
    }

    /*
     * Propagate changes from child to parent.
     * We well break it apart into key/vals and set those individually because
     * some values may be locked.
     */

    function onChange (updates) {
      each(updates, function (value, key) {
        lock(child._guid + key, function () {
          parent.set(keypath + '.' + key, value);
        });
      });
    }

    /*
     * Returns all attributes of the child, including computed properties.
     * See: https://github.com/ractivejs/ractive/issues/1250
     */

    function get () {
      // Optimization: if there are no computed properties, returning all
      // non-computed data should suffice.
      if (!child.computed) return child.get();

      var re = {};

      each(child.get(), function (val, key) {
        re[key] = val;
      });

      each(child.computed, function (_, key) {
        if (typeof re[key] === 'undefined')
          re[key] = child.get(key);
      });

      return re;
    }

    function set (key, value) {
      lock(child._guid + key, function () {
        child.set(key, value);
      });
    }

    /*
     * Allow setting values by passing a POJO to .set(), for instance,
     * `.set('child', { ... })`. If anything else is passed onto .set()
     * (like another Ractive instance, or another adaptor'able), destroy
     * this wrapper.
     */

    function reset (object) {
      if (object && object.constructor === Object) {
        child.set(object);
      } else {
        return false;
      }
    }

    /*
     * Update the parent's child key RegEx pattern used by accessors
     */

    function updateChildrenPattern () {
      if (!parent._ractiveWraps) {
        delete parent._childrenPattern;
        return;
      }

      // Collect the current `_ractiveWraps` keys
      var keys = [];
      each(parent._ractiveWraps, function (value, key) {
        keys.push(key);
      });

      if (keys.length === 0) {
        delete parent._childrenPattern;
        return;
      }

      parent._childrenPattern = new RegExp("^(" + keys.join(")\\..+?|(") + ")\\..+?$");
    }

    /*
     * Generates a Ractive accessor proxy for the given method and configuration.
     */

    function generateAccessor(method, config) {
      config = config || {};

      return function (keypath) {
        var child;
        var childKey;
        var result;

        // Exit early if there aren't any children or we're not targeting a keypath
        if (parent._childrenPattern == null || keypath == null) {
          return parent["_" + method].apply(parent, arguments);
        }

        // Handle a `key, value` set
        if (isString(keypath) === true) {
           childKey = parseAccessorKey(keypath);
           child = parent._ractiveWraps[childKey];

          // The key isn't a child of parent
          if (child == null) {
            return parent["_" + method].apply(parent, arguments);
          }

          // Remove the root of the key before marshalling it to the child
          arguments[0] = arguments[0].replace(childKey + ".", "");
          result = child[method].apply(child, arguments);

          if (config.modifiesData === true) {
            parent._update(childKey + "." + arguments[0]);
          }

          return result;
        }

        // Handle when only a value object is passed to the Ractive instance
        if (config.mapAllowed === true) {
          // Filter out and marshall keys which apply to children
          each(keypath, function (value, key) {
            childKey = parseAccessorKey(keypath);
            child = parent._ractiveWraps[childKey];

            if (child == null) {
              return;
            }

            delete keypath[key];

            // Remove the root of the key before marshalling it to the child
            key = key.replace(childKey + ".", "");
            child[method].call(child, key, value);

            if (config.modifiesData === true) {
              parent._update(childKey + "." + key);
            }
          });
        }

        // Default behavior
        return parent["_" + method].apply(parent, arguments);
      };
    }

    /*
     * Check all children to see if there are any matches and return the result.
     */

    function parseAccessorKey(keypath) {
      if (parent._childrenPattern == null) {
        return;
      }

      var childKeys = parent._childrenPattern.exec(keypath);

      if (childKeys == null) {
        return;
      }

      return childKeys[1];
    }

    /*
     * Overwrite the original Ractive methods with Ractive-Ractive accessor proxies.
     */

    function hookAccessors () {
      // Only hook the accessors once
      if (parent._accessorsHooked === true) {
        return;
      }

      parent._accessorsHooked = true;

      each(ractiveMethods, function (config, method) {
        parent["_" + method] = parent[method];
        parent[method] = generateAccessor(method, config);
      });
    }

    /*
     * Replace the Ractive-Ractive accessor proxies with the original implementations.
     */

    function unhookAccessors () {
      // Only hook the accessors once
      if (parent._accessorsHooked === true) {
        delete parent._accessorsHooked;

        each(ractiveMethods, function (config, method) {
          parent[method] = parent["_" + method];
          delete parent["_" + method];
        });
      }
    }

    /*
     * Die on recursion.
     * Keypath will look like 'child.sub.parent.child.sub.parent' ad nauseum.
     */

    function checkForRecursion () {
      if (keypath && keypath.length > Adaptor.maxKeyLength)
        throw new Error("Keypath too long (possible circular dependency)");
    }

    /*
     * Let future wrappers know what we have wrapped Ractive instances.
     * This value is used on `filter()`.
     */

    function markAsWrapped () {
      if (!parent._ractiveWraps) parent._ractiveWraps = {};
      parent._ractiveWraps[keypath] = child;
      updateChildrenPattern();
    }
  }

  /*
   * Cross-browser forEach helper
   */

  function each (obj, fn) {
    for (var key in obj) {
      if (obj.hasOwnProperty(key)) fn(obj[key], key);
    }
  }

  /*
   * isString helper
   */

   function isString (string) {
    return typeof string == 'string' || (!!string && typeof string == 'object' && Object.prototype.toString.call(string) == '[object String]');
   }
}));
<commit_msg>refactor: Matching unhookAccessors / hookAccessors method style<commit_after>;(function (root, factory) {

  if (typeof define === 'function' && define.amd) {
    define(['ractive'], factory);
  } else if (typeof exports === 'object') {
    module.exports = factory(require('ractive'));
  } else {
    factory(root.Ractive);
  }

}(this, function (Ractive) {

  var Adaptor = Ractive.adaptors.Ractive = {
    filter: filter,
    wrap: wrap
  };

  /*
   * Advanced options:
   * You can adjust these settings via `Ractive.adaptors.Ractive.maxKeyLength`
   * and so on. There's usually no need to do that, but it may be good for
   * optimizing tests.
   */

  Adaptor.fireWrapEvents = true;
  Adaptor.maxKeyLength = 2048;

  /*
   * Ractive instance accessor methods that will be proxied by Ractive-Ractive.
   */

  var ractiveMethods = {
    add: {
      modifiesData: true,
      mapAllowed: false
    },
    animate: {
      modifiesData: true,
      mapAllowed: true
    },
    get: {
      modifiesData: false,
      mapAllowed: false
    },
    merge: {
      modifiesData: true,
      mapAllowed: false
    },
    observe: {
      modifiesData: false,
      mapAllowed: true
    },
    observeOnce: {
      modifiesData: false,
      mapAllowed: false
    },
    pop: {
      modifiesData: true,
      mapAllowed: false
    },
    push: {
      modifiesData: true,
      mapAllowed: false
    },
    set: {
      modifiesData: true,
      mapAllowed: true
    },
    shift: {
      modifiesData: true,
      mapAllowed: false
    },
    splice: {
      modifiesData: true,
      mapAllowed: false
    },
    subtract: {
      modifiesData: true,
      mapAllowed: false
    },
    toggle: {
      modifiesData: true,
      mapAllowed: false
    },
    unshift: {
      modifiesData: true,
      mapAllowed: false
    },
    update: {
      modifiesData: false,
      mapAllowed: false
    },
    updateModel: {
      modifiesData: false,
      mapAllowed: false
    }
  };

  /*
   * Check if the child is an Ractive instance.
   *
   * Also, if this key has been wrapped before, don't rewrap it. (Happens on
   * deeply-nested values, and .reset() for some reason.)
   */

  function filter (child, keypath, parent) {
    if (!(child instanceof Ractive))
      return false;

    if (parent &&
        parent._ractiveWraps &&
        parent._ractiveWraps[keypath])
      return false;

    return true;
  }

  /*
   * Global write lock.
   * This prevents infinite loops from happening where a parent will set a
   * value on the child, and the child will attempt to write back to the
   * parent, and so on.
   */

  var locked = Adaptor.locked = {};

  function lock (key, fn) {
    if (locked[key]) return;
    try {
      locked[key] = true;
      return fn();
    } finally {
      delete locked[key];
    }
  }

  /*
   * Returns a wrapped Adaptor for Ractive.
   * See: http://docs.ractivejs.org/latest/writing-adaptor-plugins
   */

  function wrap (parent, child, keypath, prefixer) {
    setup();

    return {
      get: get,
      set: set,
      reset: reset,
      teardown: teardown
    };

    /*
     * Initializes the adaptor.
     */

    function setup () {
      hookAccessors();
      checkForRecursion();
      markAsWrapped();
      child.on('change', onChange);

      if (Adaptor.fireWrapEvents) {
        child.fire('wrap', parent, keypath);
        parent.fire('wrapchild', child, keypath);
      }
    }

    function teardown () {
      delete parent._ractiveWraps[keypath];
      updateChildrenPattern();
      unhookAccessors();
      child.off('change', onChange);

      if (Adaptor.fireWrapEvents) {
        child.fire('unwrap', parent, keypath);
        parent.fire('unwrapchild', child, keypath);
      }
    }

    /*
     * Propagate changes from child to parent.
     * We well break it apart into key/vals and set those individually because
     * some values may be locked.
     */

    function onChange (updates) {
      each(updates, function (value, key) {
        lock(child._guid + key, function () {
          parent.set(keypath + '.' + key, value);
        });
      });
    }

    /*
     * Returns all attributes of the child, including computed properties.
     * See: https://github.com/ractivejs/ractive/issues/1250
     */

    function get () {
      // Optimization: if there are no computed properties, returning all
      // non-computed data should suffice.
      if (!child.computed) return child.get();

      var re = {};

      each(child.get(), function (val, key) {
        re[key] = val;
      });

      each(child.computed, function (_, key) {
        if (typeof re[key] === 'undefined')
          re[key] = child.get(key);
      });

      return re;
    }

    function set (key, value) {
      lock(child._guid + key, function () {
        child.set(key, value);
      });
    }

    /*
     * Allow setting values by passing a POJO to .set(), for instance,
     * `.set('child', { ... })`. If anything else is passed onto .set()
     * (like another Ractive instance, or another adaptor'able), destroy
     * this wrapper.
     */

    function reset (object) {
      if (object && object.constructor === Object) {
        child.set(object);
      } else {
        return false;
      }
    }

    /*
     * Update the parent's child key RegEx pattern used by accessors
     */

    function updateChildrenPattern () {
      if (!parent._ractiveWraps) {
        delete parent._childrenPattern;
        return;
      }

      // Collect the current `_ractiveWraps` keys
      var keys = [];
      each(parent._ractiveWraps, function (value, key) {
        keys.push(key);
      });

      if (keys.length === 0) {
        delete parent._childrenPattern;
        return;
      }

      parent._childrenPattern = new RegExp("^(" + keys.join(")\\..+?|(") + ")\\..+?$");
    }

    /*
     * Generates a Ractive accessor proxy for the given method and configuration.
     */

    function generateAccessor(method, config) {
      config = config || {};

      return function (keypath) {
        var child;
        var childKey;
        var result;

        // Exit early if there aren't any children or we're not targeting a keypath
        if (parent._childrenPattern == null || keypath == null) {
          return parent["_" + method].apply(parent, arguments);
        }

        // Handle a `key, value` set
        if (isString(keypath) === true) {
           childKey = parseAccessorKey(keypath);
           child = parent._ractiveWraps[childKey];

          // The key isn't a child of parent
          if (child == null) {
            return parent["_" + method].apply(parent, arguments);
          }

          // Remove the root of the key before marshalling it to the child
          arguments[0] = arguments[0].replace(childKey + ".", "");
          result = child[method].apply(child, arguments);

          if (config.modifiesData === true) {
            parent._update(childKey + "." + arguments[0]);
          }

          return result;
        }

        // Handle when only a value object is passed to the Ractive instance
        if (config.mapAllowed === true) {
          // Filter out and marshall keys which apply to children
          each(keypath, function (value, key) {
            childKey = parseAccessorKey(keypath);
            child = parent._ractiveWraps[childKey];

            if (child == null) {
              return;
            }

            delete keypath[key];

            // Remove the root of the key before marshalling it to the child
            key = key.replace(childKey + ".", "");
            child[method].call(child, key, value);

            if (config.modifiesData === true) {
              parent._update(childKey + "." + key);
            }
          });
        }

        // Default behavior
        return parent["_" + method].apply(parent, arguments);
      };
    }

    /*
     * Check all children to see if there are any matches and return the result.
     */

    function parseAccessorKey(keypath) {
      if (parent._childrenPattern == null) {
        return;
      }

      var childKeys = parent._childrenPattern.exec(keypath);

      if (childKeys == null) {
        return;
      }

      return childKeys[1];
    }

    /*
     * Overwrite the original Ractive methods with Ractive-Ractive accessor proxies.
     */

    function hookAccessors () {
      // Only hook the accessors once
      if (parent._accessorsHooked === true) {
        return;
      }

      parent._accessorsHooked = true;

      each(ractiveMethods, function (config, method) {
        parent["_" + method] = parent[method];
        parent[method] = generateAccessor(method, config);
      });
    }

    /*
     * Replace the Ractive-Ractive accessor proxies with the original implementations.
     */

    function unhookAccessors () {
      // Only hook the accessors once
      if (parent._accessorsHooked !== true) {
        return;
      }

      delete parent._accessorsHooked;

      each(ractiveMethods, function (config, method) {
        parent[method] = parent["_" + method];
        delete parent["_" + method];
      });
    }

    /*
     * Die on recursion.
     * Keypath will look like 'child.sub.parent.child.sub.parent' ad nauseum.
     */

    function checkForRecursion () {
      if (keypath && keypath.length > Adaptor.maxKeyLength)
        throw new Error("Keypath too long (possible circular dependency)");
    }

    /*
     * Let future wrappers know what we have wrapped Ractive instances.
     * This value is used on `filter()`.
     */

    function markAsWrapped () {
      if (!parent._ractiveWraps) parent._ractiveWraps = {};
      parent._ractiveWraps[keypath] = child;
      updateChildrenPattern();
    }
  }

  /*
   * Cross-browser forEach helper
   */

  function each (obj, fn) {
    for (var key in obj) {
      if (obj.hasOwnProperty(key)) fn(obj[key], key);
    }
  }

  /*
   * isString helper
   */

   function isString (string) {
    return typeof string == 'string' || (!!string && typeof string == 'object' && Object.prototype.toString.call(string) == '[object String]');
   }
}));
<|endoftext|>


<commit_before>'use strict';

var fs              = require('fs')
  , lstatSync       = fs.lstatSync
  , promisify       = require('deferred').promisify
  , ee              = require('event-emitter')
  , getTypeFromStat = require('./get-type-from-stat')

  , lstat = promisify(fs.lstat), readdir = promisify(fs.readdir)

  , other, directory, delay = 1500;

other = function (path, emitter, stats) {
	var onEnd, close, type, timeout;

	onEnd = function () {
		if (emitter) {
			emitter.emit('end');
			emitter.allOff();
			close();
		}
	};

	close = function () {
		clearTimeout(timeout);
		emitter = null;
	};

	emitter.close = onEnd;
	emitter._close = close;
	type = getTypeFromStat(stats);
	timeout = setTimeout(function self() {
		if (!emitter) {
			return;
		}
		lstat(path).end(function (nstats) {
			var nType;
			if (!emitter) {
				return;
			}
			nType = getTypeFromStat(nstats);
			if (type !== nType) {
				onEnd();
				return;
			}
			if ((stats.ctime.valueOf() !== nstats.ctime.valueOf()) ||
				((stats.mtime.valueOf() !== nstats.mtime.valueOf()) ||
					(stats.size !== nstats.size))) {
				emitter.emit('change');
			}
			timeout = setTimeout(self, delay);
		}, onEnd);
	}, delay);
};

directory = function (path, emitter) {
	var onEnd, close, type, data, compare, timeout;

	onEnd = function () {
		if (emitter) {
			emitter.emit('end');
			emitter.allOff();
			close();
		}
	};

	close = function () {
		if (emitter) {
			clearTimeout(timeout);
			emitter = null;
		}
	};

	compare = function (file, index) {
		return data[index] === file;
	};

	emitter.close = onEnd;
	emitter._close = close;
	readdir(path).end(function (files) {
		data = files.sort();
		timeout = setTimeout(function self() {
			if (!emitter) {
				return;
			}
			readdir(path).end(function (files) {
				if (!emitter) {
					return;
				}
				if ((files.length !== data.length) || !files.sort().every(compare)) {
					data = files;
					emitter.emit('change');
				}
				timeout = setTimeout(self, delay);
			}, onEnd);
		}, delay);
	}, onEnd);
};

module.exports = function (path, emitter) {
	var stats = lstatSync(path);
	if (!emitter) {
		emitter = ee();
	}
	if (stats.isDirectory()) {
		directory(path, emitter);
	} else {
		other(path, emitter, stats);
	}
	return emitter;
};
<commit_msg>Use watchFile instead of lstat.<commit_after>'use strict';

var fs              = require('fs')
  , lstatSync       = fs.lstatSync
  , promisify       = require('deferred').promisify
  , ee              = require('event-emitter')
  , getTypeFromStat = require('./get-type-from-stat')

  , lstat = promisify(fs.lstat), readdir = promisify(fs.readdir)
  , watchFile = fs.watchFile, unwatchFile = fs.unwatchFile

  , other, directory, opts = { persistent: false, interval: 1500 };

other = function (path, emitter, stats) {
	var onEnd, close, type, listener;

	onEnd = function () {
		if (emitter) {
			emitter.emit('end');
			emitter.allOff();
			close();
		}
	};

	close = function () {
		unwatchFile(path, listener);
		emitter = null;
	};

	emitter.close = onEnd;
	emitter._close = close;
	type = getTypeFromStat(stats);
	watchFile(path, opts, listener = function (nstats) {
		var nType;
		if (!emitter) {
			return;
		}
		if (!nstats.ctime.getTime() && !nstats.mode) {
			onEnd();
			return;
		}
		nType = getTypeFromStat(nstats);
		if (type !== nType) {
			onEnd();
			return;
		}
		if ((stats.ctime.valueOf() !== nstats.ctime.valueOf()) ||
			((stats.mtime.valueOf() !== nstats.mtime.valueOf()) ||
				(stats.size !== nstats.size))) {
			emitter.emit('change');
 		}
		stats = nstats;
	});
};

directory = function (path, emitter) {
	var onEnd, close, type, data, compare, listener;

	onEnd = function () {
		if (emitter) {
			emitter.emit('end');
			emitter.allOff();
			close();
		}
	};

	close = function () {
		if (emitter) {
			if (listener) {
				unwatchFile(listener);
			}
			emitter = null;
		}
	};

	compare = function (file, index) {
		return data[index] === file;
	};

	emitter.close = onEnd;
	emitter._close = close;
	readdir(path).end(function (files) {
		data = files.sort();
		watchFile(path, opts, listener = function (stats) {
			if (!emitter) {
				return;
			}
			if (!stats.ctime.getTime() && !stats.mode) {
				onEnd();
				return;
			}
			readdir(path).end(function (files) {
				if (!emitter) {
					return;
				}
				if ((files.length !== data.length) || !files.sort().every(compare)) {
					data = files;
					emitter.emit('change');
				}
			}, onEnd);
		});
	}, onEnd);
};

module.exports = function (path, emitter) {
	var stats = lstatSync(path);
	if (!emitter) {
		emitter = ee();
	}
	if (stats.isDirectory()) {
		directory(path, emitter);
	} else {
		other(path, emitter, stats);
	}
	return emitter;
};
<|endoftext|>