Home Manual Reference Source

src/services/targets/targetsFileRules/targetFileRule.js

const ObjectUtils = require('wootils/shared/objectUtils');
/**
 * Helper service used by {@link TargetsFileRules} in order to create dynamic file rules for
 * multiple purposes.
 */
class TargetFileRule {
  /**
   * @param {Events}                events                   To reduce the rule settings updated.
   * @param {Targets}               targets                  To get the information of other targets
   *                                                         from the `includeTargets` setting.
   * @param {string}                ruleType                 A reference identifier that tells for
   *                                                         which kind file type the rule is
   *                                                         being used for.
   * @param {TargetFileRuleHandler} getSettingsForTargetRule To define the rule settings whenever
   *                                                         a new target is added.
   * @throws {Error} If `getSettingsForTargetRule` is not a function.
   */
  constructor(events, targets, ruleType, getSettingsForTargetRule) {
    /**
     * A local reference for the `events` service.
     * @type {Events}
     */
    this.events = events;
    /**
     * A local reference for the `targets` service.
     * @type {Targets}
     */
    this.targets = targets;
    // Validate the handler function.
    if (typeof getSettingsForTargetRule !== 'function') {
      throw new Error('You need to specify a handler function for when a new target is added');
    }
    /**
     * The reference identifier for the kind of rule this is for.
     * @type {string}
     * @access protected
     * @ignore
     */
    this._ruleType = ruleType;
    /**
     * The function that generates the new settings when a new target is added.
     * @type {TargetFileRuleHandler}
     * @access protected
     * @ignore
     */
    this._getSettingsForTargetRule = getSettingsForTargetRule;
    /**
     * The rule settings.
     * @type {TargetFileRuleSettings}
     * @access protected
     * @ignore
     */
    this._rule = {
      extension: /\.\w+$/i,
      glob: '**/*.*',
      paths: {
        include: [],
        exclude: [],
      },
      files: {
        include: [],
        exclude: [],
        glob: {
          include: [],
          exclude: [],
        },
      },
    };
    /**
     * Whether or not a target has been added to the rule.
     * @type {boolean}
     * @access protected
     * @ignore
     */
    this._hasTarget = false;
  }
  /**
   * Get the rule settings.
   * @return {TargetFileRuleSettings}
   */
  getRule() {
    return this._rule;
  }
  /**
   * Add a target to the rule. This means the instance will process it and eventually add its
   * paths to the settings.
   * This method uses the reducer event `target-file-rule`, and if a target was already added,
   * `target-file-rule-update` too. Both events receive the next state of the settings as well
   * as the current, and expect the final state on return.
   * @param {Target} target The target information.
   */
  addTarget(target) {
    const changes = ObjectUtils.copy(
      this._getSettingsForTargetRule(target, this._hasTarget, this._rule)
    );

    const finalRule = this._mergeRule(this._rule, changes);

    const events = ['target-file-rule'];
    if (this._hasTarget) {
      events.push('target-file-rule-update');
    }

    this._hasTarget = true;
    this._rule = this.events.reduce(events, finalRule, this._rule);
    target.includeTargets.forEach((targetName) => {
      const targetInfo = this.targets.getTarget(targetName);
      this.addTarget(targetInfo);
    });
  }
  /**
   * Merge two sets of rule settings. This is also used recursively to merge nested settings.
   * @param {TargetFileRuleSettings|Object} base    The original rule or a set of properties of
   *                                                the original rule.
   * @param {TargetFileRuleSettings|Object} changes The rule that will be merged into the origina,
   *                                                or a set of properties from it.
   * @return {TargetFileRuleSettings|Object} The merged rule, or a set of merge properties.
   * @access protected
   * @ignore
   */
  _mergeRule(base, changes) {
    const newRule = ObjectUtils.copy(base);
    Object.keys(changes).forEach((property) => {
      const value = changes[property];
      const propertyType = typeof value;
      if (Array.isArray(value)) {
        newRule[property].push(...value);
      } else if (value instanceof RegExp) {
        newRule[property] = value;
      } else if (propertyType === 'object') {
        newRule[property] = this._mergeRule(newRule[property], value);
      } else {
        newRule[property] = value;
      }
    });

    return newRule;
  }
}

module.exports = TargetFileRule;