fugit 1.3.4 → 1.5.3

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.

data/lib/fugit/nat.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module Fugit
3
4
 
@@ -15,16 +16,13 @@ module Fugit
15
16
  return nil unless s.is_a?(String)
16
17
 
17
18
  #p s; Raabro.pp(Parser.parse(s, debug: 3), colours: true)
18
- a = Parser.parse(s)
19
+ #(p s; Raabro.pp(Parser.parse(s, debug: 1), colours: true)) rescue nil
19
20
 
20
- return nil unless a
21
-
22
- return parse_crons(s, a, opts) \
23
- if a.include?([ :flag, 'every' ])
24
- return parse_crons(s, a, opts) \
25
- if a.include?([ :flag, 'from' ]) && a.find { |e| e[0] == :day_range }
26
-
27
- nil
21
+ if slots = Parser.parse(s)
22
+ slots.to_crons(opts.merge(_s: s))
23
+ else
24
+ nil
25
+ end
28
26
  end
29
27
 
30
28
  def do_parse(s, opts={})
@@ -32,239 +30,657 @@ module Fugit
32
30
  parse(s, opts) ||
33
31
  fail(ArgumentError.new("could not parse a nat #{s.inspect}"))
34
32
  end
33
+ end
35
34
 
36
- protected
35
+ module Parser include Raabro
37
36
 
38
- 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
73
+
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
88
+
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
97
+
98
+ #
99
+ # parsers bottom to top #################################################
100
+
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
106
+
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
110
+
111
+ def _and_or_or_or_comma(i)
112
+ rex(nil, i, /[ \t]*(,[ \t]*)?((and|or)[ \t]+|,[ \t]*)/i); end
113
+
114
+ def _to_or_dash(i);
115
+ rex(nil, i, /[ \t]*-[ \t]*|[ \t]+(to|through)[ \t]+/i); end
116
+
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
119
+
120
+ def _space(i); rex(nil, i, /[ \t]+/); end
121
+ def _sep(i); rex(nil, i, /([ \t]+|[ \t]*,[ \t]*)/); end
122
+
123
+ def count(i); rex(:count, i, /\d+/); end
124
+
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)
133
+ end
39
134
 
40
- dhs, aa = a
41
- .partition { |e| e[0] == :digital_hour }
42
- ms = dhs
43
- .inject({}) { |h, dh| (h[dh[1][0]] ||= []) << dh[1][1]; h }
44
- .values
45
- .uniq
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
46
137
 
47
- crons =
48
- #if ms.size <= 1 || hs.size <= 1
49
- if ms.size <= 1
50
- [ parse_cron(a, opts) ]
51
- else
52
- dhs.collect { |dh| parse_cron([ dh ] + aa, opts) }
53
- end
138
+ def weekdays(i); jseq(:weekdays, i, :weekday, :_and_or_or_or_comma); end
54
139
 
55
- fail ArgumentError.new(
56
- "multiple crons in #{s.inspect} " +
57
- "(#{crons.collect(&:original).join(' | ')})"
58
- ) if opts[:multi] == :fail && crons.size > 1
140
+ def on_the(i); seq(nil, i, :_the, :omonthdays); end
59
141
 
60
- if opts[:multi] == true || (opts[:multi] && opts[:multi] != :fail)
61
- crons
62
- else
63
- crons.first
64
- end
142
+ def _minute(i); rex(nil, i, /[ \t]*minute[ \t]+/i) end
143
+
144
+ def _dmin(i)
145
+ rex(:dmin, i, /[0-5]?[0-9]/)
146
+ end
147
+ def and_dmin(i)
148
+ seq(nil, i, :_and_or_or_or_comma, :_minute, '?', :_dmin)
65
149
  end
66
150
 
67
- def parse_cron(a, opts)
68
-
69
- h = { min: nil, hou: [], dom: nil, mon: nil, dow: nil }
70
- hkeys = h.keys
71
-
72
- a.each do |key, val|
73
- if key == :biz_day
74
- (h[:dow] ||= []) << '1-5'
75
- elsif key == :simple_hour || key == :numeral_hour
76
- h[:hou] << val
77
- elsif key == :digital_hour
78
- (h[:hou] ||= []) << val[0].to_i
79
- (h[:min] ||= []) << val[1].to_i
80
- elsif key == :name_day
81
- (h[:dow] ||= []) << val
82
- elsif key == :day_range
83
- (h[:dow] ||= []) << val.collect { |v| v.to_s[0, 3] }.join('-')
84
- elsif key == :tz
85
- h[:tz] = val
86
- elsif key == :duration
87
- process_duration(h, *val[0].to_h.first)
88
- end
89
- end
151
+ def on_minutes(i)
152
+ seq(:on_minutes, i, :_minute, :_dmin, :and_dmin, '*')
153
+ end
90
154
 
