<commit_before>const BaseDataview = require('./base');
const debug = require('debug')('windshaft:dataview:numeric-histogram');

const columnCastTpl = ctx => `date_part('epoch', ${ctx.column})`;

const filterOutSpecialNumericValues = ctx => `
        ${ctx._column} != 'infinity'::float
    AND
        ${ctx._column} != '-infinity'::float
    AND
        ${ctx._column} != 'NaN'::float
`;

const filteredQueryTpl = ctx => `
    __cdb_filtered_source AS (
        SELECT *
        FROM (${ctx._query}) __cdb_filtered_source_query
        WHERE ${ctx._column} IS NOT NULL
        ${ctx._isFloatColumn ? `AND ${filterOutSpecialNumericValues(ctx)}` : ''}
    )
`;

const basicsQueryTpl = ctx => `
    __cdb_basics AS (
        SELECT
            max(${ctx._column}) AS __cdb_max_val, min(${ctx._column}) AS __cdb_min_val,
            avg(${ctx._column}) AS __cdb_avg_val, count(1) AS __cdb_total_rows
        FROM __cdb_filtered_source
    )
`;

const overrideBasicsQueryTpl = ctx => `
    __cdb_basics AS (
        SELECT
            max(${ctx._end}) AS __cdb_max_val, min(${ctx._start}) AS __cdb_min_val,
            avg(${ctx._column}) AS __cdb_avg_val, count(1) AS __cdb_total_rows
        FROM __cdb_filtered_source
    )
`;

const iqrQueryTpl = ctx => `
    __cdb_iqrange AS (
        SELECT max(quartile_max) - min(quartile_max) AS __cdb_iqr
        FROM (
            SELECT quartile, max(_cdb_iqr_column) AS quartile_max from (
                SELECT ${ctx._column} AS _cdb_iqr_column, ntile(4) over (order by ${ctx._column}
            ) AS quartile
            FROM __cdb_filtered_source) _cdb_quartiles
            WHERE quartile = 1 or quartile = 3
            GROUP BY quartile
        ) __cdb_iqr
    )
`;

const binsQueryTpl = ctx => `
    __cdb_bins AS (
        SELECT
            CASE WHEN __cdb_total_rows = 0 OR __cdb_iqr = 0
            THEN 1
            ELSE GREATEST(
                LEAST(${ctx._minBins}, CAST(__cdb_total_rows AS INT)),
                LEAST(
                    CAST(((__cdb_max_val - __cdb_min_val) / (2 * __cdb_iqr * power(__cdb_total_rows, 1/3))) AS INT),
                    ${ctx._maxBins}
                )
            )
        END AS __cdb_bins_number
        FROM __cdb_basics, __cdb_iqrange, __cdb_filtered_source
        LIMIT 1
    )
`;

const overrideBinsQueryTpl = ctx => `
    __cdb_bins AS (
        SELECT ${ctx._override.bins} AS __cdb_bins_number
    )
`;

const nullsQueryTpl = ctx => `
    __cdb_nulls AS (
        SELECT
            count(*) AS __cdb_nulls_count
        FROM (${ctx._query}) __cdb_histogram_nulls
        WHERE ${ctx._column} IS NULL
    )
`;

const infinitiesQueryTpl = ctx => `
    __cdb_infinities AS (
        SELECT
            count(*) AS __cdb_infinities_count
        FROM (${ctx._query}) __cdb_infinities_query
        WHERE
            ${ctx._column} = 'infinity'::float
        OR
            ${ctx._column} = '-infinity'::float
    )
`;

const nansQueryTpl = ctx => `
    __cdb_nans AS (
        SELECT
            count(*) AS __cdb_nans_count
        FROM (${ctx._query}) __cdb_nans_query
        WHERE ${ctx._column} = 'NaN'::float
    )
`;

const specialNumericValuesColumnDefinitionTpl = () => `
    __cdb_infinities_count AS infinities_count,
    __cdb_nans_count AS nans_count
`;

const specialNumericValuesCTETpl = () => `
    __cdb_infinities, __cdb_nans
`;

const specialNumericValuesColumnTpl = () => `
    infinities_count, nans_count
`;

const histogramQueryTpl = ctx => `
    SELECT
        (__cdb_max_val - __cdb_min_val) / cast(__cdb_bins_number as float) AS bin_width,
        __cdb_bins_number AS bins_number,
        __cdb_nulls_count AS nulls_count,
        ${ctx._isFloatColumn ? `${specialNumericValuesColumnDefinitionTpl()},` : ''}
        __cdb_avg_val AS avg_val,
        CASE WHEN __cdb_min_val = __cdb_max_val
            THEN 0
            ELSE GREATEST(
                1,
                LEAST(
                    WIDTH_BUCKET(${ctx._column}, __cdb_min_val, __cdb_max_val, __cdb_bins_number),
                    __cdb_bins_number
                )
            ) - 1
        END AS bin,
        min(${ctx._column})::numeric AS min,
        max(${ctx._column})::numeric AS max,
        avg(${ctx._column})::numeric AS avg,
        count(*) AS freq
    FROM __cdb_filtered_source, __cdb_basics, __cdb_nulls, __cdb_bins
        ${ctx._isFloatColumn ? `, ${specialNumericValuesCTETpl()}` : ''}
    GROUP BY bin, bins_number, bin_width, nulls_count, avg_val
        ${ctx._isFloatColumn ? `, ${specialNumericValuesColumnTpl()}` : ''}
    ORDER BY bin
`;

const BIN_MIN_NUMBER = 6;
const BIN_MAX_NUMBER = 48;

const TYPE = 'histogram';

/**
Numeric histogram:
{
    type: 'histogram',
    options: {
        column: 'name', // column data type: numeric
        bins: 10 // OPTIONAL
    }
}
*/
module.exports = class NumericHistogram extends BaseDataview {
    constructor (query, options, queries) {
        super();

        if (typeof options.column !== 'string') {
            throw new Error('Histogram expects `column` in widget options');
        }

        this.query = query;
        this.queries = queries;
        this.column = options.column;
        this.bins = options.bins;
        this.aggregation = options.aggregation;
        this.offset = options.offset;

        this._columnType = null;
    }

    sql (psql, override, callback) {
        var self = this;

        if (!callback) {
            callback = override;
            override = {};
        }

        if (this._columnType === null) {
            this.getColumnType(psql, this.column, this.queries.no_filters, function (err, type) {
                // assume numeric, will fail later
                self._columnType = 'numeric';
                if (!err && !!type) {
                    self._columnType = Object.keys(type).find(function (key) {
                        return type[key];
                    });
                }
                self.sql(psql, override, callback);
            }, true); // use read-only transaction
            return null;
        }

        this._buildQuery(psql, override, callback);
    }

    _buildQuery (psql, override, callback) {
        const histogramSql = this._buildQueryTpl({
            _override: override,
            _column: this._columnType === 'date' ? columnCastTpl({ column: this.column }) : this.column,
            _isFloatColumn: this._columnType === 'float',
            _query: this.query,
            _start: this.getBinStart(override),
            _end: this.getBinEnd(override),
            _minBins: BIN_MIN_NUMBER,
            _maxBins: BIN_MAX_NUMBER,
        });

        debug(histogramSql);

        return callback(null, histogramSql);
    }

    _buildQueryTpl (ctx) {
        return `
            WITH
                ${filteredQueryTpl(ctx)},
                ${this._shouldOverride(ctx._override) ? overrideBasicsQueryTpl(ctx) : basicsQueryTpl(ctx)},
                ${this._shouldOverrideRange(ctx._override) || this._shouldOverrideBins(ctx._override) ?
                    overrideBinsQueryTpl(ctx) :
                    `${iqrQueryTpl(ctx)}, ${binsQueryTpl(ctx)}`
                },
                ${nullsQueryTpl(ctx)}
                ${ctx._isFloatColumn ? `,${infinitiesQueryTpl(ctx)}, ${nansQueryTpl(ctx)}` : ''}
            ${histogramQueryTpl(ctx)}
        `;
    }

    _shouldOverride (override) {
        return this._shouldOverrideRange(override) && this._shouldOverrideBins(override);
    }

    _shouldOverrideRange (override) {
        return override && override.hasOwnProperty('start') && override.hasOwnProperty('end');
    }

    _shouldOverrideBins (override) {
        return override && override.hasOwnProperty('bins');
    }

    format (result, override) {
        override = override || {};
        var buckets = [];

        var binsCount = this.getBinsCount(override);
        var width = this.getWidth(override);
        var binsStart = this.getBinStart(override);
        var nulls = 0;
        var infinities = 0;
        var nans = 0;
        var avg;
        var timestampStart;
        var aggregation;
        var offset;

        if (result.rows.length) {
            var firstRow = result.rows[0];
            binsCount = firstRow.bins_number;
            width = firstRow.bin_width || width;
            avg = firstRow.avg_val;
            nulls = firstRow.nulls_count;
            timestampStart = firstRow.timestamp_start;
            infinities = firstRow.infinities_count;
            nans = firstRow.nans_count;
            binsStart = this.populateBinStart(override, firstRow);

            if (Number.isFinite(timestampStart)) {
                aggregation = this.getAggregation(override);
                offset = this.getOffset(override);
            }

            buckets = result.rows.map(row => ({ bin, min, max, avg, freq } = row));
        }

        return {
            aggregation: aggregation,
            offset: offset,
            timestamp_start: timestampStart,
            bin_width: width,
            bins_count: binsCount,
            bins_start: binsStart,
            nulls: nulls,
            infinities: infinities,
            nans: nans,
            avg: avg,
            bins: buckets
        };
    }

    getType () {
        return TYPE;
    }

    toString () {
        return JSON.stringify({
            _type: TYPE,
            _column: this.column,
            _query: this.query
        });
    }

    getAggregation (override) {
        return override && override.aggregation ? override.aggregation : this.aggregation;
    }

    getOffset (override) {
        if (override && override.offset) {
            return override.offset;
        }
        if (this.offset) {
            return this.offset;
        }

        return 0;
    }

    getBinStart (override) {
        if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) {
            return Math.min(override.start, override.end);
        }
        return override.start || 0;
    }

    populateBinStart (override, firstRow) {
        var binStart;

        if (override.hasOwnProperty('start')) {
            binStart = this.getBinStart(override);
        } else {
            binStart = firstRow.min;
        }

        return binStart;
    }

    getBinEnd (override) {
        if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) {
            return Math.max(override.start, override.end);
        }
        return override.end || 0;
    }

    getBinsCount (override) {
        return override.bins || 0;
    }

    getWidth (override) {
        var width = 0;
        var binsCount = override.bins;

        if (binsCount && Number.isFinite(override.start) && Number.isFinite(override.end)) {
            width = (override.end - override.start) / binsCount;
        }

        return width;
    }
};
<commit_msg>remove unnecessary properties in format function<commit_after>const BaseDataview = require('./base');
const debug = require('debug')('windshaft:dataview:numeric-histogram');

