fugit 1.3.7 → 1.4.2

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: d3e29ff88eb9d7ce1e5214af33683f577967b86ded3ee9f90be8c628fa982e40
4
- data.tar.gz: 7adb64aaee08b7c354a3e619eda0edb96696ad4fd1d19d39c06e9a9768d058b7
3
+ metadata.gz: c0a2fdae36b04308242e58a5da6b2d5e65e749cb10b02d0d408988611067e46b
4
+ data.tar.gz: 19d7a91ec345a2b35c1b7568e3a6cfe4e70fc6f780e0332972507bbef6bda0e7
5
5
  SHA512:
6
- metadata.gz: db82aa99da670c23ba90424e015afcc8e0b5fff419d62806439bd94f171b8e0df18bbf0d3531343e814d2f9a1709a577f91f0d12024be3654df0f73922a255d5
7
- data.tar.gz: 5bd2ae16ad1e244a6a4f26f24ba8f5291c0e1ee3271b5ccbe86c1dcd3131a383731829c838826f8dbc94ecd6e14fa6d918576a3e2347882fb635e50c8c9c3ce4
6
+ metadata.gz: 1f5eccb6a96c688390dfdf231ba30812b05bdb22010ff1820b08eae5348809dd5793900c45c073500b8e1358ab90b4e40ee6bfd8badf4838d8e5b13a1d0f1428
7
+ data.tar.gz: 69fdda6d16c19beebae198a0610d69ab4496f1873b2ee7b825737f635ffd8744d054436f0b51dd372caa45e2945d8051d6a191b89ecd27d3c4a8b72bca59cffa
@@ -2,6 +2,35 @@
2
2
  # CHANGELOG.md
3
3
 
4
4
 
5
+ ## fugit 1.4.2 released 2021-01-12
6
+
7
+ * Fix Fugit::Cron.previous_time vs last day of month, gh-51
8
+ * Let Fugit::Cron.parse('') return nil, gh-49
9
+
10
+
11
+ ## fugit 1.4.1 released 2020-11-25
12
+
13
+ * Suppress warning, gh-46, thanks @amatsuda
14
+
15
+
16
+ ## fugit 1.4.0 released 2020-10-27
17
+
18
+ * Ensure cron accepts "25-L" for monthday, gh-45
19
+ * Allow for "every weekday 8am to 5pm", gh-44
20
+ * Allow "every day from the 25th to the last", gh-45
21
+ * Rework nat parser
22
+
23
+
24
+ ## fugit 1.3.9 released 2020-09-17
25
+
26
+ * Prevent "New York skip", gh-43, thanks @honglooker
27
+
28
+
29
+ ## fugit 1.3.8 released 2020-08-06
30
+
31
+ * Parse 'every day at 8:30' and ' at 8:30 pm', gh-42
32
+
33
+
5
34
  ## fugit 1.3.7 released 2020-08-05
6
35
 
7
36
  * Parse 'every 12 hours at minute 50', gh-41
data/CREDITS.md CHANGED
@@ -1,7 +1,11 @@
1
1
 
2
2
  # fugit credits
3
3
 
4
- * Jérôme Dalbert https://github.com/jeromedalbert every 12h at min 50, gh-41
4
+ * Solteszad https://github.com/solteszad gh-51, fix previous_time vs last day of month
5
+ * Niklas https://github.com/gr8bit gh-49, Fugit::Cron.parse('')
6
+ * Matsuda Akira https://github.com/amatsuda gh-46, warning suppression
7
+ * Honglooker https://github.com/honglooker gh-43, New York cron skip
8
+ * Jérôme Dalbert https://github.com/jeromedalbert gh-41, gh-42
5
9
  * Danny Ben Shitrit https://github.com/DannyBen nat variants, gh-38
6
10
  * Dominik Sander https://github.com/dsander #rough_frequency 0, gh-36
7
11
  * Milovan Zogovic https://github.com/assembler Cron#match? vs TZ, gh-31
