<commit_before>var fs = require('fs');
var moment = require('moment');
var xmldom = require('xmldom');

const PARSER_FUNCTIONS =
{
  "MBO": parseMBOPage,
};

function cleanupHtml(htmlString)
{
  return htmlString.replace(/ /g,' ');
}

function parseFromData(cell)
{
  if (cell.childNodes !== null &&
      cell.childNodes.length > 0)
  {
    const dataCell = cell.childNodes[0];
    if (dataCell.data !== null &&
        dataCell.data !== undefined)
    {
      return dataCell.data.trim();
    }
  }
}

function parseFromChild(cell, recurseLevel)
{
  const firstData = parseFromData(cell);
  if (firstData !== undefined)
  {
    return firstData;
  }

  if (firstData === undefined && recurseLevel > 0)
  {
    if (cell.childNodes !== null &&
        cell.childNodes !== undefined &&
        cell.childNodes.length > 0)
    {
      return parseFromChild(cell.childNodes[0], recurseLevel - 1);
    }
  }
}

function makeParseFromChildFunction(recurseLevel)
{
  return function(cell) { return parseFromChild(cell, recurseLevel); };
}

const PARSER_MAP =
{
  start:
  {
    required: true,
    column: [ 'Start time' ],
    parser: parseFromData,
  },
  course:
  {
    required: true,
    column: [ 'Classes' ],
    parser: makeParseFromChildFunction(1),
  },
  teacher:
  {
    required: true,
    column: [ 'Teacher', 'Yoga + Pilates Mat Teacher' ],
    parser: makeParseFromChildFunction(2),
  },
  duration:
  {
    required: true,
    column: [ 'Duration' ],
    parser: parseFromData,
  },
  room:
  {
    required: false,
    column: [ 'Room' ],
    parser: parseFromData,
  },
  locale:
  {
    required: false,
    column: [ 'Location' ],
    parser: parseFromData,
  },
};

function propNameOfColumnHeader(col)
{
  for (var prop in PARSER_MAP)
  {
    const headers = PARSER_MAP[prop];
    if (PARSER_MAP[prop].column.indexOf(col) !== -1)
    {
      return prop;
    }
  }
  return '';
}

function makeColumnMap(headerRow)
{
  var map = {};

  for (var i = 0; i < headerRow.childNodes[0].childNodes.length; ++i)
  {
    var item = headerRow.childNodes[0].childNodes[i];
    if (item.childNodes !== null &&
        item.childNodes.length > 0)
    {
      var data = item.childNodes[0].data;
      var propName = propNameOfColumnHeader(data);
      if (propName !== undefined && propName !== "")
      {
        console.log('Mapping for "'+data+'",property "'+propName+'", index '+i);
        map[i] = propName;
      }
    }
  }

  return map;
}

function parseDateFromRow(row)
{
  const DATE_LOCATION = 2; // MAGIC NUMBER

  const firstCell = row.childNodes[0];
  if (firstCell.childNodes !== null &&
      firstCell.childNodes.length > 0)
  {
    // Pull out the date
    const firstData = firstCell.childNodes[0];
    if (firstData.childNodes !== null &&
        firstData.childNodes.length > DATE_LOCATION)
    {
      const dateElement = firstData.childNodes[DATE_LOCATION].data.trim();
      const parsedDate = moment(dateElement, 'DD MMM YYYY');
      if (parsedDate.isValid())
      {
        console.log('Current date: ' + parsedDate.format('DD MMM YYYY'));
      }
      else
      {
        console.log('Problem parsing date from data element in:"' + dateElement + '"');
      }
      return parsedDate;
    }
  }
}

function isCourseValid(course)
{
  for (var prop in PARSER_MAP)
  {
    if (PARSER_MAP[prop].required && course.hasOwnProperty(prop) === false)
    {
      return false;
    }
  }
  return true;
}

function parseCourseStart(webCourse, currentDate)
{
  var courseStart = moment(webCourse['start'], 'HH:mm');
  if (courseStart.isValid())
  {
    courseStart.year(currentDate.year());
    courseStart.month(currentDate.month());
    courseStart.date(currentDate.date());
  }
  else
  {
    console.log('Error parsing time from start time: ' + webCourse['start']);
  }
  return courseStart;
}

function parseCourseEnd(webCourse, courseStart)
{
  const NUMBER_LOCATION = 1;
  const TIME_TYPE_LOCATION = 2;
  const SECOND_NUMBER_LOCATION = 4; // needed for "1 hour & 15 minutes"
  const SECOND_TIME_TYPE_LOCATION = 5;
  const durationRegex = /(\d+) *([a-zA-Z]+)( & (\d+) *([a-zA-Z]+))?/;
  const match = webCourse.duration.match(durationRegex);

  var courseEnd = courseStart.clone().add(
      Number(match[NUMBER_LOCATION]),
      match[TIME_TYPE_LOCATION]);
  if (match[SECOND_NUMBER_LOCATION] !== undefined &&
      match[SECOND_TIME_TYPE_LOCATION] !== undefined)
  {
    courseEnd = courseEnd.add(
        Number(match[SECOND_NUMBER_LOCATION]),
        match[SECOND_TIME_TYPE_LOCATION]);
  }
  if (courseEnd.isValid() === false)
  {
    console.log('Error parsing end time from duration: ' + webCourse['Duration']);
  }
  return courseEnd;
}

function dbCourseOfWebCourse(webCourse, currentDate, studio)
{
  var dbCourse =
  {
    name: webCourse['course'],
    start: null,
    end: null,
    teacher: webCourse['teacher'],
    studio: studio.name,
    room: webCourse['room'],
    location: webCourse['location'],
    url: null,
  };

  dbCourse.start = parseCourseStart(webCourse, currentDate);
  dbCourse.end = parseCourseEnd(webCourse, dbCourse.start);

  return dbCourse;
}

function rowIsValid(row)
{
  const STRIKETHROUGH_TAG_LOCATION = 1;
  if (row.childNodes === undefined ||
      row.childNodes === null ||
      row.childNodes.length === 0)
  {
    return false;
  }
  const firstCell = row.childNodes[0];
  // test if strikethrough tag exists
  if (firstCell.childNodes !== null &&
      firstCell.childNodes.length > STRIKETHROUGH_TAG_LOCATION)
  {
    const tagCell = firstCell.childNodes[STRIKETHROUGH_TAG_LOCATION];
    if (tagCell.tagName === 's' || tagCell.nodeName === 's')
    {
      return false;
    }
  }
  return true;
}

function makeJSONCourses(columnMap, tableRows, studio)
{
  const FIRST_DATA_ROW_LOCATION = 1; // MAGIC NUMBER

  var courses = [];
  var currentDate = null;

  for (var i = FIRST_DATA_ROW_LOCATION; i < tableRows.length; ++i)
  {
    var row = tableRows[i];
    var nextDate = parseDateFromRow(row);
    if (nextDate !== undefined && nextDate.isValid())
    {
      currentDate = nextDate;
    }
    else if (rowIsValid(row))
    {
      var course = {};
      for (var j = 0; j < row.childNodes.length; ++j)
      {
        if (columnMap[j] !== undefined)
        {
          var cell = row.childNodes[j];
          var key = columnMap[j];
          course[key] = PARSER_MAP[key].parser(cell);
        }
        else
        {
          //console.log('No mapping for column: ' + j);
        }
      }
      if (isCourseValid(course))
      {
        var dbCourse = dbCourseOfWebCourse(course, currentDate, studio);
        courses.push(dbCourse);
      }
      else
      {
        console.log('No valid course found:');
        console.log(course);
      }
    }
  }
  return courses;
}

function parseMBOPage(htmlString, studio, callback)
{
  const cleanString = cleanupHtml(htmlString);
  const parser = new xmldom.DOMParser();
  const dom = parser.parseFromString(cleanString, 'text/html');

  const headerRow = dom.getElementsByTagName('thead');
  const columnMap = makeColumnMap(headerRow[0]);

  const tableRows = dom.getElementsByTagName('tr');
  const courses = makeJSONCourses(columnMap, tableRows, studio);
  if (callback !== undefined)
  {
    callback(courses);
  }
}

exports.parsePage = function (path, studio, callback)
{
  fs.readFile(path, 'utf8', function(error, data) {
    if (error)
    {
      console.log(error);
    }
    else
    {
      PARSER_FUNCTIONS[studio.provider](data, studio, callback);
    }
  });
}

if (require.main === module)
{
  // For testing
  var studioInfo =
  {
    "name": "Blue Cow Yoga",
    "provider": "MBO",
    "studioid": 23194,
    "area": "Bank"
  };

  function loggerCallback(courses)
  {
    console.log(courses);
  }

  exports.parsePage(process.argv[2], studioInfo, loggerCallback);
}
<commit_msg>Add area to course info<commit_after>var fs = require('fs');
var moment = require('moment');
var xmldom = require('xmldom');

const PARSER_FUNCTIONS =
{
  "MBO": parseMBOPage,
};

function cleanupHtml(htmlString)
{
  return htmlString.replace(/ /g,' ');
}

function parseFromData(cell)
{
  if (cell.childNodes !== null &&
      cell.childNodes.length > 0)
  {
    const dataCell = cell.childNodes[0];
    if (dataCell.data !== null &&
        dataCell.data !== undefined)
    {
      return dataCell.data.trim();
    }
  }
}

function parseFromChild(cell, recurseLevel)
{
  const firstData = parseFromData(cell);
  if (firstData !== undefined)
  {
    return firstData;
  }

  if (firstData === undefined && recurseLevel > 0)
  {
    if (cell.childNodes !== null &&
        cell.childNodes !== undefined &&
        cell.childNodes.length > 0)
    {
      return parseFromChild(cell.childNodes[0], recurseLevel - 1);
    }
  }
}

function makeParseFromChildFunction(recurseLevel)
{
  return function(cell) { return parseFromChild(cell, recurseLevel); };
}

const PARSER_MAP =
{
  start:
  {
    required: true,
    column: [ 'Start time' ],
    parser: parseFromData,
  },
  course:
  {
    required: true,
    column: [ 'Classes' ],
    parser: makeParseFromChildFunction(1),
  },
  teacher:
  {
    required: true,
    column: [ 'Teacher', 'Yoga + Pilates Mat Teacher' ],
    parser: makeParseFromChildFunction(2),
  },
  duration:
  {
    required: true,
    column: [ 'Duration' ],
    parser: parseFromData,
  },
  room:
  {
    required: false,
    column: [ 'Room' ],
    parser: parseFromData,
  },
  locale:
  {
    required: false,
    column: [ 'Location' ],
    parser: parseFromData,
  },
};

function propNameOfColumnHeader(col)
{
  for (var prop in PARSER_MAP)
  {
    const headers = PARSER_MAP[prop];
    if (PARSER_MAP[prop].column.indexOf(col) !== -1)
    {
      return prop;
    }
  }
  return '';
}

function makeColumnMap(headerRow)
{
  var map = {};

  for (var i = 0; i < headerRow.childNodes[0].childNodes.length; ++i)
  {
    var item = headerRow.childNodes[0].childNodes[i];
    if (item.childNodes !== null &&
        item.childNodes.length > 0)
    {
      var data = item.childNodes[0].data;
      var propName = propNameOfColumnHeader(data);
      if (propName !== undefined && propName !== "")
      {
        console.log('Mapping for "'+data+'",property "'+propName+'", index '+i);
        map[i] = propName;
      }
    }
  }

  return map;
}

function parseDateFromRow(row)
{
  const DATE_LOCATION = 2; // MAGIC NUMBER

  const firstCell = row.childNodes[0];
  if (firstCell.childNodes !== null &&
      firstCell.childNodes.length > 0)
  {
    // Pull out the date
    const firstData = firstCell.childNodes[0];
    if (firstData.childNodes !== null &&
        firstData.childNodes.length > DATE_LOCATION)
    {
      const dateElement = firstData.childNodes[DATE_LOCATION].data.trim();
      const parsedDate = moment(dateElement, 'DD MMM YYYY');
      if (parsedDate.isValid())
      {
        console.log('Current date: ' + parsedDate.format('DD MMM YYYY'));
      }
      else
      {
        console.log('Problem parsing date from data element in:"' + dateElement + '"');
      }
      return parsedDate;
    }
  }
}

