<commit_before>import { Options } from "./options";

/**
 * Parses and normalizes a cron expression
 * @export
 * @class CronParser
 */
export class CronParser {
  expression: string;
  dayOfWeekStartIndexZero: boolean;

  constructor(expression: string, dayOfWeekStartIndexZero: boolean = true) {
    this.expression = expression;
    this.dayOfWeekStartIndexZero = dayOfWeekStartIndexZero;
  }

  /**
   * Parses and normalizes a cron expression into an array of strings
   * @returns {string[]}
   */
  parse(): string[] {
    let parsed = this.extractParts(this.expression);
    this.normalize(parsed);
    this.validate(parsed);

    return parsed;
  }

  protected extractParts(expression: string) {
    if (!this.expression) {
      throw new Error("Expression is empty");
    }

    let parsed: string[] = expression.trim().split(" ");

    if (parsed.length < 5) {
      throw new Error(
        `Expression has only ${parsed.length} part${parsed.length == 1 ? "" : "s"}. At least 5 parts are required.`
      );
    } else if (parsed.length == 5) {
      //5 part cron so shift array past seconds element
      parsed.unshift("");
      parsed.push("");
    } else if (parsed.length == 6) {
      //If last element ends with 4 digits, a year element has been supplied and no seconds element
      if (/\d{4}$/.test(parsed[5])) {
        // year provided
        parsed.unshift("");
      } else {
        // seconds provided
        parsed.push("");
      }
    } else if (parsed.length > 7) {
      throw new Error(`Expression has ${parsed.length} parts; too many!`);
    }

    return parsed;
  }