@@ -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
@@ -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.7'
5
+ VERSION = '1.4.2'
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)
@@ -111,12 +112,7 @@ module Fugit
111
112
  end
112
113
 
113
114
  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)
115
+ dec((@t.day - 1) * 24 * 3600 + @t.hour * 3600 + @t.min * 60 + @t.sec + 1)
120
116
  end
121
117
 
122
118
  def dec_day
@@ -236,6 +232,8 @@ module Fugit
236
232
  # the translation occurs in the timezone of
237
233
  # this Fugit::Cron instance
238
234
 
235
+ zfrom = t.time.strftime('%z|%Z')
236
+
239
237
  loop do
240
238
 
241
239
  fail RuntimeError.new(
@@ -251,8 +249,14 @@ module Fugit
251
249
  min_match?(t) || (t.inc_min; next)
252
250
  sec_match?(t) || (t.inc_sec; next)
253
251
 
254
- st = t.time.strftime('%F|%T')
255
- (from, sfrom, ifrom = t.time, st, t.to_i; next) if st == sfrom
252
+ tt = t.time
253
+ st = tt.strftime('%F|%T')
254
+ zt = tt.strftime('%z|%Z')
255
+ #
256
+ if st == sfrom && zt != zfrom
257
+ from, sfrom, zfrom, ifrom = tt, st, zt, t.to_i
258
+ next
259
+ end
256
260
  #
257
261
  # when transitioning out of DST, this prevents #next_time from
258
262
  # yielding the same literal time twice in a row, see gh-6
@@ -328,7 +332,7 @@ module Fugit
328
332
  [ :seconds, 1, 60 ],
329
333
  [ :minutes, 60, 60 ],
330
334
  [ :hours, 3600, 24 ],
331
- [ :days, 24 * 3600, 365 ] ]
335
+ [ :days, 24 * 3600, 365 ] ].freeze
332
336
 
333
337
  def rough_frequency
334
338
 
@@ -487,7 +491,7 @@ module Fugit
487
491
 
488
492
  sla = 1 if sla == nil
489
493
  sta = min if sta == nil
490
- edn = max if edn == nil
494
+ edn = max if edn == nil || edn < 0 && sta > 0
491
495
 
492
496
  range(min, max, sta, edn, sla)
493
497
  end
@@ -499,12 +503,10 @@ module Fugit
499
503
  { min: min, max: max, sta: sta, edn: edn, sla: sla }.inspect
500
504
  ) if (sta < 0 && edn > 0) || (edn < 0 && sta > 0)
501
505
 
502
- #p({ min: min, max: max, sta: sta, edn: edn, sla: sla })
503
506
  a = []
504
507
 
505
508
  omin, omax = min, max
506
509
  min, max = -max, -1 if sta < 0
507
- #p({ min: min, max: max })
508
510
 
509
511
  cur = sta
510
512
 
@@ -604,10 +606,18 @@ module Fugit
604
606
 
605
607
  module Parser include Raabro
606
608
 
