<commit_before>var _ = require('underscore');
var Base = require('./base');
var constants = require('../constants');
var CategoryDataviewModel = require('../../../dataviews/category-dataview-model');
var CategoryFilter = require('../../../windshaft/filters/category');

/**
 * Category dataview object
 *
 * @param {carto.source.Base} source - The source where the dataview will fetch the data.
 * @param {string} column - The column name to get the data.
 * @param {object} options
 * @param {number} [options.limit=6] - The maximum number of categories in the response.
 * @param {carto.operation} options.operation - The operation to apply to the data.
 * @param {string} options.operationColumn - The column name used in the operation.
 *
 * @constructor
 * @extends carto.dataview.Base
 * @memberof carto.dataview
 * @api
 */
function Category (source, column, options) {
  this.DEFAULTS.operationColumn = column;

  this._initialize(source, column, options);
  this._limit = this._options.limit;
  this._operation = this._options.operation;
  this._operationColumn = this._options.operationColumn;
}

Category.prototype = Object.create(Base.prototype);

/**
 * Set the categories limit
 *
 * @param  {number} limit
 * @return {carto.dataview.Category} this
 * @api
 */
Category.prototype.setLimit = function (limit) {
  this._checkLimit(limit);
  this._limit = limit;
  if (this._internalModel) {
    this._internalModel.set('categories', limit);
  }
  return this;
};

/**
 * Return the current categories limit
 *
 * @return {number} Current dataview limit
 * @api
 */
Category.prototype.getLimit = function () {
  return this._limit;
};

/**
 * Set the dataview operation
 *
 * @param  {carto.operation} operation
 * @return {carto.dataview.Category} this
 * @api
 */
Category.prototype.setOperation = function (operation) {
  this._checkOperation(operation);
  this._operation = operation;
  if (this._internalModel) {
    this._internalModel.set('aggregation', operation);
  }
  return this;
};

/**
 * Return the current dataview operation
 *
 * @return {carto.operation} Current dataview operation
 * @api
 */
Category.prototype.getOperation = function () {
  return this._operation;
};

/**
 * Set the dataview operationColumn
 *
 * @param  {string} operationColumn
 * @return {carto.dataview.Category} this
 * @api
 */
Category.prototype.setOperationColumn = function (operationColumn) {
  this._checkOperationColumn(operationColumn);
  this._operationColumn = operationColumn;
  if (this._internalModel) {
    this._internalModel.set('aggregation_column', operationColumn);
  }
  return this;
};

/**
 * Return the current dataview operationColumn
 *
 * @return {string} Current dataview operationColumn
 * @api
 */
Category.prototype.getOperationColumn = function () {
  return this._operationColumn;
};

/**
 * Return the resulting data
 *
 * @return {CategoryData}
 * @api
 */
Category.prototype.getData = function () {
  if (this._internalModel) {
    /**
     * @typedef {object} CategoryItem
     * @property {boolean} group
     * @property {string} name
     * @property {number} value
     * @api
     */
    /**
     * @typedef {object} CategoryData
     * @property {number} count
     * @property {number} max
     * @property {number} min
     * @property {number} nulls
     * @property {string} operation
     * @property {CategoryItem[]} categorues
     * @api
     */
    var data = this._internalModel.get('data');
    var categories = _.map(data, function (item) {
      return {
        group: item.agg,
        name: item.name,
        value: item.value
      };
    });
    return {
      count: this._internalModel.get('count'),
      max: this._internalModel.get('max'),
      min: this._internalModel.get('min'),
      nulls: this._internalModel.get('nulls'),
      operation: this._operation,
      categories: categories
    };
  }
  return null;
};

Category.prototype.DEFAULTS = {
  limit: 6,
  operation: constants.operation.COUNT
};

Category.prototype._listenToInternalModelSpecificEvents = function () {
  this.listenTo(this._internalModel, 'change:categories', this._onLimitChanged);
  this.listenTo(this._internalModel, 'change:aggregation', this._onOperationChanged);
  this.listenTo(this._internalModel, 'change:aggregation_column', this._onOperationColumnChanged);
};

Category.prototype._onLimitChanged = function () {
  if (this._internalModel) {
    this._limit = this._internalModel.get('categories');
  }
  this.trigger('limitChanged', this._limit);
};

Category.prototype._onOperationChanged = function () {
  if (this._internalModel) {
    this._operation = this._internalModel.get('aggregation');
  }
  this.trigger('operationChanged', this._operation);
};

Category.prototype._onOperationColumnChanged = function () {
  if (this._internalModel) {
    this._operationColumn = this._internalModel.get('aggregation_column');
  }
  this.trigger('operationColumnChanged', this._operationColumn);
};

Category.prototype._checkOptions = function (options) {
  if (_.isUndefined(options)) {
    throw new TypeError('Category dataview options are not defined.');
  }
  this._checkLimit(options.limit);
  this._checkOperation(options.operation);
  this._checkOperationColumn(options.operationColumn);
};

Category.prototype._checkLimit = function (limit) {
  if (_.isUndefined(limit)) {
    throw new TypeError('Limit for category dataview is required.');
  }
  if (!_.isNumber(limit)) {
    throw new TypeError('Limit for category dataview must be a number.');
  }
  if (limit <= 0) {
    throw new TypeError('Limit for category dataview must be greater than 0.');
  }
};

Category.prototype._checkOperation = function (operation) {
  if (_.isUndefined(operation) || !constants.isValidOperation(operation)) {
    throw new TypeError('Operation for category dataview is not valid. Use carto.operation');
  }
};

Category.prototype._checkOperationColumn = function (operationColumn) {
  if (_.isUndefined(operationColumn)) {
    throw new TypeError('Operation column for category dataview is required.');
  }
  if (!_.isString(operationColumn)) {
    throw new TypeError('Operation column for category dataview must be a string.');
  }
  if (_.isEmpty(operationColumn)) {
    throw new TypeError('Operation column for category dataview must be not empty.');
  }
};

Category.prototype._createInternalModel = function (engine) {
  this._internalModel = new CategoryDataviewModel({
    source: this._source.$getInternalModel(),
    column: this._column,
    aggregation: this._operation,
    aggregation_column: this._operationColumn,
    categories: this._limit,
    sync_on_data_change: true,
    sync_on_bbox_change: !!this._boundingBoxFilter,
    enabled: this._enabled
  }, {
    engine: engine,
    filter: new CategoryFilter(),
    bboxFilter: this._boundingBoxFilter && this._boundingBoxFilter.$getInternalModel()
  });
};

module.exports = Category;
<commit_msg>Fix typo<commit_after>var _ = require('underscore');
var Base = require('./base');
var constants = require('../constants');
var CategoryDataviewModel = require('../../../dataviews/category-dataview-model');
var CategoryFilter = require('../../../windshaft/filters/category');

/**
 * Category dataview object
 *
 * @param {carto.source.Base} source - The source where the dataview will fetch the data.
 * @param {string} column - The column name to get the data.
 * @param {object} options
 * @param {number} [options.limit=6] - The maximum number of categories in the response.
 * @param {carto.operation} options.operation - The operation to apply to the data.
 * @param {string} options.operationColumn - The column name used in the operation.
 *
 * @constructor
 * @extends carto.dataview.Base
 * @memberof carto.dataview
 * @api
 */
function Category (source, column, options) {
  this.DEFAULTS.operationColumn = column;

  this._initialize(source, column, options);
  this._limit = this._options.limit;
  this._operation = this._options.operation;
  this._operationColumn = this._options.operationColumn;
}

Category.prototype = Object.create(Base.prototype);

/**
 * Set the categories limit
 *
 * @param  {number} limit
 * @return {carto.dataview.Category} this
 * @api
 */
Category.prototype.setLimit = function (limit) {
  this._checkLimit(limit);
  this._limit = limit;
  if (this._internalModel) {
    this._internalModel.set('categories', limit);
  }
  return this;
};

/**
 * Return the current categories limit
 *
 * @return {number} Current dataview limit
 * @api
 */
Category.prototype.getLimit = function () {
  return this._limit;
};

/**
 * Set the dataview operation
 *
 * @param  {carto.operation} operation
 * @return {carto.dataview.Category} this
 * @api
 */
Category.prototype.setOperation = function (operation) {
  this._checkOperation(operation);
  this._operation = operation;
  if (this._internalModel) {
    this._internalModel.set('aggregation', operation);
  }
  return this;
};

/**
 * Return the current dataview operation
 *
 * @return {carto.operation} Current dataview operation
 * @api
 */
Category.prototype.getOperation = function () {
  return this._operation;
};

/**
 * Set the dataview operationColumn
 *
 * @param  {string} operationColumn
 * @return {carto.dataview.Category} this
 * @api
 */
Category.prototype.setOperationColumn = function (operationColumn) {
  this._checkOperationColumn(operationColumn);
  this._operationColumn = operationColumn;
  if (this._internalModel) {
    this._internalModel.set('aggregation_column', operationColumn);
  }
  return this;
};

/**
 * Return the current dataview operationColumn
 *
 * @return {string} Current dataview operationColumn
 * @api
 */
Category.prototype.getOperationColumn = function () {
  return this._operationColumn;
};

/**
 * Return the resulting data
 *
 * @return {CategoryData}
 * @api
 */
Category.prototype.getData = function () {
  if (this._internalModel) {
    /**
     * @typedef {object} CategoryItem
     * @property {boolean} group
     * @property {string} name
     * @property {number} value
     * @api
     */
    /**
     * @typedef {object} CategoryData
     * @property {number} count
     * @property {number} max
     * @property {number} min
     * @property {number} nulls
     * @property {string} operation
     * @property {CategoryItem[]} categories
     * @api
     */
    var data = this._internalModel.get('data');
    var categories = _.map(data, function (item) {
      return {
        group: item.agg,
        name: item.name,
        value: item.value
      };
    });
    return {
      count: this._internalModel.get('count'),
      max: this._internalModel.get('max'),
      min: this._internalModel.get('min'),
      nulls: this._internalModel.get('nulls'),
      operation: this._operation,
      categories: categories
    };
  }
  return null;
};

Category.prototype.DEFAULTS = {
  limit: 6,
  operation: constants.operation.COUNT
};

Category.prototype._listenToInternalModelSpecificEvents = function () {
  this.listenTo(this._internalModel, 'change:categories', this._onLimitChanged);
  this.listenTo(this._internalModel, 'change:aggregation', this._onOperationChanged);
  this.listenTo(this._internalModel, 'change:aggregation_column', this._onOperationColumnChanged);
};

Category.prototype._onLimitChanged = function () {
  if (this._internalModel) {
    this._limit = this._internalModel.get('categories');
  }
  this.trigger('limitChanged', this._limit);
};

Category.prototype._onOperationChanged = function () {
  if (this._internalModel) {
    this._operation = this._internalModel.get('aggregation');
  }
  this.trigger('operationChanged', this._operation);
};

Category.prototype._onOperationColumnChanged = function () {
  if (this._internalModel) {
    this._operationColumn = this._internalModel.get('aggregation_column');
  }
  this.trigger('operationColumnChanged', this._operationColumn);
};