const columnCastTpl = ctx => `date_part('epoch', ${ctx.column})`;

const filterOutSpecialNumericValues = ctx => `
        ${ctx._column} != 'infinity'::float
    AND
        ${ctx._column} != '-infinity'::float
    AND
        ${ctx._column} != 'NaN'::float
`;

const filteredQueryTpl = ctx => `
    __cdb_filtered_source AS (
        SELECT *
        FROM (${ctx._query}) __cdb_filtered_source_query
        WHERE ${ctx._column} IS NOT NULL
        ${ctx._isFloatColumn ? `AND ${filterOutSpecialNumericValues(ctx)}` : ''}
    )
`;

const basicsQueryTpl = ctx => `
    __cdb_basics AS (
        SELECT
            max(${ctx._column}) AS __cdb_max_val, min(${ctx._column}) AS __cdb_min_val,
            avg(${ctx._column}) AS __cdb_avg_val, count(1) AS __cdb_total_rows
        FROM __cdb_filtered_source
    )
`;

const overrideBasicsQueryTpl = ctx => `
    __cdb_basics AS (
        SELECT
            max(${ctx._end}) AS __cdb_max_val, min(${ctx._start}) AS __cdb_min_val,
            avg(${ctx._column}) AS __cdb_avg_val, count(1) AS __cdb_total_rows
        FROM __cdb_filtered_source
    )
`;

const iqrQueryTpl = ctx => `
    __cdb_iqrange AS (
        SELECT max(quartile_max) - min(quartile_max) AS __cdb_iqr
        FROM (
            SELECT quartile, max(_cdb_iqr_column) AS quartile_max from (
                SELECT ${ctx._column} AS _cdb_iqr_column, ntile(4) over (order by ${ctx._column}
            ) AS quartile
            FROM __cdb_filtered_source) _cdb_quartiles
            WHERE quartile = 1 or quartile = 3
            GROUP BY quartile
        ) __cdb_iqr
    )
`;

const binsQueryTpl = ctx => `
    __cdb_bins AS (
        SELECT
            CASE WHEN __cdb_total_rows = 0 OR __cdb_iqr = 0
            THEN 1
            ELSE GREATEST(
                LEAST(${ctx._minBins}, CAST(__cdb_total_rows AS INT)),
                LEAST(
                    CAST(((__cdb_max_val - __cdb_min_val) / (2 * __cdb_iqr * power(__cdb_total_rows, 1/3))) AS INT),
                    ${ctx._maxBins}
                )
            )
        END AS __cdb_bins_number
        FROM __cdb_basics, __cdb_iqrange, __cdb_filtered_source
        LIMIT 1
    )
`;

const overrideBinsQueryTpl = ctx => `
    __cdb_bins AS (
        SELECT ${ctx._override.bins} AS __cdb_bins_number
    )
`;

const nullsQueryTpl = ctx => `
    __cdb_nulls AS (
        SELECT
            count(*) AS __cdb_nulls_count
        FROM (${ctx._query}) __cdb_histogram_nulls
        WHERE ${ctx._column} IS NULL
    )
`;

const infinitiesQueryTpl = ctx => `
    __cdb_infinities AS (
        SELECT
            count(*) AS __cdb_infinities_count
        FROM (${ctx._query}) __cdb_infinities_query
        WHERE
            ${ctx._column} = 'infinity'::float
        OR
            ${ctx._column} = '-infinity'::float
    )
`;

const nansQueryTpl = ctx => `
    __cdb_nans AS (
        SELECT
            count(*) AS __cdb_nans_count
        FROM (${ctx._query}) __cdb_nans_query
        WHERE ${ctx._column} = 'NaN'::float
    )
`;

const specialNumericValuesColumnDefinitionTpl = () => `
    __cdb_infinities_count AS infinities_count,
    __cdb_nans_count AS nans_count
`;

const specialNumericValuesCTETpl = () => `
    __cdb_infinities, __cdb_nans
`;

const specialNumericValuesColumnTpl = () => `
    infinities_count, nans_count
`;

const histogramQueryTpl = ctx => `
    SELECT
        (__cdb_max_val - __cdb_min_val) / cast(__cdb_bins_number as float) AS bin_width,
        __cdb_bins_number AS bins_number,
        __cdb_nulls_count AS nulls_count,
        ${ctx._isFloatColumn ? `${specialNumericValuesColumnDefinitionTpl()},` : ''}
        __cdb_avg_val AS avg_val,
        CASE WHEN __cdb_min_val = __cdb_max_val
            THEN 0
            ELSE GREATEST(
                1,
                LEAST(
                    WIDTH_BUCKET(${ctx._column}, __cdb_min_val, __cdb_max_val, __cdb_bins_number),
                    __cdb_bins_number
                )
            ) - 1
        END AS bin,
        min(${ctx._column})::numeric AS min,
        max(${ctx._column})::numeric AS max,
        avg(${ctx._column})::numeric AS avg,
        count(*) AS freq
    FROM __cdb_filtered_source, __cdb_basics, __cdb_nulls, __cdb_bins
        ${ctx._isFloatColumn ? `, ${specialNumericValuesCTETpl()}` : ''}
    GROUP BY bin, bins_number, bin_width, nulls_count, avg_val
        ${ctx._isFloatColumn ? `, ${specialNumericValuesColumnTpl()}` : ''}
    ORDER BY bin
`;

const BIN_MIN_NUMBER = 6;
const BIN_MAX_NUMBER = 48;

const TYPE = 'histogram';

