Module: Schedulability::Parser
- Extended by:
- Loggability
- Defined in:
- lib/schedulability/parser.rb
Overview
A collection of parsing functions for Schedulability schedule syntax.
Constant Summary collapse
- VALID_SCALES =
A Regexp that will match valid period scale codes
Regexp.union(%w[ year yr month mo week wk yday yd mday md wday wd hour hr minute min second sec ])
- EXCLUSIVE_RANGED_SCALES =
Scales that are parsed with exclusive end values.
%i[ hour hr minute min second sec ]
- PERIOD_PATTERN =
The Regexp for matching value periods
%r: (\A|\G\s+) # beginning of the string or the end of the last match (?<scale> #{VALID_SCALES} ) s? # Optional plural sugar \s* \{ (?<ranges>.*?) \} :ix- TIME_VALUE_PATTERN =
Pattern for matching
hour-scale values /\A(?<hour>\d+)(?<qualifier>am|pm|noon)?\z/i- ABBR_DAYNAMES =
Downcased day-name Arrays
Date::ABBR_DAYNAMES.map( &:downcase )
- DAYNAMES =
Date::DAYNAMES.map( &:downcase )
- ABBR_MONTHNAMES =
Downcased month-name Arrays
Date::ABBR_MONTHNAMES.map {|val| val && val.downcase }
- MONTHNAMES =
Date::MONTHNAMES.map {|val| val && val.downcase }
Class Method Summary collapse
-
.coalesce_ranges(ints, scale) ⇒ Object
Coalese an Array of non-contiguous Range objects from the specified
intsforscale. -
.extract_hour_ranges(ranges) ⇒ Object
Return an Array of 24-hour Integer Ranges for the specified
rangesexpression. -
.extract_hour_value(time_value) ⇒ Object
Return the integer equivalent of the specified
time_value. -
.extract_mday_ranges(ranges) ⇒ Object
Return an Array of day-of-month Integer Ranges for the specified
rangesexpression. -
.extract_minute_ranges(ranges) ⇒ Object
Return an Array of Integer minute Ranges for the specified
rangesexpression. -
.extract_month_ranges(ranges) ⇒ Object
Return an Array of month Integer Ranges for the specified
rangesexpression. -
.extract_period(expression) ⇒ Object
Return the specified period
expressionas a Hash of Ranges keyed by scale. -
.extract_periods(expression) ⇒ Object
Scan
expressionfor periods and return them in an Array. -
.extract_ranges(scale, ranges, minval, maxval) ⇒ Object
Extract an Array of Ranges from the specified
rangesstring using the givenindex_arraysfor non-numeric values. -
.extract_second_ranges(ranges) ⇒ Object
Return an Array of Integer second Ranges for the specified
rangesexpression. -
.extract_wday_ranges(ranges) ⇒ Object
Return an Array of weekday Integer Ranges for the specified
rangesexpression. -
.extract_week_ranges(ranges) ⇒ Object
Return an Array of week-of-month Integer Ranges for the specified
rangesexpression. -
.extract_yday_ranges(ranges) ⇒ Object
Return an Array of day-of-year Integer Ranges for the specified
rangesexpression. -
.extract_year_ranges(ranges) ⇒ Object
Return an Array of year integer Ranges for the specified
rangesexpression. -
.map_integer_value(scale, value, index_arrays) ⇒ Object
Map a
valuefrom a period’s range to an Integer, using the specifiedindex_arraysif it doesn’t look like an integer string. -
.strip_leading_zeros(val) ⇒ Object
Return a copy of the specified
valwith any leading zeros stripped.
Class Method Details
.coalesce_ranges(ints, scale) ⇒ Object
Coalese an Array of non-contiguous Range objects from the specified ints for scale.
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 |
# File 'lib/schedulability/parser.rb', line 262 def coalesce_ranges( ints, scale ) exclude_end = EXCLUSIVE_RANGED_SCALES.include?( scale ) self.log.debug "Coalescing %d ints to Ranges (%p, %s)" % [ ints.size, ints, exclude_end ? "exclusive" : "inclusive" ] ints.flatten! return [] if ints.empty? prev = ints[0] range_ints = ints.sort.slice_before do |v| prev, prev2 = v, prev prev2.succ != v end return range_ints.map do |values| last_val = values.last last_val += 1 if exclude_end Range.new( values.first, last_val, exclude_end ) end.tap do |ranges| self.log.debug "Coalesced range integers to Ranges: %p" % [ ranges ] end end |
.extract_hour_ranges(ranges) ⇒ Object
Return an Array of 24-hour Integer Ranges for the specified ranges expression.
181 182 183 184 185 |
# File 'lib/schedulability/parser.rb', line 181 def extract_hour_ranges( ranges ) return self.extract_ranges( :hour, ranges, 0, 24 ) do |val| self.extract_hour_value( val ) end end |
.extract_hour_value(time_value) ⇒ Object
Return the integer equivalent of the specified time_value.
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/schedulability/parser.rb', line 205 def extract_hour_value( time_value ) unless match = TIME_VALUE_PATTERN.match( time_value ) raise Schedulability::ParseError, "invalid hour range: %p" % [ time_value ] end hour, qualifier = match[:hour], match[:qualifier] hour = hour.to_i if qualifier raise Schedulability::RangeError, "invalid hour value: %p" % [ time_value ] if hour > 12 hour += 12 if qualifier == 'pm' && hour < 12 else raise Schedulability::RangeError, "invalid hour value: %p" % [ time_value ] if hour > 24 hour = 24 if hour.zero? end return hour end |
.extract_mday_ranges(ranges) ⇒ Object
Return an Array of day-of-month Integer Ranges for the specified ranges expression.
165 166 167 168 169 |
# File 'lib/schedulability/parser.rb', line 165 def extract_mday_ranges( ranges ) return self.extract_ranges( :mday, ranges, 0, 31 ) do |val| Integer( strip_leading_zeros(val) ) end end |
.extract_minute_ranges(ranges) ⇒ Object
Return an Array of Integer minute Ranges for the specified ranges expression.
189 190 191 192 193 |
# File 'lib/schedulability/parser.rb', line 189 def extract_minute_ranges( ranges ) return self.extract_ranges( :minute, ranges, 0, 59 ) do |val| Integer( strip_leading_zeros(val) ) end end |
.extract_month_ranges(ranges) ⇒ Object
Return an Array of month Integer Ranges for the specified ranges expression.
141 142 143 144 145 |
# File 'lib/schedulability/parser.rb', line 141 def extract_month_ranges( ranges ) return self.extract_ranges( :month, ranges, 0, MONTHNAMES.size - 1 ) do |val| self.map_integer_value( :month, val, [ABBR_MONTHNAMES, MONTHNAMES] ) end end |
.extract_period(expression) ⇒ Object
Return the specified period expression as a Hash of Ranges keyed by scale.
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/schedulability/parser.rb', line 79 def extract_period( expression ) hash = {} scanner = StringScanner.new( expression ) negative = scanner.skip( /\s*(!|not |except )\s*/ ) self.log.debug "Period %p is %snegative!" % [ expression, negative ? "" : "not " ] while scanner.scan( PERIOD_PATTERN ) ranges = scanner[:ranges].strip scale = scanner[:scale] case scale when 'year', 'yr' hash[:yr] = self.extract_year_ranges( ranges ) when 'month', 'mo' hash[:mo] = self.extract_month_ranges( ranges ) when 'week', 'wk' hash[:wk] = self.extract_week_ranges( ranges ) when 'yday', 'yd' hash[:yd] = self.extract_yday_ranges( ranges ) when 'mday', 'md' hash[:md] = self.extract_mday_ranges( ranges ) when 'wday', 'wd' hash[:wd] = self.extract_wday_ranges( ranges ) when 'hour', 'hr' hash[:hr] = self.extract_hour_ranges( ranges ) when 'minute', 'min' hash[:min] = self.extract_minute_ranges( ranges ) when 'second', 'sec' hash[:sec] = self.extract_second_ranges( ranges ) else # This should never happen raise ArgumentError, "Unhandled scale %p!" % [ scale ] end end unless scanner.eos? raise Schedulability::ParseError, "malformed schedule (at %d: %p)" % [ scanner.pos, scanner.rest ] end return hash, negative ensure scanner.terminate if scanner end |
.extract_periods(expression) ⇒ Object
Scan expression for periods and return them in an Array.
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/schedulability/parser.rb', line 60 def extract_periods( expression ) positive_periods = [] negative_periods = [] expression.strip.downcase.split( /\s*,\s*/ ).each do |subexpr| hash, negative = self.extract_period( subexpr ) if negative self.log.debug "Adding %p to the negative " negative_periods << hash else positive_periods << hash end end return positive_periods, negative_periods end |
.extract_ranges(scale, ranges, minval, maxval) ⇒ Object
Extract an Array of Ranges from the specified ranges string using the given index_arrays for non-numeric values. Construct the Ranges with the given minval/maxval range boundaries.
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 |
# File 'lib/schedulability/parser.rb', line 230 def extract_ranges( scale, ranges, minval, maxval ) exclude_end = EXCLUSIVE_RANGED_SCALES.include?( scale ) valid_range = Range.new( minval, maxval, exclude_end ) ints = ranges.split( /(?<!-)\s+(?!-)/ ).flat_map do |range| min, max = range.split( /\s*-\s*/, 2 ) self.log.debug "Min = %p, max = %p" % [ min, max ] min = yield( min ) raise Schedulability::ParseError, "invalid %s value: %p" % [ scale, min ] unless valid_range.cover?( min ) next [ min ] unless max max = yield( max ) raise Schedulability::ParseError, "invalid %s value: %p" % [ scale, max ] unless valid_range.cover?( max ) self.log.debug "Parsed min = %p, max = %p" % [ min, max ] if min > max self.log.debug "wrapped: %d-%d and %d-%d" % [ minval, max, min, maxval ] Range.new( minval, max, exclude_end ).to_a + Range.new( min, maxval, false ).to_a else Range.new( min, max, exclude_end ).to_a end end return self.coalesce_ranges( ints, scale ) end |
.extract_second_ranges(ranges) ⇒ Object
Return an Array of Integer second Ranges for the specified ranges expression.
197 198 199 200 201 |
# File 'lib/schedulability/parser.rb', line 197 def extract_second_ranges( ranges ) return self.extract_ranges( :second, ranges, 0, 59 ) do |val| Integer( strip_leading_zeros(val) ) end end |
.extract_wday_ranges(ranges) ⇒ Object
Return an Array of weekday Integer Ranges for the specified ranges expression.
173 174 175 176 177 |
# File 'lib/schedulability/parser.rb', line 173 def extract_wday_ranges( ranges ) return self.extract_ranges( :wday, ranges, 0, DAYNAMES.size - 1 ) do |val| self.map_integer_value( :wday, val, [ABBR_DAYNAMES, DAYNAMES] ) end end |
.extract_week_ranges(ranges) ⇒ Object
Return an Array of week-of-month Integer Ranges for the specified ranges expression.
149 150 151 152 153 |
# File 'lib/schedulability/parser.rb', line 149 def extract_week_ranges( ranges ) return self.extract_ranges( :week, ranges, 1, 5 ) do |val| Integer( strip_leading_zeros(val) ) end end |
.extract_yday_ranges(ranges) ⇒ Object
Return an Array of day-of-year Integer Ranges for the specified ranges expression.
157 158 159 160 161 |
# File 'lib/schedulability/parser.rb', line 157 def extract_yday_ranges( ranges ) return self.extract_ranges( :yday, ranges, 1, 366 ) do |val| Integer( strip_leading_zeros(val) ) end end |
.extract_year_ranges(ranges) ⇒ Object
Return an Array of year integer Ranges for the specified ranges expression.
127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/schedulability/parser.rb', line 127 def extract_year_ranges( ranges ) ranges = self.extract_ranges( :year, ranges, 2000, 9999 ) do |val| Integer( val ) end if ranges.any? {|rng| rng.end == 9999 } raise Schedulability::ParseError, "no support for wrapped year ranges" end return ranges end |
.map_integer_value(scale, value, index_arrays) ⇒ Object
Map a value from a period’s range to an Integer, using the specified index_arrays if it doesn’t look like an integer string.
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 |
# File 'lib/schedulability/parser.rb', line 287 def map_integer_value( scale, value, index_arrays ) return Integer( value ) if value =~ /\A\d+\z/ unless index = index_arrays.inject( nil ) {|res, ary| res || ary.index(value) } expected = "expected one of: %s, %d-%d" % [ index_arrays.flatten.compact.flatten.join( ', ' ), index_arrays.first.index {|val| val }, index_arrays.first.size - 1 ] raise Schedulability::ParseError, "invalid %s value: %p (%s)" % [ scale, value, expected ] end return index end |
.strip_leading_zeros(val) ⇒ Object
Return a copy of the specified val with any leading zeros stripped.
305 306 307 |
# File 'lib/schedulability/parser.rb', line 305 def strip_leading_zeros( val ) return val.sub( /\A0+/, '' ) end |