607
- WEEKDAYS = %w[ sunday monday tuesday wednesday thursday friday saturday ]
608
- WEEKDS = WEEKDAYS.collect { |d| d[0, 3] }
609
+ WEEKDAYS =
610
+ %w[ sunday monday tuesday wednesday thursday friday saturday ].freeze
611
+
612
+ WEEKDS =
613
+ WEEKDAYS.collect { |d| d[0, 3] }.freeze
614
+ DOW_REX =
615
+ /([0-7]|#{WEEKDS.join('|')})/i.freeze
609
616
 
610
- MONTHS = %w[ - jan feb mar apr may jun jul aug sep oct nov dec ]
617
+ MONTHS =
618
+ %w[ - jan feb mar apr may jun jul aug sep oct nov dec ].freeze
619
+ MONTH_REX =
620
+ /(1[0-2]|0?[1-9]|#{MONTHS[1..-1].join('|')})/i.freeze
611
621
 
612
622
  # piece parsers bottom to top
613
623
 
@@ -621,8 +631,8 @@ module Fugit
621
631
  def mos(i); rex(:mos, i, /[0-5]?\d/); end # min or sec
622
632
  def hou(i); rex(:hou, i, /(2[0-4]|[01]?[0-9])/); end
623
633
  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
634
+ def mon(i); rex(:mon, i, MONTH_REX); end
635
+ def dow(i); rex(:dow, i, DOW_REX); end
626
636
 
627
637
  def dow_hash(i); rex(:hash, i, /#(-?[1-5]|last|l)/i); end
628
638
 
@@ -765,8 +775,12 @@ module Fugit
765
775
 
766
776
  def rewrite_cron(t)
767
777
 
768
- hcron = t
778
+ st = t
769
779
  .sublookup(nil) # go to :ccron or :scron
780
+
781
+ return nil unless st
782
+
783
+ hcron = st
770
784
  .subgather(nil) # list min, hou, mon, ...
771
785
  .inject({}) { |h, tt|
772
786
  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
 
@@ -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,504 +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)
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
153
162
 
154
- e1 = e[1]
155
- h[:dow] = e1[0]
156
- h[:hms] = [ e1[1] ]
157
-
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
169
 
173
- WEEKDAYS =
174
- Fugit::Cron::Parser::WEEKDS + Fugit::Cron::Parser::WEEKDAYS
175
-
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)
242
- end
243
- def named_digit(i)
244
- seq(:named_digit, i, :dname, :integer)
203
+ def ampm(i)
204
+ rex(:ampm, i, /[ \t]*(am|pm)/i)
245
205
  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 digital_hour(i)
276
- rex(:digital_hour, i, /(2[0-4]|[01][0-9]):?[0-5]\d/)
233
+ def counts(i)
234
+ jseq(nil, i, :count, :_and_or_or_or_comma)
277
235
  end
278
236
 
237
+ def at_p(i)
238
+ seq(:at_p, i, :_point, :counts)
239
+ end
279
240
  def at_point(i)
280
- alt(nil, i,
281
- :digital_hour, :simple_hour, :named_hour, :numeral_hour,
282
- :named_digits)
241
+ jseq(nil, i, :at_p, :_and_or_or)
283
242
  end
284
243
 
285
- def weekday(i)
286
- 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)
287
252
  end
288
-
289
- def and_at(i)
290
- seq(nil, i, :_and_or_comma, :at_point)
253
+ def at_objects(i)
254
+ jseq(nil, i, :at_object, :_and_or_or_or_comma)
291
255
  end
292
256
 
293
- def _intervals(i)
294
- rex(:intervals, i,
295
- /(
296
- y(ears?)?|months?|w(eeks?)?|d(ays?)?|
297
- h(ours?)?|m(in(ute)?s?)?|s(ec(ond)?s?)?
298
- )(\s+|$)/ix)
257
+ def at(i)
258
+ seq(:at, i, :_at, '?', :at_objects)
299
259
  end
300
260
 
301
- def sinterval(i)
302
- rex(:sinterval, i,
303
- /(year|month|week|day|hour|min(ute)?|sec(ond)?)(\s+|$)/i)
261
+ def interval(i)
262
+ rex(:interval, i, INTERVAL_REX)
304
263
  end
305
- def ninterval(i)
306
- 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)
307
269
  end
308
270
 
