Module: Kaal::CronUtils

Defined in:
lib/kaal/utils/cron_utils.rb

Overview

Utility helpers for cron expression validation, simplification, and linting.

Constant Summary collapse

MACRO_MAP =
{
  '@yearly' => '0 0 1 1 *',
  '@annually' => '0 0 1 1 *',
  '@monthly' => '0 0 1 * *',
  '@weekly' => '0 0 * * 0',
  '@daily' => '0 0 * * *',
  '@midnight' => '0 0 * * *',
  '@hourly' => '0 * * * *'
}.freeze
CANONICAL_MACROS =
{
  '0 0 1 1 *' => '@yearly',
  '0 0 1 * *' => '@monthly',
  '0 0 * * 0' => '@weekly',
  '0 0 * * 7' => '@weekly',
  '0 0 * * *' => '@daily',
  '0 * * * *' => '@hourly'
}.freeze
FIELD_SPECS =
[
  { name: 'minute', min: 0, max: 59, names: nil },
  { name: 'hour', min: 0, max: 23, names: nil },
  { name: 'day-of-month', min: 1, max: 31, names: nil },
  {
    name: 'month',
    min: 1,
    max: 12,
    names: {
      'jan' => 1, 'feb' => 2, 'mar' => 3, 'apr' => 4, 'may' => 5, 'jun' => 6,
      'jul' => 7, 'aug' => 8, 'sep' => 9, 'oct' => 10, 'nov' => 11, 'dec' => 12
    }
  },
  {
    name: 'day-of-week',
    min: 0,
    max: 7,
    names: {
      'sun' => 0, 'mon' => 1, 'tue' => 2, 'wed' => 3, 'thu' => 4, 'fri' => 5, 'sat' => 6
    }
  }
].freeze

Class Method Summary collapse

Class Method Details

.invalid_expression_error_message(expression) ⇒ Object



72
73
74
75
# File 'lib/kaal/utils/cron_utils.rb', line 72

def invalid_expression_error_message(expression)
  shown = expression.empty? ? '<empty>' : expression
  "Invalid cron expression '#{shown}'. Examples: '*/5 * * * *', '@daily'."
end

.lint(expression) ⇒ Object



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/kaal/utils/cron_utils.rb', line 107

def lint(expression)
  normalized = safe_normalize_expression(expression)
  return [invalid_expression_error_message('')] unless normalized

  invalid_message = invalid_expression_error_message(normalized)
  return [invalid_message] if normalized.empty?

  if macro?(normalized)
    downcased = normalized.downcase
    return [] if MACRO_MAP.key?(downcased)

    return [unsupported_macro_error_message(normalized)]
  end

  return [field_count_message(normalized)] unless five_fields?(normalized)

  warnings = normalized.split.each_with_index.flat_map do |field, index|
    lint_field(field, FIELD_SPECS[index])
  end

  warnings << invalid_message unless valid?(normalized)
  warnings.uniq
end

.normalize_expression(expression) ⇒ Object



57
58
59
# File 'lib/kaal/utils/cron_utils.rb', line 57

def normalize_expression(expression)
  expression.to_s.strip.gsub(/\s+/, ' ')
end

.safe_normalize_expression(expression) ⇒ Object



61
62
63
64
65
# File 'lib/kaal/utils/cron_utils.rb', line 61

def safe_normalize_expression(expression)
  normalize_expression(expression)
rescue StandardError
  nil
end

.simplify(expression) ⇒ Object

Raises:

  • (ArgumentError)


90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/kaal/utils/cron_utils.rb', line 90

def simplify(expression)
  normalized = safe_normalize_expression(expression)
  raise ArgumentError, invalid_expression_error_message('') unless normalized

  downcased = normalized.downcase

  if macro?(normalized)
    return canonical_macro_for(downcased) if MACRO_MAP.key?(downcased)

    raise ArgumentError, unsupported_macro_error_message(normalized)
  end

  raise ArgumentError, invalid_expression_error_message(normalized) unless valid?(normalized)

  CANONICAL_MACROS.fetch(normalized, normalized)
end

.unsupported_macro_error_message(expression) ⇒ Object



67
68
69
70
# File 'lib/kaal/utils/cron_utils.rb', line 67

def unsupported_macro_error_message(expression)
  supported = MACRO_MAP.keys.sort.join(', ')
  "Unsupported cron macro '#{expression}'. Supported macros: #{supported}."
end

.valid?(expression) ⇒ Boolean

Returns:

  • (Boolean)


77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/kaal/utils/cron_utils.rb', line 77

def valid?(expression)
  normalized = normalize_expression(expression)
  return false if normalized.empty?

  return MACRO_MAP.key?(normalized.downcase) if macro?(normalized)

  return false unless five_fields?(normalized)

  !!Fugit.parse_cron(normalized)
rescue StandardError
  false
end