/**
Numeric histogram:
{
    type: 'histogram',
    options: {
        column: 'name', // column data type: numeric
        bins: 10 // OPTIONAL
    }
}
*/
module.exports = class NumericHistogram extends BaseDataview {
    constructor (query, options, queries) {
        super();

        if (typeof options.column !== 'string') {
            throw new Error('Histogram expects `column` in widget options');
        }

        this.query = query;
        this.queries = queries;
        this.column = options.column;
        this.bins = options.bins;
        this.aggregation = options.aggregation;
        this.offset = options.offset;

        this._columnType = null;
    }

    sql (psql, override, callback) {
        var self = this;

        if (!callback) {
            callback = override;
            override = {};
        }

        if (this._columnType === null) {
            this.getColumnType(psql, this.column, this.queries.no_filters, function (err, type) {
                // assume numeric, will fail later
                self._columnType = 'numeric';
                if (!err && !!type) {
                    self._columnType = Object.keys(type).find(function (key) {
                        return type[key];
                    });
                }
                self.sql(psql, override, callback);
            }, true); // use read-only transaction
            return null;
        }

        this._buildQuery(psql, override, callback);
    }

    _buildQuery (psql, override, callback) {
        const histogramSql = this._buildQueryTpl({
            _override: override,
            _column: this._columnType === 'date' ? columnCastTpl({ column: this.column }) : this.column,
            _isFloatColumn: this._columnType === 'float',
            _query: this.query,
            _start: this.getBinStart(override),
            _end: this.getBinEnd(override),
            _minBins: BIN_MIN_NUMBER,
            _maxBins: BIN_MAX_NUMBER,
        });

        debug(histogramSql);

        return callback(null, histogramSql);
    }

    _buildQueryTpl (ctx) {
        return `
            WITH
                ${filteredQueryTpl(ctx)},
                ${this._shouldOverride(ctx._override) ? overrideBasicsQueryTpl(ctx) : basicsQueryTpl(ctx)},
                ${this._shouldOverrideRange(ctx._override) || this._shouldOverrideBins(ctx._override) ?
                    overrideBinsQueryTpl(ctx) :
                    `${iqrQueryTpl(ctx)}, ${binsQueryTpl(ctx)}`
                },
                ${nullsQueryTpl(ctx)}
                ${ctx._isFloatColumn ? `,${infinitiesQueryTpl(ctx)}, ${nansQueryTpl(ctx)}` : ''}
            ${histogramQueryTpl(ctx)}
        `;
    }

    _shouldOverride (override) {
        return this._shouldOverrideRange(override) && this._shouldOverrideBins(override);
    }

    _shouldOverrideRange (override) {
        return override && override.hasOwnProperty('start') && override.hasOwnProperty('end');
    }

    _shouldOverrideBins (override) {
        return override && override.hasOwnProperty('bins');
    }

    format (result, override) {
        override = override || {};
        var buckets = [];

        var binsCount = this.getBinsCount(override);
        var width = this.getWidth(override);
        var binsStart = this.getBinStart(override);
        var nulls = 0;
        var infinities = 0;
        var nans = 0;
        var avg;

        if (result.rows.length) {
            var firstRow = result.rows[0];
            binsCount = firstRow.bins_number;
            width = firstRow.bin_width || width;
            avg = firstRow.avg_val;
            nulls = firstRow.nulls_count;
            infinities = firstRow.infinities_count;
            nans = firstRow.nans_count;
            binsStart = this.populateBinStart(override, firstRow);

            buckets = result.rows.map(row => ({ bin, min, max, avg, freq } = row));
        }

        return {
            bin_width: width,
            bins_count: binsCount,
            bins_start: binsStart,
            nulls: nulls,
            infinities: infinities,
            nans: nans,
            avg: avg,
            bins: buckets
        };
    }

    getType () {
        return TYPE;
    }

    toString () {
        return JSON.stringify({
            _type: TYPE,
            _column: this.column,
            _query: this.query
        });
    }

    getAggregation (override) {
        return override && override.aggregation ? override.aggregation : this.aggregation;
    }

    getOffset (override) {
        if (override && override.offset) {
            return override.offset;
        }
        if (this.offset) {
            return this.offset;
        }

        return 0;
    }

    getBinStart (override) {
        if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) {
            return Math.min(override.start, override.end);
        }
        return override.start || 0;
    }

    populateBinStart (override, firstRow) {
        var binStart;

        if (override.hasOwnProperty('start')) {
            binStart = this.getBinStart(override);
        } else {
            binStart = firstRow.min;
        }

        return binStart;
    }

    getBinEnd (override) {
        if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) {
            return Math.max(override.start, override.end);
        }
        return override.end || 0;
    }

    getBinsCount (override) {
        return override.bins || 0;
    }

    getWidth (override) {
        var width = 0;
        var binsCount = override.bins;

        if (binsCount && Number.isFinite(override.start) && Number.isFinite(override.end)) {
            width = (override.end - override.start) / binsCount;
        }

        return width;
    }
};
<|endoftext|>


<commit_before>import bluebird from 'bluebird'
import {Common} from './common'

export class Acl extends Common {

  logger: {};
  backend: {};
  options: {};

  /**
   * @description Create ACL class and promisify backend methods
   * @param backend
   * @param logger
   * @param options
   */
  constructor(backend: {}, logger: ?{}, options: ?{}) {
    this.options = _.extend({
      buckets: {
        meta: 'meta',
        parents: 'parents',
        permissions: 'permissions',
        resources: 'resources',
        roles: 'roles',
        users: 'users'
      }
    }, options);

    this.logger = logger;
    this.backend = backend;

    backend.endAsync = bluebird.promisify(backend.end);
    backend.getAsync = bluebird.promisify(backend.get);
    backend.cleanAsync = bluebird.promisify(backend.clean);
    backend.unionAsync = bluebird.promisify(backend.union);
    if (backend.unions) backend.unionsAsync = bluebird.promisify(backend.unions);
  }

  /**
   * @description Adds roles to a given user id
   * @param userId
   * @param roles
   * @param callback
   */
  addUserRoles(userId: string | number, roles: mixed, callback: () => void) {
    const transaction = this.backend.begin();
    this.backend.add(transaction, this.options.buckets.meta, 'users', userId);
    this.backend.add(transaction, this.options.buckets.users, userId, roles);

    if (Array.isArray(roles)) {
      roles.forEach(role => this.backend.add(transaction, this.options.buckets.roles, role, userId));
    } else {
      this.backend.add(transaction, this.options.buckets.roles, roles, userId);
    }
    return this.backend.endAsync(transaction).nodeify(callback);
  }

  /**
   * @description Remove roles from a given user
   * @param userId
   * @param roles
   * @param callback
   */
  removeUserRoles(userId: string | number, roles: mixed, callback: () => void) {
    const transaction = this.backend.begin();
    this.backend.remove(transaction, this.options.buckets.users, userId, roles);

    if (Array.isArray(roles)) {
      roles.forEach(role => this.backend.remove(transaction, this.options.buckets.roles, role, userId));
    } else {
      this.backend.remove(transaction, this.options.buckets.roles, roles, userId);
    }
    return this.backend.endAsync(transaction).nodeify(callback);
  };

  /**
   * @description Return all the roles from a given user
   * @param userId
   * @param callback
   */
  userRoles(userId: string | number, callback: () => void) {
    return this.backend.getAsync(this.options.buckets.users, userId).nodeify(callback);
  };

  /**
   * @description Return all users who has a given role
   * @param roleName
   * @param callback
   */
  roleUsers(roleName: string | number, callback: () => void) {
    return this.backend.getAsync(this.options.buckets.roles, roleName).nodeify(callback);
  };

  /**
   * @description Return boolean whether user is in the role
   * @param userId
   * @param rolename
   * @param callback
   */
  hasRole(userId: string | number, rolename: string | number, callback: () => void) {
    return this.userRoles(userId).then(roles => roles.indexOf(rolename) !== -1).nodeify(callback);
  };

  /**
   * @description Adds a parent or parent list to role
   * @param role
   * @param parents
   * @param callback
   */
  addRoleParents(role: string | number, parents: mixed, callback: () => void) {
    const transaction = this.backend.begin();
    this.backend.add(transaction, this.options.buckets.meta, 'roles', role);
    this.backend.add(transaction, this.options.buckets.parents, role, parents);
    return this.backend.endAsync(transaction).nodeify(callback);
  };

  /**
   * @description Removes a parent or parent list from role. If `parents` is not specified, removes all parents
   * @param role
   * @param parents
   * @param callback
   */
  removeRoleParents(role: string | number, parents: mixed, callback: () => void) {
    if (!callback && _.isFunction(parents)) {
      callback = parents;
      parents = null;
    }

    const transaction = this.backend.begin();
    if (parents) this.backend.remove(transaction, this.options.buckets.parents, role, parents);
    else this.backend.del(transaction, this.options.buckets.parents, role);
    return this.backend.endAsync(transaction).nodeify(callback);
  };

  /**
   * @description Removes a role from the system
   * @param role
   * @param callback
   */
  removeRole(role: string | number, callback: () => void) {
    return this.backend.getAsync(this.options.buckets.resources, role)
      .then(resources => {
        const transaction = _this.backend.begin();
        resources.forEach(resource => {
          let bucket = allowsBucket(resource);
          this.backend.del(transaction, bucket, role);
        });

        this.backend.del(transaction, this.options.buckets.resources, role);
        this.backend.del(transaction, this.options.buckets.parents, role);
        this.backend.del(transaction, this.options.buckets.roles, role);
        this.backend.remove(transaction, this.options.buckets.meta, 'roles', role);
        return this.backend.endAsync(transaction);
      }).nodeify(callback);
  };

  /**
   * @description Removes a resource from the system
   * @param resource
   * @param callback
   */
  removeResource(resource: string, callback: () => void) {
    return this.backend.getAsync(this.options.buckets.meta, 'roles')
      .then(roles => {
        const transaction = this.backend.begin();
        this.backend.del(transaction, allowsBucket(resource), roles);
        roles.forEach(role => this.backend.remove(transaction, this.options.buckets.resources, role, resource));
        return this.backend.endAsync(transaction);
      }).nodeify(callback)
  };

  /**
   * @description Remove all permissions
   * @param role
   * @param resources
   * @param permissions
   * @param callback
   * @return {*}
   */
  removeAllow(role: string, resources: mixed, permissions: mixed, callback: () => void) {
    resources = this.makeArray(resources);
    if (callback || (permissions && !_.isFunction(permissions))) {
      permissions = this.makeArray(permissions);
    } else {
      callback = permissions;
      permissions = null;
    }
    return this.removePermissions(role, resources, permissions, callback);
  }