91
- h[:min] ||= [ 0 ]
92
- h[:min].uniq!
155
+ def on_thex(i);
156
+ rex(:on_thex, i, /[ \t]*the[ \t]+(hour|minute)[ \t]*/i);
157
+ end
93
158
 
94
- h[:hou].uniq!;
95
- h[:hou].sort!
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
96
162
 
97
- h[:dow].sort! if h[:dow]
163
+ def on_object(i)
164
+ alt(nil, i, :on_days, :on_weekdays, :on_minutes, :on_thes, :on_thex)
165
+ end
166
+ def on_objects(i)
167
+ jseq(nil, i, :on_object, :_and)
168
+ end
98
169
 
99
- a = hkeys
100
- .collect { |k|
101
- v = h[k]
102
- (v && v.any?) ? v.collect(&:to_s).join(',') : '*' }
103
- a.insert(0, h[:sec]) if h[:sec]
104
- a << h[:tz].first if h[:tz]
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
105
180
 
106
- s = a.join(' ')
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/)
186
+ end
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)
192
+ end
193
+ def tzone(i)
194
+ seq(nil, i, :_in_or_on, '?', :tz)
195
+ end
107
196
 
108
- Fugit::Cron.parse(s)
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)
201
+ end
202
+
203
+ def ampm(i)
204
+ rex(:ampm, i, /[ \t]*(am|pm|noon|midday|midnight)/i)
205
+ end
206
+ def dark(i)
207
+ rex(:dark, i, /[ \t]*dark/i)
109
208
  end
110
209
 
111
- def process_duration(h, interval, value)
210
+ def simple_h(i)
211
+ rex(:simple_h, i, /#{(0..24).to_a.reverse.join('|')}/)
212
+ end
213
+ def simple_hour(i)
214
+ seq(:simple_hour, i, :simple_h, :ampm, '?')
215
+ end
216
+
217
+ def named_m(i)
218
+ rex(:named_m, i, NAMED_M_REX)
219
+ end
220
+ def named_min(i)
221
+ seq(nil, i, :_space, :named_m)
222
+ end
112
223
 
113
- send("process_duration_#{interval}", h, value)
224
+ def named_h(i)
225
+ rex(:named_h, i, NAMED_H_REX)
226
+ end
227
+ def named_hour(i)
228
+ seq(:named_hour, i, :named_h, :dark, '?', :named_min, '?', :ampm, '?')
114
229
  end
115
230
 
116
- def process_duration_mon(h, value)
231
+ def _point(i); rex(:point, i, POINT_REX); end
117
232
 
118
- h[:hou] = [ 0 ]
119
- h[:dom] = [ 1 ]
120
- h[:mon] = [ value == 1 ? '*' : "*/#{value}" ]
233
+ def counts(i)
234
+ jseq(nil, i, :count, :_and_or_or_or_comma)
121
235
  end
122
236
 
123
- def process_duration_day(h, value)
237
+ def at_p(i)
238
+ seq(:at_p, i, :_point, :counts)
239
+ end
240
+ def at_point(i)
241
+ jseq(nil, i, :at_p, :_and_or_or)
242
+ end
124
243
 
125
- h[:hou] = [ 0 ]
126
- h[:dom] = [ value == 1 ? '*' : "*/#{value}" ]
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)
252
+ end
253
+ def at_objects(i)
254
+ jseq(nil, i, :at_object, :_and_or_or_or_comma)
127
255
  end
128
256
 
129
- def process_duration_hou(h, value)
257
+ def at(i)
258
+ seq(:at, i, :_at, '?', :at_objects)
259
+ end
130
260
 
131
- h[:hou] = [ value == 1 ? '*' : "*/#{value}" ]
261
+ def interval(i)
262
+ rex(:interval, i, INTERVAL_REX)
132
263
  end
133
264
 