function isCourseValid(course)
{
  for (var prop in PARSER_MAP)
  {
    if (PARSER_MAP[prop].required && course.hasOwnProperty(prop) === false)
    {
      return false;
    }
  }
  return true;
}

function parseCourseStart(webCourse, currentDate)
{
  var courseStart = moment(webCourse['start'], 'HH:mm');
  if (courseStart.isValid())
  {
    courseStart.year(currentDate.year());
    courseStart.month(currentDate.month());
    courseStart.date(currentDate.date());
  }
  else
  {
    console.log('Error parsing time from start time: ' + webCourse['start']);
  }
  return courseStart;
}

function parseCourseEnd(webCourse, courseStart)
{
  const NUMBER_LOCATION = 1;
  const TIME_TYPE_LOCATION = 2;
  const SECOND_NUMBER_LOCATION = 4; // needed for "1 hour & 15 minutes"
  const SECOND_TIME_TYPE_LOCATION = 5;
  const durationRegex = /(\d+) *([a-zA-Z]+)( & (\d+) *([a-zA-Z]+))?/;
  const match = webCourse.duration.match(durationRegex);

  var courseEnd = courseStart.clone().add(
      Number(match[NUMBER_LOCATION]),
      match[TIME_TYPE_LOCATION]);
  if (match[SECOND_NUMBER_LOCATION] !== undefined &&
      match[SECOND_TIME_TYPE_LOCATION] !== undefined)
  {
    courseEnd = courseEnd.add(
        Number(match[SECOND_NUMBER_LOCATION]),
        match[SECOND_TIME_TYPE_LOCATION]);
  }
  if (courseEnd.isValid() === false)
  {
    console.log('Error parsing end time from duration: ' + webCourse['Duration']);
  }
  return courseEnd;
}

function dbCourseOfWebCourse(webCourse, currentDate, studio)
{
  var dbCourse =
  {
    name: webCourse['course'],
    start: null,
    end: null,
    teacher: webCourse['teacher'],
    studio: studio.name,
    room: webCourse['room'],
    location: webCourse['location'],
    url: studio.url,
    area: studio.area,
  };

  dbCourse.start = parseCourseStart(webCourse, currentDate);
  dbCourse.end = parseCourseEnd(webCourse, dbCourse.start);

  return dbCourse;
}

function rowIsValid(row)
{
  const STRIKETHROUGH_TAG_LOCATION = 1;
  if (row.childNodes === undefined ||
      row.childNodes === null ||
      row.childNodes.length === 0)
  {
    return false;
  }
  const firstCell = row.childNodes[0];
  // test if strikethrough tag exists
  if (firstCell.childNodes !== null &&
      firstCell.childNodes.length > STRIKETHROUGH_TAG_LOCATION)
  {
    const tagCell = firstCell.childNodes[STRIKETHROUGH_TAG_LOCATION];
    if (tagCell.tagName === 's' || tagCell.nodeName === 's')
    {
      return false;
    }
  }
  return true;
}

function makeJSONCourses(columnMap, tableRows, studio)
{
  const FIRST_DATA_ROW_LOCATION = 1; // MAGIC NUMBER

  var courses = [];
  var currentDate = null;

  for (var i = FIRST_DATA_ROW_LOCATION; i < tableRows.length; ++i)
  {
    var row = tableRows[i];
    var nextDate = parseDateFromRow(row);
    if (nextDate !== undefined && nextDate.isValid())
    {
      currentDate = nextDate;
    }
    else if (rowIsValid(row))
    {
      var course = {};
      for (var j = 0; j < row.childNodes.length; ++j)
      {
        if (columnMap[j] !== undefined)
        {
          var cell = row.childNodes[j];
          var key = columnMap[j];
          course[key] = PARSER_MAP[key].parser(cell);
        }
        else
        {
          //console.log('No mapping for column: ' + j);
        }
      }
      if (isCourseValid(course))
      {
        var dbCourse = dbCourseOfWebCourse(course, currentDate, studio);
        courses.push(dbCourse);
      }
      else
      {
        console.log('No valid course found:');
        console.log(course);
      }
    }
  }
  return courses;
}

function parseMBOPage(htmlString, studio, callback)
{
  const cleanString = cleanupHtml(htmlString);
  const parser = new xmldom.DOMParser();
  const dom = parser.parseFromString(cleanString, 'text/html');

  const headerRow = dom.getElementsByTagName('thead');
  const columnMap = makeColumnMap(headerRow[0]);

  const tableRows = dom.getElementsByTagName('tr');
  const courses = makeJSONCourses(columnMap, tableRows, studio);
  if (callback !== undefined)
  {
    callback(courses);
  }
}

exports.parsePage = function (path, studio, callback)
{
  fs.readFile(path, 'utf8', function(error, data) {
    if (error)
    {
      console.log(error);
    }
    else
    {
      PARSER_FUNCTIONS[studio.provider](data, studio, callback);
    }
  });
}

if (require.main === module)
{
  // For testing
  var studioInfo =
  {
    "name": "Blue Cow Yoga",
    "provider": "MBO",
    "studioid": 23194,
    "area": "Bank"
  };

  function loggerCallback(courses)
  {
    console.log(courses);
  }

  exports.parsePage(process.argv[2], studioInfo, loggerCallback);
}
<|endoftext|>


<commit_before>var cheerio = require('cheerio');
var parserUtil = require('./parserUtil');

module.exports.parse = function(html, callback) {

    var $ = cheerio.load(html);

    var menu = [];
    var todayNameRegex = new RegExp("^\\s*" + global.todaysDate.format("dddd"), "i");

    $('td.cnt', '#contentBox').children('table').each(function() {
        var precedingText = "";
        $(this).prevUntil('table').each(function() {
            precedingText = $(this).text() + precedingText;
        });
        if (todayNameRegex.test(precedingText))
        {
            menu = parseMenu($(this), precedingText);
            return false;
        }
    });

    callback(menu);

    function parseMenu(table, mixedText) {
        var temp = [];
        if (mixedText.match(/zatvorené/i))
        {
            temp.push({ isSoup: false, text: "Dnes nie je menu", price: NaN });
            return temp;
        }
        table.find('tr').each(function() {
            var priced = parserUtil.parsePrice($(this).text());
            priced.text = normalize(priced.text);
            if (priced.text) temp.push({ isSoup: false, text: priced.text, price: priced.price });
        });

        var m = /Polievk.*:(.+)Špec.*:(.+)delená.*:(.+)$/i.exec(mixedText);
        if (m)
        {
            var priced;
            for (var i = 3; i > 1; i--)
            {
                priced = parserUtil.parsePrice(m[i]);
                temp.unshift({ isSoup: false, text: normalize(priced.text), price: priced.price });
            }
            //soups (group 1)
            var soups = m[1].split(/€ ?,/);
            for (i = soups.length - 1; i >= 0; i--)
            {
                var txt = soups[i] + (i !== soups.length - 1 ? "€" : ""); //add back € to all items except last
                priced = parserUtil.parsePrice(txt);
                temp.unshift({ isSoup: true, text: normalize(priced.text), price: priced.price });
            }
        }
        return temp;
    }

    function normalize(str) {
        return str.normalizeWhitespace()
            .removeItemNumbering()
            .removeMetrics()
            .correctCommaSpacing()
            .toLowerCase()
            .capitalizeFirstLetter();
    }
};
<commit_msg>itb parser improved<commit_after>var cheerio = require('cheerio');
var parserUtil = require('./parserUtil');

module.exports.parse = function(html, callback) {

    var $ = cheerio.load(html);

    var menu = [];
    var todayNameRegex = new RegExp("^\\s*" + global.todaysDate.format("dddd"), "i");

    $('td.cnt', '#contentBox').children('table').each(function() {
        var precedingText = "";
        $(this).prevUntil('table').each(function() {
            precedingText = $(this).text() + precedingText;
        });
        if (todayNameRegex.test(precedingText))
        {
            menu = parseMenu($(this), precedingText);
            return false;
        }
    });

    callback(menu);

    function parseMenu(table, mixedText) {
        var temp = [];
        if (mixedText.match(/zatvorené/i))
        {
            temp.push({ isSoup: false, text: "Dnes nie je menu", price: NaN });
            return temp;
        }
        table.find('tr').each(function() {
            var priced = parserUtil.parsePrice($(this).text());
            priced.text = normalize(priced.text);
            if (priced.text) temp.push({ isSoup: false, text: priced.text, price: priced.price });
        });

        var m = /Polievk[ay] ?0[,\.]33l:?(.+)Špec.*:(.+)delená.*:(.+)$/i.exec(mixedText);
        if (m)
        {
            var priced;
            for (var i = 3; i > 1; i--)
            {
                priced = parserUtil.parsePrice(m[i]);
                temp.unshift({ isSoup: false, text: normalize(priced.text), price: priced.price });
            }
            //soups (group 1)
            var soups = m[1].split(/€ ?,/);
            for (i = soups.length - 1; i >= 0; i--)
            {
                var txt = soups[i] + (i !== soups.length - 1 ? "€" : ""); //add back € to all items except last
                priced = parserUtil.parsePrice(txt);
                temp.unshift({ isSoup: true, text: normalize(priced.text), price: priced.price });
            }
        }
        return temp;
    }

    function normalize(str) {
        return str.normalizeWhitespace()
            .removeItemNumbering()
            .removeMetrics()
            .correctCommaSpacing()
            .toLowerCase()
            .capitalizeFirstLetter();
    }
};
<|endoftext|>


<commit_before>module.exports = function(app, router){

  router.get('/', function(req, res) {
    app.service_drupal.get('campaigns.json?parameters[is_staff_pick]=1', {}, function(campaignListRes) {
      var randomCampaign = campaignListRes[getRandomInt(campaignListRes.length - 1, 0)];
      app.service_drupal.get('content/' + randomCampaign.nid, {}, function(campaignRes) {
        var campaignData = {
          title: campaignRes.title,
          image: campaignRes.image_cover.url.square.raw,
          signups: campaignRes.stats.signups
        };
        res.json(JSON.stringify(campaignData));
      });
    });
  });

}

function getRandomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}
<commit_msg>Quick fix<commit_after>module.exports = function(app, router){

  router.get('/', function(req, res) {
    app.service_drupal.get('campaigns.json?parameters[is_staff_pick]=1', {}, function(campaignListRes) {
      var randomCampaign = campaignListRes[getRandomInt(0, campaignListRes.length - 1)];
      app.service_drupal.get('content/' + randomCampaign.nid, {}, function(campaignRes) {
        var campaignData = {
          title: campaignRes.title,
          image: campaignRes.image_cover.url.square.raw,
          signups: campaignRes.stats.signups
        };
        res.json(JSON.stringify(campaignData));
      });
    });
  });

}

function getRandomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}
<|endoftext|>


<commit_before>export default {
  props: {
    append: Boolean,
    disabled: Boolean,
    exact: Boolean,
    href: [String, Object],
    to: [String, Object],
    nuxt: Boolean,
    replace: Boolean,
    ripple: Boolean,
    router: Boolean,
    tag: String,
    target: String,
    download: String
  },

  methods: {
    click () {},
    generateRouteLink () {
      let exact = this.exact
      let tag
      const options = this.to || this.href

      const data = {
        attrs: { disabled: this.disabled },
        class: this.classes,
        props: {},
        directives: [{
          name: 'ripple',
          value: this.ripple || false
        }]
      }

      if (!this.exact) {
        exact = this.href === '/' ||
          this.to === '/' ||
          (this.href === Object(this.href) && this.href.path === '/') ||
          (this.to === Object(this.to) && this.to.path === '/')
      }

      if (options && this.router) {
        tag = this.nuxt ? 'nuxt-link' : 'router-link'
        data.props.to = options
        data.props.exact = exact
        data.props.activeClass = this.activeClass
        data.props.append = this.append
        data.props.replace = this.replace
        data.nativeOn = { click: this.click }
      } else {
        tag = this.tag || 'a'

        if (tag === 'a') {
          data.attrs.href = options || 'javascript:;'
          if (this.target) data.attrs.target = this.target

          data.attrs.download = this.download
        }

        data.on = { click: this.click }
      }

      return { tag, data }
    }
  }
}
<commit_msg>deprecating route prop as it is no longer needed<commit_after>export default {
  props: {
    append: Boolean,
    disabled: Boolean,
    exact: Boolean,
    href: [String, Object],
    to: [String, Object],
    nuxt: Boolean,
    replace: Boolean,
    ripple: Boolean,
    tag: String,
    target: String,
    download: String
  },

  methods: {
    click () {},
    generateRouteLink () {
      let exact = this.exact
      let tag

      const data = {
        attrs: { disabled: this.disabled },
        class: this.classes,
        props: {},
        directives: [{
          name: 'ripple',
          value: this.ripple || false
        }]
      }

      if (!this.exact) {
        exact = this.href === '/' ||
          this.to === '/' ||
          (this.href === Object(this.href) && this.href.path === '/') ||
          (this.to === Object(this.to) && this.to.path === '/')
      }

      if (this.to) {
        tag = this.nuxt ? 'nuxt-link' : 'router-link'
        data.props.to = this.to
        data.props.exact = exact
        data.props.activeClass = this.activeClass
        data.props.append = this.append
        data.props.replace = this.replace
        data.nativeOn = { click: this.click }
      } else {
        tag = this.tag || 'a'

        if (tag === 'a') {
          data.attrs.href = this.href || 'javascript:;'
          if (this.target) data.attrs.target = this.target

          data.attrs.download = this.download
        }

        data.on = { click: this.click }
      }

      return { tag, data }
    }
  }
}
<|endoftext|>