  /**
   * @description Remove permissions from the given roles owned by the given role.
   * @param role
   * @param resources
   * @param permissions
   * @param callback
   */
  removePermissions(role, resources, permissions, callback: () => void) {
    const transaction = this.backend.begin();
    resources.forEach(resource => {
      let bucket = this.allowsBucket(resource);
      if (permissions) {
        this.backend.remove(transaction, bucket, role, permissions);
      } else {
        this.backend.del(transaction, bucket, role);
        this.backend.remove(transaction, this.options.buckets.resources, role, resource);
      }
    });

    return this.backend.endAsync(transaction)
      .then(() => {
        const transaction = this.backend.begin();
        return bluebird.all(resources.map(resource => {
          let bucket = this.allowsBucket(resource);
          return this.backend.getAsync(bucket, role)
            .then(permissions => {
              if (!permissions.length) this.backend.remove(transaction, this.options.buckets.resources, role, resource);
            });
        }))
          .then(this.backend.endAsync(transaction));
      }).nodeify(callback);
  };

  /**
   * @description Adds the given permissions to the given roles over the given resources
   * @param roles
   * @param resources
   * @param permissions
   * @param callback
   */
  allow(roles: mixed, resources: mixed, permissions: mixed, callback: () => void) {
    if ((arguments.length === 1) || ((arguments.length === 2) && _.isObject(roles) && _.isFunction(resources))) {
      return this._allowEx(roles).nodeify(resources);
    } else {
      roles = this.makeArray(roles);
      resources = this.makeArray(resources);

      const transaction = this.backend.begin();
      this.backend.add(transaction, this.options.buckets.meta, 'roles', roles);

      resources.forEach(resource => {
        roles.forEach(role => {
          this.backend.add(transaction, this.allowsBucket(resource), role, permissions);
        });
      });

      roles.forEach(function (role) {
        this.backend.add(transaction, this.options.buckets.resources, role, resources);
      });

      return this.backend.endAsync(transaction).nodeify(callback);
    }
  };

  /**
   * @description Same as allow but accepts a more compact input
   * @param objs
   * @return {*}
   * @private
   */
  _allowEx(objs) {
    objs = this.makeArray(objs);

    let demuxed = [];
    objs.forEach(obj => {
      obj.allows.forEach(allow => {
        demuxed.push({
          roles: obj.roles,
          resources: allow.resources,
          permissions: allow.permissions
        });
      });
    });
    return bluebird.reduce(demuxed, (values, obj) => this.allow(obj.roles, obj.resources, obj.permissions), null);
  };

}
<commit_msg>feat(classes/acl.js) allowedPermissions method created<commit_after>import bluebird from 'bluebird'
import {Common} from './common'

export class Acl extends Common {

  logger: {};
  backend: {};
  options: {};

  /**
   * @description Create ACL class and promisify backend methods
   * @param backend
   * @param logger
   * @param options
   */
  constructor(backend: {}, logger: ?{}, options: ?{}) {
    this.options = _.extend({
      buckets: {
        meta: 'meta',
        parents: 'parents',
        permissions: 'permissions',
        resources: 'resources',
        roles: 'roles',
        users: 'users'
      }
    }, options);

    this.logger = logger;
    this.backend = backend;

    backend.endAsync = bluebird.promisify(backend.end);
    backend.getAsync = bluebird.promisify(backend.get);
    backend.cleanAsync = bluebird.promisify(backend.clean);
    backend.unionAsync = bluebird.promisify(backend.union);
    if (backend.unions) backend.unionsAsync = bluebird.promisify(backend.unions);
  }

  /**
   * @description Adds roles to a given user id
   * @param userId
   * @param roles
   * @param callback
   */
  addUserRoles(userId: string | number, roles: mixed, callback: () => void) {
    const transaction = this.backend.begin();
    this.backend.add(transaction, this.options.buckets.meta, 'users', userId);
    this.backend.add(transaction, this.options.buckets.users, userId, roles);

    if (Array.isArray(roles)) {
      roles.forEach(role => this.backend.add(transaction, this.options.buckets.roles, role, userId));
    } else {
      this.backend.add(transaction, this.options.buckets.roles, roles, userId);
    }
    return this.backend.endAsync(transaction).nodeify(callback);
  }

  /**
   * @description Remove roles from a given user
   * @param userId
   * @param roles
   * @param callback
   */
  removeUserRoles(userId: string | number, roles: mixed, callback: () => void) {
    const transaction = this.backend.begin();
    this.backend.remove(transaction, this.options.buckets.users, userId, roles);

    if (Array.isArray(roles)) {
      roles.forEach(role => this.backend.remove(transaction, this.options.buckets.roles, role, userId));
    } else {
      this.backend.remove(transaction, this.options.buckets.roles, roles, userId);
    }
    return this.backend.endAsync(transaction).nodeify(callback);
  };

  /**
   * @description Return all the roles from a given user
   * @param userId
   * @param callback
   */
  userRoles(userId: string | number, callback: () => void) {
    return this.backend.getAsync(this.options.buckets.users, userId).nodeify(callback);
  };

  /**
   * @description Return all users who has a given role
   * @param roleName
   * @param callback
   */
  roleUsers(roleName: string | number, callback: () => void) {
    return this.backend.getAsync(this.options.buckets.roles, roleName).nodeify(callback);
  };

  /**
   * @description Return boolean whether user is in the role
   * @param userId
   * @param rolename
   * @param callback
   */
  hasRole(userId: string | number, rolename: string | number, callback: () => void) {
    return this.userRoles(userId).then(roles => roles.indexOf(rolename) !== -1).nodeify(callback);
  };

  /**
   * @description Adds a parent or parent list to role
   * @param role
   * @param parents
   * @param callback
   */
  addRoleParents(role: string | number, parents: mixed, callback: () => void) {
    const transaction = this.backend.begin();
    this.backend.add(transaction, this.options.buckets.meta, 'roles', role);
    this.backend.add(transaction, this.options.buckets.parents, role, parents);
    return this.backend.endAsync(transaction).nodeify(callback);
  };

  /**
   * @description Removes a parent or parent list from role. If `parents` is not specified, removes all parents
   * @param role
   * @param parents
   * @param callback
   */
  removeRoleParents(role: string | number, parents: mixed, callback: () => void) {
    if (!callback && _.isFunction(parents)) {
      callback = parents;
      parents = null;
    }

    const transaction = this.backend.begin();
    if (parents) this.backend.remove(transaction, this.options.buckets.parents, role, parents);
    else this.backend.del(transaction, this.options.buckets.parents, role);
    return this.backend.endAsync(transaction).nodeify(callback);
  };

  /**
   * @description Removes a role from the system
   * @param role
   * @param callback
   */
  removeRole(role: string | number, callback: () => void) {
    return this.backend.getAsync(this.options.buckets.resources, role)
      .then(resources => {
        const transaction = _this.backend.begin();
        resources.forEach(resource => {
          let bucket = allowsBucket(resource);
          this.backend.del(transaction, bucket, role);
        });

        this.backend.del(transaction, this.options.buckets.resources, role);
        this.backend.del(transaction, this.options.buckets.parents, role);
        this.backend.del(transaction, this.options.buckets.roles, role);
        this.backend.remove(transaction, this.options.buckets.meta, 'roles', role);
        return this.backend.endAsync(transaction);
      }).nodeify(callback);
  };

  /**
   * @description Removes a resource from the system
   * @param resource
   * @param callback
   */
  removeResource(resource: string, callback: () => void) {
    return this.backend.getAsync(this.options.buckets.meta, 'roles')
      .then(roles => {
        const transaction = this.backend.begin();
        this.backend.del(transaction, allowsBucket(resource), roles);
        roles.forEach(role => this.backend.remove(transaction, this.options.buckets.resources, role, resource));
        return this.backend.endAsync(transaction);
      }).nodeify(callback)
  };

  /**
   * @description Remove all permissions
   * @param role
   * @param resources
   * @param permissions
   * @param callback
   * @return {*}
   */
  removeAllow(role: string, resources: mixed, permissions: mixed, callback: () => void) {
    resources = this.makeArray(resources);
    if (callback || (permissions && !_.isFunction(permissions))) {
      permissions = this.makeArray(permissions);
    } else {
      callback = permissions;
      permissions = null;
    }
    return this.removePermissions(role, resources, permissions, callback);
  }

  /**
   * @description Remove permissions from the given roles owned by the given role.
   * @param role
   * @param resources
   * @param permissions
   * @param callback
   */
  removePermissions(role, resources, permissions, callback: () => void) {
    const transaction = this.backend.begin();
    resources.forEach(resource => {
      let bucket = this.allowsBucket(resource);
      if (permissions) {
        this.backend.remove(transaction, bucket, role, permissions);
      } else {
        this.backend.del(transaction, bucket, role);
        this.backend.remove(transaction, this.options.buckets.resources, role, resource);
      }
    });

    return this.backend.endAsync(transaction)
      .then(() => {
        const transaction = this.backend.begin();
        return bluebird.all(resources.map(resource => {
          let bucket = this.allowsBucket(resource);
          return this.backend.getAsync(bucket, role)
            .then(permissions => {
              if (!permissions.length) this.backend.remove(transaction, this.options.buckets.resources, role, resource);
            });
        }))
          .then(this.backend.endAsync(transaction));
      }).nodeify(callback);
  };