134
- def process_duration_min(h, value)
265
+ # every day
266
+ # every 1 minute
267
+ def every_interval(i)
268
+ seq(:every_interval, i, :count, '?', :interval)
269
+ end
135
270
 
136
- h[:hou] = [ '*' ]
137
- h[:min] = [ value == 1 ? '*' : "*/#{value}" ]
271
+ def every_single_interval(i)
272
+ rex(:every_single_interval, i, /(1[ \t]+)?(week|year)/)
138
273
  end
139
274
 
140
- def process_duration_sec(h, value)
275
+ def to_weekday(i)
276
+ seq(:to_weekday, i, :weekday, :_to_or_dash, :weekday)
277
+ end
141
278
 
142
- h[:hou] = [ '*' ]
143
- h[:min] = [ '*' ]
144
- h[:sec] = [ value == 1 ? '*' : "*/#{value}" ]
279
+ def weekday_range(i)
280
+ alt(nil, i, :to_weekday, :weekdays)
145
281
  end
146
- end
147
282
 
148
- module Parser include Raabro
283
+ def to_omonthday(i)
284
+ seq(:to_omonthday, i,
285
+ :_the, '?', :omonthday, :_to, :_the, '?', :omonthday)
286
+ end
149
287
 
150
- NUMS = %w[
151
- zero
152
- one two three four five six seven eight nine
153
- ten eleven twelve ]
288
+ def to_hour(i)
289
+ seq(:to_hour, i, :at_object, :_to, :at_object)
290
+ end
154
291
 
155
- WEEKDAYS =
156
- Fugit::Cron::Parser::WEEKDS + Fugit::Cron::Parser::WEEKDAYS
292
+ def from_object(i)
293
+ alt(nil, i, :to_weekday, :to_omonthday, :to_hour)
294
+ end
295
+ def from_objects(i)
296
+ jseq(nil, i, :from_object, :_and_or_or)
297
+ end
298
+ def from(i)
299
+ seq(nil, i, :_from, '?', :from_objects)
300
+ end
157
301
 
158
- NHOURS =
159
- { 'noon' => [ 12, 0 ], 'midnight' => [ 0, 0 ] }
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)
307
+ end
160
308
 
161
- # piece parsers bottom to top
309
+ def otm(i)
310
+ rex(nil, i, /[ \t]+of the month/)
311
+ end
162
312
 
163
- def am_pm(i)
164
- rex(:am_pm, i, / *(am|pm)/i)
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)
165
320
  end
166
321
 
167
- def digital_hour(i)
168
- rex(:digital_hour, i, /(2[0-4]|[01][0-9]):?[0-5]\d/)
322
+ def every_named(i)
323
+ rex(:every_named, i, /weekday/i)
169
324
  end
170
325
 
171
- def _simple_hour(i)
172
- rex(:sh, i, /(2[0-4]|[01]?[0-9])/)
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)
173
331
  end
174
- def simple_hour(i)
175
- seq(:simple_hour, i, :_simple_hour, :am_pm, '?')
332
+ def every_objects(i)
333
+ jseq(nil, i, :every_object, :_and_or_or)
334
+ end
335
+
336
+ def every(i)
337
+ seq(:every, i, :_every, :every_objects)
176
338
  end
177
339
 
