fugit 1.3.6 → 1.4.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of fugit might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 90b61604e15e2cb24d4fb124306affea15a644c2591fc3a49649981616351d66
4
- data.tar.gz: 235eec423691346077fd9cfdaed895e28843ecf6681b5aa00a83cfcf9ca711a3
3
+ metadata.gz: e0c9e5123de2ba25d006290c43946a4028fc3467ec3f424bf57b23ce5c41cd65
4
+ data.tar.gz: c9f6a5bdc8d40e7b6c585dca633066df98ff22b298c9158c333c54c2fbb2bab3
5
5
  SHA512:
6
- metadata.gz: 1fd5abf6871b1cb60220bf7d6089be6bff48dfae80a05db6d031def126275940d50cbc5dfcaad493f2b6d327b66162c2a44329966459bfb3d9d5b912e3b7e68c
7
- data.tar.gz: '08a846fcce60834f8f0c289e14317249123f1f4028c478953ed04f759e633080c4d843f55c56fb973a104e1b44bbbbc92eecc978a954d7b0b317d7b23b82611b'
6
+ metadata.gz: 9135ff881b272674fb1768d2548efca04541f1aab790b04c6a9e060e172c722df7c73bd9dc98a221f7ab4e566b4600cb7e3f667ced5fbc92d7683667c7ab1c21
7
+ data.tar.gz: 0f6e3113b06d2bc287a608f6215c3a902e3eedf410f6c83e5e7d9da79122b6562f9d400d3cf19f1cc45b662b72e1d740115aedf0502f9552e8af6bdb39db750f
@@ -2,6 +2,34 @@
2
2
  # CHANGELOG.md
3
3
 
4
4
 
5
+ ## fugit 1.4.1 released 2020-11-25
6
+
7
+ * Suppress warning, gh-46, thanks @amatsuda
8
+
9
+
10
+ ## fugit 1.4.0 released 2020-10-27
11
+
12
+ * Ensure cron accepts "25-L" for monthday, gh-45
13
+ * Allow for "every weekday 8am to 5pm", gh-44
14
+ * Allow "every day from the 25th to the last", gh-45
15
+ * Rework nat parser
16
+
17
+
18
+ ## fugit 1.3.9 released 2020-09-17
19
+
20
+ * Prevent "New York skip", gh-43, thanks @honglooker
21
+
22
+
23
+ ## fugit 1.3.8 released 2020-08-06
24
+
25
+ * Parse 'every day at 8:30' and ' at 8:30 pm', gh-42
26
+
27
+
28
+ ## fugit 1.3.7 released 2020-08-05
29
+
30
+ * Parse 'every 12 hours at minute 50', gh-41
31
+
32
+
5
33
  ## fugit 1.3.6 released 2020-06-01
6
34
 
7
35
  * Introduce new nat syntaxed, gh-38
data/CREDITS.md CHANGED
@@ -1,6 +1,9 @@
1
1
 
2
2
  # fugit credits
3
3
 
4
+ * Matsuda Akira https://github.com/amatsuda gh-46, warning suppression
5
+ * Honglooker https://github.com/honglooker gh-43, New York cron skip
6
+ * Jérôme Dalbert https://github.com/jeromedalbert gh-41, gh-42
4
7
  * Danny Ben Shitrit https://github.com/DannyBen nat variants, gh-38
5
8
  * Dominik Sander https://github.com/dsander #rough_frequency 0, gh-36
6
9
  * Milovan Zogovic https://github.com/assembler Cron#match? vs TZ, gh-31
data/README.md CHANGED
@@ -9,7 +9,7 @@ Time tools for [flor](https://github.com/floraison/flor) and the floraison group
9
9
 
10
10
  It uses [et-orbi](https://github.com/floraison/et-orbi) to represent time instances and [raabro](https://github.com/floraison/raabro) as a basis for its parsers.
11
11
 
12
- Fugit is a core dependency of [rufus-scheduler](https://github.com/jmettraux/rufus-scheduler) 3.5.x.
12
+ Fugit is a core dependency of [rufus-scheduler](https://github.com/jmettraux/rufus-scheduler) >= 3.5.
13
13
 
14
14
 
15
15
  ## Related projects
@@ -321,19 +321,35 @@ Fugit.parse('every day at five') # ==> Fugit::Cron instance '0 5 * * *'
321
321
 
322
322
  ### Ambiguous nats
323
323
 
324
- Not all strings result in a clean, single, cron expression.
324
+ Not all strings result in a clean, single, cron expression. The `multi: false|true|:fail` argument to `Fugit::Nat.parse` could help.
325
325
 
326
326
  ```ruby
327
+ Fugit::Nat.parse('every day at 16:00 and 18:00')
328
+ .to_cron_s
329
+ # ==> '0 16,18 * * *' (a single Fugit::Cron instances)
327
330
  Fugit::Nat.parse('every day at 16:00 and 18:00', multi: true)
328
- # ==> [ '0 16,18 * * *' ]
331
+ .collect(&:to_cron_s)
332
+ # ==> [ '0 16,18 * * *' ] (array of Fugit::Cron instances, here only one)
333
+
329
334
  Fugit::Nat.parse('every day at 16:15 and 18:30')
330
- # ==> [ '15 16 * * *' ]
335
+ .to_cron_s
336
+ # ==> '15 16 * * *' (a single of Fugit::Cron instances)
331
337
  Fugit::Nat.parse('every day at 16:15 and 18:30', multi: true)
332
- # ==> [ '15 16 * * *', '30 18 * * *' ]
338
+ .collect(&:to_cron_s)
339
+ # ==> [ '15 16 * * *', '30 18 * * *' ] (two Fugit::Cron instances)
340
+
333
341
  Fugit::Nat.parse('every day at 16:15 and 18:30', multi: :fail)
334
342
  # ==> ArgumentError: multiple crons in "every day at 16:15 and 18:30" (15 16 * * * | 30 18 * * *)
343
+ Fugit::Nat.parse('every day at 16:15 nada 18:30', multi: true)
344
+ # ==> nil
335
345
  ```
336
346
 
347
+ `multi: true` indicates to `Fugit::Nat` that an array of `Fugit::Cron` instances is expected as a result.
348
+
349
+ `multi: :fail` tells `Fugit::Nat.parse` to fail if the result is more than 1 `Fugit::Cron` instances.
350
+
351
+ `multi: false` is the default behaviour, return a single `Fugit::Cron` instance or nil when it cannot parse.
352
+
337
353
 
338
354
  ## LICENSE
339
355
 
@@ -10,7 +10,7 @@ Gem::Specification.new do |s|
10
10
  s.platform = Gem::Platform::RUBY
11
11
  s.authors = [ 'John Mettraux' ]
12
12
  s.email = [ 'jmettraux+flor@gmail.com' ]
13
- s.homepage = 'http://github.com/floraison/fugit'
13
+ s.homepage = 'https://github.com/floraison/fugit'
14
14
  s.license = 'MIT'
15
15
  s.summary = 'time tools for flor'
16
16
 
@@ -40,7 +40,7 @@ Time tools for flor and the floraison project. Cron parsing and occurrence compu
40
40
  #s.add_runtime_dependency 'tzinfo'
41
41
  # this dependency appears in 'et-orbi'
42
42
 
43
- s.add_runtime_dependency 'raabro', '~> 1.3'
43
+ s.add_runtime_dependency 'raabro', '~> 1.4'
44
44
  s.add_runtime_dependency 'et-orbi', '~> 1.1', '>= 1.1.8'
45
45
 
46
46
  s.add_development_dependency 'rspec', '~> 3.8'
@@ -1,7 +1,8 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module Fugit
3
4
 
4
- VERSION = '1.3.6'
5
+ VERSION = '1.4.1'
5
6
  end
6
7
 
7
8
  require 'time'
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module Fugit
3
4
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module Fugit
3
4
 
@@ -12,9 +13,9 @@ module Fugit
12
13
  '@daily' => '0 0 * * *',
13
14
  '@midnight' => '0 0 * * *',
14
15
  '@noon' => '0 12 * * *',
15
- '@hourly' => '0 * * * *' }
16
+ '@hourly' => '0 * * * *' }.freeze
16
17
  MAXDAYS = [
17
- nil, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ]
18
+ nil, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ].freeze
18
19
 
19
20
  attr_reader(
20
21
  :original, :zone)
@@ -236,6 +237,8 @@ module Fugit
236
237
  # the translation occurs in the timezone of
237
238
  # this Fugit::Cron instance
238
239
 
240
+ zfrom = t.time.strftime('%z|%Z')
241
+
239
242
  loop do
240
243
 
241
244
  fail RuntimeError.new(
@@ -251,8 +254,14 @@ module Fugit
251
254
  min_match?(t) || (t.inc_min; next)
252
255
  sec_match?(t) || (t.inc_sec; next)
253
256
 
254
- st = t.time.strftime('%F|%T')
255
- (from, sfrom, ifrom = t.time, st, t.to_i; next) if st == sfrom
257
+ tt = t.time
258
+ st = tt.strftime('%F|%T')
259
+ zt = tt.strftime('%z|%Z')
260
+ #
261
+ if st == sfrom && zt != zfrom
262
+ from, sfrom, zfrom, ifrom = tt, st, zt, t.to_i
263
+ next
264
+ end
256
265
  #
257
266
  # when transitioning out of DST, this prevents #next_time from
258
267
  # yielding the same literal time twice in a row, see gh-6
@@ -328,7 +337,7 @@ module Fugit
328
337
  [ :seconds, 1, 60 ],
329
338
  [ :minutes, 60, 60 ],
330
339
  [ :hours, 3600, 24 ],
331
- [ :days, 24 * 3600, 365 ] ]
340
+ [ :days, 24 * 3600, 365 ] ].freeze
332
341
 
