aws-cron-parser 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +32 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +117 -0
  5. data/lib/aws_cron_parser/describer/cron_composer.rb +198 -0
  6. data/lib/aws_cron_parser/describer/day_composer.rb +74 -0
  7. data/lib/aws_cron_parser/describer/field_describers/base.rb +108 -0
  8. data/lib/aws_cron_parser/describer/field_describers/day_of_month.rb +88 -0
  9. data/lib/aws_cron_parser/describer/field_describers/day_of_week.rb +143 -0
  10. data/lib/aws_cron_parser/describer/field_describers/month.rb +92 -0
  11. data/lib/aws_cron_parser/describer/field_describers/time_field.rb +101 -0
  12. data/lib/aws_cron_parser/describer/month_composer.rb +29 -0
  13. data/lib/aws_cron_parser/describer/template.en.yml +183 -0
  14. data/lib/aws_cron_parser/describer/template.rb +87 -0
  15. data/lib/aws_cron_parser/describer/time_composer.rb +255 -0
  16. data/lib/aws_cron_parser/describer.rb +131 -0
  17. data/lib/aws_cron_parser/fugit_adapter.rb +64 -0
  18. data/lib/aws_cron_parser/helpers/calendar.rb +115 -0
  19. data/lib/aws_cron_parser/helpers/clock.rb +50 -0
  20. data/lib/aws_cron_parser/helpers/cron_expression.rb +277 -0
  21. data/lib/aws_cron_parser/helpers/numbers.rb +32 -0
  22. data/lib/aws_cron_parser/helpers.rb +17 -0
  23. data/lib/aws_cron_parser/parser.rb +152 -0
  24. data/lib/aws_cron_parser/parsers/aws_at.rb +32 -0
  25. data/lib/aws_cron_parser/parsers/aws_cron.rb +413 -0
  26. data/lib/aws_cron_parser/parsers/aws_rate.rb +33 -0
  27. data/lib/aws_cron_parser/parsers/base.rb +21 -0
  28. data/lib/aws_cron_parser/parsers/quartz_cron.rb +21 -0
  29. data/lib/aws_cron_parser/validator.rb +31 -0
  30. data/lib/aws_cron_parser/validators/aws_at_validator.rb +28 -0
  31. data/lib/aws_cron_parser/validators/aws_cron_validator.rb +212 -0
  32. data/lib/aws_cron_parser/validators/aws_macro_validator.rb +13 -0
  33. data/lib/aws_cron_parser/validators/aws_rate_validator.rb +17 -0
  34. data/lib/aws_cron_parser/validators/base.rb +23 -0
  35. data/lib/aws_cron_parser/validators/quartz_cron_validator.rb +132 -0
  36. data/lib/aws_cron_parser/version.rb +5 -0
  37. data/lib/aws_cron_parser.rb +48 -0
  38. metadata +127 -0