178
- def _numeral_hour(i)
179
- rex(:nh, i, /(#{NUMS.join('|')})/i)
340
+ def nat_elt(i)
341
+ alt(nil, i, :every, :from, :at, :tzone, :on)
180
342
  end
181
- def numeral_hour(i)
182
- seq(:numeral_hour, i, :_numeral_hour, :am_pm, '?')
343
+ def nat(i)
344
+ jseq(:nat, i, :nat_elt, :_sep)
183
345
  end
184
346
 
185
- def name_hour(i)
186
- rex(:name_hour, i, /(#{NHOURS.keys.join('|')})/i)
347
+ #
348
+ # rewrite parsed tree ###################################################
349
+
350
+ def slot(key, data0, data1=nil, opts=nil)
351
+ Slot.new(key, data0, data1, opts)
187
352
  end
188
353
 
189
- def plain_day(i); rex(:plain_day, i, /day/i); end
190
- def biz_day(i); rex(:biz_day, i, /(biz|business|week) *day/i); end
191
- def name_day(i); rex(:name_day, i, /#{WEEKDAYS.reverse.join('|')}/i); end
354
+ def _rewrite_subs(t, key=nil)
355
+ t.subgather(key).collect { |ct| rewrite(ct) }
356
+ end
357
+ def _rewrite_sub(t, key=nil)
358
+ st = t.sublookup(key)
359
+ st ? rewrite(st) : nil
360
+ end
361
+
362
+ def rewrite_dmin(t)
363
+ t.strinp
364
+ end
192
365
 
193
- def range_sep(i); rex(nil, i, / *- *| +(to|through) +/); end
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)
371
+ end
194
372
 
195
- def day_range(i)
196
- seq(:day_range, i, :name_day, :range_sep, :name_day)
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
197
380
  end
198
381
 
199
- def _tz_name(i)
200
- rex(nil, i, /[A-Z][a-zA-Z0-9+\-]+(\/[A-Z][a-zA-Z0-9+\-_]+){0,2}/)
382
+ def rewrite_on_thes(t)
383
+ _rewrite_subs(t, :omonthday)
201
384
  end
202
- def _tz_delta(i)
203
- rex(nil, i, /[-+]([01][0-9]|2[0-4]):?(00|15|30|45)/)
385
+ def rewrite_on_days(t)
386
+ _rewrite_subs(t, :monthday)
204
387
  end
205
- def _tz(i); alt(:tz, i, :_tz_delta, :_tz_name); end
206
388
 
207
- def duration(i)
208
- rex(
209
- :duration, i,
210
- /
211
- \d+
212
- \s?
213
- (mon(ths?)?|d(ays?)?|h(ours?)?|m(in(ute)?s?)?|s(ec(ond)?s?)?)
214
- /ix)
389
+ def rewrite_on(t)
390
+ _rewrite_subs(t)
215
391
  end
216
392
 
217
- def flag(i); rex(:flag, i, /(every|from|at|after|on|in)/i); end
393
+ def rewrite_monthday(t)
394
+ slot(:monthday, t.string.to_i)
395
+ end
218
396
 
219
- def datum(i)
220
- alt(nil, i,
221
- :day_range,
222
- :plain_day, :biz_day, :name_day,
223
- :_tz,
224
- :flag,
225
- :duration,
226
- :name_hour, :numeral_hour, :digital_hour, :simple_hour)
397
+ def rewrite_omonthday(t)
398
+ slot(:monthday, OMONTHDAYS[t.string.downcase])
227
399
  end
228
400
 
229
- def sugar(i); rex(nil, i, /(and|or|[, \t]+)/i); end
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
412
+ end
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
420
+ end
230
421
 
231
- def elt(i); alt(nil, i, :sugar, :datum); end
232
- def nat(i); rep(:nat, i, :elt, 1); end
422
+ def rewrite_every_interval(t)
233
423
 
234
- # rewrite parsed tree
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
440
+ end
235
441
 
236
- def rewrite_nat(t)
442
+ def rewrite_every_named(t)
237
443
 
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
449
+ end
450
+
451
+ def rewrite_tz(t)
452
+ slot(:tz, t.string)
453
+ end
454
+
455
+ def rewrite_weekday(t)
456
+ Fugit::Cron::Parser::WEEKDS.index(t.string[0, 3].downcase)
457
+ end
458
+
459
+ def rewrite_weekdays(t)
238
460
  #Raabro.pp(t, colours: true)
239
- t
240
- .subgather(nil)
241
- .collect { |tt|
242
-
243
- k = tt.name
244
- v = tt.string.downcase
245
-
246
- case k
247
- when :tz
248
- [ k, [ tt.string.strip, EtOrbi.get_tzone(tt.string.strip) ] ]
249
- when :duration
250
- [ k, [ Fugit::Duration.parse(tt.string.strip) ] ]
251
- when :digital_hour
252
- v = v.gsub(/:/, '')
253
- [ k, [ v[0, 2], v[2, 2] ] ]
254
- when :name_hour
255
- [ :digital_hour, NHOURS[v] ]
256
- when :name_day
257
- [ k, WEEKDAYS.index(v[0, 3]) ]
258
- when :day_range
259
- [ k, tt.subgather(nil).collect { |st| st.string.downcase } ]
260
- when :numeral_hour, :simple_hour
261
- vs = tt.subgather(nil).collect { |ttt| ttt.string.downcase.strip }
262
- v = k == :simple_hour ? vs[0].to_i : NUMS.index(vs[0])
263
- v += 12 if vs[1] == 'pm'
264
- [ k, v ]
265
- else
266
- [ k, v ]
267
- end }
461
+ slot(:weekday, _rewrite_subs(t, :weekday))
462
+ end
463
+ alias rewrite_on_weekdays rewrite_weekdays
464
+
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
470
+
471
+ def rewrite_to_omonthday(t)
472
+ md0, md1 = _rewrite_subs(t, :omonthday).collect(&:_data0)
473
+ slot(:monthday, "#{md0}-#{md1}")
474
+ end
475
+
476
+ def adjust_h(h, ap)
477
+ h = h.to_i
478
+ ap = ap || ''
479
+ (h < 12 && ap == 'pm' || ap == 'midnight') ? h + 12 : h
480
+ end
481
+
482
+ def rewrite_digital_hour(t)
483
+ h, m, ap = t.strinpd.split(/[: \t]+/)
484
+ h, m = adjust_h(h, ap), m.to_i
485
+ slot(:hm, h, m)
486
+ end
487
+
488
+ def rewrite_simple_hour(t)
489
+ h, ap = t.subgather(nil).collect(&:strinpd)
490
+ h = adjust_h(h, ap)
491
+ slot(:hm, h, 0)
492
+ end
493
+
494
+ def rewrite_named_hour(t)
495
+
496
+ ht = t.sublookup(:named_h)
497
+ mt = t.sublookup(:named_m)
498
+ apt = t.sublookup(:ampm)
499
+
500
+ h = ht.strinp
501
+ m = mt ? mt.strinp : 0
502
+ #p [ 0, '-->', h, m ]
503
+ h = NHOURS[h]
504
+ m = NMINUTES[m] || m
505
+ #p [ 1, '-->', h, m ]
506
+
507
+ h = adjust_h(h, apt && apt.strinpd)
508
+
509
+ slot(:hm, h, m)
510
+ end
511
+
512
+ def rewrite_to_hour(t)
513
+ #Raabro.pp(t, colours: true)
514
+ ht0, ht1 = t.subgather(nil)
515
+ h0, h1 = rewrite(ht0), rewrite(ht1)
516
+ fail ArgumentError.new(
517
+ "cannot deal with #{ht0.strinp} to #{ht1.strinp}, minutes diverge"
518
+ ) if h0.data1 != h1.data1
519
+ slot(:hm, "#{h0._data0}-#{h1._data0}", 0, strong: 0)
520
+ end
521
+
522
+ def rewrite_at(t)
523
+ _rewrite_subs(t)
524
+ end
525
+
526
+ def rewrite_every(t)
527
+ _rewrite_sub(t)
528
+ end
529
+
530
+ def rewrite_nat(t)
531
+ #Raabro.pp(t, colours: true)
532
+ Fugit::Nat::SlotGroup.new(_rewrite_subs(t).flatten)
533
+ end
534
+ end
535
+
536
+ class Slot
537
+ attr_reader :key
538
+ attr_accessor :_data0, :_data1
539
+ def initialize(key, d0, d1=nil, opts=nil)
540
+ d1, opts = d1.is_a?(Symbol) ? [ nil, d1 ] : [ d1, opts ]
541
+ @key, @_data0, @_data1 = key, d0, d1
542
+ @opts = (opts.is_a?(Symbol) ? { opts => true } : opts) || {}
543
+ end
544
+ def data0; @data0 ||= Array(@_data0); end
545
+ def data1; @data1 ||= Array(@_data1); end
546
+ def weak; @opts[:weak]; end
547
+ def strong; @opts[:strong]; end
548
+ def graded?; weak || strong; end
549
+ def append(slot)
550
+ @_data0, @_data1 = conflate(0, slot), conflate(1, slot)
551
+ @opts.clear
552
+ self
553
+ end
554
+ def inspect
555
+ a = [ @key, @_data0 ]
556
+ a << @_data1 if @_data1 != nil
557
+ a << @opts if @opts && @opts.keys.any?
558
+ "(slot #{a.collect(&:inspect).join(' ')})"
559
+ end
560
+ def a; [ data0, data1 ]; end
561
+ protected
562
+ def to_a(x)
563
+ return [] if x == '*'
564
+ Array(x)
565
+ end
566
+ def conflate(index, slot)
567
+ a, b = index == 0 ? [ @_data0, slot._data0 ] : [ @_data1, slot._data1 ]
568
+ return a if b == nil
569
+ return b if a == nil
570
+ if ra = (index == 0 && slot.strong == 1 && hour_range)
571
+ h0, h1 = ra[0], ra[1] - 1; return h0 == h1 ? h0 : "#{h0}-#{h1}"
572
+ elsif rb = (index == 0 && strong == 1 && slot.hour_range)
573
+ h0, h1 = rb[0], rb[1] - 1; return h0 == h1 ? h0 : "#{h0}-#{h1}"
574
+ end
575
+ return a if strong == index || strong == true
576
+ return b if slot.strong == index || slot.strong == true
577
+ return a if slot.weak == index || slot.weak == true
578
+ return b if weak == index || weak == true
579
+ return [ '*' ] if a == '*' && b == '*'
580
+ to_a(a).concat(to_a(b))
581
+ end
582
+ def hour_range
583
+ m = (key == :hm && @_data1 == 0 && @_data0.match(/\A(\d+)-(\d+)\z/))
584
+ m ? [ m[1].to_i, m[2].to_i ] : nil
585
+ end
586
+ end
587
+
588
+ class SlotGroup
589
+
590
+ def initialize(slots)
591
+
592
+ #puts "SlotGroup.new " + slots.inspect
593
+ @slots = {}
594
+ @hms = []
595
+
596
+ slots.each do |s|
597
+ if s.key == :hm
598
+ #ls = @hms.last; @hms.pop if ls && ls.key == :hm && ls.weak == true
599
+ @hms << s
600
+ elsif hs = @slots[s.key]
601
+ hs.append(s)
602
+ else
603
+ @slots[s.key] = s
604
+ end
605
+ end
606
+
607
+ if @slots[:monthday] || @slots[:weekday]
608
+ @hms << make_slot(:hm, 0, 0) if @hms.empty?
609
+ elsif @slots[:month]
610
+ @hms << make_slot(:hm, 0, 0) if @hms.empty?
611
+ @slots[:monthday] ||= make_slot(:monthday, 1)
612
+ end
613
+ end
614
+
615
+ def to_crons(opts)
616
+
617
+ multi = opts.has_key?(:multi) ? opts[:multi] : false
618
+
619
+ hms = determine_hms
620
+
621
+ if multi == :fail && hms.count > 1
622
+ fail(ArgumentError.new(
623
+ "multiple crons in #{opts[:_s].inspect} - #{@slots.inspect}"))
624
+ elsif multi == true
625
+ hms.collect { |hm| parse_cron(hm) }
626
+ else
627
+ parse_cron(hms.first)
628
+ end
629
+ end
630
+
631
+ protected
632
+
633
+ def make_slot(key, data0, data1=nil)
634
+
635
+ Fugit::Nat::Slot.new(key, data0, data1)
636
+ end
637
+
638
+ def determine_hms
639
+
640
+ return [ [ [ '*' ], [ '*' ] ] ] if @hms.empty?
641
+
642
+ hms = @hms.dup
643
+ #
644
+ while ig = (hms.count > 1 && hms.index { |hm| hm.graded? }) do
645
+ sg = hms[ig]
646
+ so = hms.delete_at(ig == 0 ? 1 : ig - 1)
647
+ sg.append(so)
648
+ end
649
+
650
+ hms
651
+ .collect(&:a)
652
+ .inject({}) { |r, hm|
653
+ hm[1].each { |m| (r[m] ||= []).concat(hm[0]) }
654
+ r }
655
+ .inject({}) { |r, (m, hs)|
656
+ (r[hs.sort] ||= []) << m
657
+ r }
658
+ .to_a
659
+ end
660
+
661
+ def parse_cron(hm)
662
+
663
+ a = [
664
+ slot(:second, '0'),
665
+ hm[1],
666
+ hm[0],
667
+ slot(:monthday, '*'),
668
+ slot(:month, '*'),
669
+ slot(:weekday, '*') ]
670
+ tz = @slots[:tz]
671
+ a << tz.data0 if tz
672
+ a.shift if a.first == [ '0' ]
673
+
674
+ s = a
675
+ .collect { |e| e.uniq.sort.collect(&:to_s).join(',') }
676
+ .join(' ')
677
+
678
+ Fugit::Cron.parse(s)
679
+ end
680
+
681
+ def slot(key, default)
682
+ s = @slots[key]
683
+ s ? s.data0 : [ default ]
268
684
  end
269
685
  end
270
686
  end