<commit_before>/**
 * `AmountEntry` is used to store the data for a single
 * event. An event can be a single expense, income amount,
 * or a total such as income for a month.
 *
 * Copyright 2015 Ethan Smith
 */

var Backbone = require('backbone'),
    MoneyStack = require('moneystack'),
    crypto = require('crypto');

var AmountEntry = Backbone.Model.extend({
   defaults: {
      amount: 0,
      name: '',
      date: '',
      category: 'unknown'
   },

   initialize: function(){
      if (!(this.get('amount') instanceof MoneyStack)) {
         this.set('amount', new MoneyStack(this.get('amount')));
      }
   },

   toJSON: function() {
      var json = this._super();

      // Use the numerical value rather than the MoneyStack object
      json.amount = this.get('amount').get();

      return json;
   },

   getDateOfMonth: function () {
      return this.getMonth() + '/1';
   },

   getMonth: function () {
      var dateStr = this.get('date');

      // Custom handling for 'mm/dd'
      if (dateStr.match(/^\d{1,2}\/\d{1,2}$/i)) {
         return parseInt(dateStr.split('/')[0]);
      }

      return new Date(dateStr).getMonth() + 1;
   },

   hash: function() {
      return crypto.createHash('md5').update(String(this.get('amount').stored) + this.get('name') + this.get('date') + this.get('category')).digest("hex");
   }
});

module.exports = AmountEntry;
<commit_msg>Fixed amount check on AmountEntry<commit_after>/**
 * `AmountEntry` is used to store the data for a single
 * event. An event can be a single expense, income amount,
 * or a total such as income for a month.
 *
 * Copyright 2015 Ethan Smith
 */

var Backbone = require('backbone'),
    MoneyStack = require('moneystack'),
    crypto = require('crypto');

var AmountEntry = Backbone.Model.extend({
   defaults: {
      amount: 0,
      name: '',
      date: '',
      category: 'unknown'
   },

   initialize: function(){
      if (typeof this.get('amount') !== 'object') {
         this.set('amount', new MoneyStack(this.get('amount')));
      }
   },

   toJSON: function() {
      var json = this._super();

      // Use the numerical value rather than the MoneyStack object
      json.amount = this.get('amount').get();

      return json;
   },

   getDateOfMonth: function () {
      return this.getMonth() + '/1';
   },

   getMonth: function () {
      var dateStr = this.get('date');

      // Custom handling for 'mm/dd'
      if (dateStr.match(/^\d{1,2}\/\d{1,2}$/i)) {
         return parseInt(dateStr.split('/')[0]);
      }

      return new Date(dateStr).getMonth() + 1;
   },

   hash: function() {
      return crypto.createHash('md5').update(String(this.get('amount').stored) + this.get('name') + this.get('date') + this.get('category')).digest("hex");
   }
});

module.exports = AmountEntry;
<|endoftext|>


<commit_before>import { runInOp } from "../display/operations.js"
import { addToScrollTop } from "../display/scrolling.js"
import { regLineChange } from "../display/view_tracking.js"
import { heightAtLine, lineIsHidden } from "../line/spans.js"
import { lineNo, updateLineHeight } from "../line/utils_line.js"
import { widgetHeight } from "../measurement/widgets.js"
import { changeLine } from "./changes.js"
import { eventMixin } from "../util/event.js"
import { signalLater } from "../util/operation_group.js"

// Line widgets are block elements displayed above or below a line.

export class LineWidget {
  constructor(doc, node, options) {
    if (options) for (let opt in options) if (options.hasOwnProperty(opt))
      this[opt] = options[opt]
    this.doc = doc
    this.node = node
  }

  clear() {
    let cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line)
    if (no == null || !ws) return
    for (let i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1)
    if (!ws.length) line.widgets = null
    let height = widgetHeight(this)
    updateLineHeight(line, Math.max(0, line.height - height))
    if (cm) {
      runInOp(cm, () => {
        adjustScrollWhenAboveVisible(cm, line, -height)
        regLineChange(cm, no, "widget")
      })
      signalLater(cm, "lineWidgetCleared", cm, this, no)
    }
  }

  changed() {
    let oldH = this.height, cm = this.doc.cm, line = this.line
    this.height = null
    let diff = widgetHeight(this) - oldH
    if (!diff) return
    if (!lineIsHidden(this.doc, line)) updateLineHeight(line, line.height + diff)
    if (cm) {
      runInOp(cm, () => {
        cm.curOp.forceUpdate = true
        adjustScrollWhenAboveVisible(cm, line, diff)
        signalLater(cm, "lineWidgetChanged", cm, this, lineNo(line))
      })
    }
  }
}
eventMixin(LineWidget)

function adjustScrollWhenAboveVisible(cm, line, diff) {
  if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop))
    addToScrollTop(cm, diff)
}

export function addLineWidget(doc, handle, node, options) {
  let widget = new LineWidget(doc, node, options)
  let cm = doc.cm
  if (cm && widget.noHScroll) cm.display.alignWidgets = true
  changeLine(doc, handle, "widget", line => {
    let widgets = line.widgets || (line.widgets = [])
    if (widget.insertAt == null) widgets.push(widget)
    else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget)
    widget.line = line
    if (cm && !lineIsHidden(doc, line)) {
      let aboveVisible = heightAtLine(line) < doc.scrollTop
      updateLineHeight(line, line.height + widgetHeight(widget))
      if (aboveVisible) addToScrollTop(cm, widget.height)
      cm.curOp.forceUpdate = true
    }
    return true
  })
  if (cm) signalLater(cm, "lineWidgetAdded", cm, widget, typeof handle == "number" ? handle : lineNo(handle))
  return widget
}
<commit_msg>Fix handling of insertAt option to addLineWidget<commit_after>import { runInOp } from "../display/operations.js"
import { addToScrollTop } from "../display/scrolling.js"
import { regLineChange } from "../display/view_tracking.js"
import { heightAtLine, lineIsHidden } from "../line/spans.js"
import { lineNo, updateLineHeight } from "../line/utils_line.js"
import { widgetHeight } from "../measurement/widgets.js"
import { changeLine } from "./changes.js"
import { eventMixin } from "../util/event.js"
import { signalLater } from "../util/operation_group.js"

// Line widgets are block elements displayed above or below a line.

export class LineWidget {
  constructor(doc, node, options) {
    if (options) for (let opt in options) if (options.hasOwnProperty(opt))
      this[opt] = options[opt]
    this.doc = doc
    this.node = node
  }

  clear() {
    let cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line)
    if (no == null || !ws) return
    for (let i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1)
    if (!ws.length) line.widgets = null
    let height = widgetHeight(this)
    updateLineHeight(line, Math.max(0, line.height - height))
    if (cm) {
      runInOp(cm, () => {
        adjustScrollWhenAboveVisible(cm, line, -height)
        regLineChange(cm, no, "widget")
      })
      signalLater(cm, "lineWidgetCleared", cm, this, no)
    }
  }

  changed() {
    let oldH = this.height, cm = this.doc.cm, line = this.line
    this.height = null
    let diff = widgetHeight(this) - oldH
    if (!diff) return
    if (!lineIsHidden(this.doc, line)) updateLineHeight(line, line.height + diff)
    if (cm) {
      runInOp(cm, () => {
        cm.curOp.forceUpdate = true
        adjustScrollWhenAboveVisible(cm, line, diff)
        signalLater(cm, "lineWidgetChanged", cm, this, lineNo(line))
      })
    }
  }
}
eventMixin(LineWidget)

function adjustScrollWhenAboveVisible(cm, line, diff) {
  if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop))
    addToScrollTop(cm, diff)
}

export function addLineWidget(doc, handle, node, options) {
  let widget = new LineWidget(doc, node, options)
  let cm = doc.cm
  if (cm && widget.noHScroll) cm.display.alignWidgets = true
  changeLine(doc, handle, "widget", line => {
    let widgets = line.widgets || (line.widgets = [])
    if (widget.insertAt == null) widgets.push(widget)
    else widgets.splice(Math.min(widgets.length, Math.max(0, widget.insertAt)), 0, widget)
    widget.line = line
    if (cm && !lineIsHidden(doc, line)) {
      let aboveVisible = heightAtLine(line) < doc.scrollTop
      updateLineHeight(line, line.height + widgetHeight(widget))
      if (aboveVisible) addToScrollTop(cm, widget.height)
      cm.curOp.forceUpdate = true
    }
    return true
  })
  if (cm) signalLater(cm, "lineWidgetAdded", cm, widget, typeof handle == "number" ? handle : lineNo(handle))
  return widget
}
<|endoftext|>


