fugit 1.3.8 → 1.4.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 86abe0dec557524b3ae8c7947f48ab15a52e49b240ecdb3f4103c916348e6e4c
4
- data.tar.gz: d440e0d52d49b6ad2893b05c269b7c6f96936cfcb4a3904750b3204568d2344a
3
+ metadata.gz: 30b1280ebc1d629dbbdcba1b02718a535b0fdec70c9d690f3d5208181ffe4577
4
+ data.tar.gz: 918af2ece3f4c94eb1145b60da397a43ee574023831bae4b7c0b95e6a97e389b
5
5
  SHA512:
6
- metadata.gz: 13fe87e6bc9d37cf3822c1bed665651be254b8e4a3d262f20950dc6f14d6170495721a0cd79ca8324b35048d90ce71d996b654f23da2b4780df536b9f4bd8071
7
- data.tar.gz: 2623e274a96a689639fbbbcc22f77bc86f8bcc0f49644e459673a54eadfd39deecf8158b85948b6969339b7d6c9e2554537aa4c6575f63a56fd0333626b0ed88
6
+ metadata.gz: 37b8ad132efdd2b9be99fb9fd1ab6f505024fd309865cf007d9ade6df20207a4165e49679a4113614010f7436ddbf28e2fc6ecbdbbedc713bb3e4def3f2b03ca
7
+ data.tar.gz: 4fc6e3a8f080b698151cc6dadfb1d2869cb736a4e6c60ce7fe6c26a598f83b693d32f37edd1f42cd20dd0efdb47dd04cde097fca07d0bf5fe6eb9412b72ef0cf
data/CHANGELOG.md CHANGED
@@ -2,6 +2,35 @@
2
2
  # CHANGELOG.md
3
3
 
4
4
 
5
+ ## fugit 1.4.3 released 2021-03-23
6
+
7
+ * Fix entering DST issue, gh-53
8
+
9
+
10
+ ## fugit 1.4.2 released 2021-01-12
11
+
12
+ * Fix Fugit::Cron.previous_time vs last day of month, gh-51
13
+ * Let Fugit::Cron.parse('') return nil, gh-49
14
+
15
+
16
+ ## fugit 1.4.1 released 2020-11-25
17
+
18
+ * Suppress warning, gh-46, thanks @amatsuda
19
+
20
+
21
+ ## fugit 1.4.0 released 2020-10-27
22
+
23
+ * Ensure cron accepts "25-L" for monthday, gh-45
24
+ * Allow for "every weekday 8am to 5pm", gh-44
25
+ * Allow "every day from the 25th to the last", gh-45
26
+ * Rework nat parser
27
+
28
+
29
+ ## fugit 1.3.9 released 2020-09-17
30
+
31
+ * Prevent "New York skip", gh-43, thanks @honglooker
32
+
33
+
5
34
  ## fugit 1.3.8 released 2020-08-06
6
35
 
7
36
  * Parse 'every day at 8:30' and ' at 8:30 pm', gh-42
data/CREDITS.md CHANGED
@@ -1,6 +1,11 @@
1
1
 
2
2
  # fugit credits
3
3
 
4
+ * Andy Pfister https://github.com/andyundso gh-53, entering DST
5
+ * Solteszad https://github.com/solteszad gh-51, fix previous_time vs last day of month
6
+ * Niklas https://github.com/gr8bit gh-49, Fugit::Cron.parse('')
7
+ * Matsuda Akira https://github.com/amatsuda gh-46, warning suppression
8
+ * Honglooker https://github.com/honglooker gh-43, New York cron skip
4
9
  * Jérôme Dalbert https://github.com/jeromedalbert gh-41, gh-42
5
10
  * Danny Ben Shitrit https://github.com/DannyBen nat variants, gh-38
6
11
  * Dominik Sander https://github.com/dsander #rough_frequency 0, gh-36
data/LICENSE.txt CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- Copyright (c) 2017-2020, John Mettraux, jmettraux+flor@gmail.com
2
+ Copyright (c) 2017-2021, John Mettraux, jmettraux+flor@gmail.com
3
3
 
4
4
  Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  # fugit
3
3
 