@@ -0,0 +1,212 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AWSCronParser
4
+ module Validators
5
+ class AWSCronValidator < Base
6
+ def validate
7
+ validate_aws_cron
8
+
9
+ true
10
+ end
11
+
12
+ private
13
+
14
+ # Override method inherited from Helpers::CronExpression
15
+ def normalize_source(source)
16
+ normalized = super(source)
17
+
18
+ # Handle 6-field standard with seconds by dropping seconds, to make range validation easier
19
+ tokens = normalized.split(/\s+/)
20
+ if tokens.length == 6 && tokens.none? { |t| t.include?('?') }
21
+ normalized = tokens[1..5].join(' ')
22
+ end
23
+
24
+ normalized
25
+ end
26
+
27
+ def validate_aws_cron
28
+ fields = validate_cron_wrapper_format(source)
29
+
30
+ # Pull out fields (with/without seconds)
31
+ if fields.length == 6
32
+ minute, hour, dom, month, dow, _year = fields
33
+ else
34
+ _second, minute, hour, dom, month, dow, _year = fields
35
+ end
36
+
37
+ # Check for invalid modifiers in day-of-month field
38
+ if dom&.include?('#')
39
+ raise ArgumentError, 'day-of-month field cannot contain # (only day-of-week can)'
40
+ end
41
+
42
+ # Validate ranges for minute, hour, month
43
+ validate_range(minute, 0..59, 'minute')
44
+ validate_range(hour, 0..23, 'hour')
45
+ validate_range(normalize_month_field(month), 1..12, 'month')
46
+
47
+ # Special patterns: nth weekday (#) and last weekday (L)
48
+ if dow&.include?('#')
49
+ validate_special_cron(source, 'nth')
50
+ elsif dow&.include?('L')
51
+ validate_special_cron(source, 'last')
52
+ else
53
+ # Validate weekday_only/day_only constraints
54
+ validate_special_cron(source, 'pattern')
55
+ end
56
+ end
57
+
58
+ def validate_cron_wrapper_format(source)
59
+ return nil unless (cron_match = source.match(AWS_CRON_REGEX))
60
+
61
+ content = cron_match[:expression].strip
62
+ fields = content.split(/\s+/)
63
+
64
+ raise ArgumentError, "cron() requires 6 or 7 fields, got #{fields.length}" unless [6, 7].include?(fields.length)
65
+
66
+ # AWS rule: ? cannot be used in both day-of-month and day-of-week
67
+ # Fields order within cron(): [second] minute hour day-of-month month day-of-week year
68
+ # For 6 fields: minute hour day-of-month month day-of-week year (no seconds)
69
+ day_of_month = fields.length == 6 ? fields[2] : fields[3]
70
+ day_of_week = fields.length == 6 ? fields[4] : fields[5]
71
+ if day_of_month == '?' && day_of_week == '?'
72
+ raise ArgumentError, '? can only be used in one of day-of-month or day-of-week fields'
73
+ end
74
+
75
+ year_field = fields[-1]
76
+ raise ArgumentError, 'Year cannot have L, #, or W' if /[L#W]/.match?(year_field)
77
+
78
+ unless /^\*$|^\d+$|^\d+-\d+$|^\d+,\d+$/.match?(year_field)
79
+ raise ArgumentError, 'Year must be *, number, range, or list'
80
+ end
81
+
82
+ fields
83
+ end
84
+
85
+ def validate_special_cron(source, type)
86
+ tokens = normalize_source(source).split(/\s+/)
87
+ raise ArgumentError, 'Invalid CRON expression' unless (5..6).cover?(tokens.length)
88
+
89
+ minute, hour, dom, month, dow = tokens.length == 5 ? tokens[0..4] : tokens[1..5]
90
+
91
+ # For non-AWS expressions with special chars, validate ? usage
92
+ unless source.start_with?('cron(')
93
+ invalid_q_mark = false
94
+ [minute, hour, month].each do |field|
95
+ invalid_q_mark = true if field.include?('?')
96
+ end
97
+ if dom.include?('?') && !dow.match?(/[L#]/)
98
+ invalid_q_mark = true
99
+ end
100
+ invalid_q_mark = true if dow.include?('?')
101
+
102
+ raise ArgumentError, '? is not valid in standard CRON expressions' if invalid_q_mark
103
+ end
104
+
105
+ if source.start_with?('cron(') && dom == '?' && dow == '?'
106
+ raise ArgumentError, '? can only be used in one of day-of-month or day-of-week fields'
107
+ end
108
+
109
+ case type
110
+ when 'nth'
111
+ raise ArgumentError, 'day-of-month must be ? or *' unless ['?', '*'].include?(dom)
112
+
113
+ normalized = normalize_weekday_in_nth_spec(dow, is_aws: true)
114
+ match = normalized.match(NTH_WEEKDAY_REGEX)
115
+ raise ArgumentError, "Invalid nth weekday: #{dow}" unless match
116
+
117
+ weekday_num = match[:weekday].to_i
118
+ occurrence_num = match[:occurrence].to_i
119
+ # Allow 0..7 to be tolerant with sources that use 0 for Sunday; AWS uses 1..7, standard may use 0 or 7.
120
+ raise ArgumentError, "weekday must be 0-7, got #{weekday_num}" unless (0..7).cover?(weekday_num)
121
+ raise ArgumentError, "occurrence must be 1-5, got #{occurrence_num}" unless (1..5).cover?(occurrence_num)
122
+ when 'last'
123
+ raise ArgumentError, 'day-of-month must be ? or *' unless ['?', '*'].include?(dom)
124
+
125
+ normalized = normalize_weekday_in_last_spec(dow)
126
+ match = normalized.match(LAST_WEEKDAY_REGEX)
127
+ raise ArgumentError, "Invalid last weekday: #{dow}" unless match
128
+
129
+ weekday_num = match[:weekday].to_i
130
+ raise ArgumentError, "weekday must be 0-7, got #{weekday_num}" unless (0..7).cover?(weekday_num)
131
+ when 'pattern'
132
+ raise ArgumentError, 'day-of-month must be ?' if weekday_only?(source) && dom != '?'
133
+ raise ArgumentError, 'day-of-week must be ?' if day_only?(source) && dow != '?'
134
+ end
135
+
136
+ validate_range(minute, 0..59, 'minute')
137
+ validate_range(hour, 0..23, 'hour')
138
+ validate_range(normalize_month_field(month), 1..12, 'month')
139
+
140
+ # Validate weekday range based on CRON format (skip validation for special patterns)
141
+ # Skip if the dow field contains special modifiers (L, #, W) as they're already validated above
142
+ unless dow.match?(/[L#W]/)
143
+ weekday_range = source.start_with?('cron(') ? 1..7 : 0..6
144
+ validate_range(normalize_weekday_field(dow, is_aws: true), weekday_range, 'day-of-week')
145
+ end
146
+
147
+ true
148
+ end
149
+
150
+ def validate_range(field, range, name)
151
+ return if ['*', '?'].include?(field)
152
+
153
+ # Handle step syntax like "*/5" or "0/5"
154
+ if field.include?('/')
155
+ start, step = field.split('/', 2)
156
+ raise ArgumentError, "Invalid step syntax: #{field}" unless start && step
157
+
158
+ # Validate start value if it's not '*' or a range
159
+ unless start == '*' || start.include?('-') || start.include?(',')
160
+ raise ArgumentError, "Invalid start value in step: #{start}" unless start.match?(/^\d+$/)
161
+
162
+ start_val = start.to_i
163
+ raise ArgumentError, "#{name} start value #{start_val} out of range" unless range.include?(start_val)
164
+ end
165
+
166
+ # Validate step value
167
+ raise ArgumentError, "Invalid step value: #{step}" unless step.match?(/^\d+$/)
168
+
169
+ step_val = step.to_i
170
+ raise ArgumentError, "Step value must be positive, got #{step_val}" unless step_val.positive?
171
+
172
+ return
173
+ end
174
+
175
+ # Handle ranges like "1-5"
176
+ if field.include?('-')
177
+ start_str, end_str = field.split('-', 2)
178
+ raise ArgumentError, "Invalid range syntax: #{field}" unless start_str && end_str
179
+
180
+ start_val = start_str.to_i
181
+ end_val = end_str.to_i
182
+ raise ArgumentError, "#{name} range start #{start_val} out of range" unless range.include?(start_val)
183
+ raise ArgumentError, "#{name} range end #{end_val} out of range" unless range.include?(end_val)
184
+
185
+ # Allow reverse ranges for permissiveness
186
+
187
+ return
188
+ end
189
+
190
+ # Handle lists like "1,3,5"
191
+ if field.include?(',')
192
+ values = field.split(',')
193
+ values.each do |value_str|
194
+ next if value_str.strip.empty?
195
+ raise ArgumentError, "Invalid list value: #{value_str}" unless value_str.match?(/^\d+$/)
196
+
197
+ value = value_str.to_i
198
+ raise ArgumentError, "#{name} #{value} out of range" unless range.include?(value)
199
+ end
200
+
201
+ return
202
+ end
203
+
204
+ # Handle simple numeric values
205
+ return unless field.match?(/^\d+$/)
206
+
207
+ value = field.to_i
208
+ raise ArgumentError, "#{name} #{value} out of range" unless range.include?(value)
209
+ end
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AWSCronParser
4
+ module Validators
5
+ class AWSMacroValidator < Base
6
+ def validate
7
+ raise ArgumentError, 'Invalid macro expression' unless MACRO_REGEX.match?(source)
8
+
9
+ true
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AWSCronParser
4
+ module Validators
5
+ class AWSRateValidator < Base
6
+ def validate
7
+ match = source.match(RATE_REGEX)
8
+ raise ArgumentError, 'Invalid rate expression' unless match
9
+
10
+ value = match[1].to_i
11
+ raise ArgumentError, 'Rate value must be positive' if value <= 0
12
+
13
+ true
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AWSCronParser
4
+ module Validators
5
+ class Base
6
+ include AWSCronParser::Helpers
7
+
8
+ attr_reader :source
9
+
10
+ def initialize(source)
11
+ @source = source
12
+ end
13
+
14
+ def self.validate(source)
15
+ new(source).validate
16
+ end
17
+
18
+ def validate
19
+ raise NotImplementedError, 'This method should be implemented in a subclass'
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AWSCronParser
4
+ module Validators
5
+ class QuartzCronValidator < Base
6
+ def validate
7
+ parts = @source.split
8
+
9
+ # Validate field count (5 or 6 fields)
10
+ unless (5..6).cover?(parts.length)
11
+ raise ArgumentError, "Invalid CRON format: expected 5 or 6 fields, got #{parts.length}"
12
+ end
13
+
14
+ # Define field ranges and names
15
+ field_ranges = if parts.length == 6
16
+ [{ min: 0, max: 59, name: 'second' },
17
+ { min: 0, max: 59, name: 'minute' },
18
+ { min: 0, max: 23, name: 'hour' },
19
+ { min: 1, max: 31, name: 'day-of-month' },
20
+ { min: 1, max: 12, name: 'month' },
21
+ { min: 0, max: 7, name: 'day-of-week' }]
22
+ else
23
+ [{ min: 0, max: 59, name: 'minute' },
24
+ { min: 0, max: 23, name: 'hour' },
25
+ { min: 1, max: 31, name: 'day-of-month' },
26
+ { min: 1, max: 12, name: 'month' },
27
+ { min: 0, max: 7, name: 'day-of-week' }]
28
+ end
29
+
30
+ # Check for AWS-specific characters and validate ranges
31
+ aws_chars = %w[L W # ?]
32
+ parts.each_with_index do |field, index|
33
+ range_info = field_ranges[index]
34
+ field_name = range_info[:name]
35
+
36
+ # Normalize month and weekday fields before validation
37
+ normalized_field = if field_name == 'month'
38
+ normalize_month_field(field)
39
+ elsif field_name == 'day-of-week'
40
+ normalize_weekday_field(field, is_aws: false)
41
+ else
42
+ field
43
+ end
44
+
45
+ # Check for AWS-specific characters
46
+ aws_chars.each do |char|
47
+ next unless field.include?(char)
48
+
49
+ msg = "#{field_name} field cannot contain '#{char}'. " \
50
+ 'Use cron() wrapper for AWS expressions.'
51
+ raise ArgumentError, msg
52
+ end
53
+
54
+ # Validate numeric values in ranges (simple check for obvious errors)
55
+ validate_field_values(normalized_field, range_info)
56
+ end
57
+
58
+ # Use FugitAdapter for comprehensive validation
59
+ AWSCronParser::FugitAdapter.new(@source)
60
+ true
61
+ rescue ArgumentError => e
62
+ raise ArgumentError, e.message
63
+ end
64
+
65
+ private
66
+
67
+ # Validate individual numeric values in a field
68
+ def validate_field_values(field, range_info)
69
+ return if ['*', '?'].include?(field) # Wildcard/question mark is always valid
70
+
71
+ min_val = range_info[:min]
72
+ max_val = range_info[:max]
73
+ field_name = range_info[:name]
74
+
75
+ # Parse and validate each value (simple approach, let fugit handle complex cases)
76
+ field.split(',').each do |part|
77
+ part = part.strip
78
+ next if part.empty?
79
+
80
+ # Check for step of 0 (will cause ZeroDivisionError in fugit)
81
+ if part.include?('/')
82
+ step_parts = part.split('/')
83
+ step_val = step_parts[-1].to_i
84
+
85
+ raise ArgumentError, "Step value must be positive, got #{step_val}" if step_val.zero?
86
+
87
+ # For ranges like "1-30/0", also check if start is in range
88
+ if step_parts[0].include?('-')
89
+ range_start, range_end = step_parts[0].split('-').map(&:to_i)
90
+ unless (min_val..max_val).cover?(range_start)
91
+ raise ArgumentError, "#{field_name} #{range_start} out of range"
92
+ end
93
+
94
+ unless (min_val..max_val).cover?(range_end)
95
+ raise ArgumentError, "#{field_name} #{range_end} out of range"
96
+ end
97
+ elsif step_parts[0] != '*' && step_parts[0] != '?'
98
+ start_val = step_parts[0].to_i
99
+ unless (min_val..max_val).cover?(start_val)
100
+ raise ArgumentError, "#{field_name} #{start_val} out of range"
101
+ end
102
+ end
103
+
104
+ next
105
+ end
106
+
107
+ # Handle ranges like "1-5"
108
+ if part.include?('-')
109
+ range_start, range_end = part.split('-').map(&:to_i)
110
+ unless (min_val..max_val).cover?(range_start)
111
+ raise ArgumentError, "#{field_name} #{range_start} out of range"
112
+ end
113
+
114
+ unless (min_val..max_val).cover?(range_end)
115
+ raise ArgumentError, "#{field_name} #{range_end} out of range"
116
+ end
117
+
118
+ next
119
+ end
120
+
121
+ # Single numeric value
122
+ val = part.to_i
123
+ next if val.zero? && part != '0' # Not a number, fugit will catch it
124
+
125
+ unless (min_val..max_val).cover?(val)
126
+ raise ArgumentError, "#{field_name} #{val} out of range"
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AWSCronParser
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+ require 'set'
5
+ require 'yaml'
6
+ require 'fugit'
7
+ require 'zeitwerk'
8
+ # The whole core_ext set, not a hand-picked subset: this code was extracted from
9
+ # a Rails app and leans on blank?/present?/exclude?/delegate/durations/Time.zone
10
+ # across paths the suite does not all cover. The AS framework itself stays out.
11
+ require 'active_support'
12
+ require 'active_support/core_ext'
13
+
14
+ loader = Zeitwerk::Loader.for_gem
15
+ loader.inflector.inflect(
16
+ 'aws_cron_parser' => 'AWSCronParser',
17
+ 'aws_at' => 'AWSAt',
18
+ 'aws_cron' => 'AWSCron',
19
+ 'aws_rate' => 'AWSRate',
20
+ 'aws_at_validator' => 'AWSAtValidator',
21
+ 'aws_cron_validator' => 'AWSCronValidator',
22
+ 'aws_macro_validator' => 'AWSMacroValidator',
23
+ 'aws_rate_validator' => 'AWSRateValidator'
24
+ )
25
+ loader.ignore("#{__dir__}/aws_cron_parser/version.rb")
26
+ loader.setup
27
+
28
+ require_relative 'aws_cron_parser/version'
29
+
30
+ # ActiveSupport leaves Time.zone nil until a host application sets it, and the
31
+ # parsers build zone-aware times. Default it to UTC so the gem works outside
32
+ # Rails; Rails' own time zone initializer runs later and overrides this.
33
+ Time.zone_default ||= ActiveSupport::TimeZone['UTC']
34
+
35
+ module AWSCronParser
36
+ def self.new(expression, time_source = Time)
37
+ Parser.new(expression, time_source)
38
+ end
39
+
40
+ def self.parse(...)
41
+ new(...)
42
+ end
43
+ end
44
+
45
+ # Exposed so hosts that boot eagerly (or the test suite) can force every
46
+ # constant to resolve instead of discovering a bad autoload path at runtime.
47
+ AWSCronParser::LOADER = loader
48
+
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aws-cron-parser
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Washington Silva
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activesupport
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.1'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '7.1'
26
+ - !ruby/object:Gem::Dependency
27
+ name: fugit
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.11'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.11'
40
+ - !ruby/object:Gem::Dependency
41
+ name: zeitwerk
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '2.6'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '2.6'
54
+ description: |-
55
+ AWSCronParser parses the scheduling expressions accepted by AWS EventBridge and
56
+ Systems Manager - cron(...) with 6 fields, rate(...), @macros and at(...) - as well
57
+ as Quartz and standard 5-field CRON. It computes next/previous occurrences, validates
58
+ expressions with actionable error messages, and renders them as English descriptions
59
+ such as "Every weekday at 9:00 AM".
60
+ email:
61
+ - washington.silva@skie.io
62
+ executables: []
63
+ extensions: []
64
+ extra_rdoc_files: []
65
+ files:
66
+ - CHANGELOG.md
67
+ - LICENSE.txt
68
+ - README.md
69
+ - lib/aws_cron_parser.rb
70
+ - lib/aws_cron_parser/describer.rb
71
+ - lib/aws_cron_parser/describer/cron_composer.rb
72
+ - lib/aws_cron_parser/describer/day_composer.rb
73
+ - lib/aws_cron_parser/describer/field_describers/base.rb
74
+ - lib/aws_cron_parser/describer/field_describers/day_of_month.rb
75
+ - lib/aws_cron_parser/describer/field_describers/day_of_week.rb
76
+ - lib/aws_cron_parser/describer/field_describers/month.rb
77
+ - lib/aws_cron_parser/describer/field_describers/time_field.rb
78
+ - lib/aws_cron_parser/describer/month_composer.rb
79
+ - lib/aws_cron_parser/describer/template.en.yml
80
+ - lib/aws_cron_parser/describer/template.rb
81
+ - lib/aws_cron_parser/describer/time_composer.rb
82
+ - lib/aws_cron_parser/fugit_adapter.rb
83
+ - lib/aws_cron_parser/helpers.rb
84
+ - lib/aws_cron_parser/helpers/calendar.rb
85
+ - lib/aws_cron_parser/helpers/clock.rb
86
+ - lib/aws_cron_parser/helpers/cron_expression.rb
87
+ - lib/aws_cron_parser/helpers/numbers.rb
88
+ - lib/aws_cron_parser/parser.rb
89
+ - lib/aws_cron_parser/parsers/aws_at.rb
90
+ - lib/aws_cron_parser/parsers/aws_cron.rb
91
+ - lib/aws_cron_parser/parsers/aws_rate.rb
92
+ - lib/aws_cron_parser/parsers/base.rb
93
+ - lib/aws_cron_parser/parsers/quartz_cron.rb
94
+ - lib/aws_cron_parser/validator.rb
95
+ - lib/aws_cron_parser/validators/aws_at_validator.rb
96
+ - lib/aws_cron_parser/validators/aws_cron_validator.rb
97
+ - lib/aws_cron_parser/validators/aws_macro_validator.rb
98
+ - lib/aws_cron_parser/validators/aws_rate_validator.rb
99
+ - lib/aws_cron_parser/validators/base.rb
100
+ - lib/aws_cron_parser/validators/quartz_cron_validator.rb
101
+ - lib/aws_cron_parser/version.rb
102
+ homepage: https://github.com/w-osilva/aws-cron-parser
103
+ licenses:
104
+ - MIT
105
+ metadata:
106
+ source_code_uri: https://github.com/w-osilva/aws-cron-parser
107
+ changelog_uri: https://github.com/w-osilva/aws-cron-parser/blob/main/CHANGELOG.md
108
+ rubygems_mfa_required: 'true'
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: 3.2.0
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubygems_version: 3.6.9
124
+ specification_version: 4
125
+ summary: Parser, validator and human-readable describer for AWS, Quartz and standard
126
+ CRON expressions
127
+ test_files: []