309
- def ordinal(i)
310
- rex(:ordinal, i, /\s*(#{ORDINALS.keys.join('|')})\s*/)
271
+ def every_single_interval(i)
272
+ rex(:every_single_interval, i, /(1[ \t]+)?(week|year)/)
311
273
  end
312
274
 
313
- def _mod(i); rex(nil, i, /\s*month\s+on\s+days?\s+/i); end
314
- 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
315
278
 
316
- def dom(i)
317
- rex(:int, i, /([12][0-9]|3[01]|[0-9])/)
279
+ def weekday_range(i)
280
+ alt(nil, i, :to_weekday, :weekdays)
318
281
  end
319
- def and_or_dom(i)
320
- seq(nil, i, :_and_or_comma, :dom)
282
+
283
+ def to_omonthday(i)
284
+ seq(:to_omonthday, i,
285
+ :_the, '?', :omonthday, :_to, :_the, '?', :omonthday)
321
286
  end
322
- def dom_list(i)
323
- seq(:dom_list, i, :dom, :and_or_dom, '*')
287
+
288
+ def to_hour(i)
289
+ seq(:to_hour, i, :at_object, :_to, :at_object)
324
290
  end
325
291
 
326
- def dom_mod(i) # every month on day
327
- seq(:dom, i, :_mod, :dom_list)
292
+ def from_object(i)
293
+ alt(nil, i, :to_weekday, :to_omonthday, :to_hour)
328
294
  end
329
- def dom_noftm(i) # every nth of month
330
- seq(:dom, i, :ordinal, :_oftm)
295
+ def from_objects(i)
296
+ jseq(nil, i, :from_object, :_and_or_or)
331
297
  end
332
- def day_of_month(i)
333
- alt(nil, i, :dom_noftm, :dom_mod)
298
+ def from(i)
299
+ seq(nil, i, :_from, '?', :from_objects)
334
300
  end
335
301
 
336
- def dow_class(i)
337
- 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)
338
307
  end
339
308
 
340
- def dow(i)
341
- seq(:dow, i, :weekday)
309
+ def otm(i)
310
+ rex(nil, i, /[ \t]+of the month/)
342
311
  end
343
- def and_or_dow(i)
344
- 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)
345
320
  end
346
- def dow_list(i)
347
- seq(:dow_list, i, :dow, :and_or_dow, '*')
321
+
322
+ def every_named(i)
323
+ rex(:every_named, i, /weekday/i)
348
324
  end
349
325
 
350
- def to_dow_range(i)
351
- 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)
352
331
  end
353
- def dash_dow_range(i)
354
- seq(:dow_range, i, :weekday, :_dash, :weekday)
332
+ def every_objects(i)
333
+ jseq(nil, i, :every_object, :_and_or_or)
355
334
  end
356
- def dow_range(i)
357
- alt(nil, i, :to_dow_range, :dash_dow_range)
335
+
336
+ def every(i)
337
+ seq(:every, i, :_every, :every_objects)
358
338
  end
359
339
 
360
- def day_of_week(i)
361
- 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)
362
345
  end
363
346
 
364
- def interval(i)
365
- 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)
366
352
  end
367
353
 
368
- def every_object(i)
369
- 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) }
370
356
  end
371
- def from_object(i)
372
- alt(nil, i, :interval, :to_dow_range)
357
+ def _rewrite_sub(t, key=nil)
358
+ st = t.sublookup(key)
359
+ st ? rewrite(st) : nil
373
360
  end
374
361
 
375
- def tz(i)
376
- seq(nil, i, :_in, '?', :tzone)
362
+ def rewrite_dmin(t)
363
+ t.strinp
377
364
  end
378
- def on(i)
379
- 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)
380
371
  end
381
- def at(i)
382
- 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
383
380
  end
384
- def from(i)
385
- seq(:from, i, :_from, :from_object)
381
+
382
+ def rewrite_on_thes(t)
383
+ _rewrite_subs(t, :omonthday)
386
384
  end
387
- def every(i)
388
- seq(:every, i, :_every, :every_object)
385
+ def rewrite_on_days(t)
386
+ _rewrite_subs(t, :monthday)
389
387
  end
390
388
 
391
- def at_from(i)
392
- seq(nil, i, :at, :from, :tz, '?')
393
- end
394
- def at_every(i)
395
- seq(nil, i, :at, :every, :tz, '?')
389
+ def rewrite_on(t)
390
+ _rewrite_subs(t)
396
391
  end
