A collection of parsing functions for Schedulability schedule syntax.
Downcased day-name Arrays
Downcased month-name Arrays
Scales that are parsed with exclusive end values.
The Regexp for matching value periods
Pattern for matching hour
-scale values
A Regexp that will match valid period scale codes
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 = ""
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