module Schedulability::Parser
A collection of parsing functions for Schedulability
schedule syntax.
Constants
- ABBR_DAYNAMES
Downcased day-name Arrays
- ABBR_MONTHNAMES
Downcased month-name Arrays
- DAYNAMES
- EXCLUSIVE_RANGED_SCALES
Scales that are parsed with exclusive end values.
- MONTHNAMES
- PERIOD_PATTERN
The Regexp for matching value periods
- TIME_VALUE_PATTERN
Pattern for matching
hour
-scale values- VALID_SCALES
A Regexp that will match valid period scale codes
Public Instance Methods
Coalese an Array of non-contiguous Range objects from the specified ints
for scale
.
# File lib/schedulability/parser.rb, line 290 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
Return an Array of 24-hour Integer Ranges for the specified ranges
expression.
# File lib/schedulability/parser.rb, line 207 def extract_hour_ranges( ranges ) return self.extract_ranges( :hour, ranges, 0, 24 ) do |val| self.extract_hour_value( val ) end end
Return the integer equivalent of the specified time_value
.
# File lib/schedulability/parser.rb, line 231 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
Return an Array of day-of-month Integer Ranges for the specified ranges
expression.
# File lib/schedulability/parser.rb, line 191 def extract_mday_ranges( ranges ) return self.extract_ranges( :mday, ranges, 0, 31 ) do |val| Integer( strip_leading_zeros(val) ) end end
Return an Array of Integer minute Ranges for the specified ranges
expression.
# File lib/schedulability/parser.rb, line 215 def extract_minute_ranges( ranges ) return self.extract_ranges( :minute, ranges, 0, 60 ) do |val| Integer( strip_leading_zeros(val) ) end end
Return an Array of month Integer Ranges for the specified ranges
expression.
# File lib/schedulability/parser.rb, line 167 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
Return the specified period expression
as a Hash of Ranges keyed by scale.
# File lib/schedulability/parser.rb, line 106 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
Scan expression
for periods and return them in an Array.
# File lib/schedulability/parser.rb, line 88 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 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.
# File lib/schedulability/parser.rb, line 261 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
Return an Array of Integer second Ranges for the specified ranges
expression.
# File lib/schedulability/parser.rb, line 223 def extract_second_ranges( ranges ) return self.extract_ranges( :second, ranges, 0, 60 ) do |val| Integer( strip_leading_zeros(val) ) end end
Return an Array of weekday Integer Ranges for the specified ranges
expression.
# File lib/schedulability/parser.rb, line 199 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
Return an Array of week-of-month Integer Ranges for the specified ranges
expression.
# File lib/schedulability/parser.rb, line 175 def extract_week_ranges( ranges ) return self.extract_ranges( :week, ranges, 1, 5 ) do |val| Integer( strip_leading_zeros(val) ) end end
Return an Array of day-of-year Integer Ranges for the specified ranges
expression.
# File lib/schedulability/parser.rb, line 183 def extract_yday_ranges( ranges ) return self.extract_ranges( :yday, ranges, 1, 366 ) do |val| Integer( strip_leading_zeros(val) ) end end
Return an Array of year integer Ranges for the specified ranges
expression.
# File lib/schedulability/parser.rb, line 153 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 a value
from a period's range to an Integer, using the specified index_arrays
if it doesn't look like an integer string.
# File lib/schedulability/parser.rb, line 311 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
Normalize an array of parsed periods into a human readable string.
# File lib/schedulability/parser.rb, line 59 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
Return a copy of the specified val
with any leading zeros stripped. If the resulting string is empty, return “0”.
# File lib/schedulability/parser.rb, line 330 def strip_leading_zeros( val ) return val.sub( /\A0+(?!$)/, '' ) end