4
- [![Build Status](https://secure.travis-ci.org/floraison/fugit.svg)](http://travis-ci.org/floraison/fugit)
4
+ [![tests](https://github.com/floraison/fugit/workflows/test/badge.svg)](https://github.com/floraison/fugit/actions)
5
5
  [![Gem Version](https://badge.fury.io/rb/fugit.svg)](http://badge.fury.io/rb/fugit)
6
6
  [![Join the chat at https://gitter.im/floraison/fugit](https://badges.gitter.im/floraison/fugit.svg)](https://gitter.im/floraison/fugit?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
7
7
 
@@ -321,19 +321,39 @@ 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
+
353
+ ### Nat Midnight
354
+
355
+ `"Every day at midnight"` is supported, but `"Every monday at midnight"` will be interpreted (as of Fugit <= 1.4.x) as `"Every monday at 00:00"`. Sorry about that.
356
+
337
357
 
338
358
  ## LICENSE
339
359
 
data/fugit.gemspec CHANGED
@@ -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'
data/lib/fugit.rb CHANGED
@@ -1,7 +1,8 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module Fugit
3
4
 
4
- VERSION = '1.3.8'
5
+ VERSION = '1.4.3'
5
6
  end
6
7
 
7
8
  require 'time'
data/lib/fugit/at.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module Fugit
3
4
 
data/lib/fugit/cron.rb CHANGED
@@ -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)
@@ -78,10 +79,7 @@ module Fugit
78
79
  %w[ year month day wday hour min sec wday_in_month rweek rday ]
79
80
  .collect(&:to_sym).each { |k| define_method(k) { @t.send(k) } }
80
81
 
81
- def inc(i)
82
- @t = @t + i
83
- self
84
- end
82
+ def inc(i); @t = @t + i; self; end
85
83
  def dec(i); inc(-i); end
86
84
 
87
85
  def inc_month
@@ -94,6 +92,7 @@ module Fugit
94
92
 
95
93
  def inc_day
96
94
  inc((24 - @t.hour) * 3600 - @t.min * 60 - @t.sec)
95
+ inc( - @t.hour * 3600) if @t.hour != 0 # compensate for entering DST
97
96
  end
98
97
  def inc_hour
99
98
  inc((60 - @t.min) * 60 - @t.sec)
@@ -111,12 +110,7 @@ module Fugit
111
110
  end
112
111
 
113
112
  def dec_month
114
-
115
- #dec(@t.day * 24 * 3600 + @t.hour * 3600 + @t.min * 60 + @t.sec + 1)
116
- #
117
- # gh-18, so that '0 9 29 feb *' doesn't get skipped (over and over)
118
- #
119
- dec(@t.day * 24 * 3600 + 1)
113
+ dec((@t.day - 1) * DAY_S + @t.hour * 3600 + @t.min * 60 + @t.sec + 1)
120
114
  end
121
115
 
122
116
  def dec_day
@@ -236,6 +230,8 @@ module Fugit
236
230
  # the translation occurs in the timezone of
237
231
  # this Fugit::Cron instance
238
232
 
233
+ zfrom = t.time.strftime('%z|%Z')
234
+
239
235
  loop do
240
236
 
241
237
  fail RuntimeError.new(
@@ -251,8 +247,14 @@ module Fugit
251
247
  min_match?(t) || (t.inc_min; next)
252
248
  sec_match?(t) || (t.inc_sec; next)
253
249
 
254
- st = t.time.strftime('%F|%T')
255
- (from, sfrom, ifrom = t.time, st, t.to_i; next) if st == sfrom
250
+ tt = t.time
251
+ st = tt.strftime('%F|%T')
252
+ zt = tt.strftime('%z|%Z')
253
+ #
254
+ if st == sfrom && zt != zfrom
255
+ from, sfrom, zfrom, ifrom = tt, st, zt, t.to_i
256
+ next
257
+ end
256
258
  #
257
259
  # when transitioning out of DST, this prevents #next_time from
258
260
  # yielding the same literal time twice in a row, see gh-6
@@ -328,7 +330,7 @@ module Fugit
328
330
  [ :seconds, 1, 60 ],
329
331
  [ :minutes, 60, 60 ],
330
332
  [ :hours, 3600, 24 ],
331
- [ :days, 24 * 3600, 365 ] ]
333
+ [ :days, DAY_S, 365 ] ].freeze
332
334
 
333
335
  def rough_frequency
334
336
 
@@ -370,7 +372,7 @@ module Fugit
370
372
 
371
373
  @delta_min = deltas.min; @delta_max = deltas.max
372
374
  @occurrences = deltas.size
373
- @span_years = span / (365 * 24 * 3600)
375
+ @span_years = span / YEAR_S
374
376
  @yearly_occurrences = @occurrences.to_f / @span_years
375
377
  end
376
378
 
@@ -487,7 +489,7 @@ module Fugit
487
489
 
488
490
  sla = 1 if sla == nil
489
491
  sta = min if sta == nil
490
- edn = max if edn == nil
492
+ edn = max if edn == nil || edn < 0 && sta > 0
491
493
 
492
494
  range(min, max, sta, edn, sla)
493
495
  end
@@ -499,12 +501,10 @@ module Fugit
499
501
  { min: min, max: max, sta: sta, edn: edn, sla: sla }.inspect
500
502
  ) if (sta < 0 && edn > 0) || (edn < 0 && sta > 0)
501
503
 
502
- #p({ min: min, max: max, sta: sta, edn: edn, sla: sla })
503
504
  a = []
504
505
 
505
506
  omin, omax = min, max
506
507
  min, max = -max, -1 if sta < 0
507
- #p({ min: min, max: max })
508
508
 
509
509
  cur = sta
510
510
 
@@ -604,10 +604,18 @@ module Fugit
604
604
 
605
605
  module Parser include Raabro
606
606
 
607
- WEEKDAYS = %w[ sunday monday tuesday wednesday thursday friday saturday ]
608
- WEEKDS = WEEKDAYS.collect { |d| d[0, 3] }
607
+ WEEKDAYS =
608
+ %w[ sunday monday tuesday wednesday thursday friday saturday ].freeze
609
+
610
+ WEEKDS =
611
+ WEEKDAYS.collect { |d| d[0, 3] }.freeze
612
+ DOW_REX =
613
+ /([0-7]|#{WEEKDS.join('|')})/i.freeze
609
614
 
610
- MONTHS = %w[ - jan feb mar apr may jun jul aug sep oct nov dec ]
615
+ MONTHS =
616
+ %w[ - jan feb mar apr may jun jul aug sep oct nov dec ].freeze
617
+ MONTH_REX =
618
+ /(1[0-2]|0?[1-9]|#{MONTHS[1..-1].join('|')})/i.freeze
611
619
 
612
620
  # piece parsers bottom to top
613
621
 
@@ -621,8 +629,8 @@ module Fugit
621
629
  def mos(i); rex(:mos, i, /[0-5]?\d/); end # min or sec
622
630
  def hou(i); rex(:hou, i, /(2[0-4]|[01]?[0-9])/); end
623
631
  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
632
+ def mon(i); rex(:mon, i, MONTH_REX); end
633
+ def dow(i); rex(:dow, i, DOW_REX); end
626
634
 
627
635
  def dow_hash(i); rex(:hash, i, /#(-?[1-5]|last|l)/i); end
628
636
 
@@ -765,8 +773,12 @@ module Fugit
765
773
 
766
774
  def rewrite_cron(t)
767
775
 
768
- hcron = t
776
+ st = t
769
777
  .sublookup(nil) # go to :ccron or :scron
778
+
779
+ return nil unless st
780
+
781
+ hcron = st
770
782
  .subgather(nil) # list min, hou, mon, ...
771
783
  .inject({}) { |h, tt|
772
784
  h[tt.name] = tt.name == :tz ? rewrite_tz(tt) : rewrite_entry(tt)
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module Fugit
3
4
 
@@ -60,16 +61,15 @@ module Fugit
60
61
  end
61
62
 
62
63
  KEYS = {
63
- yea: { a: 'Y', r: 'y', i: 'Y', s: 365 * 24 * 3600, x: 0, l: 'year' },
64
- mon: { a: 'M', r: 'M', i: 'M', s: 30 * 24 * 3600, x: 1, l: 'month' },
65
- wee: { a: 'W', r: 'w', i: 'W', s: 7 * 24 * 3600, I: true, l: 'week' },
66
- day: { a: 'D', r: 'd', i: 'D', s: 24 * 3600, I: true, l: 'day' },
64
+ yea: { a: 'Y', r: 'y', i: 'Y', s: YEAR_S, x: 0, l: 'year' },
65
+ mon: { a: 'M', r: 'M', i: 'M', s: 30 * DAY_S, x: 1, l: 'month' },
66
+ wee: { a: 'W', r: 'w', i: 'W', s: 7 * DAY_S, I: true, l: 'week' },
67
+ day: { a: 'D', r: 'd', i: 'D', s: DAY_S, 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
 
data/lib/fugit/misc.rb CHANGED
@@ -1,6 +1,10 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module Fugit
3
4
 
5
+ DAY_S = (24 * 3600).freeze
6
+ YEAR_S = (365 * DAY_S).freeze
7
+
4
8
  class << self
5
9
 
6
10
  def isostamp(show_date, show_time, show_usec, time)
data/lib/fugit/nat.rb CHANGED
@@ -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,510 +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)
120
+ def _space(i); rex(nil, i, /[ \t]+/); end
121
+ def _sep(i); rex(nil, i, /([ \t]+|[ \t]*,[ \t]*)/); end
77
122
 
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]
123
+ def count(i); rex(:count, i, /\d+/); end
85
124
 
86
- Fugit::Cron.parse(s.join(' '))
125
+ def omonthday(i)
126
+ rex(:omonthday, i, OMONTHDAY_REX)
127
+ end
128
+ def monthday(i)
129
+ rex(:monthday, i, MONTHDAY_REX)
130
+ end
131
+ def weekday(i)
132
+ rex(:weekday, i, WEEKDAY_REX)
87
133
  end
88
134
 
89
- def eone(e); e1 = e[1]; e1 == 1 ? '*' : "*/#{e1}"; end
90
-
91
- def parse_interval_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
92
137
 
93
- e1 = e[1]
138
+ def weekdays(i); jseq(:weekdays, i, :weekday, :_and_or_or_or_comma); end
94
139
 
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
- when 'h', 'hour', 'hours'
101
- hms = h[:hms]
102
- if hms && hms.size == 1 && hms.first.first == '*'
103
- hms.first[0] = eone(e)
104
- elsif ! hms
105
- h[:hms] = [ [ eone(e), 0 ] ]
106
- end
107
- when 'd', 'day', 'days'
108
- h[:dom] = "*/#{e1}" if e1 > 1
109
- h[:hms] ||= [ [ 0, 0 ] ]
110
- when 'w', 'week', 'weeks'
111
- h[:_fail] = "cannot have crons for \"every #{e1} weeks\"" if e1 > 1
112
- h[:hms] ||= [ [ 0, 0 ] ]
113
- h[:dow] ||= 0
114
- when 'M', 'month', 'months'
115
- h[:_fail] = "cannot have crons for \"every #{e1} months\"" if e1 > 12
116
- h[:hms] ||= [ [ 0, 0 ] ]
117
- h[:dom] = 1
118
- h[:mon] = eone(e)
119
- when 'Y', 'y', 'year', 'years'
120
- h[:_fail] = "cannot have crons for \"every #{e1} years\"" if e1 > 1
121
- h[:hms] ||= [ [ 0, 0 ] ]
122
- h[:dom] = 1
123
- h[:mon] = 1
124
- end
125
- end
140
+ def on_the(i); seq(nil, i, :_the, :omonthdays); end
126
141
 
127
- def parse_dow_list_elt(e, opts, h)
142
+ def _minute(i); rex(nil, i, /[ \t]*minute[ \t]+/i) end
128
143
 
129
- h[:hms] ||= [ [ 0, 0 ] ]
130
- h[:dow] = e[1..-1].collect(&:to_s).sort.join(',')
144
+ def _dmin(i)
145
+ rex(:dmin, i, /[0-5]?[0-9]/)
131
146
  end
132
-
133
- def parse_dow_range_elt(e, opts, h)
134
-
135
- h[:hms] ||= [ [ 0, 0 ] ]
136
- h[:dow] = e[1] == e[2] ? e[1] : "#{e[1]}-#{e[2]}"
147
+ def and_dmin(i)
148
+ seq(nil, i, :_and_or_or_or_comma, :_minute, '?', :_dmin)
137
149
  end
138
150
 
139
- def parse_day_of_month_elt(e, opts, h)
140
-
141
- h[:dom] = e[1..-1].join(',')
151
+ def on_minutes(i)
152
+ seq(:on_minutes, i, :_minute, :_dmin, :and_dmin, '*')
142
153
  end
143
154
 
144
- def parse_at_elt(e, opts, h)
145
-
146
- (h[:hms] ||= []).concat(e[1])
147
-
148
- l = h[:hms].last
149
- h[:sec] = l.pop if l.size > 2
155
+ def on_thex(i);
156
+ rex(:on_thex, i, /[ \t]*the[ \t]+(hour|minute)[ \t]*/i);
150
157
  end
151
158
 
152
- def parse_on_elt(e, opts, h)
153
-
154
- e1 = e[1]
155
- h[:dow] = e1[0]
156
- h[:hms] = [ e1[1] ]
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
157
162
 
158
- l = h[:hms].last
159
- h[:sec] = l.pop if l.size > 2
163
+ def on_object(i)
164
+ alt(nil, i, :on_days, :on_weekdays, :on_minutes, :on_thes, :on_thex)
160
165
  end
161
-
162
- def parse_tz_elt(e, opts, h)
163
-
164
- h[:tz] = e[1]
166
+ def on_objects(i)
167
+ jseq(nil, i, :on_object, :_and)
165
168
  end
166
- end
167
-
168
- module Parser include Raabro
169
-
170
- NUMS = %w[
171
- zero one two three four five six seven eight nine ten eleven twelve ]
172
-
173
- WEEKDAYS =
174
- Fugit::Cron::Parser::WEEKDS + Fugit::Cron::Parser::WEEKDAYS
175
169
 
176
- NHOURS = {
177
- 'noon' => [ 12, 0 ],
178
- 'midnight' => [ 0, 0 ], 'oh' => [ 0, 0 ] }
179
- NMINUTES = {
180
- "o'clock" => 0, 'five' => 5,
181
- 'ten' => 10, 'fifteen' => 15,
182
- 'twenty' => 20, 'twenty-five' => 25,
183
- 'thirty' => 30, 'thirty-five' => 35,
184
- 'fourty' => 40, 'fourty-five' => 45,
185
- 'fifty' => 50, 'fifty-five' => 55 }
186
-
187
- oh = {
188
- '1st' => 1, '2nd' => 2, '3rd' => 3, '21st' => 21, '22nd' => 22,
189
- '23rd' => 23, '31st' => 31 }
190
- %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 ]
191
- .each { |i| oh["#{i}th"] = i.to_i }
192
- %w[
193
- first second third fourth fifth sixth seventh eighth ninth tenth
194
- eleventh twelfth thirteenth fourteenth fifteenth sixteenth seventeenth
195
- eighteenth nineteenth twentieth twenty-first twenty-second twenty-third
196
- twenty-fourth twenty-fifth twenty-fifth twenty-sixth twenty-seventh
197
- twenty-eighth twenty-ninth thirtieth thirty-first ]
198
- .each_with_index { |e, i| oh[e] = i + 1 }
199
- ORDINALS = oh
200
-
201
- # piece parsers bottom to top
202
-
203
- def _from(i); rex(nil, i, /\s*from\s+/i); end
204
- def _every(i); rex(nil, i, /\s*(every)\s+/i); end
205
- def _at(i); rex(nil, i, /\s*at\s+/i); end
206
- def _in(i); rex(nil, i, /\s*(in|on)\s+/i); end
207
- def _to(i); rex(nil, i, /\s*to\s+/i); end
208
- def _dash(i); rex(nil, i, /-\s*/i); end
209
- def _and(i); rex(nil, i, /\s*and\s+/i); end
210
- def _on(i); rex(nil, i, /\s*on\s+/i); end
211
-
212
- def _and_or_comma(i)
213
- rex(nil, i, /\s*(,?\s*and\s|,?\s*or\s|,)\s*/i)
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)
214
179
  end
215
- def _at_comma(i)
216
- rex(nil, i, /\s*(at\s|,|)\s*/i)
180
+
181
+ def city_tz(i)
182
+ rex(nil, i, /[A-Z][a-zA-Z0-9+\-]+(\/[A-Z][a-zA-Z0-9+\-_]+){0,2}/)
217
183
  end
218
- def _to_through(i)
219
- rex(nil, i, /\s*(to|through)\s+/i)
184
+ def named_tz(i)
185
+ rex(nil, i, /Z|UTC/)
220
186
  end
221
-
222
- def integer(i); rex(:int, i, /\d+\s*/); end
223
-
224
- def tz_name(i)
225
- rex(nil, i,
226
- /\s*[A-Z][a-zA-Z0-9+\-]+(\/[A-Z][a-zA-Z0-9+\-_]+){0,2}(\s+|$)/)
187
+ def delta_tz(i)
188
+ rex(nil, i, /[-+]([01][0-9]|2[0-4])(:?(00|15|30|45))?/)
227
189
  end
228
- def tz_delta(i)
229
- rex(nil, i,
230
- /\s*[-+]([01][0-9]|2[0-4]):?(00|15|30|45)(\s+|$)/)
190
+ def tz(i)
191
+ alt(:tz, i, :city_tz, :named_tz, :delta_tz)
231
192
  end
232
193
  def tzone(i)
233
- alt(:tzone, i, :tz_delta, :tz_name)
194
+ seq(nil, i, :_in_or_on, '?', :tz)
234
195
  end
235
196
 
236
- def and_named_digits(i)
237
- 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)
238
201
  end
239
202
 
240
- def dname(i)
241
- rex(:dname, i, /(s(ec(onds?)?)?|m(in(utes?)?)?)\s+/i)
203
+ def ampm(i)
204
+ rex(:ampm, i, /[ \t]*(am|pm)/i)
242
205
  end
243
- def named_digit(i)
244
- seq(:named_digit, i, :dname, :integer)
245
- end
246
- def named_digits(i)
247
- seq(nil, i, :named_digit, '+', :and_named_digits, '*')
206
+ def dark(i)
207
+ rex(:dark, i, /[ \t]*dark/i)
248
208
  end
249
209
 
250
- def am_pm(i)
251
- 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('|')}/)
252
212
  end
253
-
254
- def nminute(i)
255
- rex(:nminute, i, /(#{NMINUTES.keys.join('|')})\s*/i)
213
+ def simple_hour(i)
214
+ seq(:simple_hour, i, :simple_h, :ampm, '?')
256
215
  end
257
- def nhour(i)
258
- rex(:nhour, i, /(#{NUMS.join('|')})\s*/i)
216
+
217
+ def named_m(i)
218
+ rex(:named_m, i, NAMED_M_REX)
259
219
  end
260
- def numeral_hour(i)
261
- seq(:numeral_hour, i, :nhour, :am_pm, '?', :nminute, '?')
220
+ def named_min(i)
221
+ seq(nil, i, :_space, :named_m)
262
222
  end
263
223
 
224
+ def named_h(i)
225
+ rex(:named_h, i, NAMED_H_REX)
226
+ end
264
227
  def named_hour(i)
265
- rex(:named_hour, i, /(#{NHOURS.keys.join('|')})/i)
228
+ seq(:named_hour, i, :named_h, :dark, '?', :named_min, '?', :ampm, '?')
266
229
  end
267
230
 
268
- def shour(i)
269
- rex(:shour, i, /(2[0-4]|[01]?[0-9])/)
270
- end
271
- def simple_hour(i)
272
- seq(:simple_hour, i, :shour, :am_pm, '?')
273
- end
231
+ def _point(i); rex(:point, i, POINT_REX); end
274
232
 
275
- def dig_hour_b(i); rex(nil, i, /(2[0-4]|[01][0-9]|[0-9]):[0-5]\d/); end
276
- def dig_hour_a(i); rex(nil, i, /(2[0-4]|[01][0-9])[0-5]\d/); end
277
- def dig_hour(i); alt(nil, i, :dig_hour_a, :dig_hour_b); end
278
- #
279
- def digital_hour(i)
280
- seq(:digital_hour, i, :dig_hour, :am_pm, '?')
233
+ def counts(i)
234
+ jseq(nil, i, :count, :_and_or_or_or_comma)
281
235
  end
282
236
 
237
+ def at_p(i)
238
+ seq(:at_p, i, :_point, :counts)
239
+ end
283
240
  def at_point(i)
284
- alt(nil, i,
285
- :digital_hour, :simple_hour, :named_hour, :numeral_hour,
286
- :named_digits)
241
+ jseq(nil, i, :at_p, :_and_or_or)
287
242
  end
288
243
 
289
- def weekday(i)
290
- 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)
291
252
  end
292
-
293
- def and_at(i)
294
- seq(nil, i, :_and_or_comma, :at_point)
253
+ def at_objects(i)
254
+ jseq(nil, i, :at_object, :_and_or_or_or_comma)
295
255
  end
296
256
 
297
- def _intervals(i)
298
- rex(:intervals, i,
299
- /(
300
- y(ears?)?|months?|w(eeks?)?|d(ays?)?|
301
- h(ours?)?|m(in(ute)?s?)?|s(ec(ond)?s?)?
302
- )(\s+|$)/ix)
257
+ def at(i)
258
+ seq(:at, i, :_at, '?', :at_objects)
303
259
  end
304
260
 
305
- def sinterval(i)
306
- rex(:sinterval, i,
307
- /(year|month|week|day|hour|min(ute)?|sec(ond)?)(\s+|$)/i)
261
+ def interval(i)
262
+ rex(:interval, i, INTERVAL_REX)
308
263
  end
309
- def ninterval(i)
310
- 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)
311
269
  end
312
270
 
313
- def ordinal(i)
314
- rex(:ordinal, i, /\s*(#{ORDINALS.keys.join('|')})\s*/)
271
+ def every_single_interval(i)
272
+ rex(:every_single_interval, i, /(1[ \t]+)?(week|year)/)
315
273
  end
316
274
 
317
- def _mod(i); rex(nil, i, /\s*month\s+on\s+days?\s+/i); end
318
- 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
319
278
 
320
- def dom(i)
321
- rex(:int, i, /([12][0-9]|3[01]|[0-9])/)
279
+ def weekday_range(i)
280
+ alt(nil, i, :to_weekday, :weekdays)
322
281
  end
323
- def and_or_dom(i)
324
- seq(nil, i, :_and_or_comma, :dom)
282
+
283
+ def to_omonthday(i)
284
+ seq(:to_omonthday, i,
285
+ :_the, '?', :omonthday, :_to, :_the, '?', :omonthday)
325
286
  end
326
- def dom_list(i)
327
- seq(:dom_list, i, :dom, :and_or_dom, '*')
287
+
288
+ def to_hour(i)
289
+ seq(:to_hour, i, :at_object, :_to, :at_object)
328
290
  end
329
291
 
330
- def dom_mod(i) # every month on day
331
- seq(:dom, i, :_mod, :dom_list)
292
+ def from_object(i)
293
+ alt(nil, i, :to_weekday, :to_omonthday, :to_hour)
332
294
  end
333
- def dom_noftm(i) # every nth of month
334
- seq(:dom, i, :ordinal, :_oftm)
295
+ def from_objects(i)
296
+ jseq(nil, i, :from_object, :_and_or_or)
335
297
  end
336
- def day_of_month(i)
337
- alt(nil, i, :dom_noftm, :dom_mod)
298
+ def from(i)
299
+ seq(nil, i, :_from, '?', :from_objects)
338
300
  end
339
301
 
340
- def dow_class(i)
341
- 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)
342
307
  end
343
308
 
344
- def dow(i)
345
- seq(:dow, i, :weekday)
309
+ def otm(i)
310
+ rex(nil, i, /[ \t]+of the month/)
346
311
  end
347
- def and_or_dow(i)
348
- 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)
349
320
  end
350
- def dow_list(i)
351
- seq(:dow_list, i, :dow, :and_or_dow, '*')
321
+
322
+ def every_named(i)
323
+ rex(:every_named, i, /weekday/i)
352
324
  end
353
325
 
354
- def to_dow_range(i)
355
- 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)
356
331
  end
357
- def dash_dow_range(i)
358
- seq(:dow_range, i, :weekday, :_dash, :weekday)
332
+ def every_objects(i)
333
+ jseq(nil, i, :every_object, :_and_or_or)
359
334
  end
360
- def dow_range(i)
361
- alt(nil, i, :to_dow_range, :dash_dow_range)
335
+
336
+ def every(i)
337
+ seq(:every, i, :_every, :every_objects)
362
338
  end
363
339
 
364
- def day_of_week(i)
365
- 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)
366
345
  end
367
346
 
368
- def interval(i)
369
- 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)
370
352
  end
371
353
 
372
- def every_object(i)
373
- 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) }
374
356
  end
375
- def from_object(i)
376
- alt(nil, i, :interval, :to_dow_range)
357
+ def _rewrite_sub(t, key=nil)
358
+ st = t.sublookup(key)
359
+ st ? rewrite(st) : nil
377
360
  end
378
361
 
379
- def tz(i)
380
- seq(nil, i, :_in, '?', :tzone)
362
+ def rewrite_dmin(t)
363
+ t.strinp
381
364
  end
382
- def on(i)
383
- 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)
384
371
  end
385
- def at(i)
386
- 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
387
380
  end
388
- def from(i)
389
- seq(:from, i, :_from, :from_object)
381
+
382
+ def rewrite_on_thes(t)
383
+ _rewrite_subs(t, :omonthday)
390
384
  end
391
- def every(i)
392
- seq(:every, i, :_every, :every_object)
385
+ def rewrite_on_days(t)
386
+ _rewrite_subs(t, :monthday)
393
387
  end
394
388
 
395
- def at_from(i)
396
- seq(nil, i, :at, :from, :tz, '?')
397
- end
398
- def at_every(i)
399
- seq(nil, i, :at, :every, :tz, '?')
389
+ def rewrite_on(t)
390
+ _rewrite_subs(t)
400
391
  end
401
392
 
402
- def from_at(i)
403
- seq(nil, i, :from, :at, '?', :tz, '?')
393
+ def rewrite_monthday(t)
394
+ slot(:monthday, t.string.to_i)
404
395
  end
405
396
 
406
- def every_(i)
407
- seq(nil, i, :every, :tz, '?')
397
+ def rewrite_omonthday(t)
398
+ slot(:monthday, OMONTHDAYS[t.string.downcase])
408
399
  end
409
- def every_on(i)
410
- 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
411
412
  end
412
- def every_at(i)
413
- 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
414
420
  end
415
421
 
416
- def nat(i)
417
- alt(:nat, i,
418
- :every_at, :every_on, :every_,
419
- :from_at,
420
- :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
421
440
  end
422
441
 
423
- # rewrite parsed tree
442
+ def rewrite_every_named(t)
424
443
 
425
- #def _rewrite_single(t)
426
- # [ t.name, rewrite(t.sublookup(nil)) ]
427
- #end
428
- def _rewrite_children(t)
429
- 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
430
449
  end
431
- def _rewrite_multiple(t)
432
- [ t.name, _rewrite_children(t) ]
450
+
451
+ def rewrite_tz(t)
452
+ slot(:tz, t.string)
433
453
  end
434
- def _rewrite_child(t)
435
- rewrite(t.sublookup(nil))
454
+
455
+ def rewrite_weekday(t)
456
+ Fugit::Cron::Parser::WEEKDS.index(t.string[0, 3].downcase)
436
457
  end
437
458
 
438
- 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
439
464
 
440
- 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
441
470
 
442
- [ :tz, t.strim ]
471
+ def rewrite_to_omonthday(t)
472
+ md0, md1 = _rewrite_subs(t, :omonthday).collect(&:_data0)
473
+ slot(:monthday, "#{md0}-#{md1}")
443
474
  end
444
475
 
445
- 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
446
482
 
447
- [ :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)
448
488
  end
449
489
 
450
- def rewrite_ninterval(t)
490
+ def rewrite_named_hour(t)
451
491
 
452
- [ :interval,
453
- t.sublookup(:int).string.to_i,
454
- t.sublookup(:intervals).strim ]
455
- end
492
+ ht = t.sublookup(:named_h)
493
+ mt = t.sublookup(:named_m)
494
+ apt = t.sublookup(:ampm)
456
495
 
457
- 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 ]
458
502
 
459
- i = t.sublookup(:int).string.to_i
503
+ h += 12 if h < 13 && apt && apt.strinpd == 'pm'
460
504
 
461
- case n = t.sublookup(:dname).strim
462
- when /^s/ then [ '*', '*', i ]
463
- when /^m/ then [ '*', i ]
464
- end
505
+ slot(:hm, h, m)
465
506
  end
466
507
 
467
- def rewrite_named_hour(t)
468
- NHOURS[t.strim.downcase]
469
- end
470
- def rewrite_numeral_hour(t)
471
- vs = t.subgather(nil).collect { |st| st.strim.downcase }
472
- v = NUMS.index(vs[0])
473
- v += 12 if vs[1] == 'pm'
474
- m = NMINUTES[vs[2]] || 0
475
- [ 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)
476
516
  end
477
- def rewrite_simple_hour(t)
478
- vs = t.subgather(nil).collect { |st| st.strim.downcase }
479
- v = vs[0].to_i
480
- v += 12 if vs[1] == 'pm'
481
- [ v, 0 ]
517
+
518
+ def rewrite_at(t)
519
+ _rewrite_subs(t)
482
520
  end
483
- def rewrite_digital_hour(t)
484
- m = t.string.match(/(\d\d?):?(\d\d)(\s+pm)?/i)
485
- hou = m[1].to_i; hou += 12 if m[3] && hou < 12
486
- min = m[2].to_i
487
- [ hou, min ]
521
+
522
+ def rewrite_every(t)
523
+ _rewrite_sub(t)
488
524
  end
489
525
 
490
- 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
491
531
 
492
- 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
493
581
  end
582
+ end
494
583
 
495
- def rewrite_ordinal(t); ORDINALS[t.strim]; end
584
+ class SlotGroup
496
585
 
497
- def rewrite_dom(t)
586
+ def initialize(slots)
498
587
 
499
- #Raabro.pp(t, colours: true)
500
- [ :day_of_month,
501
- *_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
502
609
  end
503
610
 
504
- alias rewrite_dow _rewrite_child
611
+ def to_crons(opts)
612
+
613
+ multi = opts.has_key?(:multi) ? opts[:multi] : false
614
+
615
+ hms = determine_hms
616
+
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
625
+ end
505
626
 
506
- def rewrite_dom_list(t); [ :dom_list, *_rewrite_children(t) ]; end
507
- def rewrite_dow_list(t); [ :dow_list, *_rewrite_children(t) ]; end
627
+ protected
508
628
 
509
- def rewrite_dow_class(t)
629
+ def make_slot(key, data0, data1=nil)
510
630
 
511
- [ :dow_range, 1, 5 ] # only "weekday" for now
631
+ Fugit::Nat::Slot.new(key, data0, data1)
512
632
  end
513
633
 
514
- def rewrite_dow_range(t)
634
+ def determine_hms
635
+
636
+ return [ [ [ '*' ], [ '*' ] ] ] if @hms.empty?
515
637
 
516
- tts = t.subgather(nil)
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
517
645
 
518
- [ :dow_range, rewrite(tts[0]), rewrite(tts[1]) ]
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
519
655
  end
520
656
 
521
- alias rewrite_on _rewrite_multiple
522
- alias rewrite_at _rewrite_multiple
657
+ def parse_cron(hm)
523
658
 
524
- alias rewrite_from _rewrite_child
525
- alias rewrite_every _rewrite_child
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' ]
526
669
 
527
- def rewrite_nat(t)
670
+ s = a
671
+ .collect { |e| e.uniq.sort.collect(&:to_s).join(',') }
672
+ .join(' ')
673
+
674
+ Fugit::Cron.parse(s)
675
+ end
528
676
 
529
- t.subgather(nil).collect { |tt| rewrite(tt) }
530
- #.tap { |x| pp x }
677
+ def slot(key, default)
678
+ s = @slots[key]
679
+ s ? s.data0 : [ default ]
531
680
  end
532
681
  end
533
682
  end