class Cron::Parser::Field
Attributes
meaning[R]
pattern[R]
warning[R]
Public Class Methods
field_name()
click to toggle source
# File lib/cron/parser/field.rb, line 177 def self.field_name; self.new.field_name end
field_preposition(field)
click to toggle source
Returns preposition for the current field to be prepended while generating meaning (partial sentence).
# File lib/cron/parser/field.rb, line 181 def self.field_preposition(field) case field when "minute" "at" when "hour", "day_of_month", "day_of_week" "on" when "month" "in" else "at" end end
new(pattern)
click to toggle source
# File lib/cron/parser/field.rb, line 7 def initialize(pattern) raise NotImplementedError.new("'Cron::Parser::Field' can't be initialized directly".squish) if self.class == Cron::Parser::Field @pattern = pattern # for current field only @warning = nil @meaning = validate! # current field end
range_step_values(from, to, step)
click to toggle source
An algorithm calculates and returns values exist for the expression <from>-<to>/<step>.
# File lib/cron/parser/field.rb, line 196 def self.range_step_values(from, to, step) values = [from] (from..to).map do |value| value += step if (value == values.last + step) and value <= to values.push value end end values end
specifications()
click to toggle source
List of regular expressions to match the different kind of values in cron pattern fields, also produces partial meaning for the matched values
# File lib/cron/parser/field.rb, line 85 def self.specifications [ { # e.g.: "*" rule: /\A\*\Z/, yields: ->(options) do "every " + options[:unit].split("_").join(" ") end, for_fields: %w{ minute hour day_of_month month day_of_week }, can_dominate_all: true }, { # e.g.: "*/2" rule: /\A\*\/(?<step>\d+)\Z/, yields: ->(step, options) do range = [] (self.lower_bound.to_i..self.upper_bound.to_i).map do |value| range << value if value.modulo(step.to_i).zero? end list = range.any? ? range : [step] return list if options[:exclude_preposition] return self.generate_meaning(list, options[:unit]) end, for_fields: %w{ minute hour day_of_month month day_of_week } }, { # e.g.: "4" rule: /\A(?<value>\d+)\Z/, yields: ->(value, options) do return [value] if options[:exclude_preposition] return self.generate_meaning([value], options[:unit]) end, for_fields: %w{ minute hour day_of_month month day_of_week } }, { # e.g.: "3-26" rule: /\A(?<from>\d+)\-(?<to>\d+)\Z/, yields: ->(from, to, options) do range = (from..to).to_a list = range.any? ? range : [from] return list.map(&:to_i) if options[:exclude_preposition] return self.generate_meaning(list, options[:unit]) end, for_fields: %w{ minute hour day_of_month month day_of_week } }, { # e.g.: "3-26/2" rule: /\A(?<from>\d+)\-(?<to>\d+)\/(?<step>\d+)\Z/, yields: ->(from, to, step, options) do range = self.range_step_values(from.to_i, to.to_i, step.to_i) return range if options[:exclude_preposition] return self.generate_meaning(range, options[:unit]) end, for_fields: %w{ minute hour day_of_month month day_of_week } }, { # e.g.: "12/3" rule: /\A(?<value>\d+)\/\d+\Z/, yields: ->(value, options) do return [value] if options[:exclude_preposition] return self.generate_meaning([value], options[:unit]) end, for_fields: %w{ minute hour day_of_month month day_of_week }, can_halt: true } ] end
Public Instance Methods
field_name()
click to toggle source
Returns current field’s name, for e.g. ‘minute’, ‘hour’, and likewise.
# File lib/cron/parser/field.rb, line 172 def field_name self.class.name.split("::").last.downcase.sub("of", "_of_"). \ sub("field", "").downcase end
inspect()
click to toggle source
# File lib/cron/parser/field.rb, line 15 def inspect %Q{ #<#{self.class.name}:#{Object::o_hexy_id(self)}> { :pattern => "#@pattern", #{":warning => #@warning," if @warning} :meaning => "#@meaning" } }.squish end
invalid_field_error_class()
click to toggle source
Generates error class for the current cron field.
# File lib/cron/parser/field.rb, line 208 def invalid_field_error_class ("Invalid" + self.field_name.split("_").map(&:capitalize).join + \ "FieldError").classify.safe_constantize end
investigate_invalid_values!()
click to toggle source
Checks for invalid characters and values for the current field.
# File lib/cron/parser/field.rb, line 154 def investigate_invalid_values! invalids = @pattern.split(/,|\/|\-/).uniq.collect do |value| value unless self.class.allowed_values.to_a.include?(value.upcase) end.compact invalids.delete("*") err = nil if invalids.include?('') || invalids.include?(' ') err = "#{field_name} field's pattern is invalid, please run: '#{self.class}.allowed_values' to know valid values".squish elsif invalids.any? err = "value: '#{invalids.join(', ')}' not allowed for '#{field_name}' field, run: '#{self.class}.allowed_values' to know valid values".squish end raise self.invalid_field_error_class.new(err) if err end
validate!()
click to toggle source
Recognizes and validates the current field in cron pattern. FYI, all magic happens over here!
# File lib/cron/parser/field.rb, line 28 def validate! investigate_invalid_values! meaning = "" halt_loop = false match_data = nil list = [] values = @pattern.split(",") options = { unit: self.field_name, exclude_preposition: true } # iterate over all comma seperated values for current field values.map.with_index do |value, index| # look whether the value matches any of regex rule self.class.specifications.map do |spec| next unless spec[:for_fields].include? self.field_name next if (match_data = spec[:rule].match(value)).nil? # some special kind of values can dominate all other values for # current field, e.g. value such as '*' if spec[:can_dominate_all] meaning = spec[:yields].(*match_data.captures, options) halt_loop = true and break end list << spec[:yields].(*match_data.captures, options) # some values are confusing or ambiguous, e.g. '8/2' -- but crontab # allows such values, but they can halt next comma seperated values # from being parsed for the current field of cron pattern if spec[:can_halt] @warning = "'#{value}' is valid but confusing pattern" halt_loop = true break end # stop looking for other regex rules if values is matched here break if match_data end # stop iterating over comma seperated values if special-purpose flag: # `halt_loop` is set to `true` somewhere break if halt_loop end list = list.flatten.uniq.map(&:to_s).sort list = list.map(&:to_i).sort if !!(/minute|hour|day_of_month/ =~ field_name) if meaning.==("") and list.empty? raise invalid_field_error_class.new("\"#{self.field_name}\" field's pattern is invalid".squish) end meaning = self.class.generate_meaning(list, field_name) if meaning.==("") meaning end