记一次复杂正则,匹配Crontab

93 阅读1分钟
const MONTH_VAL = 'JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC'
const WEEKDAY_VAL = 'SUN|MON|TUE|WED|THU|FRI|SAT'
const WILDCARD_VAL = '[*]([/][0-9]+)?'
const NUMBER_VAL = '[0-9]+(-[0-9]+([/][0-9]+)?)?'
const GROUP_VAL = `(${WILDCARD_VAL}|${NUMBER_VAL})(,(${WILDCARD_VAL}|${NUMBER_VAL}))*`
const CORNTAB_CHARS = '[*,-/0-9]+'
const CRONTAB_GLOBAL_REG = new RegExp(
  `^(${CORNTAB_CHARS}) (${CORNTAB_CHARS}) (${CORNTAB_CHARS}) (${CORNTAB_CHARS}|${MONTH_VAL}) (${CORNTAB_CHARS}|${WEEKDAY_VAL})$`
)
const CRONTAB_KEY = {
  minute: 1,
  hour: 2,
  monthDay: 3,
  month: 4,
  weekDay: 5
} as const
type KEYOF_CRONTAB_KEY = keyof typeof CRONTAB_KEY
interface CorntabValOption {
  min: number
  max: number
  itemReg: RegExp
  groupReg: RegExp
}
interface CorntabStandard {
  minute: CorntabValOption
  hour: CorntabValOption
  monthDay: CorntabValOption
  month: CorntabValOption
  weekDay: CorntabValOption
}
const STANDARD: CorntabStandard = {
  minute: {
    min: 0,
    max: 59,
    itemReg: new RegExp(`^${WILDCARD_VAL}$`),
    groupReg: new RegExp(`^${GROUP_VAL}$`)
  },
  hour: {
    min: 0,
    max: 23,
    itemReg: new RegExp(`^${WILDCARD_VAL}$`),
    groupReg: new RegExp(`^${GROUP_VAL}$`)
  },
  monthDay: {
    min: 1,
    max: 31,
    itemReg: new RegExp(`^${WILDCARD_VAL}$`),
    groupReg: new RegExp(`^${GROUP_VAL}$`)
  },
  month: {
    min: 1,
    max: 12,
    itemReg: new RegExp(`^(${MONTH_VAL}|${WILDCARD_VAL})$`),
    groupReg: new RegExp(`^(${MONTH_VAL}|${GROUP_VAL})$`)
  },
  weekDay: {
    min: 0,
    max: 6,
    itemReg: new RegExp(`^(${WEEKDAY_VAL}|${WILDCARD_VAL})$`),
    groupReg: new RegExp(`^(${WEEKDAY_VAL}|${GROUP_VAL})$`)
  }
}
const checkNumerators = (
  numerators: string[],
  key: KEYOF_CRONTAB_KEY
): string => {
  const { min, max } = STANDARD[key]
  const nums = numerators.map(n => Number(n))
  let prev = min
  for (let i = 0; i < nums.length; i++) {
    if (nums[i] > max) {
      return `${key}: ${nums[i]} 不能大于 ${max}`
    } else if (nums[i] < prev) {
      return `${key}: ${nums[i]} 不能小于 ${prev}`
    }
    prev = Math.max(prev, nums[i])
  }
  return ''
}

const checkGroup = (key: keyof typeof CRONTAB_KEY, group: string[]): string => {
  const { itemReg } = STANDARD[key]
  for (const item of group) {
    if (new RegExp(`^${NUMBER_VAL}$`).test(item)) {
      const numerators = item.split('/')[0].match(/[0-9]+/g)
      if (!numerators) {
        return `${item} 不是标准的语法`
      }
      const numeratorError = checkNumerators(numerators, key)
      if (numeratorError) {
        return numeratorError
      }
    } else if (itemReg.test(item)) {
    } else {
      return `${item} 不是标准的语法`
    }
  }
  return ''
}

export const checkCrontab = (value: string): string => {
  const fullMatch = value.match(CRONTAB_GLOBAL_REG)
  if (!fullMatch || fullMatch.length !== 6) {
    return `${value} 不是标准的语法`
  }
  const keys = Object.keys(CRONTAB_KEY) as KEYOF_CRONTAB_KEY[]
  for (const key of keys) {
    const index = CRONTAB_KEY[key]
    const { groupReg } = STANDARD[key]
    const groupStr = fullMatch[index]
    if (!groupReg.test(groupStr)) {
      return `${groupStr} 不是标准的语法`
    }
    const errMsg = checkGroup(key, groupStr.split(','))
    if (errMsg) {
      return errMsg
    }
  }
  return ''
}