Category.prototype._checkOptions = function (options) {
  if (_.isUndefined(options)) {
    throw new TypeError('Category dataview options are not defined.');
  }
  this._checkLimit(options.limit);
  this._checkOperation(options.operation);
  this._checkOperationColumn(options.operationColumn);
};

Category.prototype._checkLimit = function (limit) {
  if (_.isUndefined(limit)) {
    throw new TypeError('Limit for category dataview is required.');
  }
  if (!_.isNumber(limit)) {
    throw new TypeError('Limit for category dataview must be a number.');
  }
  if (limit <= 0) {
    throw new TypeError('Limit for category dataview must be greater than 0.');
  }
};

Category.prototype._checkOperation = function (operation) {
  if (_.isUndefined(operation) || !constants.isValidOperation(operation)) {
    throw new TypeError('Operation for category dataview is not valid. Use carto.operation');
  }
};

Category.prototype._checkOperationColumn = function (operationColumn) {
  if (_.isUndefined(operationColumn)) {
    throw new TypeError('Operation column for category dataview is required.');
  }
  if (!_.isString(operationColumn)) {
    throw new TypeError('Operation column for category dataview must be a string.');
  }
  if (_.isEmpty(operationColumn)) {
    throw new TypeError('Operation column for category dataview must be not empty.');
  }
};

Category.prototype._createInternalModel = function (engine) {
  this._internalModel = new CategoryDataviewModel({
    source: this._source.$getInternalModel(),
    column: this._column,
    aggregation: this._operation,
    aggregation_column: this._operationColumn,
    categories: this._limit,
    sync_on_data_change: true,
    sync_on_bbox_change: !!this._boundingBoxFilter,
    enabled: this._enabled
  }, {
    engine: engine,
    filter: new CategoryFilter(),
    bboxFilter: this._boundingBoxFilter && this._boundingBoxFilter.$getInternalModel()
  });
};

module.exports = Category;
<|endoftext|>


<commit_before>const { stripIndent, source } = require('common-tags')

function template2({ profile, schools, jobs, projects, skills }) {
  return stripIndent`
    ${generateCommentHeader()}
    \\documentclass[]{deedy-resume-openfont}

    \\begin{document}
      ${generateProfileSection(profile)}
      ${generateEducationSection(schools)}
      ${generateExperienceSection(jobs)}

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %
    %     Skills
    %
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    \\section{Skills}
    \\raggedright
    \\begin{tabular}{ l l }
      \\descript{Programming Languages:} & {\\location{JavaScript, Java, Ruby, HTML, CSS}} \\\\

      \\descript{Frameworks/Libraries:} & {\\location{Node.js, Koa, Express, React, Redux, jQuery}} \\\\

      \\descript{Miscellaneous:} & {\\location{PostgreSQL, Bash, Git, SVN, Mercurial}} \\\\
    \\end{tabular}

    \\sectionsep

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %
    %     Projects
    %
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    \\section{Projects}
    \\raggedright
    \\runsubsection{\\large{Resume Generator}}
    \\descript{| Node.js, Koa, React, Redux} \\hfill \\location{https://latexresu.me} \\\\
    A webapp for generating LaTeX resumes from form data (including this one).
    \\sectionsep

    \\runsubsection{\\large{Flow Timer}}
    \\descript{| Node.js, Koa, React, Redux} \\hfill \\location{https://flowtimer.com} \\\\
    A modern speedcubing app with a scrambler, timer, and analyzer for cubing statistics.
    \\sectionsep

    \\runsubsection{\\large{Reddit Image Scraper}}
    \\descript{| Ruby, Sinatra} \\hfill \\location{https://github.com/saadq/reddit-scraper} \\\\
    A web app that lets you view a collage of images/videos from a subreddit.
    \\sectionsep

    \\runsubsection{\\large{Anagrams}}
    \\descript{| HTML, CSS, JavaScript} \\hfill \\location{https://saadq.github.io/Anagrams} \\\\
    A cognitive, anagram-recognition game where the player must quickly find the answer.
    \\sectionsep

    \\runsubsection{\\large{Materialize}}
    \\descript{| XML, JSON} \\hfill \\location{https://packagecontrol.io/packages/Materialize} \\\\
    A collection of custom themes for Sublime Text, inspired by Material design with 30k+ installations.
    \\sectionsep

    \\end{document}  \\documentclass[]{article}
  `
}

function generateProfileSection(profile) {
  if (!profile) {
    return ''
  }

  const { fullName, email, phoneNumber, address, link } = profile

  let nameStart = ''
  let nameEnd = ''

  if (fullName) {
    const names = fullName.split(' ')

    if (names.length === 1) {
      nameStart = names[0]
      nameEnd = ''
    } else if (names.length === 2) {
      nameStart = names[0]
      nameEnd = names[1]
    } else {
      nameStart = names.slice(0, names.length - 1).join(' ')
      nameEnd = names[names.length - 1]
    }
  }

  const info = [email, phoneNumber, address, link].filter(Boolean).join(' | ')

  const sectionHeader = stripIndent`
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %
    %     Profile
    %
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  `

  if (!fullName) {
    return stripIndent`
      ${sectionHeader}
      \\centering{
        \\color{headings}
        \\fontspec[Path = fonts/raleway/]{Raleway-Medium}
        \\fontsize{11pt}{14pt}
        \\selectfont ${info}
      }
    `
  }

  return stripIndent`
    ${sectionHeader}
    \\namesection{${nameStart}}{${nameEnd}}{${info}}
  `
}

function generateEducationSection(schools) {
  if (!schools) {
    return ''
  }

  return source`
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %
    %     Education
    %
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    \\section{Education}
    \\raggedright
    ${schools.map((school) => {
      const { name, location, degree, major, gpa, graduationDate } = school

      let line1 = ''
      let line2 = ''

      if (name) {
        line1 += `\\runsubsection{${name}}`
      }

      if (degree && major) {
        line1 += `\\descript{| ${degree} ${major}}`
      } else if (degree) {
        line1 += `\\descript{| ${degree}}`
      } else if (major) {
        line1 += `\\descript{| ${major}}`
      }

      const locationAndDate = [location, graduationDate].filter(Boolean).join(' | ')

      if (locationAndDate) {
        line1 += `\\hfill \\location{${locationAndDate}}`
      }

      if (line1) {
        line1 += '\\\\'
      }

      if (gpa) {
        line2 += `GPA: ${gpa}\\\\`
      }

      return `
        ${line1}
        ${line2}
        \\sectionsep
      `
    })}
  `
}

function generateExperienceSection(jobs) {
  if (!jobs) {
    return ''
  }

  return source`
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %
    %     Experience
    %
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    \\section{Experience}
    \\raggedright
    ${jobs.map((job) => {
      const { name, title, location, startDate, endDate, duties } = job

      let line1 = ''
      let dateRange = ''
      let dutyLines = ''

      if (name) {
        line1 += `\\runsubsection{${name}}`
      }

      if (title) {
        line1 += `\\descript{| ${title}}`
      }

      if (startDate && endDate) {
        dateRange = `${startDate} – ${endDate}`
      } else if (startDate) {
        dateRange = `${startDate} – Present`
      } else {
        dateRange = endDate
      }

      if (location && dateRange) {
        line1 += `\\hfill \\location{${location} | ${dateRange}}`
      } else if (location) {
        line1 += `\\hfill \\location{${location}}`
      } else if (dateRange) {
        line1 += `\\hfill \\location{${dateRange}}`
      }

      if (duties) {
        dutyLines = source`
          \\begin{tightemize}
            ${duties.map(duty => `\\item ${duty}`)}
          \\end{tightemize}
        `
      }

      return stripIndent`
        ${line1}
        ${dutyLines}
        \\sectionsep
      `
    })}
  `
}

function generateCommentHeader() {
  return stripIndent`
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % This is a modified ONE COLUMN version of
    % the following template:
    %
    % Deedy - One Page Two Column Resume
    % LaTeX Template
    % Version 1.1 (30/4/2014)
    %
    % Original author:
    % Debarghya Das (http://debarghyadas.com)
    %
    % Original repository:
    % https://github.com/deedydas/Deedy-Resume
    %
    % IMPORTANT: THIS TEMPLATE NEEDS TO BE COMPILED WITH XeLaTeX
    %
    % This template uses several fonts not included with Windows/Linux by
    % default. If you get compilation errors saying a font is missing, find the line
    % on which the font is used and either change it to a font included with your
    % operating system or comment the line out to use the default font.
    %
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %
    % TODO:
    % 1. Integrate biber/bibtex for article citation under publications.
    % 2. Figure out a smoother way for the document to flow onto the next page.
    % 3. Add styling information for a "Projects/Hacks" section.
    % 4. Add location/address information
    % 5. Merge OpenFont and MacFonts as a single sty with options.
    %
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %
    % CHANGELOG:
    % v1.1:
    % 1. Fixed several compilation bugs with \\renewcommand
    % 2. Got Open-source fonts (Windows/Linux support)
    % 3. Added Last Updated
    % 4. Move Title styling into .sty
    % 5. Commented .sty file.
    %
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %
    % Known Issues:
    % 1. Overflows onto second page if any column's contents are more than the
    % vertical limit
    % 2. Hacky space on the first bullet point on the second column.
    %
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  `
}

module.exports = template2
<commit_msg>add skills section to template2<commit_after>const { stripIndent, source } = require('common-tags')

function template2({ profile, schools, jobs, projects, skills }) {
  return stripIndent`
    ${generateCommentHeader()}
    \\documentclass[]{deedy-resume-openfont}

    \\begin{document}
      ${generateProfileSection(profile)}
      ${generateEducationSection(schools)}
      ${generateExperienceSection(jobs)}
      ${generateSkillsSection(skills)}

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %
    %     Projects
    %
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    \\section{Projects}
    \\raggedright
    \\runsubsection{\\large{Resume Generator}}
    \\descript{| Node.js, Koa, React, Redux} \\hfill \\location{https://latexresu.me} \\\\
    A webapp for generating LaTeX resumes from form data (including this one).
    \\sectionsep

    \\runsubsection{\\large{Flow Timer}}
    \\descript{| Node.js, Koa, React, Redux} \\hfill \\location{https://flowtimer.com} \\\\
    A modern speedcubing app with a scrambler, timer, and analyzer for cubing statistics.
    \\sectionsep

    \\runsubsection{\\large{Reddit Image Scraper}}
    \\descript{| Ruby, Sinatra} \\hfill \\location{https://github.com/saadq/reddit-scraper} \\\\
    A web app that lets you view a collage of images/videos from a subreddit.
    \\sectionsep

    \\runsubsection{\\large{Anagrams}}
    \\descript{| HTML, CSS, JavaScript} \\hfill \\location{https://saadq.github.io/Anagrams} \\\\
    A cognitive, anagram-recognition game where the player must quickly find the answer.
    \\sectionsep

    \\runsubsection{\\large{Materialize}}
    \\descript{| XML, JSON} \\hfill \\location{https://packagecontrol.io/packages/Materialize} \\\\
    A collection of custom themes for Sublime Text, inspired by Material design with 30k+ installations.
    \\sectionsep

    \\end{document}  \\documentclass[]{article}
  `
}