  /**
   * @description Adds the given permissions to the given roles over the given resources
   * @param roles
   * @param resources
   * @param permissions
   * @param callback
   */
  allow(roles: mixed, resources: mixed, permissions: mixed, callback: () => void) {
    if ((arguments.length === 1) || ((arguments.length === 2) && _.isObject(roles) && _.isFunction(resources))) {
      return this._allowEx(roles).nodeify(resources);
    } else {
      roles = this.makeArray(roles);
      resources = this.makeArray(resources);

      const transaction = this.backend.begin();
      this.backend.add(transaction, this.options.buckets.meta, 'roles', roles);

      resources.forEach(resource => {
        roles.forEach(role => {
          this.backend.add(transaction, this.allowsBucket(resource), role, permissions);
        });
      });

      roles.forEach(function (role) {
        this.backend.add(transaction, this.options.buckets.resources, role, resources);
      });

      return this.backend.endAsync(transaction).nodeify(callback);
    }
  };

  /**
   * @description Returns all the allowable permissions a given user have to access the given resources.
   * It returns an array of objects where every object maps a resource name to a list of permissions for that resource.
   * @param userId
   * @param resources
   * @param callback
   * @return {*}
   */
  allowedPermissions(userId: string | number, resources: mixed, callback: () => void) {
    if (!userId) return callback(null, {});
    if (this.backend.unionsAsync) return this.optimizedAllowedPermissions(userId, resources, callback);

    resources = this.makeArray(resources);
    return this.userRoles(userId)
      .then(roles => {
        let result = {};
        return bluebird.all(resources.map(resource => {
          return this._resourcePermissions(roles, resource)
            .then(permissions => result[resource] = permissions);
        }))
          .then(() => {
            return result
          });
      }).nodeify(callback);
  };

  /**
   * @description Same as allow but accepts a more compact input
   * @param objs
   * @return {*}
   * @private
   */
  _allowEx(objs) {
    objs = this.makeArray(objs);

    let demuxed = [];
    objs.forEach(obj => {
      obj.allows.forEach(allow => {
        demuxed.push({
          roles: obj.roles,
          resources: allow.resources,
          permissions: allow.permissions
        });
      });
    });
    return bluebird.reduce(demuxed, (values, obj) => this.allow(obj.roles, obj.resources, obj.permissions), null);
  };

}
<|endoftext|>


<commit_before>'use strict';
// Operations on equation nodes

const math = require('../../../index');

const Equation = require('./Equation');
const EquationStatus = require('./EquationStatus');
const MathChangeTypes = require('./MathChangeTypes');
const Negative = require('./Negative.js');
const NodeCreator = require('./NodeCreator');
const NodeType = require('./NodeType');
const PolynomialTermNode = require('./PolynomialTermNode');
const Symbols = require('./Symbols.js');

const COMPARATOR_TO_INVERSE = {
  '>': '<',
  '>=': '<=',
  '<': '>',
  '<=': '>=',
  '=': '='
}

class EquationOperations {};

// Ensures that the given equation has the given symbolName on the left side,
// by swapping the right and left sides if it is only in the right side.
// So 3 = x would become x = 3.
EquationOperations.ensureSymbolInLeftNode = function(equation, symbolName) {
  const leftSideSymbolTerm = Symbols.getLastSymbolTerm(
    equation.leftNode, symbolName);
  const rightSideSymbolTerm = Symbols.getLastSymbolTerm(
    equation.rightNode, symbolName);

  if (!leftSideSymbolTerm) {
    if (rightSideSymbolTerm) {
      equation = new Equation(
        equation.rightNode, equation.leftNode, equation.comparator)
      return new EquationStatus(equation, true, MathChangeTypes.SWAP_SIDES);
    }
    else {
      throw Error('No term with symbol: ' + symbolName)
    }
  }
  return new EquationStatus(equation);
}

// TODO: Ensures that a symbol is not in the denominator by multiplying
// both sides by the whatever order of the symbol necessary.
// This is blocked on the simplifying functionality of canceling symbols in fractions
EquationOperations.removeSymbolFromDenominator = function(equation, symbolName) {
  // pass for now
  return new EquationStatus(equation);
}

// Removes the given symbolName from the right side by adding or subtracting
// it from both sides as appropriate.
// e.g. 2x = 3x + 5 --> 2x - 3x = 5
// There are actually no cases where we'd remove symbols from the right side
// by multiplying or dividing by a symbol term.
// TODO: support inverting functions e.g. sqrt, ^, log etc.
EquationOperations.removeSymbolFromRightSide = function(equation, symbolName) {
  const rightNode = equation.rightNode;
  const symbolTerm = Symbols.getLastSymbolTerm(rightNode, symbolName);

  let inverseOp, inverseTerm, changeType;
  if (!symbolTerm){
    return new EquationStatus(equation);
  }

  if (PolynomialTermNode.isPolynomialTerm(rightNode)) {
    if (Negative.isNegative(symbolTerm)) {
      inverseOp = '+';
      changeType = MathChangeTypes.ADD_TO_BOTH_SIDES;
      inverseTerm = Negative.negate(symbolTerm);
    }
    else {
      inverseOp = '-';
      changeType = MathChangeTypes.SUBTRACT_FROM_BOTH_SIDES;
      inverseTerm = symbolTerm;
    }
  }
  else if (NodeType.isOperator(rightNode)) {
    if (rightNode.op === '+') {
      if (Negative.isNegative(symbolTerm)) {
        inverseOp = '+';
        changeType = MathChangeTypes.ADD_TO_BOTH_SIDES;
        inverseTerm = Negative.negate(symbolTerm);
      }
      else {
        inverseOp = '-';
        changeType = MathChangeTypes.SUBTRACT_FROM_BOTH_SIDES;
        inverseTerm = symbolTerm;
      }
    }
    else {
      // Note that operator '-' won't show up here because subtraction is
      // flattened into adding the negative. See 'TRICKY catch' in the README
      // for more details.
      throw Error('Unsupported operation: ' + symbolTerm.op);
    }
  }
  else if (NodeType.isUnaryMinus(rightNode)) {
    inverseOp = '+';
    changeType = MathChangeTypes.ADD_TO_BOTH_SIDES;
    inverseTerm = symbolTerm.args[0];
  }
  else {
    throw Error('Unsupported node type: ' + rightNode.type);
  }
  return performTermOperationOnEquation(
      equation, inverseOp, inverseTerm, changeType)
}

// Isolates the given symbolName to the left side by adding, multiplying, subtracting
// or dividing all other symbols and constants from both sides appropriately
// TODO: support inverting functions e.g. sqrt, ^, log etc.
EquationOperations.isolateSymbolOnLeftSide = function(equation, symbolName) {
  const leftNode = equation.leftNode;
  let nonSymbolTerm = Symbols.getLastNonSymbolTerm(leftNode, symbolName);

  let inverseOp, inverseTerm, changeType;
  if (!nonSymbolTerm) {
    return new EquationStatus(equation);
  }

  if (NodeType.isOperator(leftNode)) {
    if (leftNode.op === '+') {
      if (Negative.isNegative(nonSymbolTerm)) {
        inverseOp = '+';
        changeType = MathChangeTypes.ADD_TO_BOTH_SIDES;
        inverseTerm = Negative.negate(nonSymbolTerm);
      }
      else {
        inverseOp = '-';
        changeType = MathChangeTypes.SUBTRACT_FROM_BOTH_SIDES;
        inverseTerm = nonSymbolTerm;
      }
    }
    else if (leftNode.op === '*') {
      if (NodeType.isConstantFraction(nonSymbolTerm)) {
        inverseOp = '*';
        changeType = MathChangeTypes.MULTIPLY_BOTH_SIDES_BY_INVERSE_FRACTION;
        inverseTerm = NodeCreator.operator(
          '/', [nonSymbolTerm.args[1], nonSymbolTerm.args[0]]);
      }
      else {
        inverseOp = '/';
        changeType = MathChangeTypes.DIVIDE_FROM_BOTH_SIDES;
        inverseTerm = nonSymbolTerm;
      }
    }
    else if (leftNode.op === '/') {
      if (NodeType.isConstantFraction(nonSymbolTerm) &&
          PolynomialTermNode.isPolynomialTerm(leftNode)) {
        inverseOp = '*';
        changeType = MathChangeTypes.MULTIPLY_BOTH_SIDES_BY_INVERSE_FRACTION;
        inverseTerm = NodeCreator.operator(
          '/', [nonSymbolTerm.args[1], nonSymbolTerm.args[0]]);
      }
      else {
        inverseOp = '*';
        changeType = MathChangeTypes.MULTIPLY_TO_BOTH_SIDES;
        inverseTerm = nonSymbolTerm;
      }
    }
    else if (leftNode.op === '^') {
      // TODO: support roots
      return new EquationStatus(equation);
    }
    else {
      throw Error('Unsupported operation: ' + leftNode.op);
    }
  }
  else if (NodeType.isUnaryMinus(leftNode)) {
    inverseOp = '*';
    changeType = MathChangeTypes.MULTIPLY_BOTH_SIDES_BY_NEGATIVE_ONE;
    inverseTerm =  NodeCreator.constant(-1);
  }
  else {
    throw Error('Unsupported node type: ' + leftNode.type);
  }

  return performTermOperationOnEquation(
      equation, inverseOp, inverseTerm, changeType);
}

