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