function generateProfileSection(profile) {
  if (!profile) {
    return ''
  }

  const { fullName, email, phoneNumber, address, link } = profile

  let nameStart = ''
  let nameEnd = ''

  if (fullName) {
    const names = fullName.split(' ')

    if (names.length === 1) {
      nameStart = names[0]
      nameEnd = ''
    } else if (names.length === 2) {
      nameStart = names[0]
      nameEnd = names[1]
    } else {
      nameStart = names.slice(0, names.length - 1).join(' ')
      nameEnd = names[names.length - 1]
    }
  }

  const info = [email, phoneNumber, address, link].filter(Boolean).join(' | ')

  const sectionHeader = stripIndent`
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %
    %     Profile
    %
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  `

  if (!fullName) {
    return stripIndent`
      ${sectionHeader}
      \\centering{
        \\color{headings}
        \\fontspec[Path = fonts/raleway/]{Raleway-Medium}
        \\fontsize{11pt}{14pt}
        \\selectfont ${info}
      }
    `
  }

  return stripIndent`
    ${sectionHeader}
    \\namesection{${nameStart}}{${nameEnd}}{${info}}
  `
}

function generateEducationSection(schools) {
  if (!schools) {
    return ''
  }

  return source`
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %
    %     Education
    %
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    \\section{Education}
    \\raggedright
    ${schools.map((school) => {
      const { name, location, degree, major, gpa, graduationDate } = school

      let line1 = ''
      let line2 = ''

      if (name) {
        line1 += `\\runsubsection{${name}}`
      }

      if (degree && major) {
        line1 += `\\descript{| ${degree} ${major}}`
      } else if (degree) {
        line1 += `\\descript{| ${degree}}`
      } else if (major) {
        line1 += `\\descript{| ${major}}`
      }

      const locationAndDate = [location, graduationDate].filter(Boolean).join(' | ')

      if (locationAndDate) {
        line1 += `\\hfill \\location{${locationAndDate}}`
      }

      if (line1) {
        line1 += '\\\\'
      }

      if (gpa) {
        line2 += `GPA: ${gpa}\\\\`
      }

      return `
        ${line1}
        ${line2}
        \\sectionsep
      `
    })}
  `
}

function generateExperienceSection(jobs) {
  if (!jobs) {
    return ''
  }

  return source`
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %
    %     Experience
    %
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    \\section{Experience}
    \\raggedright
    ${jobs.map((job) => {
      const { name, title, location, startDate, endDate, duties } = job

      let line1 = ''
      let dateRange = ''
      let dutyLines = ''

      if (name) {
        line1 += `\\runsubsection{${name}}`
      }

      if (title) {
        line1 += `\\descript{| ${title}}`
      }

      if (startDate && endDate) {
        dateRange = `${startDate} – ${endDate}`
      } else if (startDate) {
        dateRange = `${startDate} – Present`
      } else {
        dateRange = endDate
      }

      if (location && dateRange) {
        line1 += `\\hfill \\location{${location} | ${dateRange}}`
      } else if (location) {
        line1 += `\\hfill \\location{${location}}`
      } else if (dateRange) {
        line1 += `\\hfill \\location{${dateRange}}`
      }

      if (duties) {
        dutyLines = source`
          \\begin{tightemize}
            ${duties.map(duty => `\\item ${duty}`)}
          \\end{tightemize}
        `
      }

      return stripIndent`
        ${line1}
        ${dutyLines}
        \\sectionsep
      `
    })}
  `
}

function generateSkillsSection(skills) {
  if (!skills) {
    return ''
  }

  return source`
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %
    %     Skills
    %
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    \\section{Skills}
    \\raggedright
    \\begin{tabular}{ l l }
      ${skills.map(skill => `\\descript{${skill.name}} & {\\location{${skill.details}}} \\\\`)}
    \\end{tabular}
    \\sectionsep
  `
}

function generateCommentHeader() {
  return stripIndent`
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % This is a modified ONE COLUMN version of
    % the following template:
    %
    % Deedy - One Page Two Column Resume
    % LaTeX Template
    % Version 1.1 (30/4/2014)
    %
    % Original author:
    % Debarghya Das (http://debarghyadas.com)
    %
    % Original repository:
    % https://github.com/deedydas/Deedy-Resume
    %
    % IMPORTANT: THIS TEMPLATE NEEDS TO BE COMPILED WITH XeLaTeX
    %
    % This template uses several fonts not included with Windows/Linux by
    % default. If you get compilation errors saying a font is missing, find the line
    % on which the font is used and either change it to a font included with your
    % operating system or comment the line out to use the default font.
    %
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %
    % TODO:
    % 1. Integrate biber/bibtex for article citation under publications.
    % 2. Figure out a smoother way for the document to flow onto the next page.
    % 3. Add styling information for a "Projects/Hacks" section.
    % 4. Add location/address information
    % 5. Merge OpenFont and MacFonts as a single sty with options.
    %
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %
    % CHANGELOG:
    % v1.1:
    % 1. Fixed several compilation bugs with \\renewcommand
    % 2. Got Open-source fonts (Windows/Linux support)
    % 3. Added Last Updated
    % 4. Move Title styling into .sty
    % 5. Commented .sty file.
    %
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %
    % Known Issues:
    % 1. Overflows onto second page if any column's contents are more than the
    % vertical limit
    % 2. Hacky space on the first bullet point on the second column.
    %
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  `
}

module.exports = template2
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import Selection from 'react-ds';

export default class Example extends React.PureComponent {

  constructor() {
    super();

    this.state = {
      ref: null,
      elRefs: [],
      selectedElements: [], // track the elements that are selected
    };
  }

  handleSelection = (indexes) => {
    this.setState({
      selectedElements: indexes,
    });
  };

  getStyle = (index) => {
    if (this.state.selectedElements.indexOf(index) > -1) {
      // Selected state
      return {
        background: '#2185d0',
        borderColor: '#2185d0',
        color: 'white',
      };
    }
    return {};
  };

  addElementRef = (ref) => {
    const elRefs = this.state.elRefs;
    elRefs.push(ref);
    this.setState({
      elRefs,
    });
  };

  renderSelection() {
    if (!this.state.ref || !this.state.elRefs) {
      return null;
    }
    return (
      <Selection
        target={ this.state.ref}
        elements={ this.state.elRefs }
        onSelectionChange={ this.handleSelection }
        style={ this.props.style }
        ignoreTargets={ this.props.ignoreTargets }
      />
    );
  }

  render() {
    const selectableElements = [
      'one',
      'another',
      'hey there',
      'item',
      'two',
      'three',
      'something longer?',
      'last'
    ];
    return (

 { this.setState({ ref }); } } className='item-container'>
        { selectableElements.map((el, index) => (
          <div
            key={ el }
            ref={ this.addElementRef }
            style={ this.getStyle(index) }
            className={ `item item-${el}` }
          >
            { el }

        )) }
        { this.renderSelection() }

    );
  }
}

Example.PropTypes = {
  style: PropTypes.object,
  ignoreTargets: PropTypes.array,
};<commit_msg>for some reason methods are now undefined...<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import Selection from 'react-ds';

export default class Example extends React.PureComponent {

  constructor() {
    super();

    this.state = {
      ref: null,
      elRefs: [],
      selectedElements: [], // track the elements that are selected
    };
  }

  handleSelection = (indexes) => { // eslint-disable-line no-undef
    this.setState({
      selectedElements: indexes,
    });
  };

  getStyle = (index) => { // eslint-disable-line no-undef
    if (this.state.selectedElements.indexOf(index) > -1) {
      // Selected state
      return {
        background: '#2185d0',
        borderColor: '#2185d0',
        color: 'white',
      };
    }
    return {};
  };

  addElementRef = (ref) => { // eslint-disable-line no-undef
    const elRefs = this.state.elRefs;
    elRefs.push(ref);
    this.setState({
      elRefs,
    });
  };

  renderSelection() {
    if (!this.state.ref || !this.state.elRefs) {
      return null;
    }
    return (
      <Selection
        target={ this.state.ref}
        elements={ this.state.elRefs }
        onSelectionChange={ this.handleSelection }
        style={ this.props.style }
        ignoreTargets={ this.props.ignoreTargets }
      />
    );
  }

  render() {
    const selectableElements = [
      'one',
      'another',
      'hey there',
      'item',
      'two',
      'three',
      'something longer?',
      'last'
    ];
    return (

 { this.setState({ ref }); } } className='item-container'>
        { selectableElements.map((el, index) => (
          <div
            key={ el }
            ref={ this.addElementRef }
            style={ this.getStyle(index) }
            className={ `item item-${el}` }
          >
            { el }

        )) }
        { this.renderSelection() }

    );
  }
}

Example.PropTypes = {
  style: PropTypes.object,
  ignoreTargets: PropTypes.array,
};<|endoftext|>


<commit_before>import styled from 'styled-components'
import PropTypes from 'prop-types'
import {Box} from '@primer/components'
import {fontSize} from 'styled-system'

const Table = styled.table`
  display: table !important;
  border-collapse: separate;
  border-spacing: ${props => props.borderSpacing};
  ${fontSize};

  tr,
  td,
  th {
    border: 0 !important;
  }

  caption,
  td,
  th {
    padding: ${props => props.cellPadding} !important;
    text-align: left;
  }

  tr {
    background-color: transparent !important;
  }
`

Table.propTypes = {
  borderSpacing: PropTypes.any,
  cellPadding: PropTypes.any,
  ...fontSize.propTypes
}

Table.defaultProps = {
  borderSpacing: 0,
  cellPadding: '4px 8px',
  fontSize: [1, 1, 2]
}

Table.Row = styled(Box).attrs({as: 'tr'})``
Table.Cell = styled(Box).attrs({as: 'td'})``

export default Table
<commit_msg>Update table component<commit_after>import {Box} from '@primer/components'
import PropTypes from 'prop-types'
import React from 'react'
import styled from 'styled-components'
import {fontSize} from 'styled-system'

const Table = styled.table`
  display: table !important;
  border-collapse: separate;
  border-spacing: ${props => props.borderSpacing};
  ${fontSize};

  tr,
  td,
  th {
    border: 0 !important;
  }

  caption,
  td,
  th {
    padding: ${props => props.cellPadding} !important;
    text-align: left;
  }

  tr {
    background-color: transparent !important;
  }
`

Table.propTypes = {
  borderSpacing: PropTypes.any,
  cellPadding: PropTypes.any,
  ...fontSize.propTypes
}

Table.defaultProps = {
  borderSpacing: 0,
  cellPadding: '4px 8px',
  fontSize: [1, 1, 2]
}

Table.Row = props => <Box as="tr" {...props} />
Table.Cell = props => <Box as="td" {...props} />

export default Table
<|endoftext|>


<commit_before>/**
 * List of exchanges and corresponding REST url to retrieve current price
 */