// Modifies the left and right sides of an equation by `op`-ing `term`
// to both sides. Returns an EquationStatus object.
function performTermOperationOnEquation(equation, op, term, changeType) {
  let leftNode = performTermOperationOnExpression(equation.leftNode, op, term);
  let rightNode = performTermOperationOnExpression(equation.rightNode, op, term);

  let comparator = equation.comparator;
  if (Negative.isNegative(term) && (op === '*' || op === '/')) {
    comparator = COMPARATOR_TO_INVERSE[equation.comparator];
  }

  equation = new Equation(leftNode, rightNode, comparator);
  return new EquationStatus(equation, true, changeType);
}

// Performs an operation of a term on an entire given expression
function performTermOperationOnExpression(expression, op, term) {
  let node = (NodeType.isOperator(expression) ?
    NodeCreator.parenthesis(expression) : expression);

  return NodeCreator.operator(op, [node, term]);
}

module.exports = EquationOperations;
<commit_msg>just comparator (not equation.comparator)<commit_after>'use strict';
// Operations on equation nodes

const math = require('../../../index');

const Equation = require('./Equation');
const EquationStatus = require('./EquationStatus');
const MathChangeTypes = require('./MathChangeTypes');
const Negative = require('./Negative.js');
const NodeCreator = require('./NodeCreator');
const NodeType = require('./NodeType');
const PolynomialTermNode = require('./PolynomialTermNode');
const Symbols = require('./Symbols.js');

const COMPARATOR_TO_INVERSE = {
  '>': '<',
  '>=': '<=',
  '<': '>',
  '<=': '>=',
  '=': '='
}

class EquationOperations {};

// Ensures that the given equation has the given symbolName on the left side,
// by swapping the right and left sides if it is only in the right side.
// So 3 = x would become x = 3.
EquationOperations.ensureSymbolInLeftNode = function(equation, symbolName) {
  const leftSideSymbolTerm = Symbols.getLastSymbolTerm(
    equation.leftNode, symbolName);
  const rightSideSymbolTerm = Symbols.getLastSymbolTerm(
    equation.rightNode, symbolName);

  if (!leftSideSymbolTerm) {
    if (rightSideSymbolTerm) {
      equation = new Equation(
        equation.rightNode, equation.leftNode, equation.comparator)
      return new EquationStatus(equation, true, MathChangeTypes.SWAP_SIDES);
    }
    else {
      throw Error('No term with symbol: ' + symbolName)
    }
  }
  return new EquationStatus(equation);
}

// TODO: Ensures that a symbol is not in the denominator by multiplying
// both sides by the whatever order of the symbol necessary.
// This is blocked on the simplifying functionality of canceling symbols in fractions
EquationOperations.removeSymbolFromDenominator = function(equation, symbolName) {
  // pass for now
  return new EquationStatus(equation);
}

// Removes the given symbolName from the right side by adding or subtracting
// it from both sides as appropriate.
// e.g. 2x = 3x + 5 --> 2x - 3x = 5
// There are actually no cases where we'd remove symbols from the right side
// by multiplying or dividing by a symbol term.
// TODO: support inverting functions e.g. sqrt, ^, log etc.
EquationOperations.removeSymbolFromRightSide = function(equation, symbolName) {
  const rightNode = equation.rightNode;
  const symbolTerm = Symbols.getLastSymbolTerm(rightNode, symbolName);

  let inverseOp, inverseTerm, changeType;
  if (!symbolTerm){
    return new EquationStatus(equation);
  }

  if (PolynomialTermNode.isPolynomialTerm(rightNode)) {
    if (Negative.isNegative(symbolTerm)) {
      inverseOp = '+';
      changeType = MathChangeTypes.ADD_TO_BOTH_SIDES;
      inverseTerm = Negative.negate(symbolTerm);
    }
    else {
      inverseOp = '-';
      changeType = MathChangeTypes.SUBTRACT_FROM_BOTH_SIDES;
      inverseTerm = symbolTerm;
    }
  }
  else if (NodeType.isOperator(rightNode)) {
    if (rightNode.op === '+') {
      if (Negative.isNegative(symbolTerm)) {
        inverseOp = '+';
        changeType = MathChangeTypes.ADD_TO_BOTH_SIDES;
        inverseTerm = Negative.negate(symbolTerm);
      }
      else {
        inverseOp = '-';
        changeType = MathChangeTypes.SUBTRACT_FROM_BOTH_SIDES;
        inverseTerm = symbolTerm;
      }
    }
    else {
      // Note that operator '-' won't show up here because subtraction is
      // flattened into adding the negative. See 'TRICKY catch' in the README
      // for more details.
      throw Error('Unsupported operation: ' + symbolTerm.op);
    }
  }
  else if (NodeType.isUnaryMinus(rightNode)) {
    inverseOp = '+';
    changeType = MathChangeTypes.ADD_TO_BOTH_SIDES;
    inverseTerm = symbolTerm.args[0];
  }
  else {
    throw Error('Unsupported node type: ' + rightNode.type);
  }
  return performTermOperationOnEquation(
      equation, inverseOp, inverseTerm, changeType)
}

// Isolates the given symbolName to the left side by adding, multiplying, subtracting
// or dividing all other symbols and constants from both sides appropriately
// TODO: support inverting functions e.g. sqrt, ^, log etc.
EquationOperations.isolateSymbolOnLeftSide = function(equation, symbolName) {
  const leftNode = equation.leftNode;
  let nonSymbolTerm = Symbols.getLastNonSymbolTerm(leftNode, symbolName);

  let inverseOp, inverseTerm, changeType;
  if (!nonSymbolTerm) {
    return new EquationStatus(equation);
  }

  if (NodeType.isOperator(leftNode)) {
    if (leftNode.op === '+') {
      if (Negative.isNegative(nonSymbolTerm)) {
        inverseOp = '+';
        changeType = MathChangeTypes.ADD_TO_BOTH_SIDES;
        inverseTerm = Negative.negate(nonSymbolTerm);
      }
      else {
        inverseOp = '-';
        changeType = MathChangeTypes.SUBTRACT_FROM_BOTH_SIDES;
        inverseTerm = nonSymbolTerm;
      }
    }
    else if (leftNode.op === '*') {
      if (NodeType.isConstantFraction(nonSymbolTerm)) {
        inverseOp = '*';
        changeType = MathChangeTypes.MULTIPLY_BOTH_SIDES_BY_INVERSE_FRACTION;
        inverseTerm = NodeCreator.operator(
          '/', [nonSymbolTerm.args[1], nonSymbolTerm.args[0]]);
      }
      else {
        inverseOp = '/';
        changeType = MathChangeTypes.DIVIDE_FROM_BOTH_SIDES;
        inverseTerm = nonSymbolTerm;
      }
    }
    else if (leftNode.op === '/') {
      if (NodeType.isConstantFraction(nonSymbolTerm) &&
          PolynomialTermNode.isPolynomialTerm(leftNode)) {
        inverseOp = '*';
        changeType = MathChangeTypes.MULTIPLY_BOTH_SIDES_BY_INVERSE_FRACTION;
        inverseTerm = NodeCreator.operator(
          '/', [nonSymbolTerm.args[1], nonSymbolTerm.args[0]]);
      }
      else {
        inverseOp = '*';
        changeType = MathChangeTypes.MULTIPLY_TO_BOTH_SIDES;
        inverseTerm = nonSymbolTerm;
      }
    }
    else if (leftNode.op === '^') {
      // TODO: support roots
      return new EquationStatus(equation);
    }
    else {
      throw Error('Unsupported operation: ' + leftNode.op);
    }
  }
  else if (NodeType.isUnaryMinus(leftNode)) {
    inverseOp = '*';
    changeType = MathChangeTypes.MULTIPLY_BOTH_SIDES_BY_NEGATIVE_ONE;
    inverseTerm =  NodeCreator.constant(-1);
  }
  else {
    throw Error('Unsupported node type: ' + leftNode.type);
  }

  return performTermOperationOnEquation(
      equation, inverseOp, inverseTerm, changeType);
}

