lib/fugit/nat.rb in fugit-1.3.9 vs lib/fugit/nat.rb in fugit-1.4.0

- old
+ new

@@ -1,5 +1,6 @@ +# frozen_string_literal: true module Fugit # A natural language set of parsers for fugit. # Focuses on cron expressions. The rest is better left to Chronic and friends. @@ -14,521 +15,669 @@ return nil unless s.is_a?(String) #p s; Raabro.pp(Parser.parse(s, debug: 3), colours: true) #(p s; Raabro.pp(Parser.parse(s, debug: 1), colours: true)) rescue nil - parse_crons(s, Parser.parse(s), opts) + + if slots = Parser.parse(s) + slots.to_crons(opts.merge(_s: s)) + else + nil + end end def do_parse(s, opts={}) parse(s, opts) || fail(ArgumentError.new("could not parse a nat #{s.inspect}")) end + end - protected + module Parser include Raabro - def parse_crons(s, a, opts) + one_to_nine = + %w[ one two three four five six seven eight nine ] + sixties = + %w[ zero ] + one_to_nine + + %w[ ten eleven twelve thirteen fourteen fifteen sixteen seventeen + eighteen nineteen ] + + %w[ twenty thirty fourty fifty ] + .collect { |a| + ([ nil ] + one_to_nine) + .collect { |b| [ a, b ].compact.join('-') } } + .flatten -#p a - return nil unless a + NHOURS = sixties[0, 13] + .each_with_index + .inject({}) { |h, (n, i)| h[n] = i; h } + .merge!( + 'midnight' => 0, 'oh' => 0, 'noon' => 12) + .freeze + NMINUTES = sixties + .each_with_index + .inject({}) { |h, (n, i)| h[n] = i; h } + .merge!( + "o'clock" => 0, 'hundred' => 0) + .freeze - h = a - .reverse - .inject({}) { |r, e| send("parse_#{e[0]}_elt", e, opts, r); r } - # - # the reverse ensure that in "every day at five", the - # "at five" is placed before the "every day" so that - # parse_x_elt calls have the right sequence -#p h + WEEKDAYS = ( + Fugit::Cron::Parser::WEEKDAYS + + Fugit::Cron::Parser::WEEKDS).freeze - if f = h[:_fail] - #fail ArgumentError.new(f) - return nil - end + POINTS = %w[ + minutes? mins? seconds? secs? hours? hou h ].freeze - hms = h[:hms] + INTERVALS = %w[ + seconds? minutes? hours? days? months? + sec min + s m h d M ].freeze - hours = (hms || []) - .uniq - .inject({}) { |r, hm| (r[hm[1]] ||= []) << hm[0]; r } - .inject({}) { |r, (m, hs)| (r[hs.sort] ||= []) << m; r } - .to_a - .sort_by { |hs, ms| -hs.size } - if hours.empty? - hours << (h[:dom] ? [ [ '0' ], [ '0' ] ] : [ [ '*' ], [ '*' ] ]) - end + oh = { + '1st' => 1, '2nd' => 2, '3rd' => 3, '21st' => 21, '22nd' => 22, + '23rd' => 23, '31st' => 31, + 'last' => 'L' } + (4..30) + .each { |i| oh["#{i}th"] = i.to_i } + %w[ + first second third fourth fifth sixth seventh eighth ninth tenth + eleventh twelfth thirteenth fourteenth fifteenth sixteenth seventeenth + eighteenth nineteenth twentieth twenty-first twenty-second twenty-third + twenty-fourth twenty-fifth twenty-sixth twenty-seventh twenty-eighth + twenty-ninth thirtieth thirty-first ] + .each_with_index { |e, i| oh[e] = i + 1 } + OMONTHDAYS = oh.freeze - crons = hours - .collect { |hm| assemble_cron(h.merge(hms: hm)) } + OMONTHDAY_REX = /#{OMONTHDAYS.keys.join('|')}/i.freeze + MONTHDAY_REX = /3[0-1]|[0-2]?[0-9]/.freeze + WEEKDAY_REX = /(#{WEEKDAYS.join('|')})(?=($|[-, \t]))/i.freeze + # prevent "mon" from eating "monday" + NAMED_M_REX = /#{NMINUTES.keys.join('|')}/i.freeze + NAMED_H_REX = /#{NHOURS.keys.join('|')}/i.freeze + POINT_REX = /(#{POINTS.join('|')})[ \t]+/i.freeze + INTERVAL_REX = /[ \t]*(#{INTERVALS.join('|')})/.freeze - fail ArgumentError.new( - "multiple crons in #{s.inspect} " + - "(#{crons.collect(&:original).join(' | ')})" - ) if opts[:multi] == :fail && crons.size > 1 + # + # parsers bottom to top ################################################# - if opts[:multi] == true || (opts[:multi] && opts[:multi] != :fail) - crons - else - crons.first - end - end + def _every(i); rex(nil, i, /[ \t]*every[ \t]+/i); end + def _from(i); rex(nil, i, /[ \t]*from[ \t]+/i); end + def _at(i); rex(nil, i, /[ \t]*at[ \t]+/i); end + def _on(i); rex(nil, i, /[ \t]*on[ \t]+/i); end + def _to(i); rex(nil, i, /[ \t]*to[ \t]+/i); end - def assemble_cron(h) + def _and(i); rex(nil, i, /[ \t]*and[ \t]+/i); end + def _and_or_or(i); rex(nil, i, /[ \t]*(and|or)[ \t]+/i); end + def _in_or_on(i); rex(nil, i, /(in|on)[ \t]+/i); end -#puts "ac: " + h.inspect - s = [] - s << h[:sec] if h[:sec] - s << h[:hms][1].join(',') - s << h[:hms][0].join(',') - s << (h[:dom] || '*') << (h[:mon] || '*') << (h[:dow] || '*') - s << h[:tz] if h[:tz] + def _and_or_or_or_comma(i) + rex(nil, i, /[ \t]*(,[ \t]*)?((and|or)[ \t]+|,[ \t]*)/i); end - Fugit::Cron.parse(s.join(' ')) - end + def _to_or_dash(i); + rex(nil, i, /[ \t]*-[ \t]*|[ \t]+(to|through)[ \t]+/i); end - def eone(e); e1 = e[1]; e1 == 1 ? '*' : "*/#{e1}"; end + def _day_s(i); rex(nil, i, /[ \t]*days?[ \t]+/i); end + def _the(i); rex(nil, i, /[ \t]*the[ \t]+/i); end - def parse_interval_elt(e, opts, h) + def _space(i); rex(nil, i, /[ \t]+/); end + def _sep(i); rex(nil, i, /([ \t]+|[ \t]*,[ \t]*)/); end - e1 = e[1] + def count(i); rex(:count, i, /\d+/); end - case e[2] - when 's', 'sec', 'second', 'seconds' - h[:sec] = eone(e) - when 'm', 'min', 'mins', 'minute', 'minutes' - h[:hms] ||= [ [ '*', eone(e) ] ] - when 'h', 'hour', 'hours' - hms = h[:hms] - if hms && hms.size == 1 && hms.first.first == '*' - hms.first[0] = eone(e) - elsif ! hms - h[:hms] = [ [ eone(e), 0 ] ] - end - when 'd', 'day', 'days' - h[:dom] = "*/#{e1}" if e1 > 1 - h[:hms] ||= [ [ 0, 0 ] ] - when 'w', 'week', 'weeks' - h[:_fail] = "cannot have crons for \"every #{e1} weeks\"" if e1 > 1 - h[:hms] ||= [ [ 0, 0 ] ] - h[:dow] ||= 0 - when 'M', 'month', 'months' - h[:_fail] = "cannot have crons for \"every #{e1} months\"" if e1 > 12 - h[:hms] ||= [ [ 0, 0 ] ] - h[:dom] = 1 - h[:mon] = eone(e) - when 'Y', 'y', 'year', 'years' - h[:_fail] = "cannot have crons for \"every #{e1} years\"" if e1 > 1 - h[:hms] ||= [ [ 0, 0 ] ] - h[:dom] = 1 - h[:mon] = 1 - end + def omonthday(i) + rex(:omonthday, i, OMONTHDAY_REX) end - - def parse_dow_list_elt(e, opts, h) - - h[:hms] ||= [ [ 0, 0 ] ] - h[:dow] = e[1..-1].collect(&:to_s).sort.join(',') + def monthday(i) + rex(:monthday, i, MONTHDAY_REX) end - - def parse_dow_range_elt(e, opts, h) - - h[:hms] ||= [ [ 0, 0 ] ] - h[:dow] = e[1] == e[2] ? e[1] : "#{e[1]}-#{e[2]}" + def weekday(i) + rex(:weekday, i, WEEKDAY_REX) end - def parse_day_of_month_elt(e, opts, h) + def omonthdays(i); jseq(nil, i, :omonthday, :_and_or_or_or_comma); end + def monthdays(i); jseq(nil, i, :monthday, :_and_or_or_or_comma); end - h[:dom] = e[1..-1].join(',') - end + def weekdays(i); jseq(:weekdays, i, :weekday, :_and_or_or_or_comma); end - def parse_at_elt(e, opts, h) + def on_the(i); seq(nil, i, :_the, :omonthdays); end - (h[:hms] ||= []).concat(e[1]) + def _minute(i); rex(nil, i, /[ \t]*minute[ \t]+/i) end - l = h[:hms].last - h[:sec] = l.pop if l.size > 2 + def _dmin(i) + rex(:dmin, i, /[0-5]?[0-9]/) end + def and_dmin(i) + seq(nil, i, :_and_or_or_or_comma, :_minute, '?', :_dmin) + end - def parse_on_elt(e, opts, h) - - e1 = e[1] - h[:dow] = e1[0] - h[:hms] = [ e1[1] ] - - l = h[:hms].last - h[:sec] = l.pop if l.size > 2 + def on_minutes(i) + seq(:on_minutes, i, :_minute, :_dmin, :and_dmin, '*') end - def parse_tz_elt(e, opts, h) - - h[:tz] = e[1] + def on_thex(i); + rex(:on_thex, i, /[ \t]*the[ \t]+(hour|minute)[ \t]*/i); end - end - module Parser include Raabro + def on_thes(i); jseq(:on_thes, i, :on_the, :_and_or_or_or_comma); end + def on_days(i); seq(:on_days, i, :_day_s, :monthdays); end + def on_weekdays(i); ren(:on_weekdays, i, :weekdays); end - NUMS = %w[ - zero one two three four five six seven eight nine ten eleven twelve ] - - WEEKDAYS = - Fugit::Cron::Parser::WEEKDS + Fugit::Cron::Parser::WEEKDAYS - - NHOURS = { - 'noon' => [ 12, 0 ], - 'midnight' => [ 0, 0 ], 'oh' => [ 0, 0 ] } - NMINUTES = { - "o'clock" => 0, 'five' => 5, - 'ten' => 10, 'fifteen' => 15, - 'twenty' => 20, 'twenty-five' => 25, - 'thirty' => 30, 'thirty-five' => 35, - 'fourty' => 40, 'fourty-five' => 45, - 'fifty' => 50, 'fifty-five' => 55 } - - oh = { - '1st' => 1, '2nd' => 2, '3rd' => 3, '21st' => 21, '22nd' => 22, - '23rd' => 23, '31st' => 31 } - %w[ 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 24 25 26 27 28 29 30 ] - .each { |i| oh["#{i}th"] = i.to_i } - %w[ - first second third fourth fifth sixth seventh eighth ninth tenth - eleventh twelfth thirteenth fourteenth fifteenth sixteenth seventeenth - eighteenth nineteenth twentieth twenty-first twenty-second twenty-third - twenty-fourth twenty-fifth twenty-fifth twenty-sixth twenty-seventh - twenty-eighth twenty-ninth thirtieth thirty-first ] - .each_with_index { |e, i| oh[e] = i + 1 } - ORDINALS = oh - - # piece parsers bottom to top - - def _from(i); rex(nil, i, /\s*from\s+/i); end - def _every(i); rex(nil, i, /\s*(every)\s+/i); end - def _at(i); rex(nil, i, /\s*at\s+/i); end - def _in(i); rex(nil, i, /\s*(in|on)\s+/i); end - def _to(i); rex(nil, i, /\s*to\s+/i); end - def _dash(i); rex(nil, i, /-\s*/i); end - def _and(i); rex(nil, i, /\s*and\s+/i); end - def _on(i); rex(nil, i, /\s*on\s+/i); end - - def _and_or_comma(i) - rex(nil, i, /\s*(,?\s*and\s|,?\s*or\s|,)\s*/i) + def on_object(i) + alt(nil, i, :on_days, :on_weekdays, :on_minutes, :on_thes, :on_thex) end - def _at_comma(i) - rex(nil, i, /\s*(at\s|,|)\s*/i) + def on_objects(i) + jseq(nil, i, :on_object, :_and) end - def _to_through(i) - rex(nil, i, /\s*(to|through)\s+/i) + + #'every month on day 2 at 10:00' => '0 10 2 * *', + #'every month on day 2 and 5 at 10:00' => '0 10 2,5 * *', + #'every month on days 1,15 at 10:00' => '0 10 1,15 * *', + # + #'every week on monday 18:23' => '23 18 * * 1', + # + # every month on the 1st + def on(i) + seq(:on, i, :_on, :on_objects) end - def integer(i); rex(:int, i, /\d+\s*/); end - - def tz_name(i) - rex(nil, i, - /\s*[A-Z][a-zA-Z0-9+\-]+(\/[A-Z][a-zA-Z0-9+\-_]+){0,2}(\s+|$)/) + def city_tz(i) + rex(nil, i, /[A-Z][a-zA-Z0-9+\-]+(\/[A-Z][a-zA-Z0-9+\-_]+){0,2}/) end - def tz_delta(i) - rex(nil, i, - /\s*[-+]([01][0-9]|2[0-4]):?(00|15|30|45)(\s+|$)/) + def named_tz(i) + rex(nil, i, /Z|UTC/) end + def delta_tz(i) + rex(nil, i, /[-+]([01][0-9]|2[0-4])(:?(00|15|30|45))?/) + end + def tz(i) + alt(:tz, i, :city_tz, :named_tz, :delta_tz) + end def tzone(i) - alt(:tzone, i, :tz_delta, :tz_name) + seq(nil, i, :_in_or_on, '?', :tz) end - def and_named_digits(i) -rex(:xxx, i, 'TODO') + def digital_hour(i) + rex( + :digital_hour, i, + /(2[0-4]|[0-1]?[0-9]):([0-5][0-9])([ \t]*(am|pm))?/i) end - def dname(i) - rex(:dname, i, /(s(ec(onds?)?)?|m(in(utes?)?)?)\s+/i) + def ampm(i) + rex(:ampm, i, /[ \t]*(am|pm)/i) end - def named_digit(i) - seq(:named_digit, i, :dname, :integer) + def dark(i) + rex(:dark, i, /[ \t]*dark/i) end - def named_digits(i) - seq(nil, i, :named_digit, '+', :and_named_digits, '*') + + def simple_h(i) + rex(:simple_h, i, /#{(0..24).to_a.reverse.join('|')}/) end + def simple_hour(i) + seq(:simple_hour, i, :simple_h, :ampm, '?') + end - def am_pm(i) - rex(:am_pm, i, /\s*(am|pm|dark)\s*/i) + def named_m(i) + rex(:named_m, i, NAMED_M_REX) end + def named_min(i) + seq(nil, i, :_space, :named_m) + end - def nminute(i) - rex(:nminute, i, /(#{NMINUTES.keys.join('|')})\s*/i) + def named_h(i) + rex(:named_h, i, NAMED_H_REX) end - def nhour(i) - rex(:nhour, i, /(#{NUMS.join('|')})\s*/i) + def named_hour(i) + seq(:named_hour, i, :named_h, :dark, '?', :named_min, '?', :ampm, '?') end - def numeral_hour(i) - seq(:numeral_hour, i, :nhour, :am_pm, '?', :nminute, '?') + + def _point(i); rex(:point, i, POINT_REX); end + + def counts(i) + jseq(nil, i, :count, :_and_or_or_or_comma) end - def named_hour(i) - rex(:named_hour, i, /(#{NHOURS.keys.join('|')})/i) + def at_p(i) + seq(:at_p, i, :_point, :counts) end + def at_point(i) + jseq(nil, i, :at_p, :_and_or_or) + end - def shour(i) - rex(:shour, i, /(2[0-4]|[01]?[0-9])/) + # at five + # at five pm + # at five o'clock + # at 16:30 + # at noon + # at 18:00 UTC <-- ...tz + def at_object(i) + alt(nil, i, :named_hour, :digital_hour, :simple_hour, :at_point) end - def simple_hour(i) - seq(:simple_hour, i, :shour, :am_pm, '?') + def at_objects(i) + jseq(nil, i, :at_object, :_and_or_or_or_comma) end - def dig_hour_b(i); rex(nil, i, /(2[0-4]|[01][0-9]|[0-9]):[0-5]\d/); end - def dig_hour_a(i); rex(nil, i, /(2[0-4]|[01][0-9])[0-5]\d/); end - def dig_hour(i); alt(nil, i, :dig_hour_a, :dig_hour_b); end - # - def digital_hour(i) - seq(:digital_hour, i, :dig_hour, :am_pm, '?') + def at(i) + seq(:at, i, :_at, '?', :at_objects) end - def at_point(i) - alt(nil, i, - :digital_hour, :simple_hour, :named_hour, :numeral_hour, - :named_digits) + def interval(i) + rex(:interval, i, INTERVAL_REX) end - def weekday(i) - rex(:weekday, i, /(#{WEEKDAYS.reverse.join('|')})\s*/i) + # every day + # every 1 minute + def every_interval(i) + seq(:every_interval, i, :count, '?', :interval) end - def and_at(i) - seq(nil, i, :_and_or_comma, :at_point) + def every_single_interval(i) + rex(:every_single_interval, i, /(1[ \t]+)?(week|year)/) end - def _intervals(i) - rex(:intervals, i, - /( - y(ears?)?|months?|w(eeks?)?|d(ays?)?| - h(ours?)?|m(in(ute)?s?)?|s(ec(ond)?s?)? - )(\s+|$)/ix) + def to_weekday(i) + seq(:to_weekday, i, :weekday, :_to_or_dash, :weekday) end - def sinterval(i) - rex(:sinterval, i, - /(year|month|week|day|hour|min(ute)?|sec(ond)?)(\s+|$)/i) + def weekday_range(i) + alt(nil, i, :to_weekday, :weekdays) end - def ninterval(i) - seq(:ninterval, i, :integer, :_intervals) + + def to_omonthday(i) + seq(:to_omonthday, i, + :_the, '?', :omonthday, :_to, :_the, '?', :omonthday) end - def ordinal(i) - rex(:ordinal, i, /\s*(#{ORDINALS.keys.join('|')})\s*/) + def to_hour(i) + seq(:to_hour, i, :at_object, :_to, :at_object) end - def _mod(i); rex(nil, i, /\s*month\s+on\s+days?\s+/i); end - def _oftm(i); rex(nil, i, /\s*(day\s)?\s*of\s+the\s+month\s*/i); end - - def dom(i) - rex(:int, i, /([12][0-9]|3[01]|[0-9])/) + def from_object(i) + alt(nil, i, :to_weekday, :to_omonthday, :to_hour) end - def and_or_dom(i) - seq(nil, i, :_and_or_comma, :dom) + def from_objects(i) + jseq(nil, i, :from_object, :_and_or_or) end - def dom_list(i) - seq(:dom_list, i, :dom, :and_or_dom, '*') + def from(i) + seq(nil, i, :_from, '?', :from_objects) end - def dom_mod(i) # every month on day - seq(:dom, i, :_mod, :dom_list) + # every monday + # every Fri-Sun + # every Monday and Tuesday + def every_weekday(i) + jseq(nil, i, :weekday_range, :_and_or_or) end - def dom_noftm(i) # every nth of month - seq(:dom, i, :ordinal, :_oftm) + + def otm(i) + rex(nil, i, /[ \t]+of the month/) end - def day_of_month(i) - alt(nil, i, :dom_noftm, :dom_mod) + + # every 1st of the month + # every first of the month + # Every 2nd of the month + # Every second of the month + # every 15th of the month + def every_of_the_month(i) + seq(nil, i, :omonthdays, :otm) end - def dow_class(i) - rex(:dow_class, i, /(weekday)(\s+|$)/i) + def every_named(i) + rex(:every_named, i, /weekday/i) end - def dow(i) - seq(:dow, i, :weekday) + def every_object(i) + alt( + nil, i, + :every_weekday, :every_of_the_month, + :every_interval, :every_named, :every_single_interval) end - def and_or_dow(i) - seq(nil, i, :_and_or_comma, :dow) + def every_objects(i) + jseq(nil, i, :every_object, :_and_or_or) end - def dow_list(i) - seq(:dow_list, i, :dow, :and_or_dow, '*') + + def every(i) + seq(:every, i, :_every, :every_objects) end - def to_dow_range(i) - seq(:dow_range, i, :weekday, :_to_through, :weekday) + def nat_elt(i) + alt(nil, i, :every, :from, :at, :tzone, :on) end - def dash_dow_range(i) - seq(:dow_range, i, :weekday, :_dash, :weekday) + def nat(i) + jseq(:nat, i, :nat_elt, :_sep) end - def dow_range(i) - alt(nil, i, :to_dow_range, :dash_dow_range) + + # + # rewrite parsed tree ################################################### + + def slot(key, data0, data1=nil, opts=nil) + Slot.new(key, data0, data1, opts) end - def day_of_week(i) - alt(nil, i, :dow_range, :dow_list, :dow_class) + def _rewrite_subs(t, key=nil) + t.subgather(key).collect { |ct| rewrite(ct) } end + def _rewrite_sub(t, key=nil) + st = t.sublookup(key) + st ? rewrite(st) : nil + end - def interval(i) - alt(nil, i, :sinterval, :ninterval) + def rewrite_dmin(t) + t.strinp end - def every_object(i) - alt(nil, i, :day_of_month, :interval, :day_of_week) + def rewrite_on_minutes(t) +#Raabro.pp(t, colours: true) + mins = t.subgather(:dmin).collect(&:strinp) + #slot(:m, mins.join(',')) + slot(:hm, '*', mins.join(','), strong: 1) end - def from_object(i) - alt(nil, i, :interval, :to_dow_range) + + def rewrite_on_thex(t) + case s = t.string + #when /hour/i then slot(:h, 0) + #else slot(:m, '*') + when /hour/i then slot(:hm, 0, '*', strong: 0) + else slot(:hm, '*', '*', strong: 1) + end end - def tz(i) - seq(nil, i, :_in, '?', :tzone) + def rewrite_on_thes(t) + _rewrite_subs(t, :omonthday) end - def on(i) - seq(:on, i, :_on, :weekday, :at_point, :and_at, '*') + def rewrite_on_days(t) + _rewrite_subs(t, :monthday) end - def at(i) - seq(:at, i, :_at_comma, :at_point, :and_at, '*') + + def rewrite_on(t) + _rewrite_subs(t) end - def from(i) - seq(:from, i, :_from, :from_object) + + def rewrite_monthday(t) + slot(:monthday, t.string.to_i) end - def every(i) - seq(:every, i, :_every, :every_object) + + def rewrite_omonthday(t) + slot(:monthday, OMONTHDAYS[t.string.downcase]) end - def at_from(i) - seq(nil, i, :at, :from, :tz, '?') + def rewrite_at_p(t) + pt = t.sublookup(:point).strinpd + pt = pt.start_with?('mon') ? 'M' : pt[0, 1] + pts = t.subgather(:count).collect { |e| e.string.to_i } +#p [ pt, pts ] + case pt + #when 'm' then slot(:m, pts) + when 'm' then slot(:hm, '*', pts, strong: 1) + when 's' then slot(:second, pts) +else slot(pt.to_sym, pts) + end end - def at_every(i) - seq(nil, i, :at, :every, :tz, '?') + + def rewrite_every_single_interval(t) + case t.string + when /year/i then [ slot(:month, 1, :weak), slot(:monthday, 1, :weak) ] + #when /week/i then xxx... + else slot(:weekday, 0, :weak) + end end - def from_at(i) - seq(nil, i, :from, :at, '?', :tz, '?') + def rewrite_every_interval(t) + +#Raabro.pp(t, colours: true) + ci = t.subgather(nil).collect(&:string) + i = ci.pop.strip[0, 3] + c = (ci.pop || '1').strip + i = (i == 'M' || i.downcase == 'mon') ? 'M' : i[0, 1].downcase + cc = c == '1' ? '*' : "*/#{c}" + + case i + when 'M' then slot(:month, cc) + when 'd' then slot(:monthday, cc, :weak) + #when 'h' then slot(:hm, cc, 0, weak: :minute) + when 'h' then slot(:hm, cc, 0, weak: 1) + when 'm' then slot(:hm, '*', cc, strong: 1) + when 's' then slot(:second, cc) + else {} + end end - def every_(i) - seq(nil, i, :every, :tz, '?') + def rewrite_every_named(t) + + case s = t.string + when /weekday/i then slot(:weekday, '1-5', :weak) + when /week/i then slot(:weekday, '0', :weak) + else fail "cannot rewrite #{s.inspect}" + end end - def every_on(i) - seq(nil, i, :every, :on, :tz, '?') + + def rewrite_tz(t) + slot(:tz, t.string) end - def every_at(i) - seq(nil, i, :every, :at, :tz, '?') + + def rewrite_weekday(t) + Fugit::Cron::Parser::WEEKDS.index(t.string[0, 3].downcase) end - def nat(i) - alt(:nat, i, - :every_at, :every_on, :every_, - :from_at, - :at_every, :at_from) + def rewrite_weekdays(t) +#Raabro.pp(t, colours: true) + slot(:weekday, _rewrite_subs(t, :weekday)) end + alias rewrite_on_weekdays rewrite_weekdays - # rewrite parsed tree + def rewrite_to_weekday(t) + wd0, wd1 = _rewrite_subs(t, :weekday) + #wd1 = 7 if wd1 == 0 + slot(:weekday, "#{wd0}-#{wd1}") + end - #def _rewrite_single(t) - # [ t.name, rewrite(t.sublookup(nil)) ] - #end - def _rewrite_children(t) - t.subgather(nil).collect { |tt| rewrite(tt) } + def rewrite_to_omonthday(t) + md0, md1 = _rewrite_subs(t, :omonthday).collect(&:_data0) + slot(:monthday, "#{md0}-#{md1}") end - def _rewrite_multiple(t) - [ t.name, _rewrite_children(t) ] + + def rewrite_digital_hour(t) + h, m, ap = t.strinpd.split(/[: \t]+/) + h, m = h.to_i, m.to_i + h += 12 if ap && ap == 'pm' + slot(:hm, h.to_i, m.to_i) end - def _rewrite_child(t) - rewrite(t.sublookup(nil)) + + def rewrite_simple_hour(t) + h, ap = t.subgather(nil).collect(&:strinpd) + h = h.to_i + h = h + 12 if ap == 'pm' + slot(:hm, h, 0) end - def rewrite_int(t); t.string.to_i; end + def rewrite_named_hour(t) - def rewrite_tzone(t) + ht = t.sublookup(:named_h) + mt = t.sublookup(:named_m) + apt = t.sublookup(:ampm) - [ :tz, t.strim ] - end + h = ht.strinp + m = mt ? mt.strinp : 0 +#p [ 0, '-->', h, m ] + h = NHOURS[h] + m = NMINUTES[m] || m +#p [ 1, '-->', h, m ] - def rewrite_sinterval(t) + h += 12 if h < 13 && apt && apt.strinpd == 'pm' - [ :interval, 1, t.strim ] + slot(:hm, h, m) end - def rewrite_ninterval(t) + def rewrite_to_hour(t) +#Raabro.pp(t, colours: true) + ht0, ht1 = t.subgather(nil) + h0, h1 = rewrite(ht0), rewrite(ht1) + fail ArgumentError.new( + "cannot deal with #{ht0.strinp} to #{ht1.strinp}, minutes diverge" + ) if h0.data1 != h1.data1 + slot(:hm, "#{h0._data0}-#{h1._data0}", 0, strong: 0) + end - [ :interval, - t.sublookup(:int).string.to_i, - t.sublookup(:intervals).strim ] + def rewrite_at(t) + _rewrite_subs(t) end - def rewrite_named_digit(t) + def rewrite_every(t) + _rewrite_sub(t) + end - i = t.sublookup(:int).string.to_i - - case n = t.sublookup(:dname).strim - when /^s/ then [ '*', '*', i ] - when /^m/ then [ '*', i ] - end + def rewrite_nat(t) +#Raabro.pp(t, colours: true) + Fugit::Nat::SlotGroup.new(_rewrite_subs(t).flatten) end + end - def rewrite_named_hour(t) - NHOURS[t.strim.downcase] + class Slot + attr_reader :key + attr_accessor :_data0, :_data1 + def initialize(key, d0, d1=nil, opts=nil) + d1, opts = d1.is_a?(Symbol) ? [ nil, d1 ] : [ d1, opts ] + @key, @_data0, @_data1 = key, d0, d1 + @opts = (opts.is_a?(Symbol) ? { opts => true } : opts) || {} end - def rewrite_numeral_hour(t) - vs = t.subgather(nil).collect { |st| st.strim.downcase } - v = NUMS.index(vs[0]) - v += 12 if vs[1] == 'pm' - m = NMINUTES[vs[2]] || 0 - [ v, m ] + def data0; @data0 ||= Array(@_data0); end + def data1; @data1 ||= Array(@_data1); end + def weak; @opts[:weak]; end + def strong; @opts[:strong]; end + def graded?; weak || strong; end + def append(slot) + @_data0, @_data1 = conflate(0, slot), conflate(1, slot) + @opts.clear + self end - def rewrite_simple_hour(t) - vs = t.subgather(nil).collect { |st| st.strim.downcase } - v = vs[0].to_i - v += 12 if vs[1] == 'pm' - [ v, 0 ] + def inspect + a = [ @key, @_data0 ] + a << @_data1 if @_data1 != nil + a << @opts if @opts && @opts.keys.any? + "(slot #{a.collect(&:inspect).join(' ')})" end - def rewrite_digital_hour(t) - m = t.string.match(/(\d\d?):?(\d\d)(\s+pm)?/i) - hou = m[1].to_i; hou += 12 if m[3] && hou < 12 - min = m[2].to_i - [ hou, min ] + def a; [ data0, data1 ]; end + protected + def to_a(x) + return [] if x == '*' + Array(x) end + def conflate(index, slot) + a, b = index == 0 ? [ @_data0, slot._data0 ] : [ @_data1, slot._data1 ] + return a if b == nil + return b if a == nil + if ra = (index == 0 && slot.strong == 1 && hour_range) + h0, h1 = ra[0], ra[1] - 1; return h0 == h1 ? h0 : "#{h0}-#{h1}" + elsif rb = (index == 0 && strong == 1 && slot.hour_range) + h0, h1 = rb[0], rb[1] - 1; return h0 == h1 ? h0 : "#{h0}-#{h1}" + end + return a if strong == index || strong == true + return b if slot.strong == index || slot.strong == true + return a if slot.weak == index || slot.weak == true + return b if weak == index || weak == true + return [ '*' ] if a == '*' && b == '*' + to_a(a).concat(to_a(b)) + end + def hour_range + m = (key == :hm && @_data1 == 0 && @_data0.match(/\A(\d+)-(\d+)\z/)) + m ? [ m[1].to_i, m[2].to_i ] : nil + end + end - def rewrite_weekday(t) + class SlotGroup - WEEKDAYS.index(t.strim.downcase[0, 3]) - end + def initialize(slots) - def rewrite_ordinal(t); ORDINALS[t.strim]; end +#puts "SlotGroup.new " + slots.inspect + @slots = {} + @hms = [] - def rewrite_dom(t) + slots.each do |s| + if s.key == :hm + #ls = @hms.last; @hms.pop if ls && ls.key == :hm && ls.weak == true + @hms << s + elsif hs = @slots[s.key] + hs.append(s) + else + @slots[s.key] = s + end + end -#Raabro.pp(t, colours: true) - [ :day_of_month, - *_rewrite_children(t).flatten.select { |e| e.is_a?(Integer) } ] + if @slots[:monthday] || @slots[:weekday] + @hms << make_slot(:hm, 0, 0) if @hms.empty? + elsif @slots[:month] + @hms << make_slot(:hm, 0, 0) if @hms.empty? + @slots[:monthday] ||= make_slot(:monthday, 1) + end end - alias rewrite_dow _rewrite_child + def to_crons(opts) - def rewrite_dom_list(t); [ :dom_list, *_rewrite_children(t) ]; end - def rewrite_dow_list(t); [ :dow_list, *_rewrite_children(t) ]; end + multi = opts.has_key?(:multi) ? opts[:multi] : false - def rewrite_dow_class(t) + hms = determine_hms - [ :dow_range, 1, 5 ] # only "weekday" for now + if multi == :fail && hms.count > 1 + fail(ArgumentError.new( + "multiple crons in #{opts[:_s].inspect} - #{@slots.inspect}")) + elsif multi == true + hms.collect { |hm| parse_cron(hm) } + else + parse_cron(hms.first) + end end - def rewrite_dow_range(t) + protected - tts = t.subgather(nil) + def make_slot(key, data0, data1=nil) - [ :dow_range, rewrite(tts[0]), rewrite(tts[1]) ] + Fugit::Nat::Slot.new(key, data0, data1) end - alias rewrite_on _rewrite_multiple - alias rewrite_at _rewrite_multiple + def determine_hms - alias rewrite_from _rewrite_child - alias rewrite_every _rewrite_child + return [ [ [ '*' ], [ '*' ] ] ] if @hms.empty? - def rewrite_nat(t) + hms = @hms.dup + # + while ig = (hms.count > 1 && hms.index { |hm| hm.graded? }) do + sg = hms[ig] + so = hms.delete_at(ig == 0 ? 1 : ig - 1) + sg.append(so) + end - t.subgather(nil).collect { |tt| rewrite(tt) } -#.tap { |x| pp x } + hms + .collect(&:a) + .inject({}) { |r, hm| + hm[1].each { |m| (r[m] ||= []).concat(hm[0]) } + r } + .inject({}) { |r, (m, hs)| + (r[hs.sort] ||= []) << m + r } + .to_a + end + + def parse_cron(hm) + + a = [ + slot(:second, '0'), + hm[1], + hm[0], + slot(:monthday, '*'), + slot(:month, '*'), + slot(:weekday, '*') ] + tz = @slots[:tz] + a << tz.data0 if tz + a.shift if a.first == [ '0' ] + + s = a + .collect { |e| e.uniq.sort.collect(&:to_s).join(',') } + .join(' ') + + Fugit::Cron.parse(s) + end + + def slot(key, default) + s = @slots[key] + s ? s.data0 : [ default ] end end end end