UNPKG

cron-parser

Version:

Node.js library for parsing crontab instructions

162 lines (161 loc) 5.58 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CronField = void 0; /** * Represents a field within a cron expression. * This is a base class and should not be instantiated directly. * @class CronField */ class CronField { #hasLastChar = false; #wildcard = false; #values = []; /** * Returns the minimum value allowed for this field. */ /* istanbul ignore next */ static get min() { /* istanbul ignore next */ throw new Error('min must be overridden'); } /** * Returns the maximum value allowed for this field. */ /* istanbul ignore next */ static get max() { /* istanbul ignore next */ throw new Error('max must be overridden'); } /** * Returns the allowed characters for this field. */ /* istanbul ignore next */ static get chars() { /* istanbul ignore next - this is overridden */ return Object.freeze([]); } /** * Returns the regular expression used to validate this field. */ static get validChars() { return /^[,*\d/-]+$/; } /** * Returns the constraints for this field. */ static get constraints() { return { min: this.min, max: this.max, chars: this.chars, validChars: this.validChars }; } /** * CronField constructor. Initializes the field with the provided values. * @param {number[] | string[]} values - Values for this field * @param {boolean} [wildcard=false] - Whether this field is a wildcard * @throws {TypeError} if the constructor is called directly * @throws {Error} if validation fails */ constructor(values, /* istanbul ignore next - we always pass a value */ wildcard = false) { if (!Array.isArray(values)) { throw new Error(`${this.constructor.name} Validation error, values is not an array`); } if (!(values.length > 0)) { throw new Error(`${this.constructor.name} Validation error, values contains no values`); } this.#values = values.sort(CronField.sorter); this.#wildcard = wildcard; this.#hasLastChar = values.some((expression) => { return typeof expression === 'string' && expression.indexOf('L') >= 0; }); } /** * Returns the minimum value allowed for this field. * @returns {number} */ get min() { // return the static value from the child class return this.constructor.min; } /** * Returns the maximum value allowed for this field. * @returns {number} */ get max() { // return the static value from the child class return this.constructor.max; } /** * Returns an array of allowed special characters for this field. * @returns {string[]} */ get chars() { // return the frozen static value from the child class return this.constructor.chars; } /** * Indicates whether this field has a "last" character. * @returns {boolean} */ get hasLastChar() { return this.#hasLastChar; } /** * Indicates whether this field is a wildcard. * @returns {boolean} */ get isWildcard() { return this.#wildcard; } /** * Returns an array of allowed values for this field. * @returns {CronFieldType} */ get values() { return this.#values; } /** * Helper function to sort values in ascending order. * @param {number | string} a - First value to compare * @param {number | string} b - Second value to compare * @returns {number} - A negative, zero, or positive value, depending on the sort order */ static sorter(a, b) { const aIsNumber = typeof a === 'number'; const bIsNumber = typeof b === 'number'; if (aIsNumber && bIsNumber) return a - b; if (!aIsNumber && !bIsNumber) return a.localeCompare(b); return aIsNumber ? /* istanbul ignore next - A will always be a number until L-2 is supported */ -1 : 1; } /** * Serializes the field to an object. * @todo This is really only for debugging, should it be removed? * @returns {SerializedCronField} */ serialize() { return { wildcard: this.#wildcard, values: this.#values, }; } /** * Validates the field values against the allowed range and special characters. * @throws {Error} if validation fails */ validate() { let badValue; const charsString = this.chars.length > 0 ? ` or chars ${this.chars.join('')}` : ''; const charTest = (value) => (char) => new RegExp(`^\\d{0,2}${char}$`).test(value); const rangeTest = (value) => { badValue = value; return typeof value === 'number' ? value >= this.min && value <= this.max : this.chars.some(charTest(value)); }; const isValidRange = this.#values.every(rangeTest); if (!isValidRange) { throw new Error(`${this.constructor.name} Validation error, got value ${badValue} expected range ${this.min}-${this.max}${charsString}`); } // check for duplicate value in this.#values array const duplicate = this.#values.find((value, index) => this.#values.indexOf(value) !== index); if (duplicate) { throw new Error(`${this.constructor.name} Validation error, duplicate values found: ${duplicate}`); } } } exports.CronField = CronField;