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. -
.stringify(periods) ⇒ Object
Normalize an array of parsed periods into a human readable 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.
291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 |
# File 'lib/schedulability/parser.rb', line 291 def coalesce_ranges( ints, scale ) exclude_end = EXCLUSIVE_RANGED_SCALES.include?( scale ) 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 end |
.extract_hour_ranges(ranges) ⇒ Object
Return an Array of 24-hour Integer Ranges for the specified ranges expression.
208 209 210 211 212 |
# File 'lib/schedulability/parser.rb', line 208 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.
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 |
# File 'lib/schedulability/parser.rb', line 232 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 if qualifier == 'am' && hour == 12 hour = 0 elsif qualifier == 'pm' && hour < 12 hour += 12 end else raise Schedulability::RangeError, "invalid hour value: %p" % [ time_value ] if hour < 0 || hour > 24 end return hour end |
.extract_mday_ranges(ranges) ⇒ Object
Return an Array of day-of-month Integer Ranges for the specified ranges expression.
192 193 194 195 196 |
# File 'lib/schedulability/parser.rb', line 192 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.
216 217 218 219 220 |
# File 'lib/schedulability/parser.rb', line 216 def extract_minute_ranges( ranges ) return self.extract_ranges( :minute, ranges, 0, 60 ) 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.
168 169 170 171 172 |
# File 'lib/schedulability/parser.rb', line 168 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.
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/schedulability/parser.rb', line 107 def extract_period( expression ) hash = {} scanner = StringScanner.new( expression ) negative = scanner.skip( /\s*(!|not |except )\s*/ ) 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.
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/schedulability/parser.rb', line 89 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 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.
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 |
# File 'lib/schedulability/parser.rb', line 262 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 ) 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 ) if min > max 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.
224 225 226 227 228 |
# File 'lib/schedulability/parser.rb', line 224 def extract_second_ranges( ranges ) return self.extract_ranges( :second, ranges, 0, 60 ) 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.
200 201 202 203 204 |
# File 'lib/schedulability/parser.rb', line 200 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.
176 177 178 179 180 |
# File 'lib/schedulability/parser.rb', line 176 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.
184 185 186 187 188 |
# File 'lib/schedulability/parser.rb', line 184 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.
154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/schedulability/parser.rb', line 154 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.
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 |
# File 'lib/schedulability/parser.rb', line 312 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 |
.stringify(periods) ⇒ Object
Normalize an array of parsed periods into a human readable string.
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/schedulability/parser.rb', line 60 def stringify( periods ) strings = [] periods.each do |period| period_string = [] period.sort_by{|k, v| k}.each do |scale, ranges| range_string = String.new( encoding: 'utf-8' ) range_string << "%s { " % [ scale.to_s ] range_strings = ranges.each_with_object( [] ).each do |range, acc| if range.min == range.max acc << range.min elsif range.exclude_end? acc << "%d-%d" % [ range.min, range.max + 1 ] else acc << "%d-%d" % [ range.min, range.max ] end end range_string << range_strings.join( ' ' ) << " }" period_string << range_string end strings << period_string.join( ' ' ) end return strings.join( ', ' ) end |
.strip_leading_zeros(val) ⇒ Object
Return a copy of the specified val with any leading zeros stripped. If the resulting string is empty, return “0”.
331 332 333 |
# File 'lib/schedulability/parser.rb', line 331 def strip_leading_zeros( val ) return val.sub( /\A0+(?!$)/, '' ) end |