<commit_before>Lettris.MainMenu = function (game) {

};
Lettris.MainMenu.prototype = {
    create: function () {
	this.game.add.sprite(0, 0, 'sprites', 'background');

	this.logo = this.game.add.sprite(this.game.width/2, 140, 'sprites', 'logo')
	this.logo.anchor.setTo(0.5)

	this.addLight(this.logo, -179, -92)
	this.addLight(this.logo, 163, -92)
	this.addLight(this.logo, -179, 75)
	this.addLight(this.logo, 163, 75)
	this.addLight(this.logo, 74, -84)

	// Tip of the day
	var tips = ["The red button in the lower left corner clears current word.",
		    "Gold boxes are worth three times normal!",
		    "Silver boxes are worth twice as much as regular boxes!",
		    "Multiboxes multiplies the whole word two or three times!!",
		    "Bomb boxes are always worth 0 points.",
		    "The frequency of box drops is logarithmic",
		    "Bombs destroys the closest boxes without granting you any points.",
		    "You can use several multi boxes at a time!",
		    "Hi mom!",
		    "Big boxes, balls and suitcases works like the common boxes"]
	var style = { font: "22px Arial", fill: "#EEEEEE"}
	this.tipHeader = this.game.add.text(this.game.world.centerX,
					    this.logo.bottom + 50,
					    "Random Tip:",
					    style)
	this.tipHeader.anchor.setTo(0.5, 1)
	this.tipHeader.fontWeight = 'bold';
	this.tipHeader.stroke = '#000000';
	this.tipHeader.strokeThickness = 3;

	var style = { font: "22px Arial", fill: "#EEEEEE", wordWrap: true, wordWrapWidth: this.game.world.width - 40 };
	this.tip = this.game.add.text(this.game.world.centerX,
				      this.tipHeader.bottom,
				      "\"" + Phaser.ArrayUtils.getRandomItem(tips) + "\"",
				      style)
	this.tip.fontWeight = 'italic';
	this.tip.stroke = '#000000';
	this.tip.strokeThickness = 3;

	this.tip.anchor.setTo(0.5, 0)

	// Buttons
	this.boxes = 0
	this.addMenuButton("PLAY", 'Tutorial')
	this.addMenuButton("STATS", 'Stats')
	this.addMenuButton("CREDITS", 'Credits')
	this.addMenuButton("LANGUAGE", 'LangMenu')

	// Sound buttons
	this.game.masterMute = !JSON.parse(localStorage.getItem('master-mute'))
	this.masterMuteButton = this.game.add.sprite(10, this.game.height - 50, 'sprites', 'sound')
	this.toggleMasterMute()
	this.masterMuteButton.inputEnabled = true
	this.masterMuteButton.events.onInputDown.add(this.toggleMasterMute, this)

	this.game.music = this.game.add.audio('music')
	if(!this.game.music.isPlaying)
	    this.game.music.play("", 0, 0.5, true)

	this.game.music.mute = !JSON.parse(localStorage.getItem('music-mute'))
	this.musicMuteButton = this.game.add.sprite(70, this.game.height - 50, 'sprites', 'music')
	this.toggleMusicMute()
	this.musicMuteButton.inputEnabled = true
	this.musicMuteButton.events.onInputDown.add(this.toggleMusicMute, this)

	var style = { font: "20px Arial", fill: "#F0FFF0"}
	this.version = this.game.add.text(this.game.width-5,
					  this.game.height-5,
					  this.game.version,
					  style)
	this.version.anchor.setTo(1, 1)
    },
    addLight: function(parent, x, y) {
	var light = this.game.add.sprite(x, y, 'sprites', 'lamp-off')
	parent.addChild(light)

	var speed = this.game.rnd.integerInRange(3, 6)
	light.animations.add('blink', ['lamp-off', 'lamp-on'], speed, true);
	light.animations.play('blink');
    },
    addMenuButton: function (text, next_state) {

	var choice = new TextButton(this.game,
				    text, this.game.world.centerX,
				    430 + (this.boxes * 90),
				    this.startState,
				    this)
	choice.button.state = next_state

	this.boxes++

	return choice
    },
    toggleMasterMute: function() {
	this.game.masterMute = !this.game.masterMute
	if( this.game.masterMute )
	    this.masterMuteButton.frameName = 'sound-pressed'
	else
	    this.masterMuteButton.frameName = 'sound'

	localStorage.setItem('master-mute', JSON.stringify(this.game.masterMute));
    },
    toggleMusicMute: function() {
	this.game.music.mute = !this.game.music.mute
	if( this.game.music.mute )
	    this.musicMuteButton.frameName = 'music-pressed'
	else
	    this.musicMuteButton.frameName = 'music'

	localStorage.setItem('music-mute', JSON.stringify(this.game.music.mute));
    },
    startState: function (button) {
	// start the Game state
	this.state.start(button.state);
    },
};
<commit_msg>Hide new stats-menu<commit_after>Lettris.MainMenu = function (game) {

};
Lettris.MainMenu.prototype = {
    create: function () {
	this.game.add.sprite(0, 0, 'sprites', 'background');

	this.logo = this.game.add.sprite(this.game.width/2, 140, 'sprites', 'logo')
	this.logo.anchor.setTo(0.5)

	this.addLight(this.logo, -179, -92)
	this.addLight(this.logo, 163, -92)
	this.addLight(this.logo, -179, 75)
	this.addLight(this.logo, 163, 75)
	this.addLight(this.logo, 74, -84)

	// Tip of the day
	var tips = ["The red button in the lower left corner clears current word.",
		    "Gold boxes are worth three times normal!",
		    "Silver boxes are worth twice as much as regular boxes!",
		    "Multiboxes multiplies the whole word two or three times!!",
		    "Bomb boxes are always worth 0 points.",
		    "The frequency of box drops is logarithmic",
		    "Bombs destroys the closest boxes without granting you any points.",
		    "You can use several multi boxes at a time!",
		    "Hi mom!",
		    "Big boxes, balls and suitcases works like the common boxes"]
	var style = { font: "22px Arial", fill: "#EEEEEE"}
	this.tipHeader = this.game.add.text(this.game.world.centerX,
					    this.logo.bottom + 50,
					    "Random Tip:",
					    style)
	this.tipHeader.anchor.setTo(0.5, 1)
	this.tipHeader.fontWeight = 'bold';
	this.tipHeader.stroke = '#000000';
	this.tipHeader.strokeThickness = 3;

	var style = { font: "22px Arial", fill: "#EEEEEE", wordWrap: true, wordWrapWidth: this.game.world.width - 40 };
	this.tip = this.game.add.text(this.game.world.centerX,
				      this.tipHeader.bottom,
				      "\"" + Phaser.ArrayUtils.getRandomItem(tips) + "\"",
				      style)
	this.tip.fontWeight = 'italic';
	this.tip.stroke = '#000000';
	this.tip.strokeThickness = 3;

	this.tip.anchor.setTo(0.5, 0)

	// Buttons
	this.boxes = 0
	this.addMenuButton("PLAY", 'Tutorial')
	this.addMenuButton("CREDITS", 'Credits')

	var takenWords = JSON.parse(localStorage.getItem('Stats-' + this.game.language)).length;
	if( false && takenWords > 100)
	    this.addMenuButton("STATS", 'Stats')

	this.addMenuButton("LANGUAGE", 'LangMenu')

	// Sound buttons
	this.game.masterMute = !JSON.parse(localStorage.getItem('master-mute'))
	this.masterMuteButton = this.game.add.sprite(10, this.game.height - 50, 'sprites', 'sound')
	this.toggleMasterMute()
	this.masterMuteButton.inputEnabled = true
	this.masterMuteButton.events.onInputDown.add(this.toggleMasterMute, this)

	this.game.music = this.game.add.audio('music')
	if(!this.game.music.isPlaying)
	    this.game.music.play("", 0, 0.5, true)

	this.game.music.mute = !JSON.parse(localStorage.getItem('music-mute'))
	this.musicMuteButton = this.game.add.sprite(70, this.game.height - 50, 'sprites', 'music')
	this.toggleMusicMute()
	this.musicMuteButton.inputEnabled = true
	this.musicMuteButton.events.onInputDown.add(this.toggleMusicMute, this)

	var style = { font: "20px Arial", fill: "#F0FFF0"}
	this.version = this.game.add.text(this.game.width-5,
					  this.game.height-5,
					  this.game.version,
					  style)
	this.version.anchor.setTo(1, 1)
    },
    addLight: function(parent, x, y) {
	var light = this.game.add.sprite(x, y, 'sprites', 'lamp-off')
	parent.addChild(light)

	var speed = this.game.rnd.integerInRange(3, 6)
	light.animations.add('blink', ['lamp-off', 'lamp-on'], speed, true);
	light.animations.play('blink');
    },
    addMenuButton: function (text, next_state) {

	var choice = new TextButton(this.game,
				    text, this.game.world.centerX,
				    430 + (this.boxes * 90),
				    this.startState,
				    this)
	choice.button.state = next_state

	this.boxes++

	return choice
    },
    toggleMasterMute: function() {
	this.game.masterMute = !this.game.masterMute
	if( this.game.masterMute )
	    this.masterMuteButton.frameName = 'sound-pressed'
	else
	    this.masterMuteButton.frameName = 'sound'

	localStorage.setItem('master-mute', JSON.stringify(this.game.masterMute));
    },
    toggleMusicMute: function() {
	this.game.music.mute = !this.game.music.mute
	if( this.game.music.mute )
	    this.musicMuteButton.frameName = 'music-pressed'
	else
	    this.musicMuteButton.frameName = 'music'

	localStorage.setItem('music-mute', JSON.stringify(this.game.music.mute));
    },
    startState: function (button) {
	// start the Game state
	this.state.start(button.state);
    },
};
<|endoftext|>


<commit_before>define([
    'underscore',
    'base/model'
], function(_, Model) {

    var TimeModel = Model.extend({
        init: function(values) {

            //default values for time model
            values = _.extend({
                value: 50,
                start: 0,
                end: 100,
                show_playpause: true,
                playable: true,
                playing: false,
                step: 1
            }, values);

            this._super(values);
        },

        play: function() {
            //play function
        },

        validate: function() {
            //start has to be smaller than end
            if(this.get('end') > this.get('start')) {
                var temp = this.get('start');
                this.set('start', this.get('end'));
                this.set('end', temp);
            }

            //value has to be between start and end
            if(this.get('value') < this.get('start')) {
                this.set('value', this.get('start'));
            }
            else if(this.get('value') > this.get('start')) {
                this.set('value', this.get('start'));
            }
        }

    });

    return TimeModel;
});<commit_msg>Add validation and play/pause to timeslider model<commit_after>define([
    'underscore',
    'base/model'
], function(_, Model) {

    var playInterval,
        playing;

    var TimeModel = Model.extend({
        init: function(values) {

            //default values for time model
            values = _.extend({
                value: 50,
                start: 0,
                end: 100,
                show_playpause: true,
                playable: true,
                playing: false,
                step: 1,
                speed: 5
            }, values);

            this._super(values);
        },

        validate: function(silent) {
            //don't cross validate everything
            var atomic = true;
            //end has to be bigger than start
            if(this.get('end') < this.get('start')) {
                var temp = this.get('start');
                this.set('start', this.get('end'), silent, atomic);
                this.set('end', temp, silent, atomic);
            }

            //value has to be between start and end
            if(this.get('value') < this.get('start')) {
                this.set('value', this.get('start'), silent, atomic);
            }
            else if(this.get('value') > this.get('end')) {
                this.set('value', this.get('end'), silent, atomic);
            }
        },

        play: function(silent) {
            //don't play if it's not playable or if it's already playing
            if (!this.get("playable") || this.get("playing")) return;

            var _this = this,
                time = this.get("value"),
                interval = 100/this.get("speed");

            //go to start if we start from end point
            if(time === _this.get("end")) {
                time = this.get("start");
                _this.set("value", time);
            }

            //create interval
            this.set("playing", true);
            playInterval = setInterval(function() {
                if (time >= _this.get("end")) {
                    _this.pause();
                    return;
                } else {
                    time = time + _this.get("step");
                    _this.set("value", time);
                }
            }, interval);

            this.trigger("play");
        },

        pause: function(silent) {
            this.set("playing", false);
            clearInterval(playInterval);

            this.trigger("pause");
        },

    });

    return TimeModel;
});<|endoftext|>


<commit_before>'use strict'

var Packet = require('aedes-packet')
var Redis = require('ioredis')
var through = require('through2')
var throughv = require('throughv')
var msgpack = require('msgpack-lite')
var pump = require('pump')
var EventEmitter = require('events').EventEmitter
var inherits = require('util').inherits
var Qlobber = require('qlobber').Qlobber
var qlobberOpts = {
  separator: '/',
  wildcard_one: '+',
  wildcard_some:  '#'
}
var offlineClientsCountKey = 'counter:offline:clients'
var offlineSubscriptionsCountKey = 'counter:offline:subscriptions'
var newSubTopic = 'SYS$/newsub'
var rmSubTopic = 'SYS$/rmsub'

function RedisPersistence (opts) {
  if (!(this instanceof RedisPersistence)) {
    return new RedisPersistence(opts)
  }

  this._db = new Redis(opts)
  this._sub = new Redis(opts)
  this._pipeline = null

  var that = this
  this._decodeAndAugment = function decodeAndAugment (chunk, enc, cb) {
    that._getPipeline().getBuffer(chunk, function (err, result) {
      var decoded
      if (result) {
        decoded = msgpack.decode(result)
      }
      cb(err, decoded)
    })
  }
  this._matcher = null
  this._ready = false
  this._destroyed = false
  this._setup()

  this._sub.subscribe(newSubTopic)
  this._sub.on('messageBuffer', function (channel, buf) {
    console.log('new message on', channel)
    if (channel === newSubTopic) {
      var decoded = msgpack.decode(buf)
      that._matcher.add(decoded.topic, decoded)
    } else if (channel === rmSubTopic) {
      var decoded = msgpack.decode(buf)
      that._matcher
        .match(decoded.topic)
        .filter(matching, decoded.topic)
        .forEach(rmSub, that._matcher)
    }
  })
}

function matching (sub) {
  return sub.topic === this
}

function rmSub (sub) {
  this.remove(sub.topic, sub)
}

inherits(RedisPersistence, EventEmitter)

RedisPersistence.prototype._getPipeline = function() {
  if (!this._pipeline) {
    this._pipeline = this._db.pipeline()
    process.nextTick(execPipeline, this)
  }
  return this._pipeline
}

function execPipeline (that) {
  that._pipeline.exec()
  that._pipeline = null
}

RedisPersistence.prototype.storeRetained = function (packet, cb) {
  var key = 'retained:' + packet.topic
  if (packet.payload.length === 0) {
    this._db.del(key, cb)
  } else {
    this._db.set(key, msgpack.encode(packet), cb)
  }
}