333
342
  def rough_frequency
334
343
 
@@ -487,7 +496,7 @@ module Fugit
487
496
 
488
497
  sla = 1 if sla == nil
489
498
  sta = min if sta == nil
490
- edn = max if edn == nil
499
+ edn = max if edn == nil || edn < 0 && sta > 0
491
500
 
492
501
  range(min, max, sta, edn, sla)
493
502
  end
@@ -499,12 +508,10 @@ module Fugit
499
508
  { min: min, max: max, sta: sta, edn: edn, sla: sla }.inspect
500
509
  ) if (sta < 0 && edn > 0) || (edn < 0 && sta > 0)
501
510
 
502
- #p({ min: min, max: max, sta: sta, edn: edn, sla: sla })
503
511
  a = []
504
512
 
505
513
  omin, omax = min, max
506
514
  min, max = -max, -1 if sta < 0
507
- #p({ min: min, max: max })
508
515
 
509
516
  cur = sta
510
517
 
@@ -604,10 +611,18 @@ module Fugit
604
611
 
605
612
  module Parser include Raabro
606
613
 
607
- WEEKDAYS = %w[ sunday monday tuesday wednesday thursday friday saturday ]
608
- WEEKDS = WEEKDAYS.collect { |d| d[0, 3] }
614
+ WEEKDAYS =
615
+ %w[ sunday monday tuesday wednesday thursday friday saturday ].freeze
616
+
617
+ WEEKDS =
618
+ WEEKDAYS.collect { |d| d[0, 3] }.freeze
619
+ DOW_REX =
620
+ /([0-7]|#{WEEKDS.join('|')})/i.freeze
609
621
 
610
- MONTHS = %w[ - jan feb mar apr may jun jul aug sep oct nov dec ]
622
+ MONTHS =
623
+ %w[ - jan feb mar apr may jun jul aug sep oct nov dec ].freeze
624
+ MONTH_REX =
625
+ /(1[0-2]|0?[1-9]|#{MONTHS[1..-1].join('|')})/i.freeze
611
626
 
612
627
  # piece parsers bottom to top
613
628
 
@@ -621,8 +636,8 @@ module Fugit
621
636
  def mos(i); rex(:mos, i, /[0-5]?\d/); end # min or sec
622
637
  def hou(i); rex(:hou, i, /(2[0-4]|[01]?[0-9])/); end
623
638
  def dom(i); rex(:dom, i, /(-?(3[01]|[12][0-9]|0?[1-9])|last|l)/i); end
624
- def mon(i); rex(:mon, i, /(1[0-2]|0?[1-9]|#{MONTHS[1..-1].join('|')})/i); end
625
- def dow(i); rex(:dow, i, /([0-7]|#{WEEKDS.join('|')})/i); end
639
+ def mon(i); rex(:mon, i, MONTH_REX); end
640
+ def dow(i); rex(:dow, i, DOW_REX); end
626
641
 
627
642
  def dow_hash(i); rex(:hash, i, /#(-?[1-5]|last|l)/i); end
628
643
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module Fugit
3
4
 
@@ -66,10 +67,9 @@ module Fugit
66
67
  day: { a: 'D', r: 'd', i: 'D', s: 24 * 3600, I: true, l: 'day' },
67
68
  hou: { a: 'h', r: 'h', i: 'H', s: 3600, I: true, l: 'hour' },
68
69
  min: { a: 'm', r: 'm', i: 'M', s: 60, I: true, l: 'minute' },
69
- sec: { a: 's', r: 's', i: 'S', s: 1, I: true, l: 'second' },
70
- }
70
+ sec: { a: 's', r: 's', i: 'S', s: 1, I: true, l: 'second' } }.freeze
71
71
  INFLA_KEYS, NON_INFLA_KEYS =
72
- KEYS.partition { |k, v| v[:I] }
72
+ KEYS.partition { |k, v| v[:I] }.freeze
73
73
 
74
74
  def _to_s(key)
75
75
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module Fugit
3
4
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module Fugit
3
4
 
@@ -16,7 +17,12 @@ module Fugit
16
17
 
17
18
  #p s; Raabro.pp(Parser.parse(s, debug: 3), colours: true)
18
19
  #(p s; Raabro.pp(Parser.parse(s, debug: 1), colours: true)) rescue nil
19
- parse_crons(s, Parser.parse(s), opts)
20
+
21
+ if slots = Parser.parse(s)
22
+ slots.to_crons(opts.merge(_s: s))
23
+ else
24
+ nil
25
+ end
20
26
  end
21
27
 
22
28
  def do_parse(s, opts={})
@@ -24,500 +30,653 @@ module Fugit
24
30
  parse(s, opts) ||
25
31
  fail(ArgumentError.new("could not parse a nat #{s.inspect}"))
26
32
  end
33
+ end
27
34
 
28
- protected
35
+ module Parser include Raabro
29
36
 
30
- def parse_crons(s, a, opts)
37
+ one_to_nine =
38
+ %w[ one two three four five six seven eight nine ]
39
+ sixties =
40
+ %w[ zero ] + one_to_nine +
41
+ %w[ ten eleven twelve thirteen fourteen fifteen sixteen seventeen
42
+ eighteen nineteen ] +
43
+ %w[ twenty thirty fourty fifty ]
44
+ .collect { |a|
45
+ ([ nil ] + one_to_nine)
46
+ .collect { |b| [ a, b ].compact.join('-') } }
47
+ .flatten
48
+
49
+ NHOURS = sixties[0, 13]
50
+ .each_with_index
51
+ .inject({}) { |h, (n, i)| h[n] = i; h }
52
+ .merge!(
53
+ 'midnight' => 0, 'oh' => 0, 'noon' => 12)
54
+ .freeze
55
+ NMINUTES = sixties
56
+ .each_with_index
57
+ .inject({}) { |h, (n, i)| h[n] = i; h }
58
+ .merge!(
59
+ "o'clock" => 0, 'hundred' => 0)
60
+ .freeze
61
+
62
+ WEEKDAYS = (
63
+ Fugit::Cron::Parser::WEEKDAYS +
64
+ Fugit::Cron::Parser::WEEKDS).freeze
65
+
66
+ POINTS = %w[
67
+ minutes? mins? seconds? secs? hours? hou h ].freeze
68
+
69
+ INTERVALS = %w[
70
+ seconds? minutes? hours? days? months?
71
+ sec min
72
+ s m h d M ].freeze
31
73
 
32
- #p a
33
- return nil unless a
74
+ oh = {
75
+ '1st' => 1, '2nd' => 2, '3rd' => 3, '21st' => 21, '22nd' => 22,
76
+ '23rd' => 23, '31st' => 31,
77
+ 'last' => 'L' }
78
+ (4..30)
79
+ .each { |i| oh["#{i}th"] = i.to_i }
80
+ %w[
81
+ first second third fourth fifth sixth seventh eighth ninth tenth
82
+ eleventh twelfth thirteenth fourteenth fifteenth sixteenth seventeenth
83
+ eighteenth nineteenth twentieth twenty-first twenty-second twenty-third
84
+ twenty-fourth twenty-fifth twenty-sixth twenty-seventh twenty-eighth
85
+ twenty-ninth thirtieth thirty-first ]
86
+ .each_with_index { |e, i| oh[e] = i + 1 }
87
+ OMONTHDAYS = oh.freeze
34
88
 
35
- h = a
36
- .reverse
37
- .inject({}) { |r, e| send("parse_#{e[0]}_elt", e, opts, r); r }
38
- #
39
- # the reverse ensure that in "every day at five", the
40
- # "at five" is placed before the "every day" so that
41
- # parse_x_elt calls have the right sequence
42
- #p h
89
+ OMONTHDAY_REX = /#{OMONTHDAYS.keys.join('|')}/i.freeze
90
+ MONTHDAY_REX = /3[0-1]|[0-2]?[0-9]/.freeze
91
+ WEEKDAY_REX = /(#{WEEKDAYS.join('|')})(?=($|[-, \t]))/i.freeze
92
+ # prevent "mon" from eating "monday"
93
+ NAMED_M_REX = /#{NMINUTES.keys.join('|')}/i.freeze
94
+ NAMED_H_REX = /#{NHOURS.keys.join('|')}/i.freeze
95
+ POINT_REX = /(#{POINTS.join('|')})[ \t]+/i.freeze
96
+ INTERVAL_REX = /[ \t]*(#{INTERVALS.join('|')})/.freeze
43
97
 
44
- if f = h[:_fail]
45
- #fail ArgumentError.new(f)
46
- return nil
47
- end
98
+ #
99
+ # parsers bottom to top #################################################
48
100
 
49
- hms = h[:hms]
101
+ def _every(i); rex(nil, i, /[ \t]*every[ \t]+/i); end
102
+ def _from(i); rex(nil, i, /[ \t]*from[ \t]+/i); end
103
+ def _at(i); rex(nil, i, /[ \t]*at[ \t]+/i); end
104
+ def _on(i); rex(nil, i, /[ \t]*on[ \t]+/i); end
105
+ def _to(i); rex(nil, i, /[ \t]*to[ \t]+/i); end
50
106
 
51
- hours = (hms || [])
52
- .uniq
53
- .inject({}) { |r, hm| (r[hm[1]] ||= []) << hm[0]; r }
54
- .inject({}) { |r, (m, hs)| (r[hs.sort] ||= []) << m; r }
55
- .to_a
56
- .sort_by { |hs, ms| -hs.size }
57
- if hours.empty?
58
- hours << (h[:dom] ? [ [ '0' ], [ '0' ] ] : [ [ '*' ], [ '*' ] ])
59
- end
107
+ def _and(i); rex(nil, i, /[ \t]*and[ \t]+/i); end
108
+ def _and_or_or(i); rex(nil, i, /[ \t]*(and|or)[ \t]+/i); end
109
+ def _in_or_on(i); rex(nil, i, /(in|on)[ \t]+/i); end
60
110
 
61
- crons = hours
62
- .collect { |hm| assemble_cron(h.merge(hms: hm)) }
111
+ def _and_or_or_or_comma(i)
112
+ rex(nil, i, /[ \t]*(,[ \t]*)?((and|or)[ \t]+|,[ \t]*)/i); end
63
113
 
64
- fail ArgumentError.new(
65
- "multiple crons in #{s.inspect} " +
66
- "(#{crons.collect(&:original).join(' | ')})"
67
- ) if opts[:multi] == :fail && crons.size > 1
114
+ def _to_or_dash(i);
115
+ rex(nil, i, /[ \t]*-[ \t]*|[ \t]+(to|through)[ \t]+/i); end
68
116
 
69
- if opts[:multi] == true || (opts[:multi] && opts[:multi] != :fail)
70
- crons
71
- else
72
- crons.first
73
- end
74
- end
117
+ def _day_s(i); rex(nil, i, /[ \t]*days?[ \t]+/i); end
118
+ def _the(i); rex(nil, i, /[ \t]*the[ \t]+/i); end
75
119
 
76
- def assemble_cron(h)
77
-
78
- #puts "ac: " + h.inspect
79
- s = []
80
- s << h[:sec] if h[:sec]
81
- s << h[:hms][1].join(',')
82
- s << h[:hms][0].join(',')
83
- s << (h[:dom] || '*') << (h[:mon] || '*') << (h[:dow] || '*')
84
- s << h[:tz] if h[:tz]
85
-
86
- Fugit::Cron.parse(s.join(' '))
87
- end
88
-
89
- def eone(e); e1 = e[1]; e1 == 1 ? '*' : "*/#{e1}"; end
90
-
91
- def parse_interval_elt(e, opts, h)
92
-
93
- e1 = e[1]
94
-
95
- case e[2]
96
- when 's', 'sec', 'second', 'seconds'
97
- h[:sec] = eone(e)
98
- when 'm', 'min', 'mins', 'minute', 'minutes'
99
- #(h[:hms] ||= []) << [ '*', eone(e) ]
100
- h[:hms] ||= [ [ '*', eone(e) ] ]
101
- when 'h', 'hour', 'hours'
102
- h[:hms] ||= [ [ eone(e), 0 ] ]
103
- when 'd', 'day', 'days'
104
- h[:dom] = "*/#{e1}" if e1 > 1
105
- h[:hms] ||= [ [ 0, 0 ] ]
106
- when 'w', 'week', 'weeks'
107
- h[:_fail] = "cannot have crons for \"every #{e1} weeks\"" if e1 > 1
108
- h[:hms] ||= [ [ 0, 0 ] ]
109
- h[:dow] ||= 0
110
- when 'M', 'month', 'months'
111
- h[:_fail] = "cannot have crons for \"every #{e1} months\"" if e1 > 12
112
- h[:hms] ||= [ [ 0, 0 ] ]
113
- h[:dom] = 1
114
- h[:mon] = eone(e)
115
- when 'Y', 'y', 'year', 'years'
116
- h[:_fail] = "cannot have crons for \"every #{e1} years\"" if e1 > 1
117
- h[:hms] ||= [ [ 0, 0 ] ]
118
- h[:dom] = 1
119
- h[:mon] = 1
120
- end
121
- end
120
+ def _space(i); rex(nil, i, /[ \t]+/); end
121
+ def _sep(i); rex(nil, i, /([ \t]+|[ \t]*,[ \t]*)/); end
122
122
 
123
- def parse_dow_list_elt(e, opts, h)
123
+ def count(i); rex(:count, i, /\d+/); end
124
124
 
125
- h[:hms] ||= [ [ 0, 0 ] ]
126
- h[:dow] = e[1..-1].collect(&:to_s).sort.join(',')
125
+ def omonthday(i)
126
+ rex(:omonthday, i, OMONTHDAY_REX)
127
127
  end
128
-
129
- def parse_dow_range_elt(e, opts, h)
130
-
131
- h[:hms] ||= [ [ 0, 0 ] ]
132
- h[:dow] = e[1] == e[2] ? e[1] : "#{e[1]}-#{e[2]}"
128
+ def monthday(i)
129
+ rex(:monthday, i, MONTHDAY_REX)
133
130
  end
134
-
135
- def parse_day_of_month_elt(e, opts, h)
136
-
137
- h[:dom] = e[1..-1].join(',')
131
+ def weekday(i)
132
+ rex(:weekday, i, WEEKDAY_REX)
138
133
  end
139
134
 
140
- def parse_at_elt(e, opts, h)
135
+ def omonthdays(i); jseq(nil, i, :omonthday, :_and_or_or_or_comma); end
136
+ def monthdays(i); jseq(nil, i, :monthday, :_and_or_or_or_comma); end
141
137
 
142
- (h[:hms] ||= []).concat(e[1])
143
-
144
- l = h[:hms].last
145
- h[:sec] = l.pop if l.size > 2
146
- end
138
+ def weekdays(i); jseq(:weekdays, i, :weekday, :_and_or_or_or_comma); end
147
139
 
148
- def parse_on_elt(e, opts, h)
140
+ def on_the(i); seq(nil, i, :_the, :omonthdays); end
149
141
 
150
- e1 = e[1]
151
- h[:dow] = e1[0]
152
- h[:hms] = [ e1[1] ]
142
+ def _minute(i); rex(nil, i, /[ \t]*minute[ \t]+/i) end
153
143
 
154
- l = h[:hms].last
155
- h[:sec] = l.pop if l.size > 2
144
+ def _dmin(i)
145
+ rex(:dmin, i, /[0-5]?[0-9]/)
156
146
  end
157
-
158
- def parse_tz_elt(e, opts, h)
159
-
160
- h[:tz] = e[1]
147
+ def and_dmin(i)
148
+ seq(nil, i, :_and_or_or_or_comma, :_minute, '?', :_dmin)
161
149
  end
162
- end
163
-
164
- module Parser include Raabro
165
150
 
166
- NUMS = %w[
167
- zero one two three four five six seven eight nine ten eleven twelve ]
168
-
169
- WEEKDAYS =
170
- Fugit::Cron::Parser::WEEKDS + Fugit::Cron::Parser::WEEKDAYS
171
-
172
- NHOURS = {
173
- 'noon' => [ 12, 0 ],
174
- 'midnight' => [ 0, 0 ], 'oh' => [ 0, 0 ] }
175
- NMINUTES = {
176
- "o'clock" => 0, 'five' => 5,
177
- 'ten' => 10, 'fifteen' => 15,
178
- 'twenty' => 20, 'twenty-five' => 25,
179
- 'thirty' => 30, 'thirty-five' => 35,
180
- 'fourty' => 40, 'fourty-five' => 45,
181
- 'fifty' => 50, 'fifty-five' => 55 }
182
-
183
- oh = {
184
- '1st' => 1, '2nd' => 2, '3rd' => 3, '21st' => 21, '22nd' => 22,
185
- '23rd' => 23, '31st' => 31 }
186
- %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 ]
187
- .each { |i| oh["#{i}th"] = i.to_i }
188
- %w[
189
- first second third fourth fifth sixth seventh eighth ninth tenth
190
- eleventh twelfth thirteenth fourteenth fifteenth sixteenth seventeenth
191
- eighteenth nineteenth twentieth twenty-first twenty-second twenty-third
192
- twenty-fourth twenty-fifth twenty-fifth twenty-sixth twenty-seventh
193
- twenty-eighth twenty-ninth thirtieth thirty-first ]
194
- .each_with_index { |e, i| oh[e] = i + 1 }
195
- ORDINALS = oh
151
+ def on_minutes(i)
152
+ seq(:on_minutes, i, :_minute, :_dmin, :and_dmin, '*')
153
+ end
196
154
 
197
- # piece parsers bottom to top
155
+ def on_thex(i);
156
+ rex(:on_thex, i, /[ \t]*the[ \t]+(hour|minute)[ \t]*/i);
157
+ end
198
158
 
199
- def _from(i); rex(nil, i, /\s*from\s+/i); end
200
- def _every(i); rex(nil, i, /\s*(every)\s+/i); end
201
- def _at(i); rex(nil, i, /\s*at\s+/i); end
202
- def _in(i); rex(nil, i, /\s*(in|on)\s+/i); end
203
- def _to(i); rex(nil, i, /\s*to\s+/i); end
204
- def _dash(i); rex(nil, i, /-\s*/i); end
205
- def _and(i); rex(nil, i, /\s*and\s+/i); end
206
- def _on(i); rex(nil, i, /\s*on\s+/i); end
159
+ def on_thes(i); jseq(:on_thes, i, :on_the, :_and_or_or_or_comma); end
160
+ def on_days(i); seq(:on_days, i, :_day_s, :monthdays); end
161
+ def on_weekdays(i); ren(:on_weekdays, i, :weekdays); end
207
162
 
208
- def _and_or_comma(i)
209
- rex(nil, i, /\s*(,?\s*and\s|,?\s*or\s|,)\s*/i)
210
- end
211
- def _at_comma(i)
212
- rex(nil, i, /\s*(at\s|,|)\s*/i)
163
+ def on_object(i)
164
+ alt(nil, i, :on_days, :on_weekdays, :on_minutes, :on_thes, :on_thex)
213
165
  end
214
- def _to_through(i)
215
- rex(nil, i, /\s*(to|through)\s+/i)
166
+ def on_objects(i)
167
+ jseq(nil, i, :on_object, :_and)
216
168
  end
217
169
 
218
- def integer(i); rex(:int, i, /\d+\s*/); end
170
+ #'every month on day 2 at 10:00' => '0 10 2 * *',
171
+ #'every month on day 2 and 5 at 10:00' => '0 10 2,5 * *',
172
+ #'every month on days 1,15 at 10:00' => '0 10 1,15 * *',
173
+ #
174
+ #'every week on monday 18:23' => '23 18 * * 1',
175
+ #
176
+ # every month on the 1st
177
+ def on(i)
178
+ seq(:on, i, :_on, :on_objects)
179
+ end
219
180
 
220
- def tz_name(i)
221
- rex(nil, i,
222
- /\s*[A-Z][a-zA-Z0-9+\-]+(\/[A-Z][a-zA-Z0-9+\-_]+){0,2}(\s+|$)/)
181
+ def city_tz(i)
182
+ rex(nil, i, /[A-Z][a-zA-Z0-9+\-]+(\/[A-Z][a-zA-Z0-9+\-_]+){0,2}/)
183
+ end
184
+ def named_tz(i)
185
+ rex(nil, i, /Z|UTC/)
223
186
  end
224
- def tz_delta(i)
225
- rex(nil, i,
226
- /\s*[-+]([01][0-9]|2[0-4]):?(00|15|30|45)(\s+|$)/)
187
+ def delta_tz(i)
188
+ rex(nil, i, /[-+]([01][0-9]|2[0-4])(:?(00|15|30|45))?/)
189
+ end
190
+ def tz(i)
191
+ alt(:tz, i, :city_tz, :named_tz, :delta_tz)
227
192
  end
228
193
  def tzone(i)
229
- alt(:tzone, i, :tz_delta, :tz_name)
194
+ seq(nil, i, :_in_or_on, '?', :tz)
230
195
  end
231
196
 
232
- def and_named_digits(i)
233
- rex(:xxx, i, 'TODO')
197
+ def digital_hour(i)
198
+ rex(
199
+ :digital_hour, i,
200
+ /(2[0-4]|[0-1]?[0-9]):([0-5][0-9])([ \t]*(am|pm))?/i)
234
201
  end
235
202
 
236
- def dname(i)
237
- rex(:dname, i, /(s(ec(onds?)?)?|m(in(utes?)?)?)\s+/i)
238
- end
239
- def named_digit(i)
240
- seq(:named_digit, i, :dname, :integer)
203
+ def ampm(i)
204
+ rex(:ampm, i, /[ \t]*(am|pm)/i)
241
205
  end
242
- def named_digits(i)
243
- seq(nil, i, :named_digit, '+', :and_named_digits, '*')
206
+ def dark(i)
207
+ rex(:dark, i, /[ \t]*dark/i)
244
208
  end
245
209
 
246
- def am_pm(i)
247
- rex(:am_pm, i, /\s*(am|pm|dark)\s*/i)
210
+ def simple_h(i)
211
+ rex(:simple_h, i, /#{(0..24).to_a.reverse.join('|')}/)
248
212
  end
249
-
250
- def nminute(i)
251
- rex(:nminute, i, /(#{NMINUTES.keys.join('|')})\s*/i)
213
+ def simple_hour(i)
214
+ seq(:simple_hour, i, :simple_h, :ampm, '?')
252
215
  end
253
- def nhour(i)
254
- rex(:nhour, i, /(#{NUMS.join('|')})\s*/i)
216
+
217
+ def named_m(i)
218
+ rex(:named_m, i, NAMED_M_REX)
255
219
  end
256
- def numeral_hour(i)
257
- seq(:numeral_hour, i, :nhour, :am_pm, '?', :nminute, '?')
220
+ def named_min(i)
221
+ seq(nil, i, :_space, :named_m)
258
222
  end
259
223
 
224
+ def named_h(i)
225
+ rex(:named_h, i, NAMED_H_REX)
226
+ end
260
227
  def named_hour(i)
261
- rex(:named_hour, i, /(#{NHOURS.keys.join('|')})/i)
228
+ seq(:named_hour, i, :named_h, :dark, '?', :named_min, '?', :ampm, '?')
262
229
  end
263
230
 
264
- def shour(i)
265
- rex(:shour, i, /(2[0-4]|[01]?[0-9])/)
266
- end
267
- def simple_hour(i)
268
- seq(:simple_hour, i, :shour, :am_pm, '?')
269
- end
231
+ def _point(i); rex(:point, i, POINT_REX); end
270
232
 
271
- def digital_hour(i)
272
- rex(:digital_hour, i, /(2[0-4]|[01][0-9]):?[0-5]\d/)
233
+ def counts(i)
234
+ jseq(nil, i, :count, :_and_or_or_or_comma)
273
235
  end
274
236
 
237
+ def at_p(i)
238
+ seq(:at_p, i, :_point, :counts)
239
+ end
275
240
  def at_point(i)
276
- alt(nil, i,
277
- :digital_hour, :simple_hour, :named_hour, :numeral_hour,
278
- :named_digits)
241
+ jseq(nil, i, :at_p, :_and_or_or)
279
242
  end
280
243
 
281
- def weekday(i)
282
- rex(:weekday, i, /(#{WEEKDAYS.reverse.join('|')})\s*/i)
244
+ # at five
245
+ # at five pm
246
+ # at five o'clock
247
+ # at 16:30
248
+ # at noon
249
+ # at 18:00 UTC <-- ...tz
250
+ def at_object(i)
251
+ alt(nil, i, :named_hour, :digital_hour, :simple_hour, :at_point)
283
252
  end
284
-
285
- def and_at(i)
286
- seq(nil, i, :_and_or_comma, :at_point)
253
+ def at_objects(i)
254
+ jseq(nil, i, :at_object, :_and_or_or_or_comma)
287
255
  end
288
256
 
289
- def _intervals(i)
290
- rex(:intervals, i,
291
- /(
292
- y(ears?)?|months?|w(eeks?)?|d(ays?)?|
293
- h(ours?)?|m(in(ute)?s?)?|s(ec(ond)?s?)?
294
- )(\s+|$)/ix)
257
+ def at(i)
258
+ seq(:at, i, :_at, '?', :at_objects)
295
259
  end
296
260
 
297
- def sinterval(i)
298
- rex(:sinterval, i,
299
- /(year|month|week|day|hour|min(ute)?|sec(ond)?)(\s+|$)/i)
261
+ def interval(i)
262
+ rex(:interval, i, INTERVAL_REX)
300
263
  end
301
- def ninterval(i)
302
- seq(:ninterval, i, :integer, :_intervals)
264
+
265
+ # every day
266
+ # every 1 minute
267
+ def every_interval(i)
268
+ seq(:every_interval, i, :count, '?', :interval)
303
269
  end
304
270
 
305
- def ordinal(i)
306
- rex(:ordinal, i, /\s*(#{ORDINALS.keys.join('|')})\s*/)
271
+ def every_single_interval(i)
272
+ rex(:every_single_interval, i, /(1[ \t]+)?(week|year)/)
307
273
  end
308
274
 
309
- def _mod(i); rex(nil, i, /\s*month\s+on\s+days?\s+/i); end
310
- def _oftm(i); rex(nil, i, /\s*(day\s)?\s*of\s+the\s+month\s*/i); end
275
+ def to_weekday(i)
276
+ seq(:to_weekday, i, :weekday, :_to_or_dash, :weekday)
277
+ end
311
278
 
312
- def dom(i)
313
- rex(:int, i, /([12][0-9]|3[01]|[0-9])/)
279
+ def weekday_range(i)
280
+ alt(nil, i, :to_weekday, :weekdays)
314
281
  end
315
- def and_or_dom(i)
316
- seq(nil, i, :_and_or_comma, :dom)
282
+
283
+ def to_omonthday(i)
284
+ seq(:to_omonthday, i,
285
+ :_the, '?', :omonthday, :_to, :_the, '?', :omonthday)
317
286
  end
318
- def dom_list(i)
319
- seq(:dom_list, i, :dom, :and_or_dom, '*')
287
+
288
+ def to_hour(i)
289
+ seq(:to_hour, i, :at_object, :_to, :at_object)
320
290
  end
321
291
 
322
- def dom_mod(i) # every month on day
323
- seq(:dom, i, :_mod, :dom_list)
292
+ def from_object(i)
293
+ alt(nil, i, :to_weekday, :to_omonthday, :to_hour)
324
294
  end
325
- def dom_noftm(i) # every nth of month
326
- seq(:dom, i, :ordinal, :_oftm)
295
+ def from_objects(i)
296
+ jseq(nil, i, :from_object, :_and_or_or)
327
297
  end
328
- def day_of_month(i)
329
- alt(nil, i, :dom_noftm, :dom_mod)
298
+ def from(i)
299
+ seq(nil, i, :_from, '?', :from_objects)
330
300
  end
331
301
 
332
- def dow_class(i)
333
- rex(:dow_class, i, /(weekday)(\s+|$)/i)
302
+ # every monday
303
+ # every Fri-Sun
304
+ # every Monday and Tuesday
305
+ def every_weekday(i)
306
+ jseq(nil, i, :weekday_range, :_and_or_or)
334
307
  end
335
308
 
336
- def dow(i)
337
- seq(:dow, i, :weekday)
309
+ def otm(i)
310
+ rex(nil, i, /[ \t]+of the month/)
338
311
  end
339
- def and_or_dow(i)
340
- seq(nil, i, :_and_or_comma, :dow)
312
+
313
+ # every 1st of the month
314
+ # every first of the month
315
+ # Every 2nd of the month
316
+ # Every second of the month
317
+ # every 15th of the month
318
+ def every_of_the_month(i)
319
+ seq(nil, i, :omonthdays, :otm)
341
320
  end
342
- def dow_list(i)
343
- seq(:dow_list, i, :dow, :and_or_dow, '*')
321
+
322
+ def every_named(i)
323
+ rex(:every_named, i, /weekday/i)
344
324
  end
345
325
 
346
- def to_dow_range(i)
347
- seq(:dow_range, i, :weekday, :_to_through, :weekday)
326
+ def every_object(i)
327
+ alt(
328
+ nil, i,
329
+ :every_weekday, :every_of_the_month,
330
+ :every_interval, :every_named, :every_single_interval)
348
331
  end
349
- def dash_dow_range(i)
350
- seq(:dow_range, i, :weekday, :_dash, :weekday)
332
+ def every_objects(i)
333
+ jseq(nil, i, :every_object, :_and_or_or)
351
334
  end
352
- def dow_range(i)
353
- alt(nil, i, :to_dow_range, :dash_dow_range)
335
+
336
+ def every(i)
337
+ seq(:every, i, :_every, :every_objects)
354
338
  end
355
339
 
356
- def day_of_week(i)
357
- alt(nil, i, :dow_range, :dow_list, :dow_class)
340
+ def nat_elt(i)
341
+ alt(nil, i, :every, :from, :at, :tzone, :on)
342
+ end
343
+ def nat(i)
344
+ jseq(:nat, i, :nat_elt, :_sep)
358
345
  end
359
346
 
360
- def interval(i)
361
- alt(nil, i, :sinterval, :ninterval)
347
+ #
348
+ # rewrite parsed tree ###################################################
349
+
350
+ def slot(key, data0, data1=nil, opts=nil)
351
+ Slot.new(key, data0, data1, opts)
362
352
  end
363
353
 
364
- def every_object(i)
365
- alt(nil, i, :day_of_month, :interval, :day_of_week)
354
+ def _rewrite_subs(t, key=nil)
355
+ t.subgather(key).collect { |ct| rewrite(ct) }
366
356
  end
367
- def from_object(i)
368
- alt(nil, i, :interval, :to_dow_range)
357
+ def _rewrite_sub(t, key=nil)
358
+ st = t.sublookup(key)
359
+ st ? rewrite(st) : nil
369
360
  end
370
361
 
371
- def tz(i)
372
- seq(nil, i, :_in, '?', :tzone)
362
+ def rewrite_dmin(t)
363
+ t.strinp
373
364
  end
374
- def on(i)
375
- seq(:on, i, :_on, :weekday, :at_point, :and_at, '*')
365
+
366
+ def rewrite_on_minutes(t)
367
+ #Raabro.pp(t, colours: true)
368
+ mins = t.subgather(:dmin).collect(&:strinp)
369
+ #slot(:m, mins.join(','))
370
+ slot(:hm, '*', mins.join(','), strong: 1)
376
371
  end
377
- def at(i)
378
- seq(:at, i, :_at_comma, :at_point, :and_at, '*')
372
+
373
+ def rewrite_on_thex(t)
374
+ case s = t.string
375
+ #when /hour/i then slot(:h, 0)
376
+ #else slot(:m, '*')
377
+ when /hour/i then slot(:hm, 0, '*', strong: 0)
378
+ else slot(:hm, '*', '*', strong: 1)
379
+ end
379
380
  end
380
- def from(i)
381
- seq(:from, i, :_from, :from_object)
381
+
382
+ def rewrite_on_thes(t)
383
+ _rewrite_subs(t, :omonthday)
382
384
  end
383
- def every(i)
384
- seq(:every, i, :_every, :every_object)
385
+ def rewrite_on_days(t)
386
+ _rewrite_subs(t, :monthday)
385
387
  end
386
388
 
387
- def at_from(i)
388
- seq(nil, i, :at, :from, :tz, '?')
389
- end
390
- def at_every(i)
391
- seq(nil, i, :at, :every, :tz, '?')
389
+ def rewrite_on(t)
390
+ _rewrite_subs(t)
392
391
  end
393
392
 
394
- def from_at(i)
395
- seq(nil, i, :from, :at, '?', :tz, '?')
393
+ def rewrite_monthday(t)
394
+ slot(:monthday, t.string.to_i)
396
395
  end
397
396
 
398
- def every_(i)
399
- seq(nil, i, :every, :tz, '?')
397
+ def rewrite_omonthday(t)
398
+ slot(:monthday, OMONTHDAYS[t.string.downcase])
400
399
  end
401
- def every_on(i)
402
- seq(nil, i, :every, :on, :tz, '?')
400
+
401
+ def rewrite_at_p(t)
402
+ pt = t.sublookup(:point).strinpd
403
+ pt = pt.start_with?('mon') ? 'M' : pt[0, 1]
404
+ pts = t.subgather(:count).collect { |e| e.string.to_i }
405
+ #p [ pt, pts ]
406
+ case pt
407
+ #when 'm' then slot(:m, pts)
408
+ when 'm' then slot(:hm, '*', pts, strong: 1)
409
+ when 's' then slot(:second, pts)
410
+ else slot(pt.to_sym, pts)
411
+ end
403
412
  end
404
- def every_at(i)
405
- seq(nil, i, :every, :at, :tz, '?')
413
+
414
+ def rewrite_every_single_interval(t)
415
+ case t.string
416
+ when /year/i then [ slot(:month, 1, :weak), slot(:monthday, 1, :weak) ]
417
+ #when /week/i then xxx...
418
+ else slot(:weekday, 0, :weak)
419
+ end
406
420
  end
407
421
 
408
- def nat(i)
409
- alt(:nat, i,
410
- :every_at, :every_on, :every_,
411
- :from_at,
412
- :at_every, :at_from)
422
+ def rewrite_every_interval(t)
423
+
424
+ #Raabro.pp(t, colours: true)
425
+ ci = t.subgather(nil).collect(&:string)
426
+ i = ci.pop.strip[0, 3]
427
+ c = (ci.pop || '1').strip
428
+ i = (i == 'M' || i.downcase == 'mon') ? 'M' : i[0, 1].downcase
429
+ cc = c == '1' ? '*' : "*/#{c}"
430
+
431
+ case i
432
+ when 'M' then slot(:month, cc)
433
+ when 'd' then slot(:monthday, cc, :weak)
434
+ #when 'h' then slot(:hm, cc, 0, weak: :minute)
435
+ when 'h' then slot(:hm, cc, 0, weak: 1)
436
+ when 'm' then slot(:hm, '*', cc, strong: 1)
437
+ when 's' then slot(:second, cc)
438
+ else {}
439
+ end
413
440
  end
414
441
 
415
- # rewrite parsed tree
442
+ def rewrite_every_named(t)
416
443
 
417
- #def _rewrite_single(t)
418
- # [ t.name, rewrite(t.sublookup(nil)) ]
419
- #end
420
- def _rewrite_children(t)
421
- t.subgather(nil).collect { |tt| rewrite(tt) }
444
+ case s = t.string
445
+ when /weekday/i then slot(:weekday, '1-5', :weak)
446
+ when /week/i then slot(:weekday, '0', :weak)
447
+ else fail "cannot rewrite #{s.inspect}"
448
+ end
422
449
  end
423
- def _rewrite_multiple(t)
424
- [ t.name, _rewrite_children(t) ]
450
+
451
+ def rewrite_tz(t)
452
+ slot(:tz, t.string)
425
453
  end
426
- def _rewrite_child(t)
427
- rewrite(t.sublookup(nil))
454
+
455
+ def rewrite_weekday(t)
456
+ Fugit::Cron::Parser::WEEKDS.index(t.string[0, 3].downcase)
428
457
  end
429
458
 
430
- def rewrite_int(t); t.string.to_i; end
459
+ def rewrite_weekdays(t)
460
+ #Raabro.pp(t, colours: true)
461
+ slot(:weekday, _rewrite_subs(t, :weekday))
462
+ end
463
+ alias rewrite_on_weekdays rewrite_weekdays
431
464
 
432
- def rewrite_tzone(t)
465
+ def rewrite_to_weekday(t)
466
+ wd0, wd1 = _rewrite_subs(t, :weekday)
467
+ #wd1 = 7 if wd1 == 0
468
+ slot(:weekday, "#{wd0}-#{wd1}")
469
+ end
433
470
 
434
- [ :tz, t.strim ]
471
+ def rewrite_to_omonthday(t)
472
+ md0, md1 = _rewrite_subs(t, :omonthday).collect(&:_data0)
473
+ slot(:monthday, "#{md0}-#{md1}")
435
474
  end
436
475
 
437
- def rewrite_sinterval(t)
476
+ def rewrite_digital_hour(t)
477
+ h, m, ap = t.strinpd.split(/[: \t]+/)
478
+ h, m = h.to_i, m.to_i
479
+ h += 12 if ap && ap == 'pm'
480
+ slot(:hm, h.to_i, m.to_i)
481
+ end
438
482
 
439
- [ :interval, 1, t.strim ]
483
+ def rewrite_simple_hour(t)
484
+ h, ap = t.subgather(nil).collect(&:strinpd)
485
+ h = h.to_i
486
+ h = h + 12 if ap == 'pm'
487
+ slot(:hm, h, 0)
440
488
  end
441
489
 
442
- def rewrite_ninterval(t)
490
+ def rewrite_named_hour(t)
443
491
 
444
- [ :interval,
445
- t.sublookup(:int).string.to_i,
446
- t.sublookup(:intervals).strim ]
447
- end
492
+ ht = t.sublookup(:named_h)
493
+ mt = t.sublookup(:named_m)
494
+ apt = t.sublookup(:ampm)
448
495
 
449
- def rewrite_named_digit(t)
496
+ h = ht.strinp
497
+ m = mt ? mt.strinp : 0
498
+ #p [ 0, '-->', h, m ]
499
+ h = NHOURS[h]
500
+ m = NMINUTES[m] || m
501
+ #p [ 1, '-->', h, m ]
450
502
 
451
- i = t.sublookup(:int).string.to_i
503
+ h += 12 if h < 13 && apt && apt.strinpd == 'pm'
452
504
 
453
- case n = t.sublookup(:dname).strim
454
- when /^s/ then [ '*', '*', i ]
455
- when /^m/ then [ '*', i ]
456
- end
505
+ slot(:hm, h, m)
457
506
  end
458
507
 
459
- def rewrite_named_hour(t)
460
- NHOURS[t.strim.downcase]
461
- end
462
- def rewrite_numeral_hour(t)
463
- vs = t.subgather(nil).collect { |st| st.strim.downcase }
464
- v = NUMS.index(vs[0])
465
- v += 12 if vs[1] == 'pm'
466
- m = NMINUTES[vs[2]] || 0
467
- [ v, m ]
508
+ def rewrite_to_hour(t)
509
+ #Raabro.pp(t, colours: true)
510
+ ht0, ht1 = t.subgather(nil)
511
+ h0, h1 = rewrite(ht0), rewrite(ht1)
512
+ fail ArgumentError.new(
513
+ "cannot deal with #{ht0.strinp} to #{ht1.strinp}, minutes diverge"
514
+ ) if h0.data1 != h1.data1
515
+ slot(:hm, "#{h0._data0}-#{h1._data0}", 0, strong: 0)
468
516
  end
469
- def rewrite_simple_hour(t)
470
- vs = t.subgather(nil).collect { |st| st.strim.downcase }
471
- v = vs[0].to_i
472
- v += 12 if vs[1] == 'pm'
473
- [ v, 0 ]
517
+
518
+ def rewrite_at(t)
519
+ _rewrite_subs(t)
474
520
  end
475
- def rewrite_digital_hour(t)
476
- m = t.string.match(/(\d\d?):?(\d\d)/)
477
- [ m[1].to_i, m[2].to_i ]
521
+
522
+ def rewrite_every(t)
523
+ _rewrite_sub(t)
478
524
  end
479
525
 
480
- def rewrite_weekday(t)
526
+ def rewrite_nat(t)
527
+ #Raabro.pp(t, colours: true)
528
+ Fugit::Nat::SlotGroup.new(_rewrite_subs(t).flatten)
529
+ end
530
+ end
481
531
 
482
- WEEKDAYS.index(t.strim.downcase[0, 3])
532
+ class Slot
533
+ attr_reader :key
534
+ attr_accessor :_data0, :_data1
535
+ def initialize(key, d0, d1=nil, opts=nil)
536
+ d1, opts = d1.is_a?(Symbol) ? [ nil, d1 ] : [ d1, opts ]
537
+ @key, @_data0, @_data1 = key, d0, d1
538
+ @opts = (opts.is_a?(Symbol) ? { opts => true } : opts) || {}
539
+ end
540
+ def data0; @data0 ||= Array(@_data0); end
541
+ def data1; @data1 ||= Array(@_data1); end
542
+ def weak; @opts[:weak]; end
543
+ def strong; @opts[:strong]; end
544
+ def graded?; weak || strong; end
545
+ def append(slot)
546
+ @_data0, @_data1 = conflate(0, slot), conflate(1, slot)
547
+ @opts.clear
548
+ self
549
+ end
550
+ def inspect
551
+ a = [ @key, @_data0 ]
552
+ a << @_data1 if @_data1 != nil
553
+ a << @opts if @opts && @opts.keys.any?
554
+ "(slot #{a.collect(&:inspect).join(' ')})"
555
+ end
556
+ def a; [ data0, data1 ]; end
557
+ protected
558
+ def to_a(x)
559
+ return [] if x == '*'
560
+ Array(x)
561
+ end
562
+ def conflate(index, slot)
563
+ a, b = index == 0 ? [ @_data0, slot._data0 ] : [ @_data1, slot._data1 ]
564
+ return a if b == nil
565
+ return b if a == nil
566
+ if ra = (index == 0 && slot.strong == 1 && hour_range)
567
+ h0, h1 = ra[0], ra[1] - 1; return h0 == h1 ? h0 : "#{h0}-#{h1}"
568
+ elsif rb = (index == 0 && strong == 1 && slot.hour_range)
569
+ h0, h1 = rb[0], rb[1] - 1; return h0 == h1 ? h0 : "#{h0}-#{h1}"
570
+ end
571
+ return a if strong == index || strong == true
572
+ return b if slot.strong == index || slot.strong == true
573
+ return a if slot.weak == index || slot.weak == true
574
+ return b if weak == index || weak == true
575
+ return [ '*' ] if a == '*' && b == '*'
576
+ to_a(a).concat(to_a(b))
577
+ end
578
+ def hour_range
579
+ m = (key == :hm && @_data1 == 0 && @_data0.match(/\A(\d+)-(\d+)\z/))
580
+ m ? [ m[1].to_i, m[2].to_i ] : nil
483
581
  end
582
+ end
484
583
 
485
- def rewrite_ordinal(t); ORDINALS[t.strim]; end
584
+ class SlotGroup
486
585
 
487
- def rewrite_dom(t)
586
+ def initialize(slots)
488
587
 
489
- #Raabro.pp(t, colours: true)
490
- [ :day_of_month,
491
- *_rewrite_children(t).flatten.select { |e| e.is_a?(Integer) } ]
588
+ #puts "SlotGroup.new " + slots.inspect
589
+ @slots = {}
590
+ @hms = []
591
+
592
+ slots.each do |s|
593
+ if s.key == :hm
594
+ #ls = @hms.last; @hms.pop if ls && ls.key == :hm && ls.weak == true
595
+ @hms << s
596
+ elsif hs = @slots[s.key]
597
+ hs.append(s)
598
+ else
599
+ @slots[s.key] = s
600
+ end
601
+ end
602
+
603
+ if @slots[:monthday] || @slots[:weekday]
604
+ @hms << make_slot(:hm, 0, 0) if @hms.empty?
605
+ elsif @slots[:month]
606
+ @hms << make_slot(:hm, 0, 0) if @hms.empty?
607
+ @slots[:monthday] ||= make_slot(:monthday, 1)
608
+ end
492
609
  end
493
610
 
494
- alias rewrite_dow _rewrite_child
611
+ def to_crons(opts)
495
612
 
496
- def rewrite_dom_list(t); [ :dom_list, *_rewrite_children(t) ]; end
497
- def rewrite_dow_list(t); [ :dow_list, *_rewrite_children(t) ]; end
613
+ multi = opts.has_key?(:multi) ? opts[:multi] : false
498
614
 
499
- def rewrite_dow_class(t)
615
+ hms = determine_hms
500
616
 
501
- [ :dow_range, 1, 5 ] # only "weekday" for now
617
+ if multi == :fail && hms.count > 1
618
+ fail(ArgumentError.new(
619
+ "multiple crons in #{opts[:_s].inspect} - #{@slots.inspect}"))
620
+ elsif multi == true
621
+ hms.collect { |hm| parse_cron(hm) }
622
+ else
623
+ parse_cron(hms.first)
624
+ end
502
625
  end
503
626
 
504
- def rewrite_dow_range(t)
627
+ protected
505
628
 
506
- tts = t.subgather(nil)
629
+ def make_slot(key, data0, data1=nil)
507
630
 
508
- [ :dow_range, rewrite(tts[0]), rewrite(tts[1]) ]
631
+ Fugit::Nat::Slot.new(key, data0, data1)
509
632
  end
510
633
 
511
- alias rewrite_on _rewrite_multiple
512
- alias rewrite_at _rewrite_multiple
634
+ def determine_hms
513
635
 
514
- alias rewrite_from _rewrite_child
515
- alias rewrite_every _rewrite_child
636
+ return [ [ [ '*' ], [ '*' ] ] ] if @hms.empty?
516
637
 
517
- def rewrite_nat(t)
638
+ hms = @hms.dup
639
+ #
640
+ while ig = (hms.count > 1 && hms.index { |hm| hm.graded? }) do
641
+ sg = hms[ig]
642
+ so = hms.delete_at(ig == 0 ? 1 : ig - 1)
643
+ sg.append(so)
644
+ end
645
+
646
+ hms
647
+ .collect(&:a)
648
+ .inject({}) { |r, hm|
649
+ hm[1].each { |m| (r[m] ||= []).concat(hm[0]) }
650
+ r }
651
+ .inject({}) { |r, (m, hs)|
652
+ (r[hs.sort] ||= []) << m
653
+ r }
654
+ .to_a
655
+ end
656
+
657
+ def parse_cron(hm)
658
+
659
+ a = [
660
+ slot(:second, '0'),
661
+ hm[1],
662
+ hm[0],
663
+ slot(:monthday, '*'),
664
+ slot(:month, '*'),
665
+ slot(:weekday, '*') ]
666
+ tz = @slots[:tz]
667
+ a << tz.data0 if tz
668
+ a.shift if a.first == [ '0' ]
669
+
670
+ s = a
671
+ .collect { |e| e.uniq.sort.collect(&:to_s).join(',') }
672
+ .join(' ')
673
+
674
+ Fugit::Cron.parse(s)
675
+ end
518
676
 
519
- t.subgather(nil).collect { |tt| rewrite(tt) }
520
- #.tap { |x| pp x }
677
+ def slot(key, default)
678
+ s = @slots[key]
679
+ s ? s.data0 : [ default ]
521
680
  end
522
681
  end
523
682
  end