module.exports = {
	"bitfinex" : {
		"tickerUrl" : "https://api.bitfinex.com/v1/pubticker/btcusd",
		"jsonSchema" : {
			"bid" : "bid",
			"ask" : "ask",
			"low" : "low",
			"high" : "high",
			"volume" : "volume",
			"timestamp" : "timestamp"
		}
	},
	"bitstamp" : {
		"tickerUrl" : "https://www.bitstamp.net/api/ticker/",
		"jsonSchema" : {
			"bid" : "bid",
			"ask" : "ask",
			"low" : "low",
			"high" : "high",
			"volume" : "volume",
			"timestamp" : "timestamp"
		}
	},
	"okcoin" : {
		"tickerUrl" : "https://www.okcoin.com/api/ticker.do?ok=1",
		"jsonSchema" : {
			"ticker.buy" : "bid",
			"ticker.sell" : "ask",
			"ticker.low" : "low",
			"ticker.high" : "high",
			"ticker.vol" : "volume",
			"date" : "timestamp"
		}
	},
	"btce" : {
		"tickerUrl" : "https://btc-e.com/api/3/ticker/btc_usd",
		"jsonSchema" : {
			"btc_usd.buy" : "bid",
			"btc_usd.sell" : "ask",
			"btc_usd.low" : "low",
			"btc_usd.high" : "high",
			"btc_usd.vol" : "volume",
			"btc_usd.updated" : "timestamp"
		}
	},
	"btc38" : {
		"tickerUrl" : "http://api.btc38.com/v1/ticker.php?c=btc",
		"jsonSchema" : {
			"ticker.buy" : "bid",
			"ticker.sell" : "ask",
			"ticker.low" : "low",
			"ticker.high" : "high",
			"ticker.vol" : "volume"
		}
	},
	"bter" : {
		"tickerUrl" : "http://data.bter.com/api/1/ticker/btc_usd",
		"jsonSchema" : {
			"buy" : "bid",
			"sell" : "ask",
			"low" : "low",
			"high" : "high",
			"vol_usd" : "volume"
		}
	},
	"hitbtc" : {
		"tickerUrl" : "http://api.hitbtc.com/api/1/public/BTCUSD/ticker",
		"jsonSchema" : {
			"bid" : "bid",
			"ask" : "ask",
			"low" : "low",
			"high" : "high",
			"volume" : "volume",
			"timestamp" : "timestamp"
		}
	},
	"ccex" : {
		"tickerUrl" : "https://c-cex.com/t/btc-usd.json",
		"jsonSchema" : {
			"ticker.buy" : "bid",
			"ticker.sell" : "ask",
			"ticker.low" : "low",
			"ticker.high" : "high"
		}
	},
	"kraken" : {
		"tickerUrl" : "https://api.kraken.com/0/public/Ticker?pair=XBTUSD",
		"jsonSchema" : {
			"result.XXBTZUSD.a.0" : "ask",
			"result.XXBTZUSD.b.0" : "bid",
			"result.XXBTZUSD.l.1" : "low",
			"result.XXBTZUSD.h.1" : "high",
			"result.XXBTZUSD.v.1" : "volume"
		}
	}
}
<commit_msg>updating api<commit_after>/**
 * List of exchanges and corresponding REST url to retrieve current price
 */

module.exports = {
	"bitfinex" : {
		"tickerUrl" : "https://api.bitfinex.com/v1/pubticker/btcusd",
		"jsonSchema" : {
			"ask" : "ask",
			"low" : "low",
			"high" : "high",
			"volume" : "volume",
			"timestamp" : "timestamp"
		}
	},
	"bitstamp" : {
		"tickerUrl" : "https://www.bitstamp.net/api/ticker/",
		"jsonSchema" : {
			"bid" : "bid",
			"ask" : "ask",
			"low" : "low",
			"high" : "high",
			"volume" : "volume",
			"timestamp" : "timestamp"
		}
	},
	"okcoin" : {
		"tickerUrl" : "https://www.okcoin.com/api/ticker.do?ok=1",
		"jsonSchema" : {
			"ticker.buy" : "bid",
			"ticker.sell" : "ask",
			"ticker.low" : "low",
			"ticker.high" : "high",
			"ticker.vol" : "volume",
			"date" : "timestamp"
		}
	},
	"btce" : {
		"tickerUrl" : "https://btc-e.com/api/3/ticker/btc_usd",
		"jsonSchema" : {
			"btc_usd.buy" : "bid",
			"btc_usd.sell" : "ask",
			"btc_usd.low" : "low",
			"btc_usd.high" : "high",
			"btc_usd.vol" : "volume",
			"btc_usd.updated" : "timestamp"
		}
	},
	"btc38" : {
		"tickerUrl" : "http://api.btc38.com/v1/ticker.php?c=btc",
		"jsonSchema" : {
			"ticker.buy" : "bid",
			"ticker.sell" : "ask",
			"ticker.low" : "low",
			"ticker.high" : "high",
			"ticker.vol" : "volume"
		}
	},
	"bter" : {
		"tickerUrl" : "http://data.bter.com/api/1/ticker/btc_usd",
		"jsonSchema" : {
			"buy" : "bid",
			"sell" : "ask",
			"low" : "low",
			"high" : "high",
			"vol_usd" : "volume"
		}
	},
	"hitbtc" : {
		"tickerUrl" : "http://api.hitbtc.com/api/1/public/BTCUSD/ticker",
		"jsonSchema" : {
			"bid" : "bid",
			"ask" : "ask",
			"low" : "low",
			"high" : "high",
			"volume" : "volume",
			"timestamp" : "timestamp"
		}
	},
	"ccex" : {
		"tickerUrl" : "https://c-cex.com/t/btc-usd.json",
		"jsonSchema" : {
			"ticker.buy" : "bid",
			"ticker.sell" : "ask",
			"ticker.low" : "low",
			"ticker.high" : "high"
		}
	},
	"kraken" : {
		"tickerUrl" : "https://api.kraken.com/0/public/Ticker?pair=XBTUSD",
		"jsonSchema" : {
			"result.XXBTZUSD.a.0" : "ask",
			"result.XXBTZUSD.b.0" : "bid",
			"result.XXBTZUSD.l.1" : "low",
			"result.XXBTZUSD.h.1" : "high",
			"result.XXBTZUSD.v.1" : "volume"
		}
	}
}
<|endoftext|>


<commit_before>/**
 * jQuery Router Plugin
 *
 * Copyright (c) 2010 Wu Yuntao
 *
 * Licensed under the MIT License:
 * http://www.opensource.org/licenses/mit-license.php
 *
 */

// (function($) {

/**
 * Represents a single route and can parse the dynamic route syntax
 */
var Route = function(route, target, name, static) {
    this.init(route, target, name, static);
};