397
392
 
398
- def from_at(i)
399
- seq(nil, i, :from, :at, '?', :tz, '?')
393
+ def rewrite_monthday(t)
394
+ slot(:monthday, t.string.to_i)
400
395
  end
401
396
 
402
- def every_(i)
403
- seq(nil, i, :every, :tz, '?')
397
+ def rewrite_omonthday(t)
398
+ slot(:monthday, OMONTHDAYS[t.string.downcase])
404
399
  end
405
- def every_on(i)
406
- 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
407
412
  end
408
- def every_at(i)
409
- 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
410
420
  end
411
421
 
412
- def nat(i)
413
- alt(:nat, i,
414
- :every_at, :every_on, :every_,
415
- :from_at,
416
- :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
417
440
  end
418
441
 
419
- # rewrite parsed tree
442
+ def rewrite_every_named(t)
420
443
 
421
- #def _rewrite_single(t)
422
- # [ t.name, rewrite(t.sublookup(nil)) ]
423
- #end
424
- def _rewrite_children(t)
425
- 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
426
449
  end
427
- def _rewrite_multiple(t)
428
- [ t.name, _rewrite_children(t) ]
450
+
451
+ def rewrite_tz(t)
452
+ slot(:tz, t.string)
429
453
  end
430
- def _rewrite_child(t)
431
- rewrite(t.sublookup(nil))
454
+
455
+ def rewrite_weekday(t)
456
+ Fugit::Cron::Parser::WEEKDS.index(t.string[0, 3].downcase)
432
457
  end
433
458
 
434
- 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
435
464
 
436
- 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
437
470
 
438
- [ :tz, t.strim ]
471
+ def rewrite_to_omonthday(t)
472
+ md0, md1 = _rewrite_subs(t, :omonthday).collect(&:_data0)
473
+ slot(:monthday, "#{md0}-#{md1}")
439
474
  end
440
475
 
441
- 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
442
482
 
443
- [ :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)
444
488
  end
445
489
 
446
- def rewrite_ninterval(t)
490
+ def rewrite_named_hour(t)
447
491
 
448
- [ :interval,
449
- t.sublookup(:int).string.to_i,
450
- t.sublookup(:intervals).strim ]
451
- end
492
+ ht = t.sublookup(:named_h)
493
+ mt = t.sublookup(:named_m)
494
+ apt = t.sublookup(:ampm)
452
495
 
453
- 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 ]
454
502
 
455
- i = t.sublookup(:int).string.to_i
503
+ h += 12 if h < 13 && apt && apt.strinpd == 'pm'
456
504
 
457
- case n = t.sublookup(:dname).strim
458
- when /^s/ then [ '*', '*', i ]
459
- when /^m/ then [ '*', i ]
460
- end
505
+ slot(:hm, h, m)
461
506
  end
462
507
 
463
- def rewrite_named_hour(t)
464
- NHOURS[t.strim.downcase]
465
- end
466
- def rewrite_numeral_hour(t)
467
- vs = t.subgather(nil).collect { |st| st.strim.downcase }
468
- v = NUMS.index(vs[0])
469
- v += 12 if vs[1] == 'pm'
470
- m = NMINUTES[vs[2]] || 0
471
- [ 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)
472
516
  end
473
- def rewrite_simple_hour(t)
474
- vs = t.subgather(nil).collect { |st| st.strim.downcase }
475
- v = vs[0].to_i
476
- v += 12 if vs[1] == 'pm'
477
- [ v, 0 ]
517
+
518
+ def rewrite_at(t)
519
+ _rewrite_subs(t)
478
520
  end
479
- def rewrite_digital_hour(t)
480
- m = t.string.match(/(\d\d?):?(\d\d)/)
481
- [ m[1].to_i, m[2].to_i ]
521
+
522
+ def rewrite_every(t)
523
+ _rewrite_sub(t)
482
524
  end