// Modifies the left and right sides of an equation by `op`-ing `term`
// to both sides. Returns an EquationStatus object.
function performTermOperationOnEquation(equation, op, term, changeType) {
  let leftNode = performTermOperationOnExpression(equation.leftNode, op, term);
  let rightNode = performTermOperationOnExpression(equation.rightNode, op, term);

  let comparator = equation.comparator;
  if (Negative.isNegative(term) && (op === '*' || op === '/')) {
    comparator = COMPARATOR_TO_INVERSE[comparator];
  }

  equation = new Equation(leftNode, rightNode, comparator);
  return new EquationStatus(equation, true, changeType);
}

// Performs an operation of a term on an entire given expression
function performTermOperationOnExpression(expression, op, term) {
  let node = (NodeType.isOperator(expression) ?
    NodeCreator.parenthesis(expression) : expression);

  return NodeCreator.operator(op, [node, term]);
}

module.exports = EquationOperations;
<|endoftext|>


<commit_before>// Note: You must restart bin/webpack-dev-server for changes to take effect

const { resolve } = require('path')
const merge = require('webpack-merge')
const devConfig = require('./development.js')
const { devServer, publicPath, paths } = require('./configuration.js')

module.exports = merge(devConfig, {
  devServer: {
    host: devServer.host,
    port: devServer.port,
    compress: true,
       headers: { "Access-Control-Allow-Origin": "*" },

    historyApiFallback: true,
    contentBase: resolve(paths.output, paths.entry),
    publicPath
  }
})
<commit_msg>Update development.server.js<commit_after>// Note: You must restart bin/webpack-dev-server for changes to take effect

const { resolve } = require('path')
const merge = require('webpack-merge')
const devConfig = require('./development.js')
const { devServer, publicPath, paths } = require('./configuration.js')

module.exports = merge(devConfig, {
  devServer: {
    host: devServer.host,
    port: devServer.port,
    compress: true,
    headers: { "Access-Control-Allow-Origin": "*" },
    historyApiFallback: true,
    contentBase: resolve(paths.output, paths.entry),
    publicPath
  }
})
<|endoftext|>


<commit_before>"use strict";
var ApiClient = require('nativescript-apiclient');
var Enumerable = require('nativescript-enumerable');
var TypeUtils = require('utils/types');
/**
 * List of entry types.
 */
(function (EntryType) {
    /**
     * Folder
     */
    EntryType[EntryType["Folder"] = 0] = "Folder";
    /**
     * File
     */
    EntryType[EntryType["File"] = 1] = "File";
})(exports.EntryType || (exports.EntryType = {}));
var EntryType = exports.EntryType;
/**
 * A DropBox client.
 */
var DropBoxClient = (function () {
    /**
     * Initializes a new instance of that class.
     *
     * @param {string} token The access token to use.
     */
    function DropBoxClient(token) {
        this._accessToken = token;
    }
    Object.defineProperty(DropBoxClient.prototype, "accessToken", {
        /**
         * Gets the access token.
         */
        get: function () {
            return this._accessToken;
        },
        enumerable: true,
        configurable: true
    });
    /**
     * Lists a folder.
     *
     * @param {string} Path The path.
     * @param {Function} callback The result callback.
     */
    DropBoxClient.prototype.listFolder = function (path, callback) {
        if (path === void 0) { path = ''; }
        var me = this;
        var client = ApiClient.newClient({
            baseUrl: 'https://api.dropboxapi.com/2/files/list_folder',
            authorizer: new ApiClient.BearerAuth(this._accessToken),
        });
        var code = 1;
        var entries;
        var error;
        var hasMore;
        var finish = function () {
            callback({
                code: code,
                entries: entries,
                error: error,
                hasMore: hasMore,
                path: path,
            });
        };
        client.succeededRequest(function (result) {
            var jsonResp = result.getJSON();
            code = 0;
            hasMore = jsonResp.has_more;
            if (TypeUtils.isNullOrUndefined(jsonResp.entries)) {
                entries = [];
            }
            else {
                entries = Enumerable.fromArray(jsonResp.entries)
                    .where(function (x) { return !TypeUtils.isNullOrUndefined(x); })
                    .select(function (x) {
                    // IFolderEntry
                    var entry;
                    entry = {
                        delete: function (delCallback) {
                            var terminator = ApiClient.newClient({
                                authorizer: new ApiClient.BearerAuth(me.accessToken),
                                baseUrl: 'https://api.dropboxapi.com/2/files/delete',
                            });
                            var termCode = 1;
                            var termError;
                            var termFinish = function () {
                                if (!TypeUtils.isNullOrUndefined(delCallback)) {
                                    delCallback({
                                        code: termCode,
                                        error: termError,
                                    });
                                }
                            };
                            terminator.succeededRequest(function () {
                                termCode = 0;
                            }).clientOrServerError(function (delResult) {
                                termCode = -2;
                                termError = 'Server returned code: ' + delResult.code + ' => ' + delResult.getString();
                            }).error(function (delCtx) {
                                termCode = -1;
                                termError = delCtx.error;
                            }).complete(function () {
                                termFinish();
                            });
                            terminator.post({
                                content: {
                                    path: entry.path,
                                },
                                type: ApiClient.HttpRequestType.JSON,
                            });
                        },
                        id: x.id,
                        name: x.name,
                        path: x.path_display,
                    };
                    var tag = (TypeUtils.isNullOrUndefined(x['.tag']) ? '' : ('' + x['.tag'])).toLowerCase().trim();
                    switch (tag) {
                        case 'folder':
                            entry.type = EntryType.Folder;
                            entry.uploadFile = function (fileToUpload, ulCallback) {
                                me.uploadFileTo(fileToUpload, path, ulCallback);
                            };
                            break;
                        case 'file':
                            entry.type = EntryType.File;
                            entry.download = function (callback) {
                                var downloadClient = ApiClient.newClient({
                                    authorizer: new ApiClient.BearerAuth(me.accessToken),
                                    baseUrl: 'https://content.dropboxapi.com/2/files/download',
                                });
                                downloadClient.addLogger(function (dlMsg) {
                                    console.log('DropBoxDemo.DropBoxClient.download(' + entry.path + '): ' + dlMsg.message);
                                });
                                var dlCode = 1;
                                var dlError;
                                var dlFile;
                                var finishDownload = function () {
                                    callback({
                                        code: dlCode,
                                        file: entry,
                                        localFile: dlFile,
                                    });
                                };
                                downloadClient.succeededRequest(function (dlResult) {
                                    try {
                                        dlFile = dlResult.getFile();
                                        dlCode = 0;
                                    }
                                    catch (dlE) {
                                        dlCode = -3;
                                        dlError = dlE;
                                    }
                                }).clientOrServerError(function (dlResult) {
                                    dlCode = -2;
                                    dlError = 'Server returned code: ' + dlResult.code + ' => ' + dlResult.getString();
                                }).error(function (dlCtx) {
                                    dlCode = -1;
                                    dlError = dlCtx.error;
                                }).complete(function () {
                                    finishDownload();
                                });
                                var apiArgs = {
                                    path: entry.path,
                                };
                                downloadClient.post({
                                    headers: {
                                        'Content-Type': '',
                                        'Dropbox-API-Arg': JSON.stringify(apiArgs),
                                    },
                                });
                            };
                            break;
                    }
                    return entry;
                })
                    .orderBy(function (x) { return x.type; })
                    .thenBy(function (x) { return x.path.toLowerCase().trim(); })
                    .toArray();
            }
        }).clientOrServerError(function (result) {
            code = -2;
            error = 'Server returned code: ' + result.code;
        }).error(function (ctx) {
            code = -1;
            error = ctx.error;
        }).complete(function () {
            finish();
        });
        client.post({
            content: {
                path: path,
                recursive: false,
                include_media_info: true,
                include_deleted: false,
                include_has_explicit_shared_members: false
            },
            type: ApiClient.HttpRequestType.JSON,
        });
    };
    /**
     * Uploads a file.
     *
     * @param {FileSystem.File} localFile The file to upload.
     * @param {String} [targetFolder] The custom target folder.
     * @param {Function} [callback] The optional result callback.
     */
    DropBoxClient.prototype.uploadFileTo = function (localFile, targetFolder, callback) {
        if (targetFolder === void 0) { targetFolder = ''; }
        var readError;
        var dataToUpload = localFile.readSync(function (fileErr) {
            readError = fileErr;
        });
        var code = 1;
        var error;
        var finish = function () {
            if (!TypeUtils.isNullOrUndefined(callback)) {
                callback({
                    code: code,
                    error: error,
                });
            }
        };
        if (!TypeUtils.isNullOrUndefined(readError)) {
            code = -2;
            error = readError;
            finish();
        }
        else {
            var client = ApiClient.newClient({
                authorizer: new ApiClient.BearerAuth(this.accessToken),
                baseUrl: 'https://content.dropboxapi.com/2/files/upload',
            });
            client.succeededRequest(function () {
                code = 0;
            }).clientOrServerError(function (ulResult) {
                code = -2;
                error = 'Server returned code: ' + ulResult.code + ' => ' + ulResult.getString();
            }).error(function (ulCtx) {
                code = -1;
                error = ulCtx.error;
            }).complete(function () {
                finish();
            });
            var args = {
                path: targetFolder + '/' + localFile.name,
                mode: "overwrite",
                autorename: true,
                mute: false
            };
            client.post({
                content: dataToUpload,
                type: ApiClient.HttpRequestType.Binary,
                headers: {
                    'Dropbox-API-Arg': JSON.stringify(args),
                }
            });
        }
    };
    return DropBoxClient;
}());
exports.DropBoxClient = DropBoxClient;
//# sourceMappingURL=DropBox.js.map<commit_msg>uploadFileTo targetFolder to targetPath<commit_after>"use strict";
var ApiClient = require('nativescript-apiclient');
var Enumerable = require('nativescript-enumerable');
var TypeUtils = require('utils/types');
/**
 * List of entry types.
 */