function checkAndSplit (prefix, pattern) {
  var qlobber = new Qlobber(qlobberOpts)
  qlobber.add(pattern, true)

  // TODO use ctor
  var instance = through.obj(splitArray)

  instance._qlobber = qlobber
  instance._prefix = prefix

  return instance
}

function splitArray (keys, enc, cb) {
  var prefix = this._prefix.length
  for (var i = 0, l = keys.length; i < l; i++) {
    var key = keys[i].slice(prefix)
    if (this._qlobber.match(key).length > 0) {
      this.push(keys[i])
    }
  }
  cb()
}

RedisPersistence.prototype.createRetainedStream = function (pattern) {
  return this._db.scanStream({
    match: 'retained:' + pattern.split(/[#+]/)[0] + '*',
    count: 100
  }).pipe(checkAndSplit('retained:', pattern))
    .pipe(throughv.obj(this._decodeAndAugment))
}

function asKeyValuePair (acc, sub) {
  acc[sub.topic] = sub.qos
  return acc
}

function Sub (clientId, topic, qos) {
  this.clientId = clientId
  this.topic = topic
  this.qos = qos
}

RedisPersistence.prototype.addSubscriptions = function (client, subs, cb) {
  var multi = this._db.multi()

  var clientSubKey = "client:sub:" + client.id
  var that = this

  var toStore = subs.reduce(asKeyValuePair, {})
  multi.exists(clientSubKey)
  multi.hmset(clientSubKey, toStore)

  var count = 0

  subs.forEach(function (sub) {
    if (sub.qos > 0) {
      var subClientKey = 'sub:client:' + sub.topic
      var encoded = msgpack.encode(new Sub(client.id, sub.topic, sub.qos))
      multi.hset(subClientKey, client.id, encoded)
      multi.publish(newSubTopic, encoded)
      count++
    }
  })

  multi.exec(function (err, results) {
    var existed = results.length > 0 && results[0][1] > 0
    var pipeline = that._getPipeline()
    if (!existed)
      pipeline.incr(offlineClientsCountKey)

    pipeline.incrby(offlineSubscriptionsCountKey, count)
    cb(err, client)
  })
}

RedisPersistence.prototype.removeSubscriptions = function (client, subs, cb) {
  var clientSubKey = "client:sub:" + client.id

  var that = this
  var multi = this._db.multi()
  multi.hgetall(clientSubKey)

  subs.reduce(function (multi, sub) {
    var subClientKey = 'sub:client:' + sub.topic
    multi.hdel(subClientKey, client.id)
    multi.publish(rmSubTopic, msgpack.encode({
      topic: sub.topic,
      clientId: client.id
    }))
    return multi.hdel(clientSubKey, sub)
  }, multi)

  multi.exec(function (err, results) {
    if (err) { return cb(err) }

    var prev = 0
    var skipped = 0
    for (var i = 1; i < results.length; i += 3) {
      if (results[i] === '0') {
        skipped++
      }
    }
    var pipeline = that._getPipeline()
    pipeline.decrby(offlineSubscriptionsCountKey, subs.length - skipped)

    cb(null, client)
  })
}

RedisPersistence.prototype.subscriptionsByClient = function (client, cb) {
  var pipeline = this._getPipeline()

  var clientSubKey = "client:sub:" + client.id
  var that = this

  pipeline.hgetall(clientSubKey, function (err, subs) {
    var toReturn = Object.keys(subs).map(function (sub) {
      return {
        topic: sub,
        qos: parseInt(this[sub])
      }
    }, subs)
    cb(err, toReturn.length > 0 ? toReturn : null, client)
  })
}

RedisPersistence.prototype.countOffline = function (cb) {
  var pipeline = this._getPipeline()
  var subsCount = -1
  var clientsCount = -1
  pipeline.get(offlineSubscriptionsCountKey, function (err, count) {
    if (err) { return cb(err) }

    subsCount = parseInt(count)

    if (clientsCount >= 0)
      cb(null, subsCount, clientsCount)
  })
  pipeline.get(offlineClientsCountKey, function (err, count) {
    if (err) { return cb(err) }

    clientsCount = parseInt(count)

    if (subsCount >= 0)
      cb(null, subsCount, clientsCount)
  })
}

RedisPersistence.prototype.subscriptionsByTopic = function (topic, cb) {
  if (!this._ready) {
    this.once('ready', this.subscriptionsByTopic.bind(this, topic, cb))
    return this
  }

  var result = this._matcher.match(topic)

  cb(null, result)
}

RedisPersistence.prototype._setup = function () {
  if (this._ready) {
    return
  }

  var that = this
  var prefix = 'sub:client:'

  this._matcher = new Qlobber(qlobberOpts)

  var scanStream = this._db.scanStream({
    match: prefix + '*',
    count: 100
  })
  var splitStream = through.obj(split)
  var hgetallStream = throughv.obj(function (chunk, enc, cb) {
    var pipeline = that._getPipeline()
    pipeline.hgetallBuffer(chunk, cb)
  }, function (cb) {
    that._ready = true
    that.emit('ready')
  })
  .on('data', function (all) {
    Object.keys(all).forEach(insert, all)
  })

  pump(scanStream, splitStream, hgetallStream, function (err) {
    if (that._destroyed) {
      return
    }

    if (err) {
      that.emit('error', err)
    }
  })

  function insert (key) {
    var decoded = msgpack.decode(this[key])
    decoded.clientId = key

    that._matcher.add(decoded.topic, decoded)
  }
}

RedisPersistence.prototype.cleanSubscriptions = function (client, cb) {
  var clientSubKey = "client:sub:" + client.id
  var pipeline = this._getPipeline()
  var that = this
  pipeline.hgetallBuffer(clientSubKey, function (err, subs) {
    if (err) { return cb(err) }

    var multi = that._db.multi()

    multi.del(clientSubKey)

    Object.keys(subs).forEach(function (topic) {
      var subClientKey = 'sub:client:' + topic
      multi.hdel(subClientKey, client.id)
      multi.publish(rmSubTopic, msgpack.encode({
        topic: topic,
        clientId: client.id
      }))
    })

    multi.exec(function (err) {
      cb(err, client)
    })
  })
}

RedisPersistence.prototype.outgoingEnqueue = function (sub, packet, cb) {
  var key = 'outgoing:' + sub.clientId + ':' + packet.brokerId + ':' + packet.brokerCounter
  this._getPipeline().set(key, msgpack.encode(new Packet(packet)), cb)
}

function updateWithBrokerData (that, client, packet, cb) {
  var prekey = 'outgoing:' + client.id + ':' + packet.brokerId + ':' + packet.brokerCounter
  var postkey = 'outgoing-id:' + client.id + ':' + packet.messageId

  that._db.getBuffer(prekey, function (err, buf) {
    if (err || !buf) { return }
    var decoded = msgpack.decode(buf)
    if (decoded.messageId > 0) {
      var todel = 'outgoing-id:' + client.id + ':' + decoded.messageId
      that._getPipeline().del(todel)
    }
  })

  var multi = that._db.multi()
  multi.set(postkey, msgpack.encode(packet))
  multi.set(prekey, msgpack.encode(packet))

  multi.exec(function (err, results) {
    if (err) { return cb(err, client, packet) }

    if (results[0][1] !== 'OK') {
      cb(new Error('no such packet'), client, packet)
    } else {
      cb(null, client, packet)
    }
  })
}

function augmentWithBrokerData (that, client, packet, cb) {
  var postkey = 'outgoing-id:' + client.id + ':' + packet.messageId
  that._getPipeline().getBuffer(postkey, function (err, buf) {
    if (err) { return cb(err) }
    if (!buf) { return cb(new Error('no suck packet')) }
    var decoded = msgpack.decode(buf)
    packet.brokerId = decoded.brokerId
    packet.brokerCounter = decoded.brokerCounter
    cb(null)
  })
}

RedisPersistence.prototype.outgoingUpdate = function (client, packet, cb) {
  var that = this
  if (packet.brokerId) {
    updateWithBrokerData(this, client, packet, cb)
  } else {
    augmentWithBrokerData(this, client, packet, function (err) {
      if (err) { return cb(err, client, packet) }

      updateWithBrokerData(that, client, packet, cb)
    })
  }
}

RedisPersistence.prototype.outgoingClearMessageId = function (client, packet, cb) {
  var that = this
  var key = 'outgoing-id:' + client.id + ':' + packet.messageId
  this._getPipeline().getBuffer(key, function (err, buf) {
    if (err) { return cb(err) }
    if (!buf) { return cb(new Error('no suck packet')) }

    var packet = msgpack.decode(buf)
    var prekey = 'outgoing:' + client.id + ':' + packet.brokerId + ':' + packet.brokerCounter
    var multi = that._db.multi()
    multi.del(key)
    multi.del(prekey)
    multi.exec(function (err) {
      cb(err, client)
    })
  })
}

function split (keys, enc, cb) {
  for (var i = 0, l = keys.length; i < l; i++) {
    this.push(keys[i])
  }
  cb()
}

RedisPersistence.prototype.outgoingStream = function (client) {
  return this._db.scanStream({
    match: 'outgoing:' + client.id + ':*',
    count: 16
  }).pipe(through.obj(split))
    .pipe(throughv.obj(this._decodeAndAugment))
}

RedisPersistence.prototype.incomingStorePacket = function (client, packet, cb) {
  var key = 'incoming:' + client.id + ':' + packet.messageId
  var newp = new Packet(packet)
  newp.messageId = packet.messageId
  this._getPipeline().set(key, msgpack.encode(newp), cb)
}

RedisPersistence.prototype.incomingGetPacket = function (client, packet, cb) {
  var key = 'incoming:' + client.id + ':' + packet.messageId
  this._getPipeline().getBuffer(key, function (err, buf) {
    if (err) { return cb(err) }
    if (!buf) { return cb(new Error('no such packet')) }
    cb(null, msgpack.decode(buf), client)
  })
}

RedisPersistence.prototype.incomingDelPacket = function (client, packet, cb) {
  var key = 'incoming:' + client.id + ':' + packet.messageId
  this._getPipeline().del(key, cb)
}

RedisPersistence.prototype.putWill = function (client, packet, cb) {
  var key = 'will:' + this.broker.id + ':' + client.id
  packet.clientId = client.id
  packet.brokerId = this.broker.id
  this._getPipeline().setBuffer(key, msgpack.encode(packet), function (err) {
    cb(err, client)
  })
}

RedisPersistence.prototype.getWill = function (client, cb) {
  var key = 'will:' + this.broker.id + ':' + client.id
  this._getPipeline().getBuffer(key, function (err, packet) {
    if (err) { return cb(err) }

    var result = null

    if (packet) {
      result = msgpack.decode(packet)
    }

    cb(null, result, client)
  })
}

RedisPersistence.prototype.delWill = function (client, cb) {
  var key = 'will:' + this.broker.id + ':' + client.id
  var result = null
  var pipeline = this._getPipeline()

  pipeline.getBuffer(key, function (err, packet) {
    if (err) { return cb(err) }

    if (packet) {
      result = msgpack.decode(packet)
    }
  })

  pipeline.del(key, function (err) {
    cb(err, result, client)
  })
}

RedisPersistence.prototype.streamWill = function (brokers) {
  return this._db.scanStream({
    match: 'will:*',
    count: 100
  })
  .pipe(through.obj(function (chunk, enc, cb) {
    for (var i = 0, l = chunk.length; i < l; i++) {
      if (!brokers || !brokers[chunk[i].split(':')[1]]) {
        this.push(chunk[i])
      }
    }
    cb()
  }))
  .pipe(throughv.obj(this._decodeAndAugment))
}

RedisPersistence.prototype.destroy = function (cb) {
  this._destroyed = true
  this._db.disconnect()
  this._sub.disconnect()
  if (cb) {
    cb(null)
  }
}

module.exports = RedisPersistence
<commit_msg>Better deferred open and cleanSubscription implementation.<commit_after>'use strict'

var Packet = require('aedes-packet')
var Redis = require('ioredis')
var through = require('through2')
var throughv = require('throughv')
var msgpack = require('msgpack-lite')
var pump = require('pump')
var EventEmitter = require('events').EventEmitter
var inherits = require('util').inherits
var Qlobber = require('qlobber').Qlobber
var qlobberOpts = {
  separator: '/',
  wildcard_one: '+',
  wildcard_some:  '#'
}
var offlineClientsCountKey = 'counter:offline:clients'
var offlineSubscriptionsCountKey = 'counter:offline:subscriptions'
var newSubTopic = 'SYS$/sub/add'
var rmSubTopic = 'SYS$/sub/rm'

function RedisPersistence (opts) {
  if (!(this instanceof RedisPersistence)) {
    return new RedisPersistence(opts)
  }

  this._db = new Redis(opts)
  this._sub = new Redis(opts)
  this._pipeline = null

  var that = this
  this._decodeAndAugment = function decodeAndAugment (chunk, enc, cb) {
    that._getPipeline().getBuffer(chunk, function (err, result) {
      var decoded
      if (result) {
        decoded = msgpack.decode(result)
      }
      cb(err, decoded)
    })
  }
  this._matcher = null
  this._ready = false
  this._destroyed = false

  this._waiting = {}
  this._sub.subscribe(newSubTopic, function (err) {
    if (err) {
      that.emit('error', err)
      return
    }
    that._setup()
  })
  this._sub.subscribe(rmSubTopic)
  this._sub.on('messageBuffer', function (channel, buf) {
    channel = channel.toString()
    if (channel.indexOf('SYS$/sub' === 0)) {
      var decoded = msgpack.decode(buf)
      if (channel === newSubTopic) {
        that._matcher.add(decoded.topic, decoded)
      } else if (channel === rmSubTopic) {
        that._matcher
          .match(decoded.topic)
          .filter(matching, decoded.topic)
          .forEach(rmSub, that._matcher)
      }
      var key = decoded.clientId + '-' + decoded.topic
      var cb = that._waiting[key]
      delete that._waiting[key]
      if (cb) {
        process.nextTick(cb)
      }
    }
  })
}

function matching (sub) {
  return sub.topic === this
}

function rmSub (sub) {
  this.remove(sub.topic, sub)
}

inherits(RedisPersistence, EventEmitter)

RedisPersistence.prototype._getPipeline = function() {
  if (!this._pipeline) {
    this._pipeline = this._db.pipeline()
    process.nextTick(execPipeline, this)
  }
  return this._pipeline
}

function execPipeline (that) {
  that._pipeline.exec()
  that._pipeline = null
}

RedisPersistence.prototype.storeRetained = function (packet, cb) {
  var key = 'retained:' + packet.topic
  if (packet.payload.length === 0) {
    this._db.del(key, cb)
  } else {
    this._db.set(key, msgpack.encode(packet), cb)
  }
}

function checkAndSplit (prefix, pattern) {
  var qlobber = new Qlobber(qlobberOpts)
  qlobber.add(pattern, true)

  // TODO use ctor
  var instance = through.obj(splitArray)

  instance._qlobber = qlobber
  instance._prefix = prefix

  return instance
}

function splitArray (keys, enc, cb) {
  var prefix = this._prefix.length
  for (var i = 0, l = keys.length; i < l; i++) {
    var key = keys[i].slice(prefix)
    if (this._qlobber.match(key).length > 0) {
      this.push(keys[i])
    }
  }
  cb()
}

RedisPersistence.prototype.createRetainedStream = function (pattern) {
  return this._db.scanStream({
    match: 'retained:' + pattern.split(/[#+]/)[0] + '*',
    count: 100
  }).pipe(checkAndSplit('retained:', pattern))
    .pipe(throughv.obj(this._decodeAndAugment))
}

function asKeyValuePair (acc, sub) {
  acc[sub.topic] = sub.qos
  return acc
}

function Sub (clientId, topic, qos) {
  this.clientId = clientId
  this.topic = topic
  this.qos = qos
}

RedisPersistence.prototype.addSubscriptions = function (client, subs, cb) {
  if (!this._ready) {
    this.once('ready', this.addSubscriptions.bind(this, client, subs, cb))
    return
  }

  var multi = this._db.multi()

  var clientSubKey = "client:sub:" + client.id
  var that = this

  var toStore = subs.reduce(asKeyValuePair, {})
  multi.exists(clientSubKey)
  multi.hmset(clientSubKey, toStore)

  var count = 0
  var published = 0
  var errored = null

  subs.forEach(function (sub) {
    if (sub.qos > 0) {
      var subClientKey = 'sub:client:' + sub.topic
      var encoded = msgpack.encode(new Sub(client.id, sub.topic, sub.qos))
      multi.hset(subClientKey, client.id, encoded)
      multi.publish(newSubTopic, encoded)
      count++
      that._waiting[client.id + '-' + sub.topic] = finish
    }
  })

  multi.exec(function (err, results) {
    if (err) {
      errored = true
      return cb(err, client)
    }

    var existed = results.length > 0 && results[0][1] > 0
    var pipeline = that._getPipeline()
    if (!existed)
      pipeline.incr(offlineClientsCountKey)

    pipeline.incrby(offlineSubscriptionsCountKey, count, finish)
  })

  function finish () {
    published++
    if (published === count + 1 && !errored) {
      cb(null, client)
    }
  }
}

RedisPersistence.prototype.removeSubscriptions = function (client, subs, cb) {
  if (!this._ready) {
    this.once('ready', this.removeSubscriptions.bind(this, client, subs, cb))
    return
  }

  var clientSubKey = "client:sub:" + client.id

  var that = this
  var multi = this._db.multi()
  multi.hgetall(clientSubKey)
  var published = 0
  var count = 0
  var errored = false

  subs.reduce(function (multi, topic) {
    var subClientKey = 'sub:client:' + topic
    multi.hdel(subClientKey, client.id)
    multi.publish(rmSubTopic, msgpack.encode({
      topic: topic,
      clientId: client.id
    }))
    that._waiting[client.id + '-' + topic] = finish
    count++
    return multi.hdel(clientSubKey, topic)
  }, multi)

  multi.exec(function (err, results) {
    if (err) {
      errored = true
      return cb(err)
    }

    var prev = 0
    var skipped = 0
    for (var i = 1; i < results.length; i += 3) {
      if (results[i] === '0') {
        skipped++
      }
    }
    var pipeline = that._getPipeline()
    pipeline.decrby(offlineSubscriptionsCountKey, subs.length - skipped, finish)
  })

  function finish () {
    published++
    if (published === count + 1 && !errored) {
      cb(null, client)
    }
  }
}

RedisPersistence.prototype.subscriptionsByClient = function (client, cb) {
  var pipeline = this._getPipeline()

  var clientSubKey = "client:sub:" + client.id
  var that = this

  pipeline.hgetall(clientSubKey, function (err, subs) {
    var toReturn = Object.keys(subs).map(function (sub) {
      return {
        topic: sub,
        qos: parseInt(this[sub])
      }
    }, subs)
    cb(err, toReturn.length > 0 ? toReturn : null, client)
  })
}

RedisPersistence.prototype.countOffline = function (cb) {
  var pipeline = this._getPipeline()
  var subsCount = -1
  var clientsCount = -1
  pipeline.get(offlineSubscriptionsCountKey, function (err, count) {
    if (err) { return cb(err) }

    subsCount = parseInt(count)

    if (clientsCount >= 0)
      cb(null, subsCount, clientsCount)
  })
  pipeline.get(offlineClientsCountKey, function (err, count) {
    if (err) { return cb(err) }

    clientsCount = parseInt(count)

    if (subsCount >= 0)
      cb(null, subsCount, clientsCount)
  })
}

RedisPersistence.prototype.subscriptionsByTopic = function (topic, cb) {
  if (!this._ready) {
    this.once('ready', this.subscriptionsByTopic.bind(this, topic, cb))
    return this
  }

  var result = this._matcher.match(topic)

  cb(null, result)
}

RedisPersistence.prototype._setup = function () {
  if (this._ready) {
    return
  }

  var that = this
  var prefix = 'sub:client:'

  this._matcher = new Qlobber(qlobberOpts)

  var scanStream = this._db.scanStream({
    match: prefix + '*',
    count: 100
  })
  var splitStream = through.obj(split)
  var hgetallStream = throughv.obj(function (chunk, enc, cb) {
    var pipeline = that._getPipeline()
    pipeline.hgetallBuffer(chunk, cb)
  }, function (cb) {
    that._ready = true
    that.emit('ready')
  })
  .on('data', function (all) {
    Object.keys(all).forEach(insert, all)
  })

  pump(scanStream, splitStream, hgetallStream, function (err) {
    if (that._destroyed) {
      return
    }

    if (err) {
      that.emit('error', err)
    }
  })

  function insert (key) {
    var decoded = msgpack.decode(this[key])
    decoded.clientId = key

    that._matcher.add(decoded.topic, decoded)
  }
}

RedisPersistence.prototype.cleanSubscriptions = function (client, cb) {
  var that = this
  this.subscriptionsByClient(client, function (err, subs, client) {
    if (err) { return cb(err) }
    subs = subs.map(subToTopic)
    that.removeSubscriptions(client, subs, cb)
  })
}

function subToTopic (sub) {
  return sub.topic
}

RedisPersistence.prototype.outgoingEnqueue = function (sub, packet, cb) {
  var key = 'outgoing:' + sub.clientId + ':' + packet.brokerId + ':' + packet.brokerCounter
  this._getPipeline().set(key, msgpack.encode(new Packet(packet)), cb)
}

function updateWithBrokerData (that, client, packet, cb) {
  var prekey = 'outgoing:' + client.id + ':' + packet.brokerId + ':' + packet.brokerCounter
  var postkey = 'outgoing-id:' + client.id + ':' + packet.messageId

  that._db.getBuffer(prekey, function (err, buf) {
    if (err || !buf) { return }
    var decoded = msgpack.decode(buf)
    if (decoded.messageId > 0) {
      var todel = 'outgoing-id:' + client.id + ':' + decoded.messageId
      that._getPipeline().del(todel)
    }
  })

  var multi = that._db.multi()
  multi.set(postkey, msgpack.encode(packet))
  multi.set(prekey, msgpack.encode(packet))

  multi.exec(function (err, results) {
    if (err) { return cb(err, client, packet) }

    if (results[0][1] !== 'OK') {
      cb(new Error('no such packet'), client, packet)
    } else {
      cb(null, client, packet)
    }
  })
}

function augmentWithBrokerData (that, client, packet, cb) {
  var postkey = 'outgoing-id:' + client.id + ':' + packet.messageId
  that._getPipeline().getBuffer(postkey, function (err, buf) {
    if (err) { return cb(err) }
    if (!buf) { return cb(new Error('no suck packet')) }
    var decoded = msgpack.decode(buf)
    packet.brokerId = decoded.brokerId
    packet.brokerCounter = decoded.brokerCounter
    cb(null)
  })
}

RedisPersistence.prototype.outgoingUpdate = function (client, packet, cb) {
  var that = this
  if (packet.brokerId) {
    updateWithBrokerData(this, client, packet, cb)
  } else {
    augmentWithBrokerData(this, client, packet, function (err) {
      if (err) { return cb(err, client, packet) }

      updateWithBrokerData(that, client, packet, cb)
    })
  }
}

RedisPersistence.prototype.outgoingClearMessageId = function (client, packet, cb) {
  var that = this
  var key = 'outgoing-id:' + client.id + ':' + packet.messageId
  this._getPipeline().getBuffer(key, function (err, buf) {
    if (err) { return cb(err) }
    if (!buf) { return cb(new Error('no suck packet')) }

    var packet = msgpack.decode(buf)
    var prekey = 'outgoing:' + client.id + ':' + packet.brokerId + ':' + packet.brokerCounter
    var multi = that._db.multi()
    multi.del(key)
    multi.del(prekey)
    multi.exec(function (err) {
      cb(err, client)
    })
  })
}

function split (keys, enc, cb) {
  for (var i = 0, l = keys.length; i < l; i++) {
    this.push(keys[i])
  }
  cb()
}

RedisPersistence.prototype.outgoingStream = function (client) {
  return this._db.scanStream({
    match: 'outgoing:' + client.id + ':*',
    count: 16
  }).pipe(through.obj(split))
    .pipe(throughv.obj(this._decodeAndAugment))
}

RedisPersistence.prototype.incomingStorePacket = function (client, packet, cb) {
  var key = 'incoming:' + client.id + ':' + packet.messageId
  var newp = new Packet(packet)
  newp.messageId = packet.messageId
  this._getPipeline().set(key, msgpack.encode(newp), cb)
}

RedisPersistence.prototype.incomingGetPacket = function (client, packet, cb) {
  var key = 'incoming:' + client.id + ':' + packet.messageId
  this._getPipeline().getBuffer(key, function (err, buf) {
    if (err) { return cb(err) }
    if (!buf) { return cb(new Error('no such packet')) }
    cb(null, msgpack.decode(buf), client)
  })
}

RedisPersistence.prototype.incomingDelPacket = function (client, packet, cb) {
  var key = 'incoming:' + client.id + ':' + packet.messageId
  this._getPipeline().del(key, cb)
}

RedisPersistence.prototype.putWill = function (client, packet, cb) {
  var key = 'will:' + this.broker.id + ':' + client.id
  packet.clientId = client.id
  packet.brokerId = this.broker.id
  this._getPipeline().setBuffer(key, msgpack.encode(packet), function (err) {
    cb(err, client)
  })
}

RedisPersistence.prototype.getWill = function (client, cb) {
  var key = 'will:' + this.broker.id + ':' + client.id
  this._getPipeline().getBuffer(key, function (err, packet) {
    if (err) { return cb(err) }

    var result = null

    if (packet) {
      result = msgpack.decode(packet)
    }

    cb(null, result, client)
  })
}

RedisPersistence.prototype.delWill = function (client, cb) {
  var key = 'will:' + this.broker.id + ':' + client.id
  var result = null
  var pipeline = this._getPipeline()

  pipeline.getBuffer(key, function (err, packet) {
    if (err) { return cb(err) }

    if (packet) {
      result = msgpack.decode(packet)
    }
  })

  pipeline.del(key, function (err) {
    cb(err, result, client)
  })
}

RedisPersistence.prototype.streamWill = function (brokers) {
  return this._db.scanStream({
    match: 'will:*',
    count: 100
  })
  .pipe(through.obj(function (chunk, enc, cb) {
    for (var i = 0, l = chunk.length; i < l; i++) {
      if (!brokers || !brokers[chunk[i].split(':')[1]]) {
        this.push(chunk[i])
      }
    }
    cb()
  }))
  .pipe(throughv.obj(this._decodeAndAugment))
}

RedisPersistence.prototype.destroy = function (cb) {
  this._destroyed = true
  this._db.disconnect()
  this._sub.disconnect()

  if (cb) {
    this._sub.on('end', cb)
  }
}

module.exports = RedisPersistence
<|endoftext|>


<commit_before>function get(i){
    return document.getElementById(i)
}
function make_png(){
    window.open(get('canvas').toDataURL('image/png'))
}
function reset(){
    if(confirm('Reset pixels?')){
        warn_onbeforeunload=0;

        i=624;
        do{
            get(i).style.backgroundColor='#000'
        }while(i--);

        view=1;/*edit*/
        switch_view()
    }
}
function set_pixeldiv_color(i){
    warn_onbeforeunload=1;

    j=2;
    do{
        q=['red','green','blue'][j];
        if(isNaN(get(q).value)||get(q).value<0||get(q).value>255){
            get(q).value=255
        }
    }while(j--);

    get(i).style.background='rgb('+parseInt(get('red').value)+','+parseInt(get('green').value)+','+parseInt(get('blue').value)+')'
}
function switch_view(){
    view=!view;

    if(view){/*preview*/
        /*paint canvas pixels based on colors of divs*/
        j=get('canvas').height=get('canvas').width=25;
        x=get('canvas').getContext('2d');
        i=624;
        do{
            x.fillStyle=get(i).style.background;
            x.fillRect(j*25-i-1,25-j,1,1);
            x.fillStyle='#000';
            if(i%25==0){
                j-=1
            }
        }while(i--)
    }

    get('switch-button').value=view?'Edit':'Preview';
    get('edit-div').style.display=view?'none':'inline';
    get('preview-div').style.display=view?'inline':'none'
}
var q=view=warn_onbeforeunload=x=0,
i=624,

/*create pixel divs*/
j='';
do{
    j+='

';
    if(i%25==0){
        j+='
'
    }
}while(i--);

get('edit-div').innerHTML=j;
j=0;

window.onbeforeunload=function(){
    if(warn_onbeforeunload){/*if any pixels have been changed*/
        return'Save feature not yet implemented.'
    }
}
<commit_msg>improved formatting<commit_after>function get(i){
    return document.getElementById(i)
}
function make_png(){
    window.open(get('canvas').toDataURL('image/png'))
}
function reset(){
    if(confirm('Reset pixels?')){
        warn_onbeforeunload = 0;

        i = 624;
        do{
            get(i).style.backgroundColor = '#000'
        }while(i--);

        view = 1;/*edit*/
        switch_view()
    }
}
function set_pixeldiv_color(i){
    warn_onbeforeunload = 1;

    j = 2;
    do{
        q = ['red','green','blue'][j];
        if(isNaN(get(q).value) || get(q).value<0 || get(q).value>255){
            get(q).value = 255
        }
    }while(j--);

    get(i).style.background = 'rgb('+parseInt(get('red').value)+','+parseInt(get('green').value)+','+parseInt(get('blue').value)+')'
}
function switch_view(){
    view = !view;

    if(view){/*preview*/
        /*paint canvas pixels based on colors of divs*/
        j = get('canvas').height = get('canvas').width = 25;
        x = get('canvas').getContext('2d');
        i = 624;
        do{
            x.fillStyle = get(i).style.background;
            x.fillRect(
                j*25-i-1,
                25-j,
                1,
                1
            );
            x.fillStyle = '#000';
            if(i%25===0){
                j -= 1
            }
        }while(i--)
    }

    get('switch-button').value = view ? 'Edit' : 'Preview';
    get('edit-div').style.display = view ? 'none' : 'inline';
    get('preview-div').style.display = view ? 'inline' : 'none'
}
var i = 624;
var j = '';
var q = 0;
var view = 0;
var warn_onbeforeunload = 0;
var x = 0;

/*create pixel divs*/
do{
    j += '

';
    if(i%25===0){
        j += '
'
    }
}while(i--);

get('edit-div').innerHTML = j;
j = 0;

window.onbeforeunload = function(){
    if(warn_onbeforeunload){/*if any pixels have been changed*/
        return'Save feature not yet implemented.'
    }
}
<|endoftext|>


<commit_before>var JSPATH = require('jspath');

module.exports = function(jsdoc) {
    jsdoc
        .registerParser('bem', function(comment) {
            var matches = comment.match(/([a-zA-Z0-9-]+)(?:__([a-zA-Z0-9-]+))?(?:_([a-zA-Z0-9-]+)(?:_([a-zA-Z0-9-_]+))?)?/) || [],
                val = matches[4] && matches[4].split('_');
            return {
                block : matches[1],
                elem : matches[2],
                modName : matches[3],
                modVal : val && (val.length > 1? val : val[0])
            };
        })
        .registerParser('bemmod', function(comment) {
            var matches = comment.match(/^(?:(\S+)\s*)(.*?)?\s*$/);
            return {
                mod : matches[1],
                description : matches[2]
            };
        })
        .registerParser('bemval', function(comment) {
            var matches = comment.match(/^(?:(\S+)\s*)(.*?)?\s*$/);
            return {
                val : matches[1],
                description : matches[2]
            };
        })
        .registerBuilder('bem', function(tag, curJsdocNode, _, astNode) {
            if(curJsdocNode.jsdocType !== 'class')
                throw Error('@bem can be mixed with @class only');

            if(tag.block) {
                curJsdocNode.bem = {
                    jsdocType : 'bem',
                    block : tag.block,
                    elem : tag.elem,
                    modName : tag.modName,
                    modVal : tag.modVal
                };
            } else {
                var bem = curJsdocNode.bem = parseBEMFromAST(astNode),
                    block = bem.block || (bem.block = this.currentClass.name);
                if(~block.indexOf('__')) {
                    var splitted = block.split('__');
                    bem.block = splitted[0];
                    bem.elem = splitted[1];
                }
            }
        })
        .registerBuilder('bemmod', function(tag, curJsdocNode) {
            var curMod = this.curMod = {
                    jsdocType : 'bemmod',
                    mod : tag.mod,
                    description : tag.description
                },
                bem = curJsdocNode.bem;
            (bem.mods || (bem.mods = [])).push(curMod);
        })
        .registerBuilder('bemval', function(tag) {
            (this.curMod.vals || (this.curMod.vals = [])).push({
                jsdocType : 'bemval',
                val : tag.val,
                description : tag.description
            });
        })
        .registerBuilder('param', function(tag, curJsdocNode) {
            var bem = curJsdocNode.bem;
            if(bem) {
                // take out param from class node
                (bem.params || (bem.params = [])).push(curJsdocNode.params.shift());
                // and delete params from class (@param can be used for @bem not for @class)
                if(!curJsdocNode.params.length) delete curJsdocNode.params;
            }
        });
};

function parseBEMFromAST(astNode) {
    astNode = JSPATH(
            '..*{.type === "CallExpression" && .callee.property.name === "decl"}' +
                '.arguments[0]',
        astNode);

    var bem = { jsdocType : 'bem' };

    if(astNode && astNode.type === 'Literal')
        bem.block = astNode.value;
    else if(astNode && astNode.type === 'ObjectExpression') {
        astNode.properties.forEach(function(prop) {
            var name = prop.key.value || prop.key.name,
                valueNode = prop.value;
            if(valueNode.type === 'Literal')
                bem[name] = valueNode.value;
            else if(name === 'modVal' && valueNode.type === 'ArrayExpression') {
                bem[name] = valueNode.elements.map(function(itemNode) {
                    if(itemNode.type === 'Literal') return itemNode.value;
                        else throw Error('Can\'t implicit parse BEM modifier values from source, use explicit @bem tag.');
                });
            }
        });
    } else
        throw Error('Can\'t implicit parse BEM item from source, use explicit @bem tag.');

    return bem;
}
<commit_msg>fix implicit parsing from bem<commit_after>var JSPATH = require('jspath');

module.exports = function(jsdoc) {
    jsdoc
        .registerParser('bem', function(comment) {
            var matches = comment.match(/([a-zA-Z0-9-]+)(?:__([a-zA-Z0-9-]+))?(?:_([a-zA-Z0-9-]+)(?:_([a-zA-Z0-9-_]+))?)?/) || [],
                val = matches[4] && matches[4].split('_');
            return {
                block : matches[1],
                elem : matches[2],
                modName : matches[3],
                modVal : val && (val.length > 1? val : val[0])
            };
        })
        .registerParser('bemmod', function(comment) {
            var matches = comment.match(/^(?:(\S+)\s*)(.*?)?\s*$/);
            return {
                mod : matches[1],
                description : matches[2]
            };
        })
        .registerParser('bemval', function(comment) {
            var matches = comment.match(/^(?:(\S+)\s*)(.*?)?\s*$/);
            return {
                val : matches[1],
                description : matches[2]
            };
        })
        .registerBuilder('bem', function(tag, curJsdocNode, _, astNode) {
            if(curJsdocNode.jsdocType !== 'class')
                throw Error('@bem can be mixed with @class only');

            if(tag.block) {
                curJsdocNode.bem = {
                    jsdocType : 'bem',
                    block : tag.block,
                    elem : tag.elem,
                    modName : tag.modName,
                    modVal : tag.modVal
                };
            } else {
                var bem = curJsdocNode.bem = parseBEMFromAST(astNode),
                    block = bem.block || (bem.block = this.currentClass.name);
                if(~block.indexOf('__')) {
                    var splitted = block.split('__');
                    bem.block = splitted[0];
                    bem.elem = splitted[1];
                }
            }
        })
        .registerBuilder('bemmod', function(tag, curJsdocNode) {
            var curMod = this.curMod = {
                    jsdocType : 'bemmod',
                    mod : tag.mod,
                    description : tag.description
                },
                bem = curJsdocNode.bem;
            (bem.mods || (bem.mods = [])).push(curMod);
        })
        .registerBuilder('bemval', function(tag) {
            (this.curMod.vals || (this.curMod.vals = [])).push({
                jsdocType : 'bemval',
                val : tag.val,
                description : tag.description
            });
        })
        .registerBuilder('param', function(tag, curJsdocNode) {
            var bem = curJsdocNode.bem;
            if(bem) {
                // take out param from class node
                (bem.params || (bem.params = [])).push(curJsdocNode.params.shift());
                // and delete params from class (@param can be used for @bem not for @class)
                if(!curJsdocNode.params.length) delete curJsdocNode.params;
            }
        });
};

function parseBEMFromAST(astNode) {
    astNode = JSPATH(
            '..*{.type === "CallExpression" && .callee.property.name === "decl"}' +
                '.arguments[0]',
        astNode);

    var bem = { jsdocType : 'bem' };

    if(astNode && astNode.type === 'Literal')
        bem.block = astNode.value;
    else if(astNode && astNode.type === 'ObjectExpression') {
        astNode.properties.forEach(function(prop) {
            var name = prop.key.value || prop.key.name,
                valueNode = prop.value;
            if(valueNode.type === 'Literal')
                bem[name] = valueNode.value;
            else if(name === 'modVal' && valueNode.type === 'ArrayExpression') {
                bem[name] = valueNode.elements.map(function(itemNode) {
                    if(itemNode.type === 'Literal') return itemNode.value;
                        else throw Error('Can\'t implicit parse BEM modifier values from source, use explicit @bem tag.');
                });
            }
        });
    }

    return bem;
}
<|endoftext|>


<commit_before>var hoverZoomPlugins = hoverZoomPlugins || [];
hoverZoomPlugins.push({
    name:'GOG',
    version:'1.0',
    prepareImgLinks:function (callback) {
        var res = [];

        // sample: https://images.gog-statics.com/53256264ce6987835ad2d4384117178b8a5aad6b6a6cbc27c4ec1c45f3dfa151_product_card_v2_mobile_slider_639.jpg
        //      -> https://images.gog-statics.com/53256264ce6987835ad2d4384117178b8a5aad6b6a6cbc27c4ec1c45f3dfa151.jpg
        hoverZoom.urlReplace(res,
            'img.productcard-thumbnails-slider__image',
            /_product_card_.+\d+/,
            ''
        );

        $('div.product-tile__cover,div.product-tile__image-wrapper').one('mouseover', function () {
            var link = $(this);
            var sources = link.find('source');
            if (sources.length == 0) return;
            var srcset = sources.first().attr('srcset').split(',');
            var src = srcset[srcset.length-1].trim().split(' ')[0];
            // sample: //images-2.gog-statics.com/c0a40a3c31a46d4bf1f7d4454f4c4da5bb4f76d85ddaba20485b7c3a793c5610_product_tile_116.webp
            //      -> //images-2.gog-statics.com/c0a40a3c31a46d4bf1f7d4454f4c4da5bb4f76d85ddaba20485b7c3a793c5610.webp
            src = src.replace(/_product_tile_.+?\d+w?(_\dx)?/, '');
            hoverZoom.prepareLink(link, src);
        });

        $('img.menu-product__image,img.product-row__img').one('mouseover', function () {
            var link = $(this);
            var srcset = link.attr('srcset').split(',');
            var src = srcset[srcset.length-1].trim().split(' ')[0];
            // sample: https://images.gog-statics.com/b063c46a7e35ba1925653cefcea5014728f1cc0c54880b2f5978eff8f488e9d7_392.jpg
            //      -> https://images.gog-statics.com/b063c46a7e35ba1925653cefcea5014728f1cc0c54880b2f5978eff8f488e9d7.jpg
            src = src.replace(/_\d+/, '');
            hoverZoom.prepareLink(link, src);
        });

        callback($(res), this.name);
    }
});
<commit_msg>Enhancements for plug-in: GOG<commit_after>var hoverZoomPlugins = hoverZoomPlugins || [];
hoverZoomPlugins.push({
    name:'GOG',
    version:'1.1',
    prepareImgLinks:function (callback) {
        var res = [];

        // avatar
        // sample: https://images.gog.com/156ed6a71203b25f9d7becf989140145b6abcac73f96ecc81395e65d3eb2ac55_avs.jpg
        //      -> https://images.gog.com/156ed6a71203b25f9d7becf989140145b6abcac73f96ecc81395e65d3eb2ac55.jpg
        hoverZoom.urlReplace(res,
            'img[src*="_av"]',
            /_av.*\./,
            '.'
        );

        // gallery
        $('img[srcset]').one('mouseover', function () {
            const link = $(this);
            const srcset = link.attr('srcset').split(',');
            let src = srcset[srcset.length-1].trim().split(' ')[0];
            if (src.indexOf('/blog/') != -1) return;
            src = src.replace(/_.*\./, '.').replace(/-\d+x\d+\./, '.');
            hoverZoom.prepareLink(link, src);
        });

        // sample: https://images.gog-statics.com/53256264ce6987835ad2d4384117178b8a5aad6b6a6cbc27c4ec1c45f3dfa151_product_card_v2_mobile_slider_639.jpg
        //      -> https://images.gog-statics.com/53256264ce6987835ad2d4384117178b8a5aad6b6a6cbc27c4ec1c45f3dfa151.jpg
        hoverZoom.urlReplace(res,
            'img.productcard-thumbnails-slider__image',
            /_product_card_.+\d+/,
            ''
        );

        $('div.product-tile__cover, div.product-tile__image-wrapper, div.product-tile-extended__slider-wrapper, div.picture-slider__image-wrapper').one('mouseover', function () {
            const link = $(this);
            const sources = link.find('source');
            if (sources.length == 0) return;
            const srcset = sources.first().attr('srcset').split(',');
            let src = srcset[srcset.length-1].trim().split(' ')[0];
            // sample: images-2.gog-statics.com/c0a40a3c31a46d4bf1f7d4454f4c4da5bb4f76d85ddaba20485b7c3a793c5610_product_tile_116.webp
            //      -> images-2.gog-statics.com/c0a40a3c31a46d4bf1f7d4454f4c4da5bb4f76d85ddaba20485b7c3a793c5610.jpg
            // sample: https://images.gog-statics.com/79429e097ac499290497415f13c0a1747a07659fc7b53f8cc07f1b0afd569333_product_tile_extended_432x243.webp
            //      -> https://images.gog-statics.com/79429e097ac499290497415f13c0a1747a07659fc7b53f8cc07f1b0afd569333.jpg
            src = src.replace(/_product_tile_.*/, '.jpg');
            hoverZoom.prepareLink(link, src);
        });

        $('img.menu-product__image, img.product-row__img').one('mouseover', function () {
            const link = $(this);
            const srcset = link.attr('srcset').split(',');
            let src = srcset[srcset.length-1].trim().split(' ')[0];
            // sample: https://images.gog-statics.com/b063c46a7e35ba1925653cefcea5014728f1cc0c54880b2f5978eff8f488e9d7_392.jpg
            //      -> https://images.gog-statics.com/b063c46a7e35ba1925653cefcea5014728f1cc0c54880b2f5978eff8f488e9d7.jpg
            src = src.replace(/_\d+/, '');
            hoverZoom.prepareLink(link, src);
        });

        callback($(res), this.name);
    }
});
<|endoftext|>


<commit_before>angular
  .module('momentum.actions', []);<commit_msg>cleaned up assets<commit_after>angular
  .module('momentum.actions', []);
  <|endoftext|>


<commit_before>#!/usr/bin/env node
const fs = require('fs')
const esbuild = require('esbuild')
const pathLib = require('path')
const packagesDir = pathLib.resolve(__dirname, '../..')

const localPackages = fs.readdirSync(packagesDir)

const pnpmPackageJson = JSON.parse(fs.readFileSync(pathLib.join(__dirname, 'package.json'), 'utf8'))

// This plugin rewrites imports to reference the `src` dir instead of `lib` so
// esbuild can compile the original TypeScript
const spnpmImportsPlugin = {
  name: 'spnpmImports',
  setup: (build) => {
    // This is an exception to the rule that all local packages start with `@pnpm`
    build.onResolve({ filter: /^dependency-path$/ }, ({path, resolveDir}) => ({
      path: pathLib.resolve(packagesDir, 'dependency-path', 'src', 'index.ts')
    }))

    // E.g. @pnpm/config -> /<some_dir>/pnpm/packages/config/src/index.ts
    build.onResolve({ filter: /@pnpm\// }, ({path, resolveDir}) => {
      const pathParts = path.split('/')
      const packageName = pathParts[1]

      // Bail if the package isn't present locally
      if (!localPackages.includes(packageName)) {
        return
      }

      const newPath = pathLib.resolve(packagesDir, packageName, 'src', 'index.ts')

      return {
        path: newPath
      }
    })
  }
}

;(async () => {
  await esbuild.build({
    bundle: true,
    platform: 'node',
    target: 'node14',
    entryPoints: [pathLib.resolve(__dirname, '../lib/pnpm.js')],
    outfile: pathLib.resolve(__dirname, 'dist/pnpm.cjs'),
    external: [
      'node-gyp',
      './get-uid-gid.js', // traces back to: https://github.com/npm/uid-number/blob/6e9bdb302ae4799d05abf12e922ccdb4bd9ea023/uid-number.js#L31
    ],
    define: {
      'process.env.npm_package_name': JSON.stringify(pnpmPackageJson.name),
      'process.env.npm_package_version': JSON.stringify(pnpmPackageJson.version),
    },
    sourcemap: true, // nice for local debugging
    logLevel: 'warning', // keeps esbuild quiet unless there's a problem
    plugins: [spnpmImportsPlugin],
  })

  // Require the file just built by esbuild
  require('./dist/pnpm.cjs')
})()
<commit_msg>chore: fix pd.js<commit_after>#!/usr/bin/env node
const fs = require('fs')
const esbuild = require('esbuild')
const pathLib = require('path')
const { findWorkspacePackagesNoCheck } = require('@pnpm/find-workspace-packages')
const { findWorkspaceDir } = require('@pnpm/find-workspace-dir')

const pnpmPackageJson = JSON.parse(fs.readFileSync(pathLib.join(__dirname, 'package.json'), 'utf8'))

;(async () => {
  const workspaceDir = await findWorkspaceDir(process.cwd())
  const pkgs = await findWorkspacePackagesNoCheck(workspaceDir)
  const localPackages = pkgs.map(pkg => pkg.manifest.name)
  const dirByPackageName = pkgs.reduce((acc, pkg) => {
    acc[pkg.manifest.name] = pkg.dir
    return acc
  })

  // This plugin rewrites imports to reference the `src` dir instead of `lib` so
  // esbuild can compile the original TypeScript
  const spnpmImportsPlugin = {
    name: 'spnpmImports',
    setup: (build) => {
      // This is an exception to the rule that all local packages start with `@pnpm`
      build.onResolve({ filter: /^dependency-path$/ }, ({path, resolveDir}) => ({
        path: pathLib.resolve(dirByPackageName['dependency-path'], 'src', 'index.ts')
      }))

      // E.g. @pnpm/config -> /<some_dir>/pnpm/packages/config/src/index.ts
      build.onResolve({ filter: /@pnpm\// }, ({path, resolveDir}) => {
        const pathParts = path.split('/')
        const packageName = pathParts[1]

        // Bail if the package isn't present locally
        if (!localPackages.includes(packageName)) {
          return
        }

        const newPath = pathLib.resolve(dirByPackageName[packageName], packageName, 'src', 'index.ts')

        return {
          path: newPath
        }
      })
    }
  }

  await esbuild.build({
    bundle: true,
    platform: 'node',
    target: 'node14',
    entryPoints: [pathLib.resolve(__dirname, '../lib/pnpm.js')],
    outfile: pathLib.resolve(__dirname, 'dist/pnpm.cjs'),
    external: [
      'node-gyp',
      './get-uid-gid.js', // traces back to: https://github.com/npm/uid-number/blob/6e9bdb302ae4799d05abf12e922ccdb4bd9ea023/uid-number.js#L31
    ],
    define: {
      'process.env.npm_package_name': JSON.stringify(pnpmPackageJson.name),
      'process.env.npm_package_version': JSON.stringify(pnpmPackageJson.version),
    },
    sourcemap: true, // nice for local debugging
    logLevel: 'warning', // keeps esbuild quiet unless there's a problem
    plugins: [spnpmImportsPlugin],
  })

  // Require the file just built by esbuild
  require('./dist/pnpm.cjs')
})()
<|endoftext|>