  protected normalize(expressionParts: string[]): void {
    // Convert ? to * only for DOM and DOW
    expressionParts[3] = expressionParts[3].replace("?", "*");
    expressionParts[5] = expressionParts[5].replace("?", "*");

    // Convert 0/, 1/ to */
    if (expressionParts[0].indexOf("0/") == 0) {
      // Seconds
      expressionParts[0] = expressionParts[0].replace("0/", "*/");
    }

    if (expressionParts[1].indexOf("0/") == 0) {
      // Minutes
      expressionParts[1] = expressionParts[1].replace("0/", "*/");
    }

    if (expressionParts[2].indexOf("0/") == 0) {
      // Hours
      expressionParts[2] = expressionParts[2].replace("0/", "*/");
    }

    if (expressionParts[3].indexOf("1/") == 0) {
      // DOM
      expressionParts[3] = expressionParts[3].replace("1/", "*/");
    }

    if (expressionParts[4].indexOf("1/") == 0) {
      // Month
      expressionParts[4] = expressionParts[4].replace("1/", "*/");
    }

    if (expressionParts[5].indexOf("1/") == 0) {
      // DOW
      expressionParts[5] = expressionParts[5].replace("1/", "*/");
    }

    if (expressionParts[6].indexOf("1/") == 0) {
      // Years
      expressionParts[6] = expressionParts[6].replace("1/", "*/");
    }

    // Adjust DOW based on dayOfWeekStartIndexZero option
    // Normalized DOW: 0=Sunday/6=Saturday
    expressionParts[5] = expressionParts[5].replace(/(^\d)|([^#/\s]\d)/g, t => {
      //skip anything preceeded by # or /
      let dowDigits = t.replace(/\D/, ""); // extract digit part (i.e. if "-2" or ",2", just take 2)
      let dowDigitsAdjusted: string = dowDigits;

      if (this.dayOfWeekStartIndexZero) {
        // "7" also means Sunday so we will convert to "0" to normalize it
        if (dowDigits == "7") {
          dowDigitsAdjusted = "0";
        }
      } else {
        // If dayOfWeekStartIndexZero==false, Sunday is specified as 1 and Saturday is specified as 7.
        // To normalize, we will shift the  DOW number down so that 1 becomes 0, 2 becomes 1, and so on.
        dowDigitsAdjusted = (parseInt(dowDigits) - 1).toString();
      }

      return t.replace(dowDigits, dowDigitsAdjusted);
    });

    // Convert DOW 'L' to '6' (Saturday)
    if (expressionParts[5] == "L") {
      expressionParts[5] = "6";
    }

    // Convert DOM '?' to '*'
    if (expressionParts[3] == "?") {
      expressionParts[3] = "*";
    }

    if (
      expressionParts[3].indexOf("W") > -1 &&
      (expressionParts[3].indexOf(",") > -1 || expressionParts[3].indexOf("-") > -1)
    ) {
      throw new Error(
        "The 'W' character can be specified only when the day-of-month is a single day, not a range or list of days."
      );
    }

    // Convert DOW SUN-SAT format to 0-6 format
    var days: { [key: string]: number } = {
      SUN: 0,
      MON: 1,
      TUE: 2,
      WED: 3,
      THU: 4,
      FRI: 5,
      SAT: 6
    };
    for (let day in days) {
      expressionParts[5] = expressionParts[5].replace(new RegExp(day, "gi"), days[day].toString());
    }

    // Convert JAN-DEC format to 1-12 format
    var months: { [key: string]: number } = {
      JAN: 1,
      FEB: 2,
      MAR: 3,
      APR: 4,
      MAY: 5,
      JUN: 6,
      JUL: 7,
      AUG: 8,
      SEP: 9,
      OCT: 10,
      NOV: 11,
      DEC: 12
    };

    for (let month in months) {
      expressionParts[4] = expressionParts[4].replace(new RegExp(month, "gi"), months[month].toString());
    }

    // Convert 0 second to (empty)
    if (expressionParts[0] == "0") {
      expressionParts[0] = "";
    }

    // If time interval is specified for minutes and hours part is single item, make it a "self-range" so
    // the expression can be interpreted as an interval 'between' range.
    //     For example:
    //     0-20/3 9 * * * => 0-20/3 9-9 * * * (9 => 9-9)
    //     */5 3 * * * => */5 3-3 * * * (3 => 3-3)
    if (!/^\*|\-|\,/.test(expressionParts[2]) && expressionParts[1].indexOf("/") > -1) {
      expressionParts[2] += `-${expressionParts[2]}`;
    }

    // Loop through all parts and apply global normalization
    for (let i = 0; i < expressionParts.length; i++) {
      // convert all '*/1' to '*'
      if (expressionParts[i] == "*/1") {<commit_msg>Convert hour to self range if seconds or minutes is interval or * (every)<commit_after>import { Options } from "./options";

/**
 * Parses and normalizes a cron expression
 * @export
 * @class CronParser
 */
export class CronParser {
  expression: string;
  dayOfWeekStartIndexZero: boolean;

  constructor(expression: string, dayOfWeekStartIndexZero: boolean = true) {
    this.expression = expression;
    this.dayOfWeekStartIndexZero = dayOfWeekStartIndexZero;
  }

  /**
   * Parses and normalizes a cron expression into an array of strings
   * @returns {string[]}
   */
  parse(): string[] {
    let parsed = this.extractParts(this.expression);
    this.normalize(parsed);
    this.validate(parsed);

    return parsed;
  }

  protected extractParts(expression: string) {
    if (!this.expression) {
      throw new Error("Expression is empty");
    }

    let parsed: string[] = expression.trim().split(" ");

    if (parsed.length < 5) {
      throw new Error(
        `Expression has only ${parsed.length} part${parsed.length == 1 ? "" : "s"}. At least 5 parts are required.`
      );
    } else if (parsed.length == 5) {
      //5 part cron so shift array past seconds element
      parsed.unshift("");
      parsed.push("");
    } else if (parsed.length == 6) {
      //If last element ends with 4 digits, a year element has been supplied and no seconds element
      if (/\d{4}$/.test(parsed[5])) {
        // year provided
        parsed.unshift("");
      } else {
        // seconds provided
        parsed.push("");
      }
    } else if (parsed.length > 7) {
      throw new Error(`Expression has ${parsed.length} parts; too many!`);
    }

    return parsed;
  }

  protected normalize(expressionParts: string[]): void {
    // Convert ? to * only for DOM and DOW
    expressionParts[3] = expressionParts[3].replace("?", "*");
    expressionParts[5] = expressionParts[5].replace("?", "*");

    // Convert 0/, 1/ to */
    if (expressionParts[0].indexOf("0/") == 0) {
      // Seconds
      expressionParts[0] = expressionParts[0].replace("0/", "*/");
    }

    if (expressionParts[1].indexOf("0/") == 0) {
      // Minutes
      expressionParts[1] = expressionParts[1].replace("0/", "*/");
    }

    if (expressionParts[2].indexOf("0/") == 0) {
      // Hours
      expressionParts[2] = expressionParts[2].replace("0/", "*/");
    }

    if (expressionParts[3].indexOf("1/") == 0) {
      // DOM
      expressionParts[3] = expressionParts[3].replace("1/", "*/");
    }

    if (expressionParts[4].indexOf("1/") == 0) {
      // Month
      expressionParts[4] = expressionParts[4].replace("1/", "*/");
    }

    if (expressionParts[5].indexOf("1/") == 0) {
      // DOW
      expressionParts[5] = expressionParts[5].replace("1/", "*/");
    }

    if (expressionParts[6].indexOf("1/") == 0) {
      // Years
      expressionParts[6] = expressionParts[6].replace("1/", "*/");
    }

    // Adjust DOW based on dayOfWeekStartIndexZero option
    // Normalized DOW: 0=Sunday/6=Saturday
    expressionParts[5] = expressionParts[5].replace(/(^\d)|([^#/\s]\d)/g, t => {
      //skip anything preceeded by # or /
      let dowDigits = t.replace(/\D/, ""); // extract digit part (i.e. if "-2" or ",2", just take 2)
      let dowDigitsAdjusted: string = dowDigits;

      if (this.dayOfWeekStartIndexZero) {
        // "7" also means Sunday so we will convert to "0" to normalize it
        if (dowDigits == "7") {
          dowDigitsAdjusted = "0";
        }
      } else {
        // If dayOfWeekStartIndexZero==false, Sunday is specified as 1 and Saturday is specified as 7.
        // To normalize, we will shift the  DOW number down so that 1 becomes 0, 2 becomes 1, and so on.
        dowDigitsAdjusted = (parseInt(dowDigits) - 1).toString();
      }

      return t.replace(dowDigits, dowDigitsAdjusted);
    });

    // Convert DOW 'L' to '6' (Saturday)
    if (expressionParts[5] == "L") {
      expressionParts[5] = "6";
    }

    // Convert DOM '?' to '*'
    if (expressionParts[3] == "?") {
      expressionParts[3] = "*";
    }

    if (
      expressionParts[3].indexOf("W") > -1 &&
      (expressionParts[3].indexOf(",") > -1 || expressionParts[3].indexOf("-") > -1)
    ) {
      throw new Error(
        "The 'W' character can be specified only when the day-of-month is a single day, not a range or list of days."
      );
    }

    // Convert DOW SUN-SAT format to 0-6 format
    var days: { [key: string]: number } = {
      SUN: 0,
      MON: 1,
      TUE: 2,
      WED: 3,
      THU: 4,
      FRI: 5,
      SAT: 6
    };
    for (let day in days) {
      expressionParts[5] = expressionParts[5].replace(new RegExp(day, "gi"), days[day].toString());
    }

    // Convert JAN-DEC format to 1-12 format
    var months: { [key: string]: number } = {
      JAN: 1,
      FEB: 2,
      MAR: 3,
      APR: 4,
      MAY: 5,
      JUN: 6,
      JUL: 7,
      AUG: 8,
      SEP: 9,
      OCT: 10,
      NOV: 11,
      DEC: 12
    };

    for (let month in months) {
      expressionParts[4] = expressionParts[4].replace(new RegExp(month, "gi"), months[month].toString());
    }

    // Convert 0 second to (empty)
    if (expressionParts[0] == "0") {
      expressionParts[0] = "";
    }

    // If time interval or * (every) is specified for seconds or minutes and hours part is single item, make it a "self-range" so
    // the expression can be interpreted as an interval 'between' range.  This will allow us to easily interpret an hour
    // part as 'between' a second or minute duration.
    //     For example:
    //     0-20/3 9 * * * => 0-20/3 9-9 * * * (9 => 9-9) => Every 3 minutes, minutes 0 through 20 past the hour, between 09:00 AM and 09:59 AM
    //     */5 3 * * * => */5 3-3 * * * (3 => 3-3) => Every 5 minutes, between 03:00 AM and 03:59 AM
    if (
      !/\*|\-|\,|\//.test(expressionParts[2]) &&
      (/\*|\//.test(expressionParts[1]) || /\*|\//.test(expressionParts[0]))
    ) {
      expressionParts[2] += `-${expressionParts[2]}`;
    }

    // Loop through all parts and apply global normalization
    for (let i = 0; i < expressionParts.length; i++) {
      // convert all '*/1' to '*'
      if (expressionParts[i] == "*/1") {
{'fraction_non_alphanumeric': 0.16713928273561302, 'fraction_numerical': 0.02727272727272727, 'mean_word_length': 2.3373225716671304, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 24, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4466466', 'n_tokens_mistral': 4050, 'n_tokens_neox': 3846, 'n_words': 1072}