(function (EntryType) {
    /**
     * Folder
     */
    EntryType[EntryType["Folder"] = 0] = "Folder";
    /**
     * File
     */
    EntryType[EntryType["File"] = 1] = "File";
})(exports.EntryType || (exports.EntryType = {}));
var EntryType = exports.EntryType;
/**
 * A DropBox client.
 */
var DropBoxClient = (function () {
    /**
     * Initializes a new instance of that class.
     *
     * @param {string} token The access token to use.
     */
    function DropBoxClient(token) {
        this._accessToken = token;
    }
    Object.defineProperty(DropBoxClient.prototype, "accessToken", {
        /**
         * Gets the access token.
         */
        get: function () {
            return this._accessToken;
        },
        enumerable: true,
        configurable: true
    });
    /**
     * Lists a folder.
     *
     * @param {string} Path The path.
     * @param {Function} callback The result callback.
     */
    DropBoxClient.prototype.listFolder = function (path, callback) {
        if (path === void 0) { path = ''; }
        var me = this;
        var client = ApiClient.newClient({
            baseUrl: 'https://api.dropboxapi.com/2/files/list_folder',
            authorizer: new ApiClient.BearerAuth(this._accessToken),
        });
        var code = 1;
        var entries;
        var error;
        var hasMore;
        var finish = function () {
            callback({
                code: code,
                entries: entries,
                error: error,
                hasMore: hasMore,
                path: path,
            });
        };
        client.succeededRequest(function (result) {
            var jsonResp = result.getJSON();
            code = 0;
            hasMore = jsonResp.has_more;
            if (TypeUtils.isNullOrUndefined(jsonResp.entries)) {
                entries = [];
            }
            else {
                entries = Enumerable.fromArray(jsonResp.entries)
                    .where(function (x) { return !TypeUtils.isNullOrUndefined(x); })
                    .select(function (x) {
                    // IFolderEntry
                    var entry;
                    entry = {
                        delete: function (delCallback) {
                            var terminator = ApiClient.newClient({
                                authorizer: new ApiClient.BearerAuth(me.accessToken),
                                baseUrl: 'https://api.dropboxapi.com/2/files/delete',
                            });
                            var termCode = 1;
                            var termError;
                            var termFinish = function () {
                                if (!TypeUtils.isNullOrUndefined(delCallback)) {
                                    delCallback({
                                        code: termCode,
                                        error: termError,
                                    });
                                }
                            };
                            terminator.succeededRequest(function () {
                                termCode = 0;
                            }).clientOrServerError(function (delResult) {
                                termCode = -2;
                                termError = 'Server returned code: ' + delResult.code + ' => ' + delResult.getString();
                            }).error(function (delCtx) {
                                termCode = -1;
                                termError = delCtx.error;
                            }).complete(function () {
                                termFinish();
                            });
                            terminator.post({
                                content: {
                                    path: entry.path,
                                },
                                type: ApiClient.HttpRequestType.JSON,
                            });
                        },
                        id: x.id,
                        name: x.name,
                        path: x.path_display,
                    };
                    var tag = (TypeUtils.isNullOrUndefined(x['.tag']) ? '' : ('' + x['.tag'])).toLowerCase().trim();
                    switch (tag) {
                        case 'folder':
                            entry.type = EntryType.Folder;
                            entry.uploadFile = function (fileToUpload, ulCallback) {
                                me.uploadFileTo(fileToUpload, path, ulCallback);
                            };
                            break;
                        case 'file':
                            entry.type = EntryType.File;
                            entry.download = function (callback) {
                                var downloadClient = ApiClient.newClient({
                                    authorizer: new ApiClient.BearerAuth(me.accessToken),
                                    baseUrl: 'https://content.dropboxapi.com/2/files/download',
                                });
                                downloadClient.addLogger(function (dlMsg) {
                                    console.log('DropBoxDemo.DropBoxClient.download(' + entry.path + '): ' + dlMsg.message);
                                });
                                var dlCode = 1;
                                var dlError;
                                var dlFile;
                                var finishDownload = function () {
                                    callback({
                                        code: dlCode,
                                        file: entry,
                                        localFile: dlFile,
                                    });
                                };
                                downloadClient.succeededRequest(function (dlResult) {
                                    try {
                                        dlFile = dlResult.getFile();
                                        dlCode = 0;
                                    }
                                    catch (dlE) {
                                        dlCode = -3;
                                        dlError = dlE;
                                    }
                                }).clientOrServerError(function (dlResult) {
                                    dlCode = -2;
                                    dlError = 'Server returned code: ' + dlResult.code + ' => ' + dlResult.getString();
                                }).error(function (dlCtx) {
                                    dlCode = -1;
                                    dlError = dlCtx.error;
                                }).complete(function () {
                                    finishDownload();
                                });
                                var apiArgs = {
                                    path: entry.path,
                                };
                                downloadClient.post({
                                    headers: {
                                        'Content-Type': '',
                                        'Dropbox-API-Arg': JSON.stringify(apiArgs),
                                    },
                                });
                            };
                            break;
                    }
                    return entry;
                })
                    .orderBy(function (x) { return x.type; })
                    .thenBy(function (x) { return x.path.toLowerCase().trim(); })
                    .toArray();
            }
        }).clientOrServerError(function (result) {
            code = -2;
            error = 'Server returned code: ' + result.code;
        }).error(function (ctx) {
            code = -1;
            error = ctx.error;
        }).complete(function () {
            finish();
        });
        client.post({
            content: {
                path: path,
                recursive: false,
                include_media_info: true,
                include_deleted: false,
                include_has_explicit_shared_members: false
            },
            type: ApiClient.HttpRequestType.JSON,
        });
    };
    /**
     * Uploads a file.
     *
     * @param {FileSystem.File} localFile The file to upload.
     * @param {String} [targetFolder] The custom target folder.
     * @param {Function} [callback] The optional result callback.
     */
    DropBoxClient.prototype.uploadFileTo = function (localFile, targetPath, callback) {
        if (targetFolder === void 0) { targetFolder = ''; }
        var readError;
        var dataToUpload = localFile.readSync(function (fileErr) {
            readError = fileErr;
        });
        var code = 1;
        var error;
        var finish = function () {
            if (!TypeUtils.isNullOrUndefined(callback)) {
                callback({
                    code: code,
                    error: error,
                });
            }
        };
        if (!TypeUtils.isNullOrUndefined(readError)) {
            code = -2;
            error = readError;
            finish();
        }
        else {
            var client = ApiClient.newClient({
                authorizer: new ApiClient.BearerAuth(this.accessToken),
                baseUrl: 'https://content.dropboxapi.com/2/files/upload',
            });
            client.succeededRequest(function () {
                code = 0;
            }).clientOrServerError(function (ulResult) {
                code = -2;
                error = 'Server returned code: ' + ulResult.code + ' => ' + ulResult.getString();
            }).error(function (ulCtx) {
                code = -1;
                error = ulCtx.error;
            }).complete(function () {
                finish();
            });
            var args = {
                path: targetPath,
                mode: "overwrite",
                autorename: true,
                mute: false
            };
            client.post({
                content: dataToUpload,
                type: ApiClient.HttpRequestType.Binary,
                headers: {
                    'Dropbox-API-Arg': JSON.stringify(args),
                }
            });
        }
    };
    return DropBoxClient;
}());
exports.DropBoxClient = DropBoxClient;
//# sourceMappingURL=DropBox.js.map<|endoftext|>


<commit_before>function parse(content)
{
	// Trim
	name = content.replace(/^\s+|\s+$/g, "");

	if(content.length === 0) return null;
	return {
		defineSuccessStructure: name
	};
} // parse

function pushTo()
{
	return "global";
} // pushTo

/**
 * Exports.
 */
module.exports = {
	parse: parse,
	pushTo: pushTo
};<commit_msg>Refactor: Not declared var<commit_after>function parse(content)
{
	// Trim
	var name = content.replace(/^\s+|\s+$/g, "");

	if(content.length === 0) return null;
	return {
		defineSuccessStructure: name
	};
} // parse

function pushTo()
{
	return "global";
} // pushTo

/**
 * Exports.
 */
module.exports = {
	parse: parse,
	pushTo: pushTo
};<|endoftext|>