fugit 1.3.9 → 1.4.0

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: ff6ed980e9137d5c51dda0b2ca0a66dffb35ae99352168a50e92fadbf272442a
4
- data.tar.gz: '00977091b588564591b180debb728806bce562eaf2145b413e752c2eda90652e'
3
+ metadata.gz: 123f788edd6b7510be158be7ffdca00424eea5755aff767ae643180689947636
4
+ data.tar.gz: c789eaf6ecf0ae363faafa8536b11b97e6c6e02af35983ae13f9c8649fe63ab3
5
5
  SHA512:
6
- metadata.gz: 1c9290c85529fefb3ec1d8c664f4dabaa0e1bbe8d9430555a1af1c5c382e15afeb9f729fbc4b9eba00bf7db0a47625daf0f12a5328743b3f25af66728bd3d0b2
7
- data.tar.gz: 67e65d620ebfb16c4c9d649e61ac743691c1bb77a2573ce750d8202bf5bb4f090a8d1feb86aab362b6916de9b028c3e7e00bdad8a708d3397736ede1c14f1b61
6
+ metadata.gz: a7af25bbc69dcf5f891900d1620f171451a38ea269cb888ff440061ba0d8f1357a3789bcc446b71beba27a5029ad3214a65197d32165c4ed8e7fcc98fd25ad7e
7
+ data.tar.gz: 89dad0285342e14e9ba1e346a1aae742775048c87bd569b1b8ec5a5edde914435cc3ffb414baa52b948b7af948e5c2cff9ea75cf61503133454eebf780eaebe3
@@ -2,6 +2,14 @@
2
2
  # CHANGELOG.md
3
3
 
4
4
 
5
+ ## fugit 1.4.0 released 2020-10-27
6
+
7
+ * Ensure cron accepts "25-L" for monthday, gh-45
8
+ * Allow for "every weekday 8am to 5pm", gh-44
9
+ * Allow "every day from the 25th to the last", gh-45
10
+ * Rework nat parser
11
+
12
+
5
13
  ## fugit 1.3.9 released 2020-09-17
6
14
 
7
15
  * Prevent "New York skip", gh-43, thanks @honglooker
data/README.md CHANGED
@@ -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.9'
5
+ VERSION = '1.4.0'
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)
@@ -336,7 +337,7 @@ module Fugit
336
337
  [ :seconds, 1, 60 ],
337
338
  [ :minutes, 60, 60 ],
338
339
  [ :hours, 3600, 24 ],
339
- [ :days, 24 * 3600, 365 ] ]
340
+ [ :days, 24 * 3600, 365 ] ].freeze
340
341
 
341
342
  def rough_frequency
342
343
 
@@ -495,7 +496,7 @@ module Fugit
495
496
 
496
497
  sla = 1 if sla == nil
497
498
  sta = min if sta == nil
498
- edn = max if edn == nil
499
+ edn = max if edn == nil || edn < 0 && sta > 0
499
500
 
500
501
  range(min, max, sta, edn, sla)
501
502
  end
@@ -507,12 +508,10 @@ module Fugit
507
508
  { min: min, max: max, sta: sta, edn: edn, sla: sla }.inspect
508
509
  ) if (sta < 0 && edn > 0) || (edn < 0 && sta > 0)
509
510
 
510
- #p({ min: min, max: max, sta: sta, edn: edn, sla: sla })
511
511
  a = []
512
512
 
513
513
  omin, omax = min, max
514
514
  min, max = -max, -1 if sta < 0
515
- #p({ min: min, max: max })
516
515
 
517
516
  cur = sta
518
517
 
@@ -612,10 +611,18 @@ module Fugit
612
611
 
613
612
  module Parser include Raabro
614
613
 
615
- WEEKDAYS = %w[ sunday monday tuesday wednesday thursday friday saturday ]
616
- WEEKDS = WEEKDAYS.collect { |d| d[0, 3] }
614
+ WEEKDAYS =
615
+ %w[ sunday monday tuesday wednesday thursday friday saturday ].freeze
617
616
 
618
- MONTHS = %w[ - jan feb mar apr may jun jul aug sep oct nov dec ]
617
+ WEEKDS =
618
+ WEEKDAYS.collect { |d| d[0, 3] }.freeze
619
+ DOW_REX =
620
+ /([0-7]|#{WEEKDS.join('|')})/i.freeze
621
+
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
619
626
 
620
627
  # piece parsers bottom to top
621
628
 
@@ -629,8 +636,8 @@ module Fugit
629
636
  def mos(i); rex(:mos, i, /[0-5]?\d/); end # min or sec
630
637
  def hou(i); rex(:hou, i, /(2[0-4]|[01]?[0-9])/); end
631
638
  def dom(i); rex(:dom, i, /(-?(3[01]|[12][0-9]|0?[1-9])|last|l)/i); end
632
- def mon(i); rex(:mon, i, /(1[0-2]|0?[1-9]|#{MONTHS[1..-1].join('|')})/i); end
633
- 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
634
641
 
635
642
  def dow_hash(i); rex(:hash, i, /#(-?[1-5]|last|l)/i); end
636
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,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