lib/fugit/nat.rb in fugit-1.3.5 vs lib/fugit/nat.rb in fugit-1.3.6

- old
+ new

@@ -14,20 +14,11 @@ 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 - a = Parser.parse(s) - - return nil unless a - - return parse_crons(s, a, opts) \ - if a.include?([ :flag, 'every' ]) - return parse_crons(s, a, opts) \ - if a.include?([ :flag, 'from' ]) && a.find { |e| e[0] == :day_range } - - nil + parse_crons(s, Parser.parse(s), opts) end def do_parse(s, opts={}) parse(s, opts) || @@ -36,21 +27,42 @@ protected def parse_crons(s, a, opts) - hms, aa = a - .partition { |e| e[0] == :point && e[1][0] == :hour } - ms = hms - .inject({}) { |h, e| (h[e[1][1]] ||= []) << e[1][2]; h } - .values +#p a + return nil unless a + + 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 + + if f = h[:_fail] + #fail ArgumentError.new(f) + return nil + end + + hms = h[:hms] + + hours = (hms || []) .uniq - crons = - ms.size > 1 ? - hms.collect { |e| parse_cron([ e ] + aa, opts) } : - [ parse_cron(a, opts) ] + .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 + crons = hours + .collect { |hm| assemble_cron(h.merge(hms: hm)) } + fail ArgumentError.new( "multiple crons in #{s.inspect} " + "(#{crons.collect(&:original).join(' | ')})" ) if opts[:multi] == :fail && crons.size > 1 @@ -59,302 +71,454 @@ else crons.first end end - def parse_cron(a, opts) + def assemble_cron(h) - h = { min: nil, hou: nil, dom: nil, mon: nil, dow: nil } - hkeys = h.keys +#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] - i0s, es = a.partition { |e| e[0] == :interval0 } - a = es + i0s - # interval0s are fallback + Fugit::Cron.parse(s.join(' ')) + end - a.each do |key, val| - case key - when :biz_day - (h[:dow] ||= []) << '1-5' - when :name_day - (h[:dow] ||= []) << val - when :day_range - (h[:dow] ||= []) << val.collect { |v| v.to_s[0, 3] }.join('-') - when :tz - h[:tz] = val - when :point - process_point(h, *val) - when :interval1 - process_interval1(h, *val[0].to_h.first) - when :interval0 - process_interval0(h, val) - end - end + def eone(e); e1 = e[1]; e1 == 1 ? '*' : "*/#{e1}"; end - return nil if h[:fail] + def parse_interval_elt(e, opts, h) - h[:min] = (h[:min] || [ 0 ]).uniq - h[:hou] = (h[:hou] || []).uniq.sort - h[:dow].sort! if h[:dow] + e1 = e[1] - a = hkeys - .collect { |k| - v = h[k] - (v && v.any?) ? v.collect(&:to_s).join(',') : '*' } - a.insert(0, h[:sec]) if h[:sec] - a << h[:tz].first if h[:tz] + case e[2] + when 's', 'sec', 'second', 'seconds' + h[:sec] = eone(e) + when 'm', 'min', 'mins', 'minute', 'minutes' + #(h[:hms] ||= []) << [ '*', eone(e) ] + h[:hms] ||= [ [ '*', eone(e) ] ] + when 'h', 'hour', 'hours' + h[:hms] ||= [ [ eone(e), 0 ] ] + 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 + end - s = a.join(' ') + def parse_dow_list_elt(e, opts, h) - Fugit::Cron.parse(s) + h[:hms] ||= [ [ 0, 0 ] ] + h[:dow] = e[1..-1].collect(&:to_s).sort.join(',') end - def process_point(h, key, *value) + def parse_dow_range_elt(e, opts, h) - case key - when :hour - v0, v1 = value - v0 = v0.to_i if v0.is_a?(String) && v0.match(/^\d+$/) - (h[:hou] ||= []) << v0 - (h[:min] ||= []) << v1.to_i if v1 - when :sec, :min - (h[key] ||= []) << value[0] - end + h[:hms] ||= [ [ 0, 0 ] ] + h[:dow] = e[1] == e[2] ? e[1] : "#{e[1]}-#{e[2]}" end - def process_interval0(h, value) + def parse_day_of_month_elt(e, opts, h) - case value - when 'sec', 'second' - h[:min] = [ '*' ] - h[:sec] = [ '*' ] - when 'min', 'minute' - h[:min] = [ '*' ] - when 'hour' - unless h[:min] || h[:hou] - h[:min] = [ 0 ] - h[:hou] = [ '*' ] - end - when 'day' - unless h[:min] || h[:hou] - h[:min] = [ 0 ] - h[:hou] = [ 0 ] - end - when 'week' - unless h[:min] || h[:hou] || h[:dow] - h[:min] = [ 0 ] - h[:hou] = [ 0 ] - h[:dow] = [ 0 ] - end - when 'month' - unless h[:min] || h[:hou] - h[:min] = [ 0 ] - h[:hou] = [ 0 ] - end - (h[:dom] ||= []) << 1 - when 'year' - unless h[:min] || h[:hou] - h[:min] = [ 0 ] - h[:hou] = [ 0 ] - end - (h[:dom] ||= []) << 1 - (h[:mon] ||= []) << 1 - end + h[:dom] = e[1..-1].join(',') end - def process_interval1(h, interval, value) + def parse_at_elt(e, opts, h) - if value != 1 && [ :yea, :wee ].include?(interval) - int = interval == :year ? 'years' : 'weeks' - h[:fail] = "cannot cron for \"every #{value} #{int}\"" - return - end + (h[:hms] ||= []).concat(e[1]) - case interval - when :yea - h[:hou] = [ 0 ] - h[:mon] = [ 1 ] - h[:dom] = [ 1 ] - when :mon - h[:hou] = [ 0 ] - h[:dom] = [ 1 ] - h[:mon] = [ value == 1 ? '*' : "*/#{value}" ] - when :wee - h[:hou] = [ 0 ] - h[:dow] = [ 0 ] # Sunday - when :day - h[:hou] = [ 0 ] - h[:dom] = [ value == 1 ? '*' : "*/#{value}" ] - when :hou - h[:hou] = [ value == 1 ? '*' : "*/#{value}" ] - when :min - h[:hou] = [ '*' ] - h[:min] = [ value == 1 ? '*' : "*/#{value}" ] - when :sec - h[:hou] = [ '*' ] - h[:min] = [ '*' ] - h[:sec] = [ value == 1 ? '*' : "*/#{value}" ] - end + l = h[:hms].last + h[:sec] = l.pop if l.size > 2 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 + end + + def parse_tz_elt(e, opts, h) + + h[:tz] = e[1] + end end module Parser include Raabro NUMS = %w[ - zero - one two three four five six seven eight nine - ten eleven twelve ] + 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 ] } + 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 interval0(i) - rex(:interval0, i, - /(year|month|week|day|hour|min(ute)?|sec(ond)?)(?![a-z])/i) + 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) end + def _at_comma(i) + rex(nil, i, /\s*(at\s|,|)\s*/i) + end + def _to_through(i) + rex(nil, i, /\s*(to|through)\s+/i) + 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+|$)/) + end + def tz_delta(i) + rex(nil, i, + /\s*[-+]([01][0-9]|2[0-4]):?(00|15|30|45)(\s+|$)/) + end + def tzone(i) + alt(:tzone, i, :tz_delta, :tz_name) + end + + def and_named_digits(i) +rex(:xxx, i, 'TODO') + end + + def dname(i) + rex(:dname, i, /(s(ec(onds?)?)?|m(in(utes?)?)?)\s+/i) + end + def named_digit(i) + seq(:named_digit, i, :dname, :integer) + end + def named_digits(i) + seq(nil, i, :named_digit, '+', :and_named_digits, '*') + end + def am_pm(i) - rex(:am_pm, i, / *(am|pm)/i) + rex(:am_pm, i, /\s*(am|pm|dark)\s*/i) end + def nminute(i) + rex(:nminute, i, /(#{NMINUTES.keys.join('|')})\s*/i) + end + def nhour(i) + rex(:nhour, i, /(#{NUMS.join('|')})\s*/i) + end + def numeral_hour(i) + seq(:numeral_hour, i, :nhour, :am_pm, '?', :nminute, '?') + end + + def named_hour(i) + rex(:named_hour, i, /(#{NHOURS.keys.join('|')})/i) + end + + def shour(i) + rex(:shour, i, /(2[0-4]|[01]?[0-9])/) + end + def simple_hour(i) + seq(:simple_hour, i, :shour, :am_pm, '?') + end + def digital_hour(i) rex(:digital_hour, i, /(2[0-4]|[01][0-9]):?[0-5]\d/) end - def _simple_hour(i) - rex(:sh, i, /(2[0-4]|[01]?[0-9])/) + def at_point(i) + alt(nil, i, + :digital_hour, :simple_hour, :named_hour, :numeral_hour, + :named_digits) end - def simple_hour(i) - seq(:simple_hour, i, :_simple_hour, :am_pm, '?') + + def weekday(i) + rex(:weekday, i, /(#{WEEKDAYS.reverse.join('|')})\s*/i) end - def _numeral_hour(i) - rex(:nh, i, /(#{NUMS.join('|')})/i) + def and_at(i) + seq(nil, i, :_and_or_comma, :at_point) end - def numeral_hour(i) - seq(:numeral_hour, i, :_numeral_hour, :am_pm, '?') + + 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) end - def name_hour(i) - rex(:name_hour, i, /(#{NHOURS.keys.join('|')})/i) + def sinterval(i) + rex(:sinterval, i, + /(year|month|week|day|hour|min(ute)?|sec(ond)?)(\s+|$)/i) end + def ninterval(i) + seq(:ninterval, i, :integer, :_intervals) + end - def biz_day(i); rex(:biz_day, i, /(biz|business|week) *day/i); end - def name_day(i); rex(:name_day, i, /#{WEEKDAYS.reverse.join('|')}/i); end + def ordinal(i) + rex(:ordinal, i, /\s*(#{ORDINALS.keys.join('|')})\s*/) + end - def range_sep(i); rex(nil, i, / *- *| +(to|through) +/); 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 day_range(i) - seq(:day_range, i, :name_day, :range_sep, :name_day) + def dom(i) + rex(:int, i, /([12][0-9]|3[01]|[0-9])/) end + def and_or_dom(i) + seq(nil, i, :_and_or_comma, :dom) + end + def dom_list(i) + seq(:dom_list, i, :dom, :and_or_dom, '*') + end - def _tz_name(i) - rex(nil, i, /[A-Z][a-zA-Z0-9+\-]+(\/[A-Z][a-zA-Z0-9+\-_]+){0,2}/) + def dom_mod(i) # every month on day + seq(:dom, i, :_mod, :dom_list) end - def _tz_delta(i) - rex(nil, i, /[-+]([01][0-9]|2[0-4]):?(00|15|30|45)/) + def dom_noftm(i) # every nth of month + seq(:dom, i, :ordinal, :_oftm) end - def _tz(i); alt(:tz, i, :_tz_delta, :_tz_name); end + def day_of_month(i) + alt(nil, i, :dom_noftm, :dom_mod) + end - def interval1(i) - rex(:interval1, i, - / - \d+ - \s? - (y(ears?)?|months?|w(eeks?)?|d(ays?)?| - h(ours?)?|m(in(ute)?s?)?|s(ec(ond)?s?)?) - /ix) + def dow_class(i) + rex(:dow_class, i, /(weekday)(\s+|$)/i) end - def min_or_sec(i) - rex(:min_or_sec, i, /(min(ute)?|sec(ond)?)\s+\d+/i) + def dow(i) + seq(:dow, i, :weekday) end + def and_or_dow(i) + seq(nil, i, :_and_or_comma, :dow) + end + def dow_list(i) + seq(:dow_list, i, :dow, :and_or_dow, '*') + end - def point(i) - alt(:point, i, - :min_or_sec, - :name_hour, :numeral_hour, :digital_hour, :simple_hour) + def to_dow_range(i) + seq(:dow_range, i, :weekday, :_to_through, :weekday) end + def dash_dow_range(i) + seq(:dow_range, i, :weekday, :_dash, :weekday) + end + def dow_range(i) + alt(nil, i, :to_dow_range, :dash_dow_range) + end - def flag(i); rex(:flag, i, /(every|from|at|after|on|in)/i); end + def day_of_week(i) + alt(nil, i, :dow_range, :dow_list, :dow_class) + end - def datum(i) - alt(nil, i, - :flag, - :interval1, - :point, - :interval0, - :day_range, :biz_day, :name_day, - :_tz) + def interval(i) + alt(nil, i, :sinterval, :ninterval) end - def sugar(i); rex(nil, i, /(and|or|[, \t]+)/i); end + def every_object(i) + alt(nil, i, :day_of_month, :interval, :day_of_week) + end + def from_object(i) + alt(nil, i, :interval, :to_dow_range) + end - def elt(i); alt(nil, i, :sugar, :datum); end - def nat(i); rep(:nat, i, :elt, 1); end + def tz(i) + seq(nil, i, :_in, '?', :tzone) + end + def on(i) + seq(:on, i, :_on, :weekday, :at_point, :and_at, '*') + end + def at(i) + seq(:at, i, :_at_comma, :at_point, :and_at, '*') + end + def from(i) + seq(:from, i, :_from, :from_object) + end + def every(i) + seq(:every, i, :_every, :every_object) + end + def at_from(i) + seq(nil, i, :at, :from, :tz, '?') + end + def at_every(i) + seq(nil, i, :at, :every, :tz, '?') + end + + def from_at(i) + seq(nil, i, :from, :at, '?', :tz, '?') + end + + def every_(i) + seq(nil, i, :every, :tz, '?') + end + def every_on(i) + seq(nil, i, :every, :on, :tz, '?') + end + def every_at(i) + seq(nil, i, :every, :at, :tz, '?') + end + + def nat(i) + alt(:nat, i, + :every_at, :every_on, :every_, + :from_at, + :at_every, :at_from) + end + # rewrite parsed tree - def _rewrite(t) - [ t.name, t.string.downcase ] + #def _rewrite_single(t) + # [ t.name, rewrite(t.sublookup(nil)) ] + #end + def _rewrite_children(t) + t.subgather(nil).collect { |tt| rewrite(tt) } end - alias rewrite_flag _rewrite - alias rewrite_interval0 _rewrite - alias rewrite_biz_day _rewrite + def _rewrite_multiple(t) + [ t.name, _rewrite_children(t) ] + end + def _rewrite_child(t) + rewrite(t.sublookup(nil)) + end - def rewrite_name_day(t) - [ :name_day, WEEKDAYS.index(t.string.downcase[0, 3]) ] + def rewrite_int(t); t.string.to_i; end + + def rewrite_tzone(t) + + [ :tz, t.strim ] end - def rewrite_day_range(t) - [ :day_range, t.subgather(nil).collect { |st| st.string.downcase } ] + def rewrite_sinterval(t) + + [ :interval, 1, t.strim ] end - def rewrite_name_hour(t) - [ :hour, *NHOURS[t.string.strip.downcase] ] + def rewrite_ninterval(t) + + [ :interval, + t.sublookup(:int).string.to_i, + t.sublookup(:intervals).strim ] end + + def rewrite_named_digit(t) + + i = t.sublookup(:int).string.to_i + + case n = t.sublookup(:dname).strim + when /^s/ then [ '*', '*', i ] + when /^m/ then [ '*', i ] + end + end + + def rewrite_named_hour(t) + NHOURS[t.strim.downcase] + end def rewrite_numeral_hour(t) - vs = t.subgather(nil).collect { |st| st.string.downcase.strip } + vs = t.subgather(nil).collect { |st| st.strim.downcase } v = NUMS.index(vs[0]) v += 12 if vs[1] == 'pm' - [ :hour, v, 0 ] + m = NMINUTES[vs[2]] || 0 + [ v, m ] end def rewrite_simple_hour(t) - vs = t.subgather(nil).collect { |st| st.string.downcase.strip } + vs = t.subgather(nil).collect { |st| st.strim.downcase } v = vs[0].to_i v += 12 if vs[1] == 'pm' - [ :hour, v, 0 ] + [ v, 0 ] end def rewrite_digital_hour(t) - v = t.string.gsub(/:/, '') - [ :hour, v[0, 2], v[2, 2] ] + m = t.string.match(/(\d\d?):?(\d\d)/) + [ m[1].to_i, m[2].to_i ] end - def rewrite_min_or_sec(t) - unit, num = t.string.split(/\s+/) - [ unit[0, 3].to_sym, num.to_i ] + def rewrite_weekday(t) + + WEEKDAYS.index(t.strim.downcase[0, 3]) end - def rewrite_point(t) - [ :point, rewrite(t.sublookup) ] + def rewrite_ordinal(t); ORDINALS[t.strim]; end + + def rewrite_dom(t) + +#Raabro.pp(t, colours: true) + [ :day_of_month, + *_rewrite_children(t).flatten.select { |e| e.is_a?(Integer) } ] end - def rewrite_tz(t) - [ :tz, [ t.string.strip, EtOrbi.get_tzone(t.string.strip) ] ] + alias rewrite_dow _rewrite_child + + def rewrite_dom_list(t); [ :dom_list, *_rewrite_children(t) ]; end + def rewrite_dow_list(t); [ :dow_list, *_rewrite_children(t) ]; end + + def rewrite_dow_class(t) + + [ :dow_range, 1, 5 ] # only "weekday" for now end - def rewrite_interval1(t) - [ t.name, [ Fugit::Duration.parse(t.string.strip) ] ] + def rewrite_dow_range(t) + + tts = t.subgather(nil) + + [ :dow_range, rewrite(tts[0]), rewrite(tts[1]) ] end + alias rewrite_on _rewrite_multiple + alias rewrite_at _rewrite_multiple + + alias rewrite_from _rewrite_child + alias rewrite_every _rewrite_child + def rewrite_nat(t) -#Raabro.pp(t, colours: true) t.subgather(nil).collect { |tt| rewrite(tt) } +#.tap { |x| pp x } end end end end