$.extend(Route.prototype, {
    // JavaScript does not support negative lookbehind assertion
    // syntax: /(?<!\\):([a-zA-Z_]+)?(?:#(.*?)#)?/i,
    syntax: /:([a-zA-Z_]+)?(?:#(.*?)#)?/i,

    default: '[^/]+',

    /**
     * Create a Route.
     * The route string may contain `:key`, `:key#regexp#` or `:#regexp#` tokens
     * for each dynamic part of the route. These can be escaped with a backslash
     * in front of the `:` and are completely ignored if static is true.
     * A name may be used to refer to this route later (depends on Router)
     */
    init: function(route, target, name, static) {
        this.route = route.replace('\\:', ':');
        if ($.isArray(target)) {
            this.target = target;
        } else if (!target) {
            this.target = [];
        } else {
            this.target = [target];
        }

        this.name = name;
        this.realroute = static ? route.replace(':', '\\:') : route;
        this.tokens = this.realroute.split(this.syntax);
    },

    /**
     * Return a regexp pattern with groups
     */
    group: function() {
        out = '';
        $.each(this.tokens, $.proxy(function(i, part) {
            if (i % 3 == 0) {
                out += part.replace('\:', ':').replace('(', '\\(').replace(')', '\\)');
            } else if (i % 3 == 1) {
                out += '(';   // Javascript does not support named groups
            } else {
                out += (part || this.default) + ')';
            }
        }, this));
        return out;
    },

    /**
     * Return a format string with named fields.
     */
    format: function() {
        var out = '', c = 0;
        $.each(this.tokens, function(i, part) {
            if (i % 3 == 0) {
                out += part.replace('\\:', ':').replace('%', '%%');
            } else if (i % 3 == 1) {
                if (!part) part = 'anon' + (c++);
                out += '#{' + part + '}';
            }
        });
        return out;
    },

    static: function() {
        return this.tokens.length == 1;
    },

    equals: function(route) {
        return this.realroute === route.realroute;
    }
});

/**
 * A route associates a string (e.g. URL) with an object (e.g. function)
 * Some dynamic routes may extract parts of the string and provide them as
 * a dictionary. This router matches a string against multiple routes and
 * returns the associated object along with the extracted data.
 */
var Router = function() {
    this.init();
}

$.extend(Router.prototype, {
    init: function() {
        this.routes  = [];  // List of all installed routes
        this.named   = {};  // Cache for named routes and their format strings
        this.static  = {};  // Cache for static routes
        this.dynamic = [];  // Search structure for dynamic routes
        this.compiled = false;
    },

    /**
     * Add a route->target pair or a :class:`Route` object to the Router.
     * Return the Route object. See :class:`Route` for details.
     */
    add: function(route, target, options) {
        options = options || {};
        if (typeof route == 'string') {
            route = new Route(route, target, options.name, options.static);
        }
        var known = this.get(route);
        if (known) return known;
        this.routes.push(route);
        this.reset();
        if (options.compile) this.compile();
        return route;
    },

    /**
     * Set compiled flag to false and clean route caches
     */
    reset: function() {
        this.compiled = false;
        this.named = {};
        this.static = {};
        this.dynamic = [];
    },

    /**
     * Get a route from the router by specifying either the same
     * parameters as in :meth:`add` or comparing to an instance of
     * :class:`Route`. Note that not all parameters are considered by the
     * compare function.
     */
    get: function(route, target, options) {
        options = options || {};
        if (typeof route == 'string') {
            route = new Route(route, target, options.name, options.static);
        }
        for (var i = 0, len = this.routes.length; i < len; ++i) {
            if (route.equals(this.routes[i])) {
                return this.routes[i];
            }
        }
    },

    /**
     * Match an URI and return a (target, urlargs) tuple
     */
    match: function(uri) {
        if (uri in this.static) {
            return [this.static[uri], []];
        }
        for (var i = 0, len = this.dynamic.length; i < len; ++i) {
            match = uri.match(this.dynamic[i][0]);
            if (!match) continue;
            return [this.dynamic[i][1], match.slice(1, match.length)];
        }
        // Late check to reduce overhead on hits
        if (!this.compiled) {
            // Compile and try again.
            this.compile();
            return this.match(uri);
        }
        return [null, []];
    },

    /**
     * Build an URI out of a named route and values for the wildcards.
     */
    build: function(name, options) {
        if (name in this.named) {
            return this.format(this.named[name], options);
        } else {
            // Late check to reduce overhead on hits
            if (!this.compiled) {
                // Compile and try again.
                this.compile();
                return this.build(name, options);
            }
        }
    },

    format: function(template, options) {
        for (var key in options)
            template = template.replace('#{' + key + '}', options[key]);
        return template;
    },

    /**
     * Build the search structures. Call this before actually using the router.
     */
    compile: function() {
        this.reset();
        for (var i = 0, len = this.routes.length, route = null; i < len; ++i) {
            route = this.routes[i];
            if (route.name) {
                this.named[route.name] = route.format();
            }
            if (route.static()) {
                this.static[route.route] = route.target;
                continue;
            }
            var regexp = new RegExp('^' + route.group() + '$', 'i');
            this.dynamic.push([regexp, route.target]);
        }
        this.dynamic.reverse();
        this.compiled = true;
    },

    equals: function(router) {
        return this.routes == router.routes;
    }
});

// })(jQuery);
<commit_msg>Add a history class to observe location changes.<commit_after>/**
 * jQuery Router Plugin
 *
 * Copyright (c) 2010 Wu Yuntao
 *
 * Licensed under the MIT License:
 * http://www.opensource.org/licenses/mit-license.php
 *
 */

// (function($) {

/**
 * Represents a single route and can parse the dynamic route syntax
 */
var Route = function(route, target, name, static) {
    this.init(route, target, name, static);
};

$.extend(Route.prototype, {
    // JavaScript does not support negative lookbehind assertion
    // syntax: /(?<!\\):([a-zA-Z_]+)?(?:#(.*?)#)?/i,
    syntax: /:([a-zA-Z_]+)?(?:#(.*?)#)?/i,

    default: '[^/]+',

    /**
     * Create a Route.
     * The route string may contain `:key`, `:key#regexp#` or `:#regexp#` tokens
     * for each dynamic part of the route. These can be escaped with a backslash
     * in front of the `:` and are completely ignored if static is true.
     * A name may be used to refer to this route later (depends on Router)
     */
    init: function(route, target, name, static) {
        this.route = route.replace('\\:', ':');
        if ($.isArray(target)) {
            this.target = target;
        } else if (!target) {
            this.target = [];
        } else {
            this.target = [target];
        }

        this.name = name;
        this.realroute = static ? route.replace(':', '\\:') : route;
        this.tokens = this.realroute.split(this.syntax);
    },

    /**
     * Return a regexp pattern with groups
     */
    group: function() {
        out = '';
        $.each(this.tokens, $.proxy(function(i, part) {
            if (i % 3 == 0) {
                out += part.replace('\:', ':').replace('(', '\\(').replace(')', '\\)');
            } else if (i % 3 == 1) {
                out += '(';   // Javascript does not support named groups
            } else {
                out += (part || this.default) + ')';
            }
        }, this));
        return out;
    },

    /**
     * Return a format string with named fields.
     */
    format: function() {
        var out = '', c = 0;
        $.each(this.tokens, function(i, part) {
            if (i % 3 == 0) {
                out += part.replace('\\:', ':').replace('%', '%%');
            } else if (i % 3 == 1) {
                if (!part) part = 'anon' + (c++);
                out += '#{' + part + '}';
            }
        });
        return out;
    },

    static: function() {
        return this.tokens.length == 1;
    },

    equals: function(route) {
        return this.realroute === route.realroute;
    }
});

/**
 * A route associates a string (e.g. URL) with an object (e.g. function)
 * Some dynamic routes may extract parts of the string and provide them as
 * a dictionary. This router matches a string against multiple routes and
 * returns the associated object along with the extracted data.
 */
var Router = function() {
    this.init();
}

$.extend(Router.prototype, {
    init: function() {
        this.routes  = [];  // List of all installed routes
        this.named   = {};  // Cache for named routes and their format strings
        this.static  = {};  // Cache for static routes
        this.dynamic = [];  // Search structure for dynamic routes
        this.compiled = false;
    },

    /**
     * Add a route->target pair or a :class:`Route` object to the Router.
     * Return the Route object. See :class:`Route` for details.
     */
    add: function(route, target, options) {
        options = options || {};
        if (typeof route == 'string') {
            route = new Route(route, target, options.name, options.static);
        }
        var known = this.get(route);
        if (known) return known;
        this.routes.push(route);
        this.reset();
        if (options.compile) this.compile();
        return route;
    },

    /**
     * Set compiled flag to false and clean route caches
     */
    reset: function() {
        this.compiled = false;
        this.named = {};
        this.static = {};
        this.dynamic = [];
    },

    /**
     * Get a route from the router by specifying either the same
     * parameters as in :meth:`add` or comparing to an instance of
     * :class:`Route`. Note that not all parameters are considered by the
     * compare function.
     */
    get: function(route, target, options) {
        options = options || {};
        if (typeof route == 'string') {
            route = new Route(route, target, options.name, options.static);
        }
        for (var i = 0, len = this.routes.length; i < len; ++i) {
            if (route.equals(this.routes[i])) {
                return this.routes[i];
            }
        }
    },

    /**
     * Match an URI and return a (target, urlargs) tuple
     */
    match: function(uri) {
        if (uri in this.static) {
            return [this.static[uri], []];
        }
        for (var i = 0, len = this.dynamic.length; i < len; ++i) {
            match = uri.match(this.dynamic[i][0]);
            if (!match) continue;
            return [this.dynamic[i][1], match.slice(1, match.length)];
        }
        // Late check to reduce overhead on hits
        if (!this.compiled) {
            // Compile and try again.
            this.compile();
            return this.match(uri);
        }
        return [null, []];
    },

    /**
     * Build an URI out of a named route and values for the wildcards.
     */
    build: function(name, options) {
        if (name in this.named) {
            return this.format(this.named[name], options);
        } else {
            // Late check to reduce overhead on hits
            if (!this.compiled) {
                // Compile and try again.
                this.compile();
                return this.build(name, options);
            }
        }
    },

    format: function(template, options) {
        for (var key in options)
            template = template.replace('#{' + key + '}', options[key]);
        return template;
    },

    /**
     * Build the search structures. Call this before actually using the router.
     */
    compile: function() {
        this.reset();
        for (var i = 0, len = this.routes.length, route = null; i < len; ++i) {
            route = this.routes[i];
            if (route.name) {
                this.named[route.name] = route.format();
            }
            if (route.static()) {
                this.static[route.route] = route.target;
                continue;
            }
            var regexp = new RegExp('^' + route.group() + '$', 'i');
            this.dynamic.push([regexp, route.target]);
        }
        this.dynamic.reverse();
        this.compiled = true;
    },

    equals: function(router) {
        return this.routes == router.routes;
    }
});

var History = function(router) {
    this.init(router);
};

$.extend(History.prototype, {

    interval: 100,

    init: function(router) {
        this.state = this.get();
        this.router = router;
        this.timer = setInterval(this.check, this.interval);
        var request = this.request(this.state);
        var match = this.router.match(request.path);
        if (match[0]) {
        }
    },

    get: function() {
    },

    put: function() {
    },

    check: function() {
        var current = this.get();
        if (current != this.state) {
            this.state = current;
        }
    },

    request: function(uri) {
        var tokens = uri.split('?', 2);
        return {path: tokens[0], params: $.values(tokens[1])};

    }
});

$.values = function (value) {
    // Object that holds values.
    var params = [],
        // Get query string pieces (separated by &)
        pieces = value.split('&'),

    // Loop through query string pieces and assign params.
    for (var i = 0, len = pieces.length, pair; i < len; ++i{
        pair = pieces[i].split('=', 2);
        // Repeated parameters with the same name are overwritten. Parameters
        // with no value get set to boolean true.
        params.push(pair.length == 2 ? decodeURIComponent(pair[1].replace(/\+/g, ' ')) : true);
    }

    return params;
};

// })(jQuery);
<|endoftext|>


<commit_before>function DateTimeInput(input) {
    this.input = input;
    this.set_date = function(d, preserve) {
        var fields = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})/.exec( $(this.input).val() );
        var year, month, day, hour, minute, dow;
        if (!fields) preserve = {};
        else {
            year   = fields[1];
            month  = fields[2];
            day    = fields[3];
            hour   = fields[4];
            minute = fields[5];
            month = new Number(month) - 1;
        }
        if (!preserve) preserve = { };

        if (preserve.year  ) d.setFullYear(year  );
        if (preserve.month ) d.setMonth   (month );
        if (preserve.day   ) d.setDate    (day   );
        if (preserve.hour  ) d.setHours   (hour  );
        if (preserve.minute) d.setMinutes (minute);

        year = d.getFullYear();
        month = new Number(d.getMonth()) + 1;
        day = d.getDate();
        hour = d.getHours();
        minute = d.getMinutes();
        dow = ([
            gettext('Su'), gettext('Mo'), gettext('Tu'), gettext('We'), gettext('Th'), gettext('Fr'), gettext('Sa')
        ])[ d.getDay() ];

        function pad(str,n) {
            var s = new String(str);
            while (s.length < n) s = '0'+s;
            return s;
        }

        var nval = ''
        + pad(  year,4) + '-'
        + pad( month,2) + '-'
        + pad(   day,2) + ' '
        + pad(  hour,2) + ':'
        + pad(minute,2) + ' '
        + dow;
        $(this.input).val( nval ).change();
    };
}

function DateInput(input) {
    this.input = input;
    this.set_date = function(d, preserve) {
        var fields = /^(\d{4})-(\d{2})-(\d{2})/.exec( $(this.input).val() );
        var year, month, day,  dow;
        if (!fields) preserve = {};
        else {
            year   = fields[1];
            month  = fields[2];
            day    = fields[3];
            month = new Number(month) - 1;
        }
        if (!preserve) preserve = { };

        if (preserve.year ) d.setFullYear(year );
        if (preserve.month) d.setMonth   (month);
        if (preserve.day  ) d.setDate    (day  );

        year = d.getFullYear();
        month = new Number(d.getMonth()) + 1;
        day = d.getDate();
        dow = ([
            gettext('Su'), gettext('Mo'), gettext('Tu'), gettext('We'), gettext('Th'), gettext('Fr'), gettext('Sa')
        ])[ d.getDay() ];

        function pad(str,n) {
            var s = new String(str);
            while (s.length < n) s = '0'+s;
            return s;
        }

        var nval = ''
        + pad(  year,4) + '-'
        + pad( month,2) + '-'
        + pad(   day,2) + ' '
        + dow;
        $(this.input).val( nval ).change();
    };
}

(function($) {
    $('span.js-dtpicker-trigger').live('click', function() {
        var $dtp = $('.datetimepicker');
        var x = $(this).offset().left + $(this).width();
        var y = simple_offset(this).top - $('#container').offset().top; // not $(this).offset().top because of scrolling fixed #container, in which the dtpicker also is
        var $input = $(this).data('input');

        // check if Y doesn't reach below the current screen height
        var d = y + $dtp.outerHeight()  -  $(window).outerHeight();
        if (d > 0) y -= d;
        if (y < 0) y = 0;   // But rather reach below bottom than above top

        $('.datetimepicker').css({
            top : y + 'px',
            left: x + 'px'
        }).toggle().data( 'input', $input );
    });

    function datetime_init() {
        $('.vDateTimeInput,.vDateInput').each( function() {
            var $input = $(this);
            var is_datetime = $input.hasClass('vDateTimeInput');
            if (! $input.data('dti')) {
                if (is_datetime) {
                    $input.data('dti', new DateTimeInput(this));
                }
                else {
                    $input.data('dti', new DateInput(this));
                }

                $(  ''
                )
                .data('input', $input)
                .insertAfter(this);

                $input.keydown(function(evt) {
                    var delta = 0;
                    if (evt.keyCode == 38) delta =  1;
                    if (evt.keyCode == 40) delta = -1;
                    if (!delta) return true;
                    var pos = this.selectionEnd;
                    $input.data('dti').scroll(pos, delta);
                    evt.preventDefault();
                });
            }
        });
    }
    datetime_init();

    var timepicker_html;
    function media_dependent_datetime_init(evt) {

        if ( ! timepicker_html ) {
            var args = arguments; args._this = this;
            get_html_chunk('timepicker', function(data) {
                timepicker_html = data;
                args.callee.apply( args._this, args );
            });
            return;
        }

        // create the datetimepicker div
        if ($('.datetimepicker').length) { }    // but only if there is none yet
        else if (
               evt.type == 'content_added'  // and we added something that uses a datepicker
            && $(evt.target).find('.js-dtpicker-trigger').length == 0    // if anything
        ) { }
        else {
            // Container creation
            var $dtpicker = $('
');

            // Date picker
            var $datepicker = $('
');
            $datepicker.datepicker(new DATEPICKER_OPTIONS({
                onSelect: function(dtext, dpick) {
                    var $dtpicker = $(this).closest('.datetimepicker');
                    var dti = $( $dtpicker.data('input') ).data('dti');
                    var d = new Date();
                    d.setFullYear(dpick.selectedYear);
                    d.setMonth(dpick.selectedMonth);
                    d.setDate(dpick.selectedDay);
                    d.setHours(0);
                    d.setMinutes(0);
                    d.setSeconds(0);
                    d.setMilliseconds(0);
                    dti.set_date(d, {/*preserve*/hour:true,minute:true});
                    $(this).closest('.datetimepicker').hide();
                },
                onClose: function() {
                    $(this).closest('.datetimepicker').hide();
                }
            }));
            $datepicker.appendTo($dtpicker);

            // Time picker
            var $timepicker = $('
')
            .html(timepicker_html).appendTo($dtpicker);

            // Container placement
            $dtpicker.appendTo(
                   $('.change-form').get(0)
                || $('#content').get(0)
                || $('body').get(0)
            );
        }
    }

    $('.js-dtpicker-close').live('click', function(evt) {
        if (evt.button != 0) return;
        $(this).closest('.datetimepicker').hide();
    });
    $('.js-timepick').live('click', function(evt) {
        if (evt.button != 0) return;
        var dti = $( $(this).closest('.datetimepicker').data('input') ).data('dti');
        var selected_time = /js-time-(\d\d)(\d\d)/.exec(this.className);
        var d = new Date();
        d.setSeconds(0);
        d.setMilliseconds(0);
        if ( ! selected_time ) {
            if ( ! $(this).hasClass('js-time-now') ) return;
        }
        else {
            var selected_hours   = selected_time[1];
            var selected_minutes = selected_time[2];
            d.setHours  (selected_hours  );
            d.setMinutes(selected_minutes);
            // let default date be the following 24 hours
            if (d.getTime() < new Date().getTime()) {
                d.setDate( d.getDate() + 1 );
            }
        }
        dti.set_date(d, {/*preserve*/year:true,month:true,day:true});
        $(this).closest('.datetimepicker').hide();
    });

    $( document ).bind('content_added', datetime_init);
    $( document ).bind('content_added', media_dependent_datetime_init);
    $( document ).one ('media_loaded' , media_dependent_datetime_init);

})(jQuery);
<commit_msg>datetime.js modified<commit_after>function DateTimeInput(input) {
    this.input = input;
    this.set_date = function(d, preserve) {
        var fields = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})/.exec( $(this.input).val() );
        var year, month, day, hour, minute, dow;
        if (!fields) preserve = {};
        else {
            year   = fields[1];
            month  = fields[2];
            day    = fields[3];
            hour   = fields[4];
            minute = fields[5];
            month = new Number(month) - 1;
        }
        if (!preserve) preserve = { };

        if (preserve.year  ) d.setFullYear(year  );
        if (preserve.month ) d.setMonth   (month );
        if (preserve.day   ) d.setDate    (day   );
        if (preserve.hour  ) d.setHours   (hour  );
        if (preserve.minute) d.setMinutes (minute);

        year = d.getFullYear();
        // Fix 0-based month numbering:
        month = new Number(d.getMonth()) + 1;
        day = d.getDate();
        hour = d.getHours();
        minute = d.getMinutes();
        dow = ([
            gettext('Su'), gettext('Mo'), gettext('Tu'), gettext('We'), gettext('Th'), gettext('Fr'), gettext('Sa')
        ])[ d.getDay() ];

        function pad(str,n) {
            var s = new String(str);
            while (s.length < n) s = '0'+s;
            return s;
        }

        var nval = [
            pad(  year,4) , '-',
            pad( month,2) , '-',
            pad(   day,2) , ' ',
            pad(  hour,2) , ':',
            pad(minute,2) , ' ',
            dow
        ].join('');
        $(this.input).val( nval ).change();
    };
}

function DateInput(input) {
    this.input = input;
    this.set_date = function(d, preserve) {
        var fields = /^(\d{4})-(\d{2})-(\d{2})/.exec( $(this.input).val() );
        var year, month, day,  dow;
        if (!fields) preserve = {};
        else {
            year   = fields[1];
            month  = fields[2];
            day    = fields[3];
            month = new Number(month) - 1;
        }
        if (!preserve) preserve = { };

        if (preserve.year ) d.setFullYear(year );
        if (preserve.month) d.setMonth   (month);
        if (preserve.day  ) d.setDate    (day  );

        year = d.getFullYear();
        month = new Number(d.getMonth()) + 1;
        day = d.getDate();
        dow = ([
            gettext('Su'), gettext('Mo'), gettext('Tu'), gettext('We'), gettext('Th'), gettext('Fr'), gettext('Sa')
        ])[ d.getDay() ];

        function pad(str,n) {
            var s = new String(str);
            while (s.length < n) s = '0'+s;
            return s;
        }

        var nval = ''
        + pad(  year,4) + '-'
        + pad( month,2) + '-'
        + pad(   day,2) + ' '
        + dow;
        $(this.input).val( nval ).change();
    };
}

(function($) {
    $('span.js-dtpicker-trigger').live('click', function() {
        var $dtp = $('.datetimepicker');
        var x = $(this).offset().left + $(this).width();
        var y = simple_offset(this).top - $('#container').offset().top; // not $(this).offset().top because of scrolling fixed #container, in which the dtpicker also is
        var $input = $(this).data('input');

        // check if Y doesn't reach below the current screen height
        var d = y + $dtp.outerHeight()  -  $(window).outerHeight();
        if (d > 0) y -= d;
        if (y < 0) y = 0;   // But rather reach below bottom than above top

        $('.datetimepicker').css({
            top : y + 'px',
            left: x + 'px'
        }).toggle().data( 'input', $input );
    });

    function datetime_init() {
        $('.vDateTimeInput,.vDateInput').each( function() {
            var $input = $(this);
            var is_datetime = $input.hasClass('vDateTimeInput');
            if (! $input.data('dti')) {
                if (is_datetime) {
                    $input.data('dti', new DateTimeInput(this));
                }
                else {
                    $input.data('dti', new DateInput(this));
                }

                $(  ''
                )
                .data('input', $input)
                .insertAfter(this);

                $input.keydown(function(evt) {
                    var delta = 0;
                    if (evt.keyCode == 38) delta =  1;
                    if (evt.keyCode == 40) delta = -1;
                    if (!delta) return true;
                    var pos = this.selectionEnd;
                    $input.data('dti').scroll(pos, delta);
                    evt.preventDefault();
                });
            }
        });
    }
    datetime_init();

    var timepicker_html;
    function media_dependent_datetime_init(evt) {

        if ( ! timepicker_html ) {
            var args = arguments; args._this = this;
            get_html_chunk('timepicker', function(data) {
                timepicker_html = data;
                args.callee.apply( args._this, args );
            });
            return;
        }

        // create the datetimepicker div
        if ($('.datetimepicker').length) { }    // but only if there is none yet
        else if (
               evt.type == 'content_added'  // and we added something that uses a datepicker
            && $(evt.target).find('.js-dtpicker-trigger').length == 0    // if anything
        ) { }
        else {
            // Container creation
            var $dtpicker = $('
');

            // Date picker
            var $datepicker = $('
');
            $datepicker.datepicker(new DATEPICKER_OPTIONS({
                onSelect: function(dtext, dpick) {
                    var $dtpicker = $(this).closest('.datetimepicker');
                    var dti = $( $dtpicker.data('input') ).data('dti');
                    var d = new Date();
                    d.setFullYear(dpick.selectedYear);
                    d.setMonth(dpick.selectedMonth);
                    d.setDate(dpick.selectedDay);
                    d.setHours(0);
                    d.setMinutes(0);
                    d.setSeconds(0);
                    d.setMilliseconds(0);
                    dti.set_date(d, {/*preserve*/hour:true,minute:true});
                    $(this).closest('.datetimepicker').hide();
                },
                onClose: function() {
                    $(this).closest('.datetimepicker').hide();
                }
            }));
            $datepicker.appendTo($dtpicker);

            // Time picker
            var $timepicker = $('
')
            .html(timepicker_html).appendTo($dtpicker);

            // Container placement
            $dtpicker.appendTo(
                   $('.change-form').get(0)
                || $('#content').get(0)
                || $('body').get(0)
            );
        }
    }

    $('.js-dtpicker-close').live('click', function(evt) {
        if (evt.button != 0) return;
        $(this).closest('.datetimepicker').hide();
    });
    $('.js-timepick').live('click', function(evt) {
        if (evt.button != 0) return;
        var dti = $( $(this).closest('.datetimepicker').data('input') ).data('dti');
        var selected_time = /js-time-(\d\d)(\d\d)/.exec(this.className);
        var d = new Date();
        d.setSeconds(0);
        d.setMilliseconds(0);
        if ( ! selected_time ) {
            if ( ! $(this).hasClass('js-time-now') ) return;
        }
        else {
            var selected_hours   = selected_time[1];
            var selected_minutes = selected_time[2];
            d.setHours  (selected_hours  );
            d.setMinutes(selected_minutes);
            // let default date be the following 24 hours
            if (d.getTime() < new Date().getTime()) {
                d.setDate( d.getDate() + 1 );
            }
        }
        dti.set_date(d, {/*preserve*/year:true,month:true,day:true});
        $(this).closest('.datetimepicker').hide();
    });

    $( document ).bind('content_added', datetime_init);
    $( document ).bind('content_added', media_dependent_datetime_init);
    $( document ).one ('media_loaded' , media_dependent_datetime_init);

})(jQuery);
<|endoftext|>


<commit_before>class ExtendableError extends Error {
  constructor (message) {
    super(message)
    this.name = this.constructor.name
    if (typeof Error.captureStackTrace === 'function') {
      Error.captureStackTrace(this, this.constructor)
    } else {
      this.stack = (new Error(message)).stack
    }
  }
}

class NotImplementedException extends ExtendableError {
  constructor () {
    super('Required method is not implemented.')
  }
}

class UnauthorizedQueryException extends ExtendableError {
  constructor () {
    super('Query not authorized.')
  }
}

class UserReachedMaxLevelException extends ExtendableError {
  constructor () {
    super('There are no more levels. The user is at the max level.')
  }
}

class MockDatabaseException extends ExtendableError {

}

class EmptyOperationStackException extends MockDatabaseException {
  constructor () {
    super('Operation stack is empty. Check if you set your expected data in the test.')
  }
}

//

export {
  NotImplementedException,
  UnauthorizedQueryException,
  UserReachedMaxLevelException,
  EmptyOperationStackException
}
<commit_msg>Add error codes to custom errors<commit_after>class ExtendableError extends Error {
  constructor (message) {
    super(message)
    this.name = this.constructor.name
    if (typeof Error.captureStackTrace === 'function') {
      Error.captureStackTrace(this, this.constructor)
    } else {
      this.stack = (new Error(message)).stack
    }
  }
}

export const METHOD_NOT_IMPLEMENTED = 'METHOD_NOT_IMPLEMENTED'
class NotImplementedException extends ExtendableError {
  constructor () {
    super('Required method is not implemented.')
    this.code = UNAUTHORIZED_QUERY
  }
}

export const UNAUTHORIZED_QUERY = 'UNAUTHORIZED_QUERY'
class UnauthorizedQueryException extends ExtendableError {
  constructor () {
    super('Query not authorized.')
    this.code = UNAUTHORIZED_QUERY
  }
}

export const USER_REACHED_MAX_LEVEL = 'USER_REACHED_MAX_LEVEL'
class UserReachedMaxLevelException extends ExtendableError {
  constructor () {
    super('There are no more levels. The user is at the max level.')
    this.code = USER_REACHED_MAX_LEVEL
  }
}

// For tests.
class MockDatabaseException extends ExtendableError {}

export const OPERATION_STACK_IS_EMPTY = 'OPERATION_STACK_IS_EMPTY'
class EmptyOperationStackException extends MockDatabaseException {
  constructor () {
    super('Operation stack is empty. Check if you set your expected data in the test.')
    this.code = OPERATION_STACK_IS_EMPTY
  }
}

// TODO:
// USER_DOES_NOT_EXIST_ERROR

export {
  NotImplementedException,
  UnauthorizedQueryException,
  UserReachedMaxLevelException,
  EmptyOperationStackException
}
<|endoftext|>


<commit_before>/**
 * modules/analytics data store: properties.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import invariant from 'invariant';
import { groupBy } from 'lodash';

/**
 * Internal dependencies
 */
import API from 'googlesitekit-api';
import Data from 'googlesitekit-data';
import { STORE_NAME } from './index';
import { actions as profileActions } from './profiles';

// Actions
const FETCH_CREATE_PROPERTY = 'FETCH_CREATE_PROPERTY';
const FETCH_PROPERTIES_PROFILES = 'FETCH_PROPERTIES_PROFILES';
const RECEIVE_CREATE_PROPERTY = 'RECEIVE_CREATE_PROPERTY';
const RECEIVE_CREATE_PROPERTY_FAILED = 'RECEIVE_CREATE_PROPERTY_FAILED';
const RECEIVE_PROPERTIES = 'RECEIVE_PROPERTIES';
const RECEIVE_PROPERTIES_PROFILES = 'RECEIVE_PROPERTIES_PROFILES';
const RECEIVE_PROPERTIES_PROFILES_FAILED = 'RECEIVE_PROPERTIES_PROFILES_FAILED';

export const INITIAL_STATE = {
	isFetchingCreateProperty: {},
	isFetchingPropertiesProfiles: {},
	properties: {},
};

export const actions = {
	/**
	 * Creates a new Analytics property.
	 *
	 * Creates a new Analytics property for an existing Google Analytics account.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} accountID Google Analytics account ID.
	 * @return {Object} Redux-style action.
	 */
	*createProperty( accountID ) {
		invariant( accountID, 'accountID is required.' );

		try {
			const property = yield actions.fetchCreateProperty( accountID );

			yield actions.receiveCreateProperty( { accountID, property } );
			yield Data.dispatch( STORE_NAME ).setPropertyID( property.id );
			return;
		} catch ( error ) {
			// TODO: Implement an error handler store or some kind of centralized
			// place for error dispatch...
			return actions.receiveCreatePropertyFailed( { accountID, error } );
		}
	},

	*fetchCreateProperty( accountID ) {
		invariant( accountID, 'accountID is required.' );

		return {
			payload: { accountID },
			type: FETCH_CREATE_PROPERTY,
		};
	},

	*fetchPropertiesProfiles( accountID ) {
		invariant( accountID, 'accountID is required.' );

		return {
			payload: { accountID },
			type: FETCH_PROPERTIES_PROFILES,
		};
	},

	/**
	 * Adds a property to the data store.
	 *
	 * Adds the newly-created property to the existing properties in
	 * the data store.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} args           Argument params.
	 * @param {string} args.accountID Google Analytics account ID.
	 * @param {Object} args.property  Google Analytics property object.
	 * @return {Object} Redux-style action.
	 */
	receiveCreateProperty( { accountID, property } ) {
		invariant( accountID, 'accountID is required' );
		invariant( property, 'property is required' );

		return {
			payload: { accountID, property },
			type: RECEIVE_CREATE_PROPERTY,
		};
	},

	/**
	 * Logs an error with property creation.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} args           Argument params.
	 * @param {string} args.accountID Google Analytics account ID.
	 * @param {Object} args.error     Error object.
	 * @return {Object} Redux-style action.
	 */
	receiveCreatePropertyFailed( { accountID, error } ) {
		invariant( accountID, 'accountID is required' );
		invariant( error, 'error is required.' );

		return {
			payload: { accountID, error },
			type: RECEIVE_CREATE_PROPERTY_FAILED,
		};
	},

	/**
	 * Adds properties to the store.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Array} properties Properties to add.
	 * @return {Object} Redux-style action.
	 */
	receiveProperties( properties ) {
		invariant( Array.isArray( properties ), 'properties must be an array.' );

		return {
			payload: { properties },
			type: RECEIVE_PROPERTIES,
		};
	},

	receivePropertiesProfiles( { accountID, properties, profiles } ) {
		invariant( accountID, 'accountID is required' );
		invariant( properties, 'properties is required' );

		return {
			payload: { accountID, properties, profiles },
			type: RECEIVE_PROPERTIES_PROFILES,
		};
	},

	receivePropertiesProfilesFailed( { accountID, error } ) {
		invariant( accountID, 'accountID is required' );
		invariant( error, 'error is required.' );

		return {
			payload: { accountID, error },
			type: RECEIVE_PROPERTIES_PROFILES_FAILED,
		};
	},
};

export const controls = {
	[ FETCH_CREATE_PROPERTY ]: ( { payload: { accountID } } ) => {
		return API.set( 'modules', 'analytics', 'create-property', { accountID } );
	},
	[ FETCH_PROPERTIES_PROFILES ]: ( { payload: { accountID } } ) => {
		return API.get( 'modules', 'analytics', 'properties-profiles', { accountID } );
	},
};

export const reducer = ( state, { type, payload } ) => {
	switch ( type ) {
		case FETCH_CREATE_PROPERTY: {
			const { accountID } = payload;

			return {
				...state,
				isFetchingCreateProperty: {
					...state.isFetchingCreateProperty,
					[ accountID ]: true,
				},
			};
		}

		case FETCH_PROPERTIES_PROFILES: {
			const { accountID } = payload;

			return {
				...state,
				isFetchingPropertiesProfiles: {
					...state.isFetchingPropertiesProfiles,
					[ accountID ]: true,
				},
			};
		}

		case RECEIVE_CREATE_PROPERTY: {
			const { accountID, property } = payload;

			return {
				...state,
				isFetchingCreateProperty: {
					...state.isFetchingCreateProperty,
					[ accountID ]: false,
				},
				properties: {
					...state.properties || {},
					[ accountID ]: [
						...( state.properties || {} )[ accountID ] || [],
						property,
					],
				},
			};
		}

		case RECEIVE_CREATE_PROPERTY_FAILED: {
			const { accountID, error } = payload;

			return {
				...state,
				error,
				isFetchingCreateProperty: {
					...state.isFetchingCreateProperty,
					[ accountID ]: false,
				},
			};
		}

		case RECEIVE_PROPERTIES: {
			const { properties } = payload;

			return {
				...state,
				properties: {
					...state.properties,
					...groupBy( properties, 'accountId' ), // Capitalization rule exception: `accountId` is a property of an API returned value.
				},
			};
		}

		case RECEIVE_PROPERTIES_PROFILES: {
			const { accountID } = payload;

			const updatedState = {
				...state,
				isFetchingPropertiesProfiles: {
					...state.isFetchingPropertiesProfiles,
					[ accountID ]: false,
				},
			};

			return updatedState;
		}

		case RECEIVE_PROPERTIES_PROFILES_FAILED: {
			const { accountID, error } = payload;

			return {
				...state,
				error,
				isFetchingPropertiesProfiles: {
					...state.isFetchingPropertiesProfiles,
					[ accountID ]: false,
				},
			};
		}

		default: {
			return { ...state };
		}
	}
};

export const resolvers = {
	*getProperties( accountID ) {
		try {
			const response = yield actions.fetchPropertiesProfiles( accountID );
			const { properties, profiles } = response;

			yield actions.receiveProperties( properties );
			yield profileActions.receiveProfiles( profiles );

			return yield actions.receivePropertiesProfiles( { accountID, properties, profiles } );
		} catch ( error ) {
			// TODO: Implement an error handler store or some kind of centralized
			// place for error dispatch...
			return actions.receivePropertiesProfilesFailed( { accountID, error } );
		}
	},
};

export const selectors = {
	/**
	 * Get all Google Analytics properties this account can access.
	 *
	 * Returns an array of all analytics properties.
	 *
	 * Returns `undefined` if accounts have not yet loaded.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} state     Data store's state.
	 * @param {string} accountID The Analytics Account ID to fetch properties for.
	 * @return {Array|undefined} An array of Analytics properties; `undefined` if not loaded.
	 */
	getProperties( state, accountID ) {
		invariant( accountID, 'accountID is required' );

		const { properties } = state;

		return properties[ accountID ];
	},

	/**
	 * Check if a property is being created for an account.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} state     Data store's state.
	 * @param {string} accountID The Analytics Account ID to check for property creation.
	 * @return {boolean} `true` if creating a property, `false` if not.
	 */
	isDoingCreateProperty( state, accountID ) {
		invariant( accountID, 'accountID is required' );

		const { isFetchingCreateProperty } = state;

		return !! isFetchingCreateProperty[ accountID ];
	},
};

export default {
	INITIAL_STATE,
	actions,
	controls,
	reducer,
	resolvers,
	selectors,
};
<commit_msg>Rename and simplify receivePropertiesProfiles.<commit_after>/**
 * modules/analytics data store: properties.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import invariant from 'invariant';
import { groupBy } from 'lodash';

/**
 * Internal dependencies
 */
import API from 'googlesitekit-api';
import Data from 'googlesitekit-data';
import { STORE_NAME } from './index';
import { actions as profileActions } from './profiles';

// Actions
const FETCH_CREATE_PROPERTY = 'FETCH_CREATE_PROPERTY';
const FETCH_PROPERTIES_PROFILES = 'FETCH_PROPERTIES_PROFILES';
const RECEIVE_CREATE_PROPERTY = 'RECEIVE_CREATE_PROPERTY';
const RECEIVE_CREATE_PROPERTY_FAILED = 'RECEIVE_CREATE_PROPERTY_FAILED';
const RECEIVE_PROPERTIES = 'RECEIVE_PROPERTIES';
const RECEIVE_PROPERTIES_PROFILES_COMPLETED = 'RECEIVE_PROPERTIES_PROFILES_COMPLETED';
const RECEIVE_PROPERTIES_PROFILES_FAILED = 'RECEIVE_PROPERTIES_PROFILES_FAILED';

export const INITIAL_STATE = {
	isFetchingCreateProperty: {},
	isFetchingPropertiesProfiles: {},
	properties: {},
};

export const actions = {
	/**
	 * Creates a new Analytics property.
	 *
	 * Creates a new Analytics property for an existing Google Analytics account.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} accountID Google Analytics account ID.
	 * @return {Object} Redux-style action.
	 */
	*createProperty( accountID ) {
		invariant( accountID, 'accountID is required.' );

		try {
			const property = yield actions.fetchCreateProperty( accountID );

			yield actions.receiveCreateProperty( { accountID, property } );
			yield Data.dispatch( STORE_NAME ).setPropertyID( property.id );
			return;
		} catch ( error ) {
			// TODO: Implement an error handler store or some kind of centralized
			// place for error dispatch...
			return actions.receiveCreatePropertyFailed( { accountID, error } );
		}
	},

	*fetchCreateProperty( accountID ) {
		invariant( accountID, 'accountID is required.' );

		return {
			payload: { accountID },
			type: FETCH_CREATE_PROPERTY,
		};
	},

	*fetchPropertiesProfiles( accountID ) {
		invariant( accountID, 'accountID is required.' );

		return {
			payload: { accountID },
			type: FETCH_PROPERTIES_PROFILES,
		};
	},

	/**
	 * Adds a property to the data store.
	 *
	 * Adds the newly-created property to the existing properties in
	 * the data store.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} args           Argument params.
	 * @param {string} args.accountID Google Analytics account ID.
	 * @param {Object} args.property  Google Analytics property object.
	 * @return {Object} Redux-style action.
	 */
	receiveCreateProperty( { accountID, property } ) {
		invariant( accountID, 'accountID is required' );
		invariant( property, 'property is required' );

		return {
			payload: { accountID, property },
			type: RECEIVE_CREATE_PROPERTY,
		};
	},

	/**
	 * Logs an error with property creation.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} args           Argument params.
	 * @param {string} args.accountID Google Analytics account ID.
	 * @param {Object} args.error     Error object.
	 * @return {Object} Redux-style action.
	 */
	receiveCreatePropertyFailed( { accountID, error } ) {
		invariant( accountID, 'accountID is required' );
		invariant( error, 'error is required.' );

		return {
			payload: { accountID, error },
			type: RECEIVE_CREATE_PROPERTY_FAILED,
		};
	},

	/**
	 * Adds properties to the store.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Array} properties Properties to add.
	 * @return {Object} Redux-style action.
	 */
	receiveProperties( properties ) {
		invariant( Array.isArray( properties ), 'properties must be an array.' );

		return {
			payload: { properties },
			type: RECEIVE_PROPERTIES,
		};
	},

	receivePropertiesProfilesCompleted( accountID ) {
		invariant( accountID, 'accountID is required' );

		return {
			payload: { accountID },
			type: RECEIVE_PROPERTIES_PROFILES_COMPLETED,
		};
	},

	receivePropertiesProfilesFailed( { accountID, error } ) {
		invariant( accountID, 'accountID is required' );
		invariant( error, 'error is required.' );

		return {
			payload: { accountID, error },
			type: RECEIVE_PROPERTIES_PROFILES_FAILED,
		};
	},
};

export const controls = {
	[ FETCH_CREATE_PROPERTY ]: ( { payload: { accountID } } ) => {
		return API.set( 'modules', 'analytics', 'create-property', { accountID } );
	},
	[ FETCH_PROPERTIES_PROFILES ]: ( { payload: { accountID } } ) => {
		return API.get( 'modules', 'analytics', 'properties-profiles', { accountID } );
	},
};

export const reducer = ( state, { type, payload } ) => {
	switch ( type ) {
		case FETCH_CREATE_PROPERTY: {
			const { accountID } = payload;

			return {
				...state,
				isFetchingCreateProperty: {
					...state.isFetchingCreateProperty,
					[ accountID ]: true,
				},
			};
		}

		case FETCH_PROPERTIES_PROFILES: {
			const { accountID } = payload;

			return {
				...state,
				isFetchingPropertiesProfiles: {
					...state.isFetchingPropertiesProfiles,
					[ accountID ]: true,
				},
			};
		}

		case RECEIVE_CREATE_PROPERTY: {
			const { accountID, property } = payload;

			return {
				...state,
				isFetchingCreateProperty: {
					...state.isFetchingCreateProperty,
					[ accountID ]: false,
				},
				properties: {
					...state.properties || {},
					[ accountID ]: [
						...( state.properties || {} )[ accountID ] || [],
						property,
					],
				},
			};
		}

		case RECEIVE_CREATE_PROPERTY_FAILED: {
			const { accountID, error } = payload;

			return {
				...state,
				error,
				isFetchingCreateProperty: {
					...state.isFetchingCreateProperty,
					[ accountID ]: false,
				},
			};
		}

		case RECEIVE_PROPERTIES: {
			const { properties } = payload;

			return {
				...state,
				properties: {
					...state.properties,
					...groupBy( properties, 'accountId' ), // Capitalization rule exception: `accountId` is a property of an API returned value.
				},
			};
		}

		case RECEIVE_PROPERTIES_PROFILES_COMPLETED: {
			const { accountID } = payload;

			return {
				...state,
				isFetchingPropertiesProfiles: {
					...state.isFetchingPropertiesProfiles,
					[ accountID ]: false,
				},
			};
		}

		case RECEIVE_PROPERTIES_PROFILES_FAILED: {
			const { accountID, error } = payload;

			return {
				...state,
				error,
				isFetchingPropertiesProfiles: {
					...state.isFetchingPropertiesProfiles,
					[ accountID ]: false,
				},
			};
		}

		default: {
			return { ...state };
		}
	}
};

export const resolvers = {
	*getProperties( accountID ) {
		try {
			const response = yield actions.fetchPropertiesProfiles( accountID );
			const { properties, profiles } = response;

			yield actions.receiveProperties( properties );
			yield profileActions.receiveProfiles( profiles );

			return yield actions.receivePropertiesProfilesCompleted( accountID );
		} catch ( error ) {
			// TODO: Implement an error handler store or some kind of centralized
			// place for error dispatch...
			return actions.receivePropertiesProfilesFailed( { accountID, error } );
		}
	},
};

export const selectors = {
	/**
	 * Get all Google Analytics properties this account can access.
	 *
	 * Returns an array of all analytics properties.
	 *
	 * Returns `undefined` if accounts have not yet loaded.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} state     Data store's state.
	 * @param {string} accountID The Analytics Account ID to fetch properties for.
	 * @return {Array|undefined} An array of Analytics properties; `undefined` if not loaded.
	 */
	getProperties( state, accountID ) {
		invariant( accountID, 'accountID is required' );

		const { properties } = state;

		return properties[ accountID ];
	},

	/**
	 * Check if a property is being created for an account.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} state     Data store's state.
	 * @param {string} accountID The Analytics Account ID to check for property creation.
	 * @return {boolean} `true` if creating a property, `false` if not.
	 */
	isDoingCreateProperty( state, accountID ) {
		invariant( accountID, 'accountID is required' );

		const { isFetchingCreateProperty } = state;

		return !! isFetchingCreateProperty[ accountID ];
	},
};

export default {
	INITIAL_STATE,
	actions,
	controls,
	reducer,
	resolvers,
	selectors,
};
<|endoftext|>


<commit_before>var fs = require('fs');
var path = require('path');
var VersionChecker = require('ember-cli-version-checker');

module.exports = function(blueprint) {
  blueprint.supportsAddon = function() {
    return false;
  };

  blueprint.filesPath = function() {
    var type;

    var dependencies = this.project.dependencies();
    if ('ember-qunit' in dependencies) {
      type = 'qunit-rfc-232';

    } else if ('ember-cli-qunit' in dependencies) {
      let checker = new VersionChecker(this.project);
      if (fs.existsSync(this.path + '/qunit-rfc-232-files') && checker.for('ember-cli-qunit', 'npm').gte('4.1.1')) {
        type = 'qunit-rfc-232';
      } else {
        type = 'qunit';
      }

    } else if ('ember-mocha' in dependencies) {
      type = 'mocha-0.12';

    } else if ('ember-cli-mocha' in dependencies) {
      let checker = new VersionChecker(this.project);
      if (fs.existsSync(this.path + '/mocha-0.12-files') && checker.for('ember-cli-mocha', 'npm').satisfies('>=0.12.0')) {
        type = 'mocha-0.12';
      } else {
        type = 'mocha';
      }

    } else {
      this.ui.writeLine('Couldn\'t determine test style - using QUnit');
      type = 'qunit';
    }

    return path.join(this.path, type + '-files');
  };

  return blueprint;
};
<commit_msg>[BUGFIX lts] blueprints: fix framework detection to work with prerelease versions of ember-cli-mocha<commit_after>var fs = require('fs');
var path = require('path');
var VersionChecker = require('ember-cli-version-checker');

module.exports = function(blueprint) {
  blueprint.supportsAddon = function() {
    return false;
  };

  blueprint.filesPath = function() {
    var type;

    var dependencies = this.project.dependencies();
    if ('ember-qunit' in dependencies) {
      type = 'qunit-rfc-232';

    } else if ('ember-cli-qunit' in dependencies) {
      let checker = new VersionChecker(this.project);
      if (fs.existsSync(this.path + '/qunit-rfc-232-files') && checker.for('ember-cli-qunit', 'npm').gte('4.1.1')) {
        type = 'qunit-rfc-232';
      } else {
        type = 'qunit';
      }

    } else if ('ember-mocha' in dependencies) {
      type = 'mocha-0.12';

    } else if ('ember-cli-mocha' in dependencies) {
      let checker = new VersionChecker(this.project);
      if (fs.existsSync(this.path + '/mocha-0.12-files') && checker.for('ember-cli-mocha', 'npm').gte('0.12.0')) {
        type = 'mocha-0.12';
      } else {
        type = 'mocha';
      }

    } else {
      this.ui.writeLine('Couldn\'t determine test style - using QUnit');
      type = 'qunit';
    }

    return path.join(this.path, type + '-files');
  };

  return blueprint;
};
<|endoftext|>