483
525
 
484
- 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
485
531
 
486
- 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))
487
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
581
+ end
582
+ end
488
583
 
489
- def rewrite_ordinal(t); ORDINALS[t.strim]; end
584
+ class SlotGroup
490
585
 
491
- def rewrite_dom(t)
586
+ def initialize(slots)
492
587
 
493
- #Raabro.pp(t, colours: true)
494
- [ :day_of_month,
495
- *_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
496
609
  end
497
610
 
498
- alias rewrite_dow _rewrite_child
611
+ def to_crons(opts)
499
612
 
500
- def rewrite_dom_list(t); [ :dom_list, *_rewrite_children(t) ]; end
501
- def rewrite_dow_list(t); [ :dow_list, *_rewrite_children(t) ]; end
613
+ multi = opts.has_key?(:multi) ? opts[:multi] : false
502
614
 
503
- def rewrite_dow_class(t)
615
+ hms = determine_hms
504
616
 
505
- [ :dow_range, 1, 5 ] # only "weekday" for now
617
+ if multi == :fail && hms.count > 1
618
+ fail(ArgumentError.new(
619
+ "multiple crons in #{opts[:_s].inspect} - #{@slots.inspect}"))
620
+ elsif multi == true
621
+ hms.collect { |hm| parse_cron(hm) }
622
+ else
623
+ parse_cron(hms.first)
624
+ end
506
625
  end
507
626
 
508
- def rewrite_dow_range(t)
627
+ protected
509
628
 
510
- tts = t.subgather(nil)
629
+ def make_slot(key, data0, data1=nil)
511
630
 
512
- [ :dow_range, rewrite(tts[0]), rewrite(tts[1]) ]
631
+ Fugit::Nat::Slot.new(key, data0, data1)
513
632
  end
514
633
 
515
- alias rewrite_on _rewrite_multiple
516
- alias rewrite_at _rewrite_multiple
634
+ def determine_hms
517
635
 
518
- alias rewrite_from _rewrite_child
519
- alias rewrite_every _rewrite_child
636
+ return [ [ [ '*' ], [ '*' ] ] ] if @hms.empty?
520
637
 
521
- def rewrite_nat(t)
638
+ hms = @hms.dup
639
+ #
640
+ while ig = (hms.count > 1 && hms.index { |hm| hm.graded? }) do
641
+ sg = hms[ig]
642
+ so = hms.delete_at(ig == 0 ? 1 : ig - 1)
643
+ sg.append(so)
644
+ end
645
+
646
+ hms
647
+ .collect(&:a)
648
+ .inject({}) { |r, hm|
649
+ hm[1].each { |m| (r[m] ||= []).concat(hm[0]) }
650
+ r }
651
+ .inject({}) { |r, (m, hs)|
652
+ (r[hs.sort] ||= []) << m
653
+ r }
654
+ .to_a
655
+ end
656
+
657
+ def parse_cron(hm)
658
+
659
+ a = [
660
+ slot(:second, '0'),
661
+ hm[1],
662
+ hm[0],
663
+ slot(:monthday, '*'),
664
+ slot(:month, '*'),
665
+ slot(:weekday, '*') ]
666
+ tz = @slots[:tz]
667
+ a << tz.data0 if tz
668
+ a.shift if a.first == [ '0' ]
669
+
670
+ s = a
671
+ .collect { |e| e.uniq.sort.collect(&:to_s).join(',') }
672
+ .join(' ')
673
+
674
+ Fugit::Cron.parse(s)
675
+ end
522
676
 
523
- t.subgather(nil).collect { |tt| rewrite(tt) }
524
- #.tap { |x| pp x }
677
+ def slot(key, default)
678
+ s = @slots[key]
679
+ s ? s.data0 : [ default ]
525
680
  end
